Documentation
¶
Index ¶
- Constants
- func AssertAnyFieldPermission(ctx corectx.Context, perm Perm, fieldNames []string) *ft.ClientErrors
- func AssertFieldNamesPermission(ctx corectx.Context, perm Perm, fieldNames []string) *ft.ClientErrors
- func AssertFieldsPermission(ctx corectx.Context, perm Perm, fields dmodel.DynamicFields) *ft.ClientErrors
- func AssertPermission(ctx corectx.Context, requiredPerm Perm) *ft.ClientErrors
- func BuildExpression(actionCode string, resourceCode string, scope ResourceScope, scopeId *model.Id) string
- func BuildFieldExpression(actionCode string, resourceCode string, fieldName string, scope ResourceScope, ...) string
- func CandidateExpressions(required Perm, evalCtx EvalContext) []string
- func HasPermission(ctx corectx.Context, requiredPerm Perm) bool
- func IsKnownScope(scope ResourceScope) bool
- func OmnipotentExpression() string
- func ReadableFields(ctx corectx.Context, perm Perm, fieldNames []string) []string
- func ResourceSegment(resourceCode string, fieldName string) string
- type EvalContext
- type ExtGetUserEntitlementsQuery
- type ExtGetUserEntitlementsResult
- type ExtGetUserEntitlementsResultData
- type GetUserEntitlementsQuery
- type GetUserEntitlementsResult
- type GetUserEntitlementsResultData
- type ParsedExpression
- type Perm
- type PermissionContext
- type RequestGuardService
- type ResourceScope
- type StaticRequestGuardServiceImpl
- func (this *StaticRequestGuardServiceImpl) CalcRequestFingerprint(_ corectx.Context, request *http.Request) (fingerprint string, err error)
- func (this *StaticRequestGuardServiceImpl) GetCorsMiddleware(_ corectx.Context) (echo.MiddlewareFunc, error)
- func (this *StaticRequestGuardServiceImpl) GetUserEntitlements(ctx corectx.Context, query GetUserEntitlementsQuery) (*GetUserEntitlementsResult, error)
- func (this *StaticRequestGuardServiceImpl) VerifyJwtDpop(ctx corectx.Context, request *http.Request) (*VerifyRequestResult, error)
- func (this *StaticRequestGuardServiceImpl) VerifyServiceAccessToken(ctx corectx.Context, request *http.Request, allowedScopes []string) (*VerifyRequestResult, error)
- func (this *StaticRequestGuardServiceImpl) VerifySessionBlacklist(ctx corectx.Context, request *http.Request) (*VerifyRequestResult, error)
- func (this *StaticRequestGuardServiceImpl) VerifyUserAccessToken(ctx corectx.Context, request *http.Request) (*VerifyRequestResult, error)
- type StaticRequestGuardServiceParams
- type VerifyRequestResult
Constants ¶
const ( ResourceScopeTenant = ResourceScope("tenant") ResourceScopeOrg = ResourceScope("org") ResourceScopeOrgUnit = ResourceScope("orgunit") ResourceScopePrivate = ResourceScope("private") )
const FieldSeparator = "."
FieldSeparator splits a field-level resource segment `{resource}.{field}`.
const Wildcard = "*"
Wildcard is the token that matches any action or any resource in an entitlement expression.
Variables ¶
This section is empty.
Functions ¶
func AssertAnyFieldPermission ¶
AssertAnyFieldPermission is the action gate of a field-aware operation: granted when the caller holds perm on the whole resource or on at least one of fieldNames. What the caller may then touch is narrowed per field by AssertFieldNamesPermission / ReadableFields. A refusal reports the resource-level expression, which is the one grant that would answer every field.
func AssertFieldNamesPermission ¶
func AssertFieldNamesPermission(ctx corectx.Context, perm Perm, fieldNames []string) *ft.ClientErrors
AssertFieldNamesPermission is AssertFieldsPermission over a list of field names. No name means nothing to authorize beyond an authenticated principal: deciding whether the action itself is allowed is AssertPermission's or AssertAnyFieldPermission's job.
func AssertFieldsPermission ¶
func AssertFieldsPermission(ctx corectx.Context, perm Perm, fields dmodel.DynamicFields) *ft.ClientErrors
AssertFieldsPermission authorizes perm on every key of fields - the field map a caller writes or asks for. perm names the action, resource and scope; its FieldName is ignored. The error lists every missing field expression at once, so one 403 tells an administrator everything to grant.
func AssertPermission ¶
func AssertPermission(ctx corectx.Context, requiredPerm Perm) *ft.ClientErrors
AssertPermission answers whether the caller may perform requiredPerm.
It holds no rules of its own: it asks CandidateExpressions which stored expressions would answer, and checks whether the caller holds any of them. The SQL matcher and the permission probe ask the same function, which is what keeps the three of them from drifting into disagreeing about the same question.
func BuildExpression ¶
func BuildExpression(actionCode string, resourceCode string, scope ResourceScope, scopeId *model.Id) string
An entitlement expression has the shape `{action}:{resource}[.{field}]:{scope}[/{scopeId}]` where action and resource may be the wildcard `*`, the optional field narrows the grant to one field of the resource, and the scope segment carries an optional id for the org / orgunit scopes.
BuildExpression and BuildFieldExpression are the ONLY producers of expressions in the codebase: the entitlement model persists what they return, the guard matches against what they return, and the SQL matcher builds its IN-list from it. Any second implementation is a defect - the two sides silently drift apart (see plan D1/D4).
func BuildFieldExpression ¶
func BuildFieldExpression( actionCode string, resourceCode string, fieldName string, scope ResourceScope, scopeId *model.Id, ) string
BuildFieldExpression is BuildExpression for a field-level grant. An empty fieldName builds the resource-level expression. A field-level expression always names its resource: a field of "any resource" means nothing, so the caller must not pass a wildcard resource with a field.
func CandidateExpressions ¶
func CandidateExpressions(required Perm, evalCtx EvalContext) []string
CandidateExpressions returns every stored expression that would satisfy the required permission for this caller. Holding ANY of them means "allowed".
This is the single definition of the evaluation semantics. The in-memory guard tests set membership against it; the SQL matcher turns it into an IN-list; the permission probe range-scans the cache with it. Because all three consume the same slice they cannot disagree - which is the whole point, since a permission system whose "can I?" and "may I?" answers differ is a security defect, not an inconsistency.
A field-level requirement (Perm.FieldName set) is answered by every resource-level candidate - a grant on the whole resource covers each of its fields - plus the field-level grants at the same scopes. Field grants are additive and never widen to other fields or resources, so they carry no resource wildcard.
Scope widening runs tenant > org > orgunit: a wider grant satisfies a narrower requirement. There is deliberately NO inheritance between org units - a grant on a parent unit does not reach its children, which is the documented entitlement semantics and what makes a unit grant auditable.
func HasPermission ¶
HasPermission is AssertPermission as a yes/no answer.
func IsKnownScope ¶
func IsKnownScope(scope ResourceScope) bool
IsKnownScope reports whether the scope is one this system evaluates.
func OmnipotentExpression ¶
func OmnipotentExpression() string
OmnipotentExpression grants everything, everywhere. It is the only expression whose scope segment is a wildcard.
func ReadableFields ¶
ReadableFields returns the subset of fieldNames the caller holds perm on, in input order. It is named for reads, its main use, but answers for whatever action perm names.
func ResourceSegment ¶
ResourceSegment is the middle segment of an expression: the resource code, followed by the field name when there is one.
Types ¶
type EvalContext ¶
type EvalContext struct {
// Orgs the caller belongs to.
UserOrgIds []model.Id
// The org unit the caller belongs to, if any.
OrgUnitId *model.Id
// The org that OrgUnitId belongs to. Needed for the orgunit -> org fallback.
OrgUnitOrgId *model.Id
}
EvalContext is what the evaluator knows about the *caller*, as opposed to Perm which describes the *record* being reached for. Both sides are needed: a bare `org` grant only answers when the caller is a member of the record's org.
func EvalContextFrom ¶
func EvalContextFrom(userPerm corectx.ContextPermissions) EvalContext
EvalContextFrom lifts the caller's org and unit membership out of the request context, so callers of CandidateExpressions do not each reach into it.
type ExtGetUserEntitlementsQuery ¶
type ExtGetUserEntitlementsQuery struct {
UserId *model.Id `json:"user_id"`
UserEmail *string `json:"user_email"`
}
func (ExtGetUserEntitlementsQuery) CqrsRequestType ¶
func (ExtGetUserEntitlementsQuery) CqrsRequestType() cqrs.RequestType
type ExtGetUserEntitlementsResult ¶
type ExtGetUserEntitlementsResult = dyn.OpResult[ExtGetUserEntitlementsResultData]
type ExtGetUserEntitlementsResultData ¶
type ExtGetUserEntitlementsResultData struct {
IsOwner bool `json:"is_owner"`
Entitlements []string `json:"entitlements"`
OrgUnitId *model.Id `json:"org_unit_id"`
OrgUnitOrgId *model.Id `json:"org_unit_org_id"`
UserId model.Id `json:"user_id"`
UserOrgIds []model.Id `json:"user_org_ids"`
User dmodel.DynamicFields `json:"user"`
}
type GetUserEntitlementsQuery ¶
type GetUserEntitlementsQuery = ExtGetUserEntitlementsQuery
type GetUserEntitlementsResult ¶
type GetUserEntitlementsResult = dyn.OpResult[GetUserEntitlementsResultData]
type GetUserEntitlementsResultData ¶
type GetUserEntitlementsResultData = ExtGetUserEntitlementsResultData
type ParsedExpression ¶
type ParsedExpression struct {
ActionCode string
ResourceCode string
// FieldName is empty for a resource-level expression.
FieldName string
Scope ResourceScope
ScopeId *model.Id
}
ParsedExpression is the decomposition of an entitlement expression.
func ParseExpression ¶
func ParseExpression(expr string) (*ParsedExpression, error)
ParseExpression decomposes an entitlement expression. It is strict on purpose: callers hand it untrusted input (the permission probe accepts an expression in a request body), so every malformed shape must come back as a plain error the transport turns into a 400, never a panic.
func (ParsedExpression) HasWildcard ¶
func (this ParsedExpression) HasWildcard() bool
HasWildcard reports whether the expression grants across all actions or all resources. A *question* asked of the permission probe must never have one: a real requirement is always concrete, and a wildcard question would turn the probe into a grant-enumeration tool.
func (ParsedExpression) String ¶
func (this ParsedExpression) String() string
String rebuilds the canonical expression, so that parsing then rebuilding is the identity for every valid input.
type Perm ¶
type Perm struct {
ResourceCode string
// FieldName narrows the requirement to one field of the resource. Empty means the whole
// resource. See CandidateExpressions for what answers a field-level requirement.
FieldName string
ActionCode string
Scope ResourceScope
// This is Org Unit ID to which the resource belongs (if any).
// If this is not nil, then OrgId must be this Org Unit's Org ID.
OrgUnitId *model.Id
// This is the Org ID to which the resource belongs (if any)
// Or, this can be the Org Unit's Org ID (if the resource belongs to an org unit)
OrgId *model.Id
// IsRecordOwnedByCaller answers the private scope: the caller may act on their
// own record. Callers whose scope is private must set it; leaving it false
// simply means "not the caller's record", which denies.
IsRecordOwnedByCaller bool
}
func PermFor ¶
func PermFor(actionCode string, resourceCode string, scope ResourceScope) Perm
PermFor starts a Perm for the given action on the given resource. Use the InOrg / InOrgUnit / OwnedByCaller builders to attach the record's context - an org- or unit-scoped check without it can only ever match an exact or tenant grant, which is how org-scoped checks silently degraded before.
func (Perm) ForField ¶
ForField narrows the requirement to one field of the resource; an empty name widens it back to the whole resource.
func (Perm) InOrgUnit ¶
InOrgUnit names the org unit the record belongs to. Pass the unit's org as well via InOrg so that the org-level fallback can apply.
func (Perm) OwnedByCaller ¶
OwnedByCaller marks the record as the caller's own, which is what a private scope grant is about.
type PermissionContext ¶
type RequestGuardService ¶
type RequestGuardService interface {
// Calculate a fingerprint for the request that can be used to identify the request.
// This can be used for caching or to prevent replay attacks.
CalcRequestFingerprint(ctx corectx.Context, request *http.Request) (fingerprint string, err error)
GetCorsMiddleware(ctx corectx.Context) (echo.MiddlewareFunc, error)
GetUserEntitlements(ctx corectx.Context, query GetUserEntitlementsQuery) (*GetUserEntitlementsResult, error)
// Verifies the user access token carried by the header named in
// CORE.REQUEST_GUARD.ACCESS_TOKEN_HTTP_HEADER_NAME.
VerifyUserAccessToken(ctx corectx.Context, request *http.Request) (result *VerifyRequestResult, err error)
// Verifies a service token carried by the header named in
// CORE.REQUEST_GUARD.SERVICE_TOKEN_HTTP_HEADER_NAME; its "scope" claim must contain at least one
// of allowedScopes.
VerifyServiceAccessToken(
ctx corectx.Context, request *http.Request, allowedScopes []string,
) (result *VerifyRequestResult, err error)
}
func NewStaticRequestGuardServiceImpl ¶
func NewStaticRequestGuardServiceImpl(params StaticRequestGuardServiceParams) RequestGuardService
type ResourceScope ¶
type ResourceScope string
type StaticRequestGuardServiceImpl ¶
type StaticRequestGuardServiceImpl struct {
// contains filtered or unexported fields
}
func (*StaticRequestGuardServiceImpl) CalcRequestFingerprint ¶
func (*StaticRequestGuardServiceImpl) GetCorsMiddleware ¶
func (this *StaticRequestGuardServiceImpl) GetCorsMiddleware(_ corectx.Context) (echo.MiddlewareFunc, error)
func (*StaticRequestGuardServiceImpl) GetUserEntitlements ¶
func (this *StaticRequestGuardServiceImpl) GetUserEntitlements( ctx corectx.Context, query GetUserEntitlementsQuery, ) (*GetUserEntitlementsResult, error)
func (*StaticRequestGuardServiceImpl) VerifyJwtDpop ¶
func (this *StaticRequestGuardServiceImpl) VerifyJwtDpop(ctx corectx.Context, request *http.Request) (*VerifyRequestResult, error)
Verify JWT DPoP (OAuth2 Demonstraing Proof of Possession)
func (*StaticRequestGuardServiceImpl) VerifyServiceAccessToken ¶
func (this *StaticRequestGuardServiceImpl) VerifyServiceAccessToken( ctx corectx.Context, request *http.Request, allowedScopes []string, ) (*VerifyRequestResult, error)
func (*StaticRequestGuardServiceImpl) VerifySessionBlacklist ¶
func (this *StaticRequestGuardServiceImpl) VerifySessionBlacklist(ctx corectx.Context, request *http.Request) (*VerifyRequestResult, error)
func (*StaticRequestGuardServiceImpl) VerifyUserAccessToken ¶
func (this *StaticRequestGuardServiceImpl) VerifyUserAccessToken( ctx corectx.Context, request *http.Request, ) (*VerifyRequestResult, error)
type StaticRequestGuardServiceParams ¶
type StaticRequestGuardServiceParams struct {
dig.In
ConfigSvc config.ConfigService
CqrsBus cqrs.CqrsBus
TokenSvc coretoken.AuthTokenService
}
type VerifyRequestResult ¶
type VerifyRequestResult struct {
IsOk bool
JwtClaims jwt.Claims
ClientError *ft.ClientErrorItem
}