Help us shape the future of Go! Take the 2026 Go Developer Survey

scim

package
v0.9.5 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 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