scim

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package scim is the pure SCIM 2.0 wire layer Wardyn serves to an identity provider's provisioning client: types, the one filter form, PatchOp parsing (including the non-RFC shapes Entra sends) and the RFC 7644 error envelope. It has no HTTP handlers, no store and no routes.

Index

Constants

View Source
const (
	TypeInvalidFilter = "invalidFilter"
	TypeInvalidSyntax = "invalidSyntax"
	TypeInvalidPath   = "invalidPath"
	TypeInvalidValue  = "invalidValue"
	TypeNoTarget      = "noTarget"
)

scimType values of the RFC 7644 error envelope (section 3.12) this package produces.

View Source
const (
	AttrActive      = "active"
	AttrUserName    = "userName"
	AttrExternalID  = "externalId"
	AttrDisplayName = "displayName"
	AttrEmail       = "emails.value"
	AttrMembers     = "members"
)

Canonical attribute names of the attributes Wardyn acts on. Op.Attr is one of these, or, for any other attribute, the path exactly as the client sent it.

View Source
const (
	SchemaUser         = "urn:ietf:params:scim:schemas:core:2.0:User"
	SchemaGroup        = "urn:ietf:params:scim:schemas:core:2.0:Group"
	SchemaListResponse = "urn:ietf:params:scim:api:messages:2.0:ListResponse"
	SchemaPatchOp      = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
	SchemaError        = "urn:ietf:params:scim:api:messages:2.0:Error"
	MediaType          = "application/scim+json"
)

Schema URNs and the media type of every SCIM body.

Variables

This section is empty.

Functions

func ParseFilter

func ParseFilter(s string, allowed ...string) (Filter, *Error)

ParseFilter parses `attr eq "value"` where attr is one of allowed (compared case-insensitively, as SCIM attribute names are) and value is a JSON string literal. Anything else, including `and`/`or`, other operators, unquoted values and unlisted attributes, is invalidFilter. A client that sends an unquoted value (a Microsoft sample shows one) is refused too: RFC 7644 requires the quotes.

func ParseGroup

func ParseGroup(body []byte) (*Group, *Error)

ParseGroup parses a POST /Groups body. displayName and externalId are required (invalidValue).

func ParsePatch

func ParsePatch(body []byte) ([]Op, *PatchError)

ParsePatch is the pre-validation pass: it resolves every operation of the PATCH before the caller applies any, so a request with one invalid operation yields a *PatchError listing each bad operation by index and no ops at all. It never returns a partial result. Whether a well-formed operation is allowed (an externalId change on a bound identity, say) is the caller's decision, per Op.

func ParseUser

func ParseUser(body []byte) (*User, *Error)

ParseUser parses a POST /Users body or a PUT /Users/{id} replacement. userName and externalId are required (invalidValue). An omitted active means true; Entra's strings "True" and "False" are accepted. The client's id and meta are read-only and dropped.

Types

type Email

type Email struct {
	Value   string `json:"value"`
	Type    string `json:"type,omitempty"`
	Primary bool   `json:"primary,omitempty"`
}

Email is one entry of a User's emails.

type Error

type Error struct {
	Status   int
	ScimType string
	Detail   string
}

Error is the RFC 7644 error envelope. Status is the HTTP status the handler answers with; the wire form carries it as a string, as the RFC's examples do.

func NewError

func NewError(status int, scimType, detail string) *Error

NewError builds an envelope for status and scimType (empty for the types the RFC leaves untyped, such as 404).

func (*Error) Error

func (e *Error) Error() string

func (*Error) MarshalJSON

func (e *Error) MarshalJSON() ([]byte, error)

MarshalJSON renders the wire envelope.

type Filter

type Filter struct {
	// Attr is spelled as in the allowed list passed to ParseFilter, whatever case the client used.
	Attr  string
	Value string
}

Filter is the one filter form Wardyn serves: `attr eq "value"`.

type Group

type Group struct {
	Schemas     []string `json:"schemas"`
	ID          string   `json:"id,omitempty"`
	ExternalID  string   `json:"externalId"`
	DisplayName string   `json:"displayName"`
	Members     []Member `json:"members,omitempty"`
	Meta        *Meta    `json:"meta,omitempty"`
}

Group is the SCIM Group subset Wardyn serves. ExternalID is required for the same reason as User's.

type ListResponse

type ListResponse[T any] struct {
	Schemas      []string `json:"schemas"`
	TotalResults int      `json:"totalResults"`
	StartIndex   int      `json:"startIndex"`
	ItemsPerPage int      `json:"itemsPerPage"`
	Resources    []T      `json:"Resources"`
}

ListResponse is the RFC 7644 list envelope. Wardyn does no paging, so StartIndex is always 1.

func NewListResponse

func NewListResponse[T any](resources []T) ListResponse[T]

NewListResponse renders resources as one page; a nil slice becomes `[]`, never null.

type Member

type Member struct {
	Value   string `json:"value"`
	Ref     string `json:"$ref,omitempty"`
	Display string `json:"display,omitempty"`
}

Member is one entry of a Group's members or of a members PatchOp value. Ref is `$ref`, which Entra sends as null.

type Meta

type Meta struct {
	ResourceType string `json:"resourceType,omitempty"`
	Created      string `json:"created,omitempty"`
	LastModified string `json:"lastModified,omitempty"`
	Location     string `json:"location,omitempty"`
}

Meta is the resource metadata block. A client's meta is ignored on input.

type Op

type Op struct {
	Index int
	Kind  OpKind
	Attr  string
	Value any
}

Op is one resolved PatchOp change. A path-less replace carrying a value object becomes one Op per key, all with the same Index.

Value by Attr: AttrActive is a bool (Entra's string "False" and "True" are accepted); AttrUserName, AttrExternalID, AttrDisplayName and AttrEmail are a non-empty string; AttrMembers is a []Member (a filter-path remove becomes one member; nil on a remove means every member); any other attribute is its json.RawMessage, unvalidated, for the caller to ignore. A remove carries a nil Value except for members. The `emails[type eq "work"]` selector is dropped: Wardyn keeps one address per person.

type OpError

type OpError struct {
	Index int
	*Error
}

OpError reports one invalid operation of a PATCH by its position in Operations.

type OpKind

type OpKind string

OpKind is a PatchOp operation, normalised to lower case.

const (
	OpAdd     OpKind = "add"
	OpReplace OpKind = "replace"
	OpRemove  OpKind = "remove"
)

The three RFC 7644 operations. Entra spells them `Add`, `Replace` and `Remove`; the match is case-insensitive.

type PatchError

type PatchError struct {
	*Error
	Invalid []OpError
}

PatchError is the refusal of a whole PATCH. Invalid is empty when the request envelope itself was bad (not JSON, wrong schema, no Operations).

type User

type User struct {
	Schemas     []string `json:"schemas"`
	ID          string   `json:"id,omitempty"`
	ExternalID  string   `json:"externalId"`
	UserName    string   `json:"userName"`
	DisplayName string   `json:"displayName,omitempty"`
	Emails      []Email  `json:"emails,omitempty"`
	Active      bool     `json:"active"`
	// ActiveSet is whether the body named active at all; a PUT that omits it leaves the identity as it is.
	ActiveSet bool  `json:"-"`
	Meta      *Meta `json:"meta,omitempty"`
}

User is the SCIM User subset Wardyn serves. Attributes outside it are dropped on parse.

ExternalID is required: Entra's default mapping sends the Entra objectId as externalId, and Wardyn binds the projection to a sign-in identity through it. ParseUser refuses a User without one.

Jump to

Keyboard shortcuts

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