Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 59 additions & 6 deletions ai_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,23 @@ func (e *Enforcer) buildExplainContext(rvals []interface{}, result bool, matched

// callAIAPI calls the configured AI API to get an explanation.
func (e *Enforcer) callAIAPI(explainContext string) (string, error) {
return e.callAIAPIWithSystemPrompt(explainContext, "You are an expert in access control and authorization systems. "+
"Explain why an authorization request was allowed or denied based on the "+
"provided access control model, policies, and enforcement result. "+
"Be clear, concise, and educational.")
}

// callAIAPIWithSystemPrompt calls the configured AI API with a custom system prompt.
func (e *Enforcer) callAIAPIWithSystemPrompt(userContent, systemPrompt string) (string, error) {
// Prepare the request
messages := []aiMessage{
{
Role: "system",
Content: "You are an expert in access control and authorization systems. " +
"Explain why an authorization request was allowed or denied based on the " +
"provided access control model, policies, and enforcement result. " +
"Be clear, concise, and educational.",
Role: "system",
Content: systemPrompt,
},
{
Role: "user",
Content: fmt.Sprintf("Please explain the following authorization decision:\n\n%s", explainContext),
Content: userContent,
},
}

Expand Down Expand Up @@ -219,3 +224,51 @@ func (e *Enforcer) callAIAPI(explainContext string) (string, error) {

return chatResp.Choices[0].Message.Content, nil
}

// evaluateAIPolicy evaluates an AI policy by calling the configured LLM API.
// It returns true if the AI policy allows the request, false otherwise.
func (e *Enforcer) evaluateAIPolicy(policyPrompt string, rvals []interface{}) (bool, error) {
if e.aiConfig.Endpoint == "" {
return false, errors.New("AI config not set, use SetAIConfig first")
}

// Build context for AI
var sb strings.Builder
sb.WriteString("Authorization Request:\n")
if len(rvals) > 0 {
sb.WriteString(fmt.Sprintf("Subject: %v\n", rvals[0]))
}
if len(rvals) > 1 {
sb.WriteString(fmt.Sprintf("Object: %v\n", rvals[1]))
}
if len(rvals) > 2 {
sb.WriteString(fmt.Sprintf("Action: %v\n", rvals[2]))
}

sb.WriteString(fmt.Sprintf("\nAI Policy Rule: %s\n", policyPrompt))
sb.WriteString("\nQuestion: Does this request satisfy the AI policy rule? Answer with 'ALLOW' if yes, 'DENY' if no.")

// Call AI API
systemPrompt := "You are an AI security policy evaluator. " +
"Your task is to determine if an authorization request satisfies the given AI policy rule. " +
"Respond with ONLY the word 'ALLOW' or 'DENY' based on your evaluation."

response, err := e.callAIAPIWithSystemPrompt(sb.String(), systemPrompt)
if err != nil {
return false, fmt.Errorf("failed to evaluate AI policy: %w", err)
}

// Parse response
response = strings.TrimSpace(strings.ToUpper(response))
// More robust parsing: check if response starts with ALLOW or DENY
// to avoid false positives like "I cannot ALLOW this"
if strings.HasPrefix(response, "ALLOW") {
return true, nil
}
if strings.HasPrefix(response, "DENY") {
return false, nil
}

// If response doesn't clearly start with ALLOW or DENY, deny by default for safety
return false, nil
}
123 changes: 123 additions & 0 deletions ai_policy_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2026 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package casbin

// GetAIPolicy gets all the AI policy rules in the policy.
func (e *Enforcer) GetAIPolicy() ([][]string, error) {
return e.GetNamedAIPolicy("a")
}

// GetFilteredAIPolicy gets all the AI policy rules in the policy, field filters can be specified.
func (e *Enforcer) GetFilteredAIPolicy(fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.GetFilteredNamedAIPolicy("a", fieldIndex, fieldValues...)
}

// GetNamedAIPolicy gets all the AI policy rules in the named policy.
func (e *Enforcer) GetNamedAIPolicy(ptype string) ([][]string, error) {
return e.model.GetPolicy("a", ptype)
}

// GetFilteredNamedAIPolicy gets all the AI policy rules in the named policy, field filters can be specified.
func (e *Enforcer) GetFilteredNamedAIPolicy(ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
return e.model.GetFilteredPolicy("a", ptype, fieldIndex, fieldValues...)
}

// HasAIPolicy determines whether an AI policy rule exists.
func (e *Enforcer) HasAIPolicy(params ...string) (bool, error) {
return e.HasNamedAIPolicy("a", params...)
}

// HasNamedAIPolicy determines whether a named AI policy rule exists.
func (e *Enforcer) HasNamedAIPolicy(ptype string, params ...string) (bool, error) {
return e.model.HasPolicy("a", ptype, params)
}

// AddAIPolicy adds an AI policy rule to the current policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddAIPolicy(params ...string) (bool, error) {
return e.AddNamedAIPolicy("a", params...)
}

// AddAIPolicies adds AI policy rules to the current policy.
// If the rule already exists, the function returns false for the corresponding rule and the rule will not be added.
// Otherwise the function returns true for the corresponding rule by adding the new rule.
func (e *Enforcer) AddAIPolicies(rules [][]string) (bool, error) {
return e.AddNamedAIPolicies("a", rules)
}

// AddNamedAIPolicy adds an AI policy rule to the current named policy.
// If the rule already exists, the function returns false and the rule will not be added.
// Otherwise the function returns true by adding the new rule.
func (e *Enforcer) AddNamedAIPolicy(ptype string, params ...string) (bool, error) {
return e.addPolicy("a", ptype, params)
}

// AddNamedAIPolicies adds AI policy rules to the current named policy.
// If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added.
// Otherwise the function returns true for the corresponding policy rule by adding the new rule.
func (e *Enforcer) AddNamedAIPolicies(ptype string, rules [][]string) (bool, error) {
return e.addPolicies("a", ptype, rules, false)
}

// RemoveAIPolicy removes an AI policy rule from the current policy.
func (e *Enforcer) RemoveAIPolicy(params ...string) (bool, error) {
return e.RemoveNamedAIPolicy("a", params...)
}

// RemoveAIPolicies removes AI policy rules from the current policy.
func (e *Enforcer) RemoveAIPolicies(rules [][]string) (bool, error) {
return e.RemoveNamedAIPolicies("a", rules)
}

// RemoveFilteredAIPolicy removes an AI policy rule from the current policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredAIPolicy(fieldIndex int, fieldValues ...string) (bool, error) {
return e.RemoveFilteredNamedAIPolicy("a", fieldIndex, fieldValues...)
}

// RemoveNamedAIPolicy removes an AI policy rule from the current named policy.
func (e *Enforcer) RemoveNamedAIPolicy(ptype string, params ...string) (bool, error) {
return e.removePolicy("a", ptype, params)
}

// RemoveNamedAIPolicies removes AI policy rules from the current named policy.
func (e *Enforcer) RemoveNamedAIPolicies(ptype string, rules [][]string) (bool, error) {
return e.removePolicies("a", ptype, rules)
}

// RemoveFilteredNamedAIPolicy removes an AI policy rule from the current named policy, field filters can be specified.
func (e *Enforcer) RemoveFilteredNamedAIPolicy(ptype string, fieldIndex int, fieldValues ...string) (bool, error) {
return e.removeFilteredPolicy("a", ptype, fieldIndex, fieldValues)
}

// UpdateAIPolicy updates an AI policy rule from the current policy.
func (e *Enforcer) UpdateAIPolicy(oldPolicy []string, newPolicy []string) (bool, error) {
return e.UpdateNamedAIPolicy("a", oldPolicy, newPolicy)
}

// UpdateAIPolicies updates AI policy rules from the current policy.
func (e *Enforcer) UpdateAIPolicies(oldPolicies [][]string, newPolicies [][]string) (bool, error) {
return e.UpdateNamedAIPolicies("a", oldPolicies, newPolicies)
}

// UpdateNamedAIPolicy updates an AI policy rule from the current named policy.
func (e *Enforcer) UpdateNamedAIPolicy(ptype string, oldPolicy []string, newPolicy []string) (bool, error) {
return e.updatePolicy("a", ptype, oldPolicy, newPolicy)
}

// UpdateNamedAIPolicies updates AI policy rules from the current named policy.
func (e *Enforcer) UpdateNamedAIPolicies(ptype string, oldPolicies [][]string, newPolicies [][]string) (bool, error) {
return e.updatePolicies("a", ptype, oldPolicies, newPolicies)
}
Loading
Loading