primitives

package
v0.7.23 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: BSD-2-Clause Imports: 10 Imported by: 0

Documentation

Overview

Package primitives holds small, general-purpose claim-derivation helpers that credentials can wire in via configuration (see e.g. pkg/model/data_sources.go's PresentationScope.Derivations). Each primitive is a pure function with a matching Args struct + Apply method used by the Derivation dispatcher.

Index

Constants

View Source
const (
	// RandomFormatUUID is a version 4 UUID in canonical textual form.
	RandomFormatUUID = "uuid"
	// RandomFormatHex is lower-case hex over RandomArgs.Bytes random bytes.
	RandomFormatHex = "hex"
	// RandomFormatBase64URL is unpadded base64url over RandomArgs.Bytes
	// random bytes.
	RandomFormatBase64URL = "base64url"
)

Random value formats. The names are the configured `format` values.

Variables

This section is empty.

Functions

func AgeOverThresholds

func AgeOverThresholds(birthdate string, now time.Time, thresholds []int) (map[string]bool, error)

AgeOverThresholds parses birthdate (ISO YYYY-MM-DD) and returns a map containing, for every threshold N: age_over_N (true if the subject had completed N years at `now`) and over_N_this_year (true if the subject reaches N at some point in `now`'s calendar year, i.e. `now.Year() - birthYear >= N`). A zero `now` falls back to time.Now(). Returns an error if birthdate is empty, malformed, or in the future. Callers pick which thresholds are meaningful for their credential type.

func CountryAlpha2

func CountryAlpha2(s string) string

CountryAlpha2 maps a country name or alpha-3 code to its ISO 3166-1 alpha-2 code. Unknown inputs are returned unchanged so an unrecognised value does not silently become empty.

func CountryAlpha3

func CountryAlpha3(s string) string

CountryAlpha3 maps a country name or alpha-2 code to its ISO 3166-1 alpha-3 code. Unknown inputs are returned unchanged.

func FieldNames

func FieldNames() []string

FieldNames returns the YAML names of every primitive field on Derivation in declaration order. Used by gen_config_docs to enumerate primitives.

func LowerCase

func LowerCase(s string) string

LowerCase returns strings.ToLower(s).

func SWAMIDHighestAssuranceLevel

func SWAMIDHighestAssuranceLevel(value any) string

SWAMIDHighestAssuranceLevel reads a SAML eduPersonAssurance claim value (single string, []string, or []any of strings) and returns the strongest recognised SWAMID Assurance Framework URI. Non-SWAMID URIs (including REFEDS IAP) are ignored. Returns "" when the value is missing, of an unsupported type, or holds no SWAMID URI.

func Trim

func Trim(s string) string

Trim returns strings.TrimSpace(s).

func UpperCase

func UpperCase(s string) string

UpperCase returns strings.ToUpper(s).

func YYYYMMDDToISO

func YYYYMMDDToISO(s string) (string, error)

YYYYMMDDToISO converts SCHAC schacDateOfBirth ("YYYYMMDD") to ISO full-date ("YYYY-MM-DD"). time.Parse validates day-of-month, so impossible dates like "20240230" surface as an error instead of being emitted verbatim.

Types

type AgeOverThresholdsArgs

type AgeOverThresholdsArgs struct {
	// Input is the birthdate claim name (value must be ISO YYYY-MM-DD).
	Input string `yaml:"input" validate:"required" doc_example:"birthdate"`

	// Thresholds are the ages (in years) to expose. Each N produces
	// age_over_N and over_N_this_year (booleans). Every entry must be
	// positive.
	Thresholds []int `yaml:"thresholds" validate:"required,min=1,dive,gt=0" doc_example:"[13, 15, 18, 21, 65]"`
}

AgeOverThresholdsArgs configures the age_over_thresholds primitive. Emits two boolean claims per threshold: age_over_N (completed years at `now`) and over_N_this_year (reaches N at some point in `now`'s calendar year, i.e. year(now) - year(birthdate) >= N).

func (*AgeOverThresholdsArgs) Apply

func (a *AgeOverThresholdsArgs) Apply(claims map[string]any, now time.Time) (map[string]any, error)

Apply implements Applier.

type Applier

type Applier interface {
	Apply(claims map[string]any, now time.Time) (map[string]any, error)
}

Applier is the shape every primitive Args struct implements. Every pointer field on Derivation must point at a type implementing Applier; the reflection-based dispatcher below relies on it.

type CountryAlpha2Args

type CountryAlpha2Args struct {
	Input  string `yaml:"input" validate:"required" doc_example:"nationalities"`
	Output string `yaml:"output,omitempty" doc_example:"nationalities"`
}

CountryAlpha2Args configures the country_alpha2 primitive. Unknown inputs pass through unchanged; applied element-wise on []string.

func (*CountryAlpha2Args) Apply

func (a *CountryAlpha2Args) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type CountryAlpha3Args

type CountryAlpha3Args struct {
	Input  string `yaml:"input" validate:"required" doc_example:"nationalities"`
	Output string `yaml:"output,omitempty" doc_example:"nationalities"`
}

CountryAlpha3Args configures the country_alpha3 primitive.

func (*CountryAlpha3Args) Apply

func (a *CountryAlpha3Args) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type Derivation

type Derivation struct {
	// AgeOverThresholds emits two boolean claims per configured threshold
	// N from an ISO YYYY-MM-DD birthdate: age_over_N (completed years at
	// `now`) and over_N_this_year (reaches N at some point in `now`'s
	// calendar year).
	AgeOverThresholds *AgeOverThresholdsArgs `yaml:"age_over_thresholds,omitempty"`

	// Lowercase applies strings.ToLower elementwise.
	Lowercase *LowercaseArgs `yaml:"lowercase,omitempty"`

	// Uppercase applies strings.ToUpper elementwise.
	Uppercase *UppercaseArgs `yaml:"uppercase,omitempty"`

	// Trim applies strings.TrimSpace elementwise.
	Trim *TrimArgs `yaml:"trim,omitempty"`

	// CountryAlpha2 maps country names or alpha-3 codes to ISO 3166-1
	// alpha-2 codes elementwise. Unknown inputs pass through unchanged.
	CountryAlpha2 *CountryAlpha2Args `yaml:"country_alpha2,omitempty"`

	// CountryAlpha3 maps country names or alpha-2 codes to ISO 3166-1
	// alpha-3 codes elementwise. Unknown inputs pass through unchanged.
	CountryAlpha3 *CountryAlpha3Args `yaml:"country_alpha3,omitempty"`

	// YYYYMMDDToISO converts a SCHAC schacDateOfBirth ("YYYYMMDD") claim to
	// ISO full-date ("YYYY-MM-DD"). Impossible calendar dates are an error.
	YYYYMMDDToISO *YYYYMMDDToISOArgs `yaml:"yyyymmdd_to_iso,omitempty"`

	// SWAMIDHighestAssuranceLevel reduces a multi-valued eduPersonAssurance
	// claim to the strongest recognised SWAMID Assurance Framework URI.
	SWAMIDHighestAssuranceLevel *SWAMIDHighestAssuranceLevelArgs `yaml:"swamid_highest_assurance_level,omitempty"`

	// Random writes a freshly generated random value to a claim the source
	// data did not supply - a document identifier, typically. The only
	// primitive here that reads nothing and is not a pure function of its
	// input; see RandomArgs for where the value is generated and how long
	// it lives.
	Random *RandomArgs `yaml:"random,omitempty"`
}

Derivation is one post-verification claim computation configured on a scope; exactly one field must be set and that field's Apply method runs.

Each list entry under a scope's `derivations` field is keyed by primitive name (e.g. `age_over_thresholds:` or `lowercase: { input: email }`). The subsections below catalog the primitives and their parameters.

func (*Derivation) Apply

func (d *Derivation) Apply(claims map[string]any, now time.Time) (map[string]any, error)

Apply runs whichever primitive is configured on d. Returns an error when zero or more than one field is set; the struct-level validator catches that case at config load time, but Apply stays defensive for callers that build a Derivation programmatically (tests, bootstrapping tools).

func (*Derivation) SelectedName

func (d *Derivation) SelectedName() string

SelectedName returns the YAML key of the configured primitive, or "" when zero or many fields are set. Provided for error-context callers.

type LowercaseArgs

type LowercaseArgs struct {
	// Input is the source claim name. Supports dot-notation claim paths
	// (e.g. "identity.email") so this primitive can target the same nested
	// claims that AttributeMapper materialises.
	Input string `yaml:"input" validate:"required" doc_example:"email"`

	// Output is the target claim name. Defaults to Input (in-place).
	// Supports dot-notation claim paths.
	Output string `yaml:"output,omitempty" doc_example:"email"`
}

LowercaseArgs configures the lowercase primitive. Scalar strings and []string are handled; other types are an error.

func (*LowercaseArgs) Apply

func (a *LowercaseArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type RandomArgs added in v0.7.23

type RandomArgs struct {
	// Output is the target claim name. Supports dot-notation claim paths
	// (e.g. "identity.document_id") so this primitive can target the same
	// nested claims that AttributeMapper materialises.
	//
	// There is no Input: this primitive reads nothing.
	Output string `yaml:"output" validate:"required" doc_example:"document_id"`

	// Format selects the shape of the value: "uuid" (the default), "hex" or
	// "base64url".
	Format string `yaml:"format,omitempty" validate:"omitempty,oneof=uuid hex base64url" doc_example:"uuid"`

	// Bytes is how many random bytes back a "hex" or "base64url" value,
	// between 8 and 64. Ignored for "uuid", whose length the format fixes.
	// Defaults to 16.
	Bytes int `yaml:"bytes,omitempty" validate:"omitempty,min=8,max=64" doc_example:"16"`

	// Overwrite replaces a value the source data already supplied.
	//
	// False by default, and deliberately: this primitive exists to fill a
	// claim authentication could not provide, and silently replacing a real
	// identifier with a random one is a worse failure than leaving the
	// derivation with nothing to do. Set it only where the claim is meant to
	// be random every time regardless of what arrived.
	Overwrite bool `yaml:"overwrite,omitempty"`
}

RandomArgs configures the random primitive.

It writes a freshly generated random value to a claim the source data did not supply. The use case is a claim a credential requires but authentication cannot provide - a document identifier that has to be unique across issued credentials being the one this was written for (SUNET/vc#736).

Where the value is generated decides how long it lives, because a derivation runs where its data source runs it:

  • assertion sources (SAML, OIDC) run derivations in the ACS or callback, BEFORE the document is cached, so one value is generated per authentication and every credential issued from that session carries it - including each credential of a batch.
  • other sources run derivations per credential request, so each request mints a new value, and a re-issuance will not match the first.

For an identifier meant to name a credential dataset, the first is what is wanted, which is also the case the issue describes.

Presentation sources are refused at config load. They run derivations twice from the same verified claims, with nothing carried between them - once to build the consent preview and again to build the credential - so a non-deterministic primitive would show the holder one identifier and issue another. Every other primitive is a function of its input and so agrees across both runs. See configuration.checkRandomDerivations.

Nothing here checks that a value has not been issued before. Uniqueness rests on the generator: a version 4 UUID has 122 random bits, and hex and base64url take theirs from crypto/rand. That is the same basis every other identifier in this system uses, and it is a statistical claim rather than an enforced one.

func (*RandomArgs) Apply added in v0.7.23

func (a *RandomArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type SWAMIDHighestAssuranceLevelArgs

type SWAMIDHighestAssuranceLevelArgs struct {
	// Input is the source claim (string or list of strings).
	Input string `yaml:"input" validate:"required" doc_example:"assurance_level"`

	// Output is the target claim. Defaults to Input (in-place).
	Output string `yaml:"output,omitempty" doc_example:"assurance_level"`
}

SWAMIDHighestAssuranceLevelArgs configures the swamid_highest_assurance_level primitive. Reduces a multi-valued eduPersonAssurance claim to the strongest recognised SWAMID URI; hard errors if the input is present but no SWAMID URI is recognised.

func (*SWAMIDHighestAssuranceLevelArgs) Apply

func (a *SWAMIDHighestAssuranceLevelArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type TrimArgs

type TrimArgs struct {
	Input  string `yaml:"input" validate:"required" doc_example:"name"`
	Output string `yaml:"output,omitempty" doc_example:"name"`
}

TrimArgs configures the trim primitive.

func (*TrimArgs) Apply

func (a *TrimArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type UppercaseArgs

type UppercaseArgs struct {
	Input  string `yaml:"input" validate:"required" doc_example:"country"`
	Output string `yaml:"output,omitempty" doc_example:"country"`
}

UppercaseArgs configures the uppercase primitive.

func (*UppercaseArgs) Apply

func (a *UppercaseArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

type YYYYMMDDToISOArgs

type YYYYMMDDToISOArgs struct {
	// Input is the source claim name (must be a non-empty YYYYMMDD string).
	Input string `yaml:"input" validate:"required" doc_example:"birthdate"`

	// Output is the target claim name. Defaults to Input.
	Output string `yaml:"output,omitempty" doc_example:"birthdate"`
}

YYYYMMDDToISOArgs configures the yyyymmdd_to_iso primitive. Impossible calendar dates surface as a hard error.

func (*YYYYMMDDToISOArgs) Apply

func (a *YYYYMMDDToISOArgs) Apply(claims map[string]any, _ time.Time) (map[string]any, error)

Apply implements Applier.

Jump to

Keyboard shortcuts

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