package permission import "strings" // Role is a platform-level role carried in OIDC groups. type Role string const ( RoleAdmin Role = "admin" RoleUser Role = "user" RoleService Role = "service" ) // Resource is a suite module protected by resource-scoped permissions. type Resource string const ( ResourceDrive Resource = "drive" ResourcePhotos Resource = "photos" ResourceContacts Resource = "contacts" ResourceCalendar Resource = "calendar" ) // Level is a resource permission with read < write < admin ordering. type Level int const ( LevelRead Level = iota + 1 LevelWrite LevelAdmin ) func (l Level) String() string { switch l { case LevelRead: return "read" case LevelWrite: return "write" case LevelAdmin: return "admin" default: return "unknown" } } func ParseLevel(s string) (Level, bool) { switch strings.ToLower(strings.TrimSpace(s)) { case "read": return LevelRead, true case "write": return LevelWrite, true case "admin": return LevelAdmin, true default: return 0, false } } func levelRank(l Level) int { return int(l) } // AdminScope is a fine-grained admin API permission with read < write ordering. type AdminScope int const ( AdminScopeRead AdminScope = iota + 1 AdminScopeWrite ) // DefaultAdminScope is the scope assumed when an endpoint requires full admin API access. const DefaultAdminScope = AdminScopeWrite const ( GroupAdminRead = "admin:read" GroupAdminWrite = "admin:write" ) func (s AdminScope) String() string { switch s { case AdminScopeRead: return "read" case AdminScopeWrite: return "write" default: return "unknown" } } func ParseAdminScope(s string) (AdminScope, bool) { switch strings.ToLower(strings.TrimSpace(s)) { case "read": return AdminScopeRead, true case "write": return AdminScopeWrite, true default: return 0, false } } func adminScopeRank(s AdminScope) int { return int(s) } // HasAdminScope reports whether groups grant at least the required admin API scope. // Platform admins (admin or role:admin) satisfy any scope for backwards compatibility. // admin:write implies admin:read; admin:read does not imply write. func HasAdminScope(groups []string, required AdminScope) bool { if HasRole(groups, RoleAdmin) { return true } max := AdminScope(0) for _, g := range groups { g = strings.ToLower(strings.TrimSpace(g)) switch g { case GroupAdminRead: if adminScopeRank(AdminScopeRead) > adminScopeRank(max) { max = AdminScopeRead } case GroupAdminWrite: if adminScopeRank(AdminScopeWrite) > adminScopeRank(max) { max = AdminScopeWrite } } } return adminScopeRank(max) >= adminScopeRank(required) } // HasRole reports whether groups grant the given platform role. func HasRole(groups []string, role Role) bool { want := string(role) for _, g := range groups { g = strings.ToLower(strings.TrimSpace(g)) if g == want || g == "role:"+want { return true } } return false } // HasAnyRole reports whether groups grant at least one of the roles. func HasAnyRole(groups []string, roles ...Role) bool { for _, role := range roles { if HasRole(groups, role) { return true } } return false } // HasPermission reports whether groups grant at least the required level on resource. // Platform admins bypass resource checks. Higher levels satisfy lower ones. func HasPermission(groups []string, resource Resource, required Level) bool { if HasRole(groups, RoleAdmin) { return true } want := string(resource) max := Level(0) for _, g := range groups { g = strings.ToLower(strings.TrimSpace(g)) prefix := want + ":" if !strings.HasPrefix(g, prefix) { continue } level, ok := ParseLevel(strings.TrimPrefix(g, prefix)) if !ok { continue } if levelRank(level) > levelRank(max) { max = level } } return levelRank(max) >= levelRank(required) }