mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-10 19:59:04 +02:00
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
150 lines
5.1 KiB
Go
150 lines
5.1 KiB
Go
package authz
|
|
|
|
// Scope is a permission that a principal must hold to call a route
|
|
// Keys follow the resource:action pattern and are valid RFC 6749 scope tokens so they can later appear in API key records and OAuth access tokens unchanged
|
|
type Scope string
|
|
|
|
// Account scopes cover the caller's own account and are held by every signed-in user
|
|
const (
|
|
AccountRead Scope = "account:read"
|
|
AccountWrite Scope = "account:write"
|
|
AccountPasskeys Scope = "account:passkeys"
|
|
AccountAPIKeys Scope = "account:api-keys"
|
|
AccountApps Scope = "account:apps"
|
|
AccountAuditLogs Scope = "account:audit-logs"
|
|
AccountSession Scope = "account:session"
|
|
AccountPasskeysEnroll Scope = "account:passkeys:enroll"
|
|
AccountAPIKeysCreate Scope = "account:api-keys:create"
|
|
)
|
|
|
|
// Admin scopes cover other users' data and the instance configuration
|
|
const (
|
|
UsersRead Scope = "users:read"
|
|
UsersWrite Scope = "users:write"
|
|
GroupsRead Scope = "groups:read"
|
|
GroupsWrite Scope = "groups:write"
|
|
OidcClientsRead Scope = "oidc-clients:read"
|
|
OidcClientsWrite Scope = "oidc-clients:write"
|
|
APIsRead Scope = "apis:read"
|
|
APIsWrite Scope = "apis:write"
|
|
ConfigRead Scope = "config:read"
|
|
ConfigWrite Scope = "config:write"
|
|
AuditLogsRead Scope = "audit-logs:read"
|
|
)
|
|
|
|
// Category groups scopes by whose data they reach
|
|
type Category int
|
|
|
|
const (
|
|
// CategoryAccount scopes act on the caller's own account
|
|
CategoryAccount Category = iota + 1
|
|
// CategoryAdmin scopes act on other users or on the instance
|
|
CategoryAdmin
|
|
)
|
|
|
|
// PrincipalKind identifies the kind of credential a principal authenticated with
|
|
// Kinds are bit flags so a scope can list every kind that may hold it
|
|
type PrincipalKind uint8
|
|
|
|
const (
|
|
// KindSession is a browser session established by signing in to Pocket ID
|
|
KindSession PrincipalKind = 1 << iota
|
|
// KindAPIKey is a personal API key sent in the X-API-Key header
|
|
KindAPIKey
|
|
// KindOAuthUser is an OAuth access token issued to a client acting on behalf of a user
|
|
KindOAuthUser
|
|
// KindOAuthClient is an OAuth access token issued to a client acting as itself through the client credentials grant
|
|
KindOAuthClient
|
|
)
|
|
|
|
// delegated lists the kinds that act for a user, which is every kind except a client acting as itself
|
|
const delegated = KindSession | KindAPIKey | KindOAuthUser
|
|
|
|
type definition struct {
|
|
scope Scope
|
|
category Category
|
|
grantableTo PrincipalKind
|
|
}
|
|
|
|
// catalog is the complete list of scopes
|
|
// grantableTo restricts which credential kinds can ever hold a scope, independent of the user's role
|
|
// Session-only scopes guard actions that must never be reachable with a long-lived or third-party credential, such as enrolling passkeys or minting API keys
|
|
var catalog = []definition{
|
|
{AccountRead, CategoryAccount, delegated},
|
|
{AccountWrite, CategoryAccount, delegated},
|
|
{AccountPasskeys, CategoryAccount, delegated},
|
|
{AccountAPIKeys, CategoryAccount, delegated},
|
|
{AccountApps, CategoryAccount, delegated},
|
|
{AccountAuditLogs, CategoryAccount, delegated},
|
|
{AccountSession, CategoryAccount, KindSession},
|
|
{AccountPasskeysEnroll, CategoryAccount, KindSession},
|
|
{AccountAPIKeysCreate, CategoryAccount, KindSession},
|
|
|
|
{UsersRead, CategoryAdmin, delegated},
|
|
{UsersWrite, CategoryAdmin, delegated},
|
|
{GroupsRead, CategoryAdmin, delegated},
|
|
{GroupsWrite, CategoryAdmin, delegated},
|
|
{OidcClientsRead, CategoryAdmin, delegated},
|
|
{OidcClientsWrite, CategoryAdmin, delegated},
|
|
{APIsRead, CategoryAdmin, delegated},
|
|
{APIsWrite, CategoryAdmin, delegated},
|
|
{ConfigRead, CategoryAdmin, delegated},
|
|
{ConfigWrite, CategoryAdmin, delegated},
|
|
{AuditLogsRead, CategoryAdmin, delegated},
|
|
}
|
|
|
|
var definitions = indexCatalog(catalog)
|
|
|
|
func indexCatalog(entries []definition) map[Scope]definition {
|
|
index := make(map[Scope]definition, len(entries))
|
|
for _, entry := range entries {
|
|
index[entry.scope] = entry
|
|
}
|
|
return index
|
|
}
|
|
|
|
// Known reports whether the scope is part of the catalog
|
|
func (s Scope) Known() bool {
|
|
_, ok := definitions[s]
|
|
return ok
|
|
}
|
|
|
|
// GrantableTo reports whether a principal of the given kind can ever hold the scope
|
|
func (s Scope) GrantableTo(kind PrincipalKind) bool {
|
|
return definitions[s].grantableTo&kind != 0
|
|
}
|
|
|
|
// ScopeSet is an unordered set of scopes
|
|
type ScopeSet map[Scope]struct{}
|
|
|
|
// NewScopeSet creates a set containing the given scopes
|
|
func NewScopeSet(scopes ...Scope) ScopeSet {
|
|
set := make(ScopeSet, len(scopes))
|
|
for _, scope := range scopes {
|
|
set[scope] = struct{}{}
|
|
}
|
|
return set
|
|
}
|
|
|
|
// Has reports whether the set contains the scope
|
|
func (s ScopeSet) Has(scope Scope) bool {
|
|
_, ok := s[scope]
|
|
return ok
|
|
}
|
|
|
|
// UserScopes returns the scopes a user holds when authenticated with a credential of the given kind
|
|
// The admin flag stands in for roles: admins hold every scope and other users hold the account scopes
|
|
func UserScopes(isAdmin bool, kind PrincipalKind) ScopeSet {
|
|
set := make(ScopeSet, len(catalog))
|
|
for _, entry := range catalog {
|
|
if entry.grantableTo&kind == 0 {
|
|
continue
|
|
}
|
|
if entry.category == CategoryAdmin && !isAdmin {
|
|
continue
|
|
}
|
|
set[entry.scope] = struct{}{}
|
|
}
|
|
return set
|
|
}
|