package lib import ( "context" "atlas9.dev/c/core" "atlas9.dev/c/core/iam" ) // Access holds the resolved permissions for a request. // The zero value denies everything. type Access struct { admin bool grants []accessGrant } type accessGrant struct { // system grants cover all tenants. // For non-system grants, tenant + path form the full scope. system bool tenant core.ID path core.Path cap iam.Cap } func (a *Access) Authenticated() { a.GrantSystem(iam.CapTenantsCreate) } func (a *Access) Grant(cap iam.Cap, tenant core.ID, path core.Path) { a.grants = append(a.grants, accessGrant{ tenant: tenant, path: path, cap: cap, }) } func (a *Access) GrantSystem(caps ...iam.Cap) { for _, cap := range caps { a.grants = append(a.grants, accessGrant{ system: true, cap: cap, }) } } func AdminAccess() Access { return Access{admin: true} } func (a *Access) Check(cap iam.Cap, tenant core.ID, path core.Path) error { if a == nil { return iam.ErrForbidden } if a.admin { return nil } if tenant.IsEmpty() { return iam.ErrForbidden } for _, g := range a.grants { if g.cap != cap { continue } if g.system { return nil } if g.tenant != tenant { continue } if g.path.Contains(path) { return nil } } return iam.ErrForbidden } func (a *Access) Partial(cap iam.Cap) error { if a == nil { return iam.ErrForbidden } if a.admin { return nil } for _, g := range a.grants { if g.cap == cap { return nil } } return iam.ErrForbidden } func (a *Access) System(cap iam.Cap) error { if a == nil { return iam.ErrForbidden } if a.admin { return nil } for _, g := range a.grants { if g.system && g.cap == cap { return nil } } return iam.ErrForbidden } type accessContextKey struct{} func GetAccess(ctx context.Context) *Access { a, _ := ctx.Value(accessContextKey{}).(*Access) return a } func PutAccess(ctx context.Context, a Access) context.Context { return context.WithValue(ctx, accessContextKey{}, &a) } func PutSystemAccess(ctx context.Context, caps ...iam.Cap) context.Context { var access Access access.GrantSystem(caps...) return PutAccess(ctx, access) } type ContextAccessGuard struct{} func (ContextAccessGuard) Check(ctx context.Context, cap iam.Cap, tenant core.ID, path core.Path) error { return GetAccess(ctx).Check(cap, tenant, path) } func (ContextAccessGuard) Partial(ctx context.Context, cap iam.Cap) error { return GetAccess(ctx).Partial(cap) } func (ContextAccessGuard) System(ctx context.Context, cap iam.Cap) error { return GetAccess(ctx).System(cap) } type AccessStore interface { Load(ctx context.Context, principalID core.ID, out *Access) error }