guard
API
guard
packageAPI reference for the guard
package.
Imports
(6)
S
struct
Guard
Guard provides the authorization engine.
pkg/guard/guard.go:15-15
type Guard struct
Methods
GetRoles
Method
GetRoles returns all roles resolved for the identity on the resource.
Parameters
user
Identity
resource
any
Returns
[]string
error
func (*Guard) GetRoles(user Identity, resource any) ([]string, error)
{
if user == nil {
return nil, errors.New("identity is nil")
}
if resource == nil {
return nil, errors.New("resource is nil")
}
val := reflect.ValueOf(resource)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return nil, errors.New("resource must be a struct or pointer to struct")
}
// We need to support GetRoles even if the new policy logic is action-centric.
// But `GetRoles` is about "What roles does this user have on this resource?".
// The new Policy tracks logic per ACTION.
// However, we can inspect the policy to see which roles match.
// OR we can keep the old logic for GetRoles?
// But optimizing GetRoles is also important if used frequently.
// But `Can` is the main entry point.
// Let's implement GetRoles by checking all possible roles in the policy against the user.
policy := getPolicy(val.Type())
// Collect matching roles
matchedRoles := make(map[string]bool)
// Check user's explicit roles against any static role used in policy?
// User has roles [A, B].
// If resource allows A for action Read, then user has role A on this resource.
// This is vague.
// `GetRoles` usually returns roles that match dynamic criteria + static ones?
// Original logic: "If `role:admin` is on field, and user has `admin` global role, then user has `admin` on resource."
userRoles := user.GetRoles()
userRolesMap := make(map[string]bool, len(userRoles))
for _, r := range userRoles {
userRolesMap[r] = true
}
// 1. Static Roles from Policy
// We don't have a simple list of "All Static Roles" in compiled policy, but we have them in `StaticRules`.
for _, roleMap := range policy.StaticRules {
for r := range roleMap {
if r == "*" {
continue
}
if userRolesMap[r] {
matchedRoles[r] = true
}
}
}
// 2. Dynamic Roles
for _, rule := range policy.DynamicRules {
fieldVal := val.Field(rule.FieldIndex)
roles := extractRoles(fieldVal, user.GetID())
for _, r := range roles {
// If dynamic role (e.g. from DB) matches user's ID or is in user's roles?
// `role:*` extraction logic in original checked `fieldVal` against `user.GetID()`.
// Wait, the new `extractRoles` logic I wrote uses `checker.IsMatch(key, userID)`.
// So it extracts roles if the KEY matches the user.
// e.g. `role:*` on `map[string]string`. User ID "123". Map["123"] = "owner".
// Then "owner" is extracted.
// Then we check if user HAS role "owner"?
// Original logic: `userRoles[roleVal.String()] = true`.
// Yes, so if extracted role is in User's roles, we add it. Or is the extracted string THE role the user has?
// If `role:*` resolves to "owner", it means "The user with ID matching the key HAS the role 'owner' on this resource".
// So we add "owner" to `matchedRoles`.
// We DO NOT check if user already has "owner" in global traits.
// Validated against original lines 92: `userRoles[roleVal.String()] = true`.
matchedRoles[r] = true
}
}
roles := make([]string, 0, len(matchedRoles))
for r := range matchedRoles {
roles = append(roles, r)
}
return roles, nil
}
Can
Method
Can checks if the identity is allowed to perform the action on the resource.
Parameters
Returns
error
func (*Guard) Can(user Identity, resource any, action string) error
{
if user == nil {
return errors.New("identity is nil")
}
if resource == nil {
return errors.New("resource is nil")
}
val := reflect.ValueOf(resource)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return errors.New("resource must be a struct or pointer to struct")
}
policy := getPolicy(val.Type())
return policy.Evaluate(user, val, action)
}
F
function
NewGuard
NewGuard creates a new guard engine.
Returns
pkg/guard/guard.go:18-20
func NewGuard() *Guard
{
return &Guard{}
}
F
function
Can
Can checks if the identity is allowed to perform the action on the resource.
Parameters
Returns
error
pkg/guard/guard.go:133-135
func Can(user Identity, resource any, action string) error
{
return NewGuard().Can(user, resource, action)
}
Uses
S
struct
CompiledPolicy
CompiledPolicy holds pre-compiled authorization rules for a resource type.
pkg/guard/policy.go:18-23
type CompiledPolicy struct
Methods
Evaluate
Method
Parameters
Returns
error
func (*CompiledPolicy) Evaluate(user Identity, resourceVal reflect.Value, action string) error
{
allowed := false
ruleFound := false
checkStatic := func(act string) {
if allowedRoles, ok := p.StaticRules[act]; ok {
ruleFound = true
userRoles := user.GetRoles()
for _, ur := range userRoles {
if allowedRoles[ur] {
allowed = true
return
}
if allowedRoles["*"] {
allowed = true
return
}
}
}
}
checkStatic(action)
if allowed {
return nil
}
checkStatic("*")
if allowed {
return nil
}
ruleIndices := p.actionIndex[action]
rRuleIndices := make([]int, len(ruleIndices))
copy(rRuleIndices, ruleIndices)
rRuleIndices = append(rRuleIndices, p.wildcardIndex...)
for _, idx := range rRuleIndices {
if idx >= len(p.DynamicRules) {
continue
}
rule := p.DynamicRules[idx]
ruleFound = true
fieldVal := resourceVal.Field(rule.FieldIndex)
dynamicRoles := extractRoles(fieldVal, user.GetID())
userRoles := user.GetRoles()
for _, dr := range dynamicRoles {
for _, ur := range userRoles {
if dr == ur {
allowed = true
break
}
}
if allowed {
break
}
}
if allowed {
break
}
}
if !ruleFound {
return fmt.Errorf("no policy defined for action '%s'", action)
}
if !allowed {
return fmt.Errorf("permission denied for action '%s'", action)
}
return nil
}
Fields
| Name | Type | Description |
|---|---|---|
| StaticRules | map[string]map[string]bool | |
| DynamicRules | []DynamicRule | |
| actionIndex | map[string][]int | |
| wildcardIndex | []int |
S
struct
DynamicRule
DynamicRule maps a dynamic role field to the actions it governs.
pkg/guard/policy.go:26-31
type DynamicRule struct
Fields
| Name | Type | Description |
|---|---|---|
| FieldIndex | int | |
| Actions | []string | |
| FieldType | reflect.Type | |
| FieldKind | reflect.Kind |
F
function
getPolicy
Parameters
typ
Returns
pkg/guard/policy.go:33-40
func getPolicy(typ reflect.Type) *CompiledPolicy
{
if val, ok := policyCache.Load(typ); ok {
return val.(*CompiledPolicy)
}
policy := compilePolicy(typ)
policyCache.Store(typ, policy)
return policy
}
F
function
compilePolicy
Parameters
typ
Returns
pkg/guard/policy.go:42-100
func compilePolicy(typ reflect.Type) *CompiledPolicy
{
fields := globalParser.ParseType(typ)
policy := &CompiledPolicy{
StaticRules: make(map[string]map[string]bool),
DynamicRules: make([]DynamicRule, 0),
actionIndex: make(map[string][]int),
wildcardIndex: make([]int, 0),
}
for _, meta := range fields {
permissions := meta.GetAll("can")
roles := meta.GetAll("role")
isDynamicRole := false
staticRoles := make([]string, 0, len(roles))
for _, r := range roles {
if r == "*" {
isDynamicRole = true
} else {
staticRoles = append(staticRoles, r)
}
}
if len(permissions) > 0 {
for _, action := range permissions {
if len(staticRoles) > 0 {
if policy.StaticRules[action] == nil {
policy.StaticRules[action] = make(map[string]bool)
}
for _, r := range staticRoles {
policy.StaticRules[action][r] = true
}
}
}
if isDynamicRole {
dr := DynamicRule{
FieldIndex: meta.Index,
Actions: permissions,
FieldType: meta.Type,
FieldKind: meta.Type.Kind(),
}
idx := len(policy.DynamicRules)
policy.DynamicRules = append(policy.DynamicRules, dr)
for _, action := range permissions {
if action == "*" {
policy.wildcardIndex = append(policy.wildcardIndex, idx)
} else {
policy.actionIndex[action] = append(policy.actionIndex[action], idx)
}
}
}
}
}
return policy
}
F
function
extractRoles
Parameters
val
userID
string
Returns
[]string
pkg/guard/policy.go:173-202
func extractRoles(val reflect.Value, userID string) []string
{
var roles []string
if val.Kind() == reflect.Map {
for _, key := range val.MapKeys() {
if checker.IsMatch(key, userID) {
roleVal := val.MapIndex(key)
if roleVal.Kind() == reflect.String {
roles = append(roles, roleVal.String())
} else if roleVal.Kind() == reflect.Slice || roleVal.Kind() == reflect.Array {
for i := 0; i < roleVal.Len(); i++ {
rv := roleVal.Index(i)
if rv.Kind() == reflect.String {
roles = append(roles, rv.String())
}
}
}
}
}
} else if val.Kind() == reflect.String {
roles = append(roles, val.String())
} else if val.Kind() == reflect.Slice {
for i := 0; i < val.Len(); i++ {
rv := val.Index(i)
if rv.Kind() == reflect.String {
roles = append(roles, rv.String())
}
}
}
return roles
}