Skip to content

feat: SPOCP-based issuance policy evaluation with dynamic OIDC params#380

Open
leifj wants to merge 5 commits into
SUNET:mainfrom
sirosfoundation:feat/spocp-issuance-policy
Open

feat: SPOCP-based issuance policy evaluation with dynamic OIDC params#380
leifj wants to merge 5 commits into
SUNET:mainfrom
sirosfoundation:feat/spocp-issuance-policy

Conversation

@leifj

@leifj leifj commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements conditional credential issuance using the SPOCP policy engine, allowing authentic source business systems to pass dynamic parameters through the OIDC authorization flow.

Closes #379

Changes

New: pkg/issuance/ — Policy engine and S-expression parser

  • policy.go: PolicyEngine wrapping spocp.AdaptiveEngine with Evaluate(scope, claims, queryTemplate) for checking OIDC claims against SPOCP rules. BuildQuery() constructs S-expression queries from claims.
  • parser.go: Human-readable S-expression parser (ParseAdvancedSExp) supporting star forms: wildcard (*), prefix (* prefix urn:example:), suffix (* suffix @example.com), set (* set loa3 loa4).
  • policy_test.go + parser_test.go: 27 tests covering simple match/deny, wrong scope, wildcard rules, prefix/suffix/set star forms, multiple rules, missing claims, boolean coercion, query building, and parser edge cases.

Model types in pkg/model/data_sources.go

  • OIDCRequestParams — per-scope OIDC authorization request customization (acr_values, claims, extra_scopes, custom_params)
  • IssuancePolicy — SPOCP rules (inline + file) with query template mapping
  • ScopePolicyConfig — combined config struct
  • LookupScopePolicyConfig() helper on DataSources

Integration

  • OIDC RP service (service.go): InitiateAuth/InitiateAuthForVCI accept OIDCRequestParams + DynamicParams; resolveOIDCRequestParams() builds oauth2.AuthCodeOption list with Go template resolution for dynamic values
  • OIDC callback (handlers_oidcrp.go): Policy evaluation block after claims retrieval — denied claims return hard error
  • PAR handler (handlers_oauth.go): Stores DynamicParams from PARRequest into AuthorizationContext
  • Consent flow (endpoints_oauth.go): Forwards dynamic params from AuthorizationContext to InitiateAuthForVCI
  • Session/cache: DynamicParams field added to OIDC session and AuthorizationContext

Configuration Example

data_sources:
  assertion:
    scopes:
      org_credential:
        oidc_request_params:
          acr_values: "urn:example:loa3"
          claims: '{"id_token":{"org_id":{"value":"{{.org_id}}"}}}'

Implement conditional credential issuance using the SPOCP policy engine,
allowing authentic source business systems to pass dynamic parameters
through the OIDC authorization flow.

New components:
- pkg/issuance/policy.go: PolicyEngine wrapping spocp.AdaptiveEngine
  with Evaluate() for checking claims against rules
- pkg/issuance/parser.go: Human-readable S-expression parser supporting
  star forms (wildcard, prefix, suffix, set)
- Model types: OIDCRequestParams, IssuancePolicy, ScopePolicyConfig
  in pkg/model/data_sources.go

Integration points:
- OIDC RP InitiateAuth/InitiateAuthForVCI accept dynamic params and
  OIDC request parameters with template resolution
- OIDCRPCallback evaluates issuance policy against OIDC claims
- PAR handler stores DynamicParams in AuthorizationContext
- Consent flow forwards dynamic params to OIDC auth initiation

Closes SUNET#379

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements per-credential-scope issuance gating and OIDC authorization-request customization by introducing (1) dynamic parameter propagation from PAR → auth context/session and (2) SPOCP-based post-login policy evaluation of OIDC claims in the OIDC RP callback.

Changes:

  • Adds DynamicParams to PAR requests, authorization context, and OIDC sessions; forwards them into OIDC auth initiation for template substitution.
  • Introduces OIDCRequestParams and IssuancePolicy scope configuration (plus a cross-data-source lookup helper) to drive OIDC request customization and SPOCP rules.
  • Adds a new pkg/issuance package with an “advanced-form” S-expression parser and policy engine wrapper; integrates evaluation into the OIDC callback path.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
pkg/openid4vci/authoriziation.go Extends PAR request model with DynamicParams.
pkg/model/data_sources.go Adds per-scope OIDCRequestParams + IssuancePolicy config models and LookupScopePolicyConfig().
pkg/issuance/policy.go New SPOCP policy engine wrapper + query builder + optional rules file loading.
pkg/issuance/policy_test.go Unit tests for policy engine behavior and query building.
pkg/issuance/parser.go New “advanced form” S-expression parser with star-form support.
pkg/issuance/parser_test.go Unit tests for the advanced S-expression parser.
pkg/cache/authcontext_types.go Adds DynamicParams persistence to AuthorizationContext.
internal/apigw/httpserver/endpoints_oauth.go Forwards per-scope OIDC params and stored dynamic params into OIDC auth initiation (VCI consent flow).
internal/apigw/auth_providers/oidcrp/session.go Persists DynamicParams into the OIDC RP session model.
internal/apigw/auth_providers/oidcrp/service.go Extends OIDC initiation to accept per-scope params + dynamic params; resolves templates into AuthCodeURL options.
internal/apigw/apiv1/handlers_oidcrp.go Evaluates issuance policy after claims retrieval (pre-issuance).
internal/apigw/apiv1/handlers_oauth.go Stores DynamicParams from PAR into the authorization context.
Comments suppressed due to low confidence (1)

internal/apigw/auth_providers/oidcrp/service.go:152

  • InitiateAuth’s signature change is a breaking API change; current repo call sites still use the old signature (e.g., internal/apigw/integration/oidc_integration_test.go calls InitiateAuth(ctx, "pid")). Update those callers (and any external consumers) or provide a backward-compatible wrapper to avoid build failures.
// InitiateAuth initiates an OIDC authentication flow.
// oidcParams and dynamicParams are optional: when non-nil, they customize the
// authorization request (e.g., acr_values, claims parameter, extra scopes).
func (s *Service) InitiateAuth(ctx context.Context, credentialType string, oidcParams *model.OIDCRequestParams, dynamicParams map[string]string) (*AuthRequest, error) {
	s.log.Debug("Initiating OIDC auth",
		"credential_type", credentialType)

	// Create session with state, nonce, and PKCE verifier
	session, err := s.createSession(ctx, credentialType)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/issuance/policy.go Outdated
Comment thread pkg/issuance/policy.go
Comment thread internal/apigw/auth_providers/oidcrp/service.go
Comment thread internal/apigw/apiv1/handlers_oidcrp.go
Comment thread internal/apigw/apiv1/handlers_oidcrp.go
Comment thread pkg/spocputil/parser.go
Comment thread pkg/cache/authcontext_types.go Outdated
Comment thread pkg/model/data_sources.go Outdated
Leif Johansson added 4 commits May 8, 2026 15:05
- Change QueryTemplate from map to ordered []QueryDimension slice to
  ensure deterministic SPOCP query dimension ordering (positional match)
- Sort claim keys in default (no-template) query path
- Cache PolicyEngine per scope via sync.Map to avoid per-request parsing
- Merge session DynamicParams into policy evaluation claims
- Add validation constraints on DynamicParams (max key=64, value=1024)
- Fix CustomParams doc: keys are static, only values support templates
Move the duplicated advancedParser implementation from both
pkg/issuance/parser.go and pkg/httphelpers/middleware_jwt.go
into a new shared pkg/spocputil package.

This eliminates 19.8% new-code duplication that was failing the
SonarCloud quality gate (threshold: 3%).
Merge TestEvaluate_PrefixStarForm and TestEvaluate_SetStarForm into a
single TestEvaluate_StarForms table-driven test to eliminate internal
duplication flagged by SonarCloud CPD.
Resolve conflict in pkg/httphelpers/middleware_jwt.go:
- Keep parser refactoring in pkg/spocputil (branch approach)
- Adopt upstream's expanded BuildSPOCPQuery (6-param, exported)
- Adopt upstream's extractSPOCPSubject helper
- Add wildcard/prefix star form handling in spocputil.parseList()
  to match upstream's inline parser behaviour
- Add starform import and spocputil import where needed
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: SPOCP-based issuance policy and dynamic OIDC request parameters

2 participants