requestguard

package
v0.0.0-...-d09923a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2026 License: LGPL-2.1 Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ResourceScopeTenant  = ResourceScope("tenant")
	ResourceScopeOrg     = ResourceScope("org")
	ResourceScopeOrgUnit = ResourceScope("orgunit")
	ResourceScopePrivate = ResourceScope("private")
)
View Source
const FieldSeparator = "."

FieldSeparator splits a field-level resource segment `{resource}.{field}`.

View Source
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

func AssertAnyFieldPermission(ctx corectx.Context, perm Perm, fieldNames []string) *ft.ClientErrors

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

func HasPermission(ctx corectx.Context, requiredPerm Perm) bool

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

func ReadableFields(ctx corectx.Context, perm Perm, fieldNames []string) []string

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

func ResourceSegment(resourceCode string, fieldName string) string

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 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

func (this Perm) ForField(fieldName string) Perm

ForField narrows the requirement to one field of the resource; an empty name widens it back to the whole resource.

func (Perm) InOrg

func (this Perm) InOrg(orgId *model.Id) Perm

InOrg names the org the record belongs to.

func (Perm) InOrgUnit

func (this Perm) InOrgUnit(orgUnitId *model.Id) Perm

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

func (this Perm) OwnedByCaller(owned bool) Perm

OwnedByCaller marks the record as the caller's own, which is what a private scope grant is about.

type PermissionContext

type PermissionContext struct {
	IsOwner      bool
	Entitlements []string
}

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 (this *StaticRequestGuardServiceImpl) CalcRequestFingerprint(_ corectx.Context, request *http.Request) (fingerprint string, err error)

func (*StaticRequestGuardServiceImpl) GetCorsMiddleware

func (this *StaticRequestGuardServiceImpl) GetCorsMiddleware(_ corectx.Context) (echo.MiddlewareFunc, error)

func (*StaticRequestGuardServiceImpl) GetUserEntitlements

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
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL