idp

package
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: AGPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package idp is external identity: the providers an administrator connects, redirect sign-in through them, linking their identities to accounts, the groups they sync, and SCIM provisioning (R-043, R-045, R-047, R-048, R-050).

Every surface calls this — the HTTP API, and through it the console and CLI — so none of them holds a rule the others lack (R-261). Adapters authenticate; everything that decides anything is here (R-044).

Index

Constants

View Source
const (
	SchemaUser         = "urn:ietf:params:scim:schemas:core:2.0:User"
	SchemaGroup        = "urn:ietf:params:scim:schemas:core:2.0:Group"
	SchemaEnterprise   = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
	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"
)

SCIM schema URNs.

View Source
const (
	LoginPath    = "/.pando/login"
	CompletePath = "/.pando/api/v1/auth/complete"
)

Paths on every hostname Pando serves (R-172).

View Source
const (
	OutcomeExisting = "existing"
	OutcomeLinked   = "linked_by_email"
	OutcomeCreated  = "created"
	OutcomeRefused  = "refused"
)

What a sign-in resolves to.

View Source
const FlowLifetime = 10 * time.Minute

FlowLifetime is how long a person has at the provider.

View Source
const RevocationWindow = 120 * time.Second

RevocationWindow is design 06 §3.1's number: the longest any revocation takes to take effect, once Pando knows about it.

Variables

This section is empty.

Functions

func ApplyPatch

func ApplyPatch(resource map[string]any, ops []PatchOp) error

ApplyPatch applies operations to a resource held as a map. The caller validates the result as it would a PUT, so a patch can do nothing a replacement could not.

Members of a group are not handled here: they are relations, not attributes, and the caller takes them out first (MemberOps).

func Digest

func Digest(v string) string

Digest is the SHA-256 of a high-entropy random value, hex-encoded. Used for the browser binding, the handoff code and the SCIM token, each of which is 256 random bits: nothing to brute-force, so the slow hash that protects passwords would only cost time on every request.

func Paging

func Paging(startIndex, count int) (offset, limit int)

Paging reads startIndex and count (1-based, RFC 7644 §3.4.2.4).

func SafeNext

func SafeNext(next string) string

SafeNext keeps a post-sign-in destination on the same host: a path, not a URL. "//evil.example" and "/\evil.example" are URLs to a browser.

Types

type Completed

type Completed struct {
	Session state.Session
	User    state.User
	Next    string
}

Completed is a finished sign-in: the session to set, and where to go.

type Filter

type Filter struct {
	Attr  string
	Value string
}

Filter is the one form of SCIM filter Pando answers: an attribute equal to a value. It is what every provisioning client sends to ask "does this person already exist", and anything richer would be a query language kept for nobody.

func ParseFilter

func ParseFilter(raw string, allowed ...string) (Filter, error)

ParseFilter reads `attr eq "value"`, with attr one of those allowed, matched without regard to case.

type Kind

type Kind struct {
	New  func() api.IdentityAdapter
	Info api.KindInfo
}

Kind is a compiled-in identity adapter kind (R-253): how to make one, and how to describe it to a form.

type Origin

type Origin struct {
	Scheme string
	Host   string
}

Origin is where a request came from, as scheme and host — the address a browser is sent back to when a flow finishes.

func (Origin) String

func (o Origin) String() string

type PatchOp

type PatchOp struct {
	Op    string          `json:"op"`
	Path  string          `json:"path"`
	Value json.RawMessage `json:"value"`
}

PatchOp is one operation of a PATCH request.

func MemberOps

func MemberOps(ops []PatchOp) (add, remove []string, clear bool, rest []PatchOp, err error)

MemberOps separates a group PATCH's membership operations from the rest. Returns who to add, who to remove, whether to clear the membership first (a replace, or a remove of "members" with no value), and the other ops.

type PatchRequest

type PatchRequest struct {
	Schemas    []string  `json:"schemas"`
	Operations []PatchOp `json:"Operations"`
}

PatchRequest is a SCIM PATCH body.

type Problem

type Problem struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Remedy  string `json:"remedy,omitempty"`
}

Problem is an error as a page shows it.

type ProviderInput

type ProviderInput struct {
	// ID is a new provider's ID, chosen ahead so the addresses Endpoints
	// builds from it can be registered with the provider before it is saved.
	// Optional; ignored on update.
	ID                    string            `json:"id,omitempty"`
	Kind                  string            `json:"kind"`
	Name                  *string           `json:"name"`
	Config                json.RawMessage   `json:"config"`
	Credentials           map[string]string `json:"credentials"`
	Enabled               *bool             `json:"enabled"`
	JITProvisioning       *bool             `json:"jit_provisioning"`
	LinkByEmail           *bool             `json:"link_by_email"`
	SCIMIdentityAttribute *string           `json:"scim_identity_attribute"`
}

ProviderInput is a create or update request. Credentials are write-only: stored encrypted, never returned (R-190). An empty credential value on update removes it; an absent one leaves it.

type ProviderView

type ProviderView struct {
	state.IdentityProvider

	// Credentials names the secrets that are set, never their values.
	Credentials []string `json:"credentials"`

	// CallbackURL and EntityID are what the provider is told about Pando:
	// the redirect URI for OIDC, the ACS URL and audience for SAML.
	CallbackURL string `json:"callback_url,omitempty"`
	EntityID    string `json:"entity_id,omitempty"`
	MetadataURL string `json:"metadata_url,omitempty"`

	// SCIMBaseURL is where a SCIM client sends pushes, with the token.
	SCIMBaseURL string `json:"scim_base_url,omitempty"`

	// Revocation is R-047 made visible: the lifetime, the mode, and the
	// window that results, per provider.
	Revocation *Revocation `json:"revocation,omitempty"`

	// Problem is why the provider cannot be used as configured, when it
	// cannot. A provider with a problem stays listed so it can be fixed.
	Problem string `json:"problem,omitempty"`
}

ProviderView is a provider as an administrator sees it: its settings, what to register with it, and how quickly it can revoke.

type PublicProvider

type PublicProvider struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Kind string `json:"kind"`
}

PublicProvider is what the sign-in page shows: a name to press.

type Revocation

type Revocation struct {
	MaxLifetimeSeconds int64  `json:"max_lifetime_seconds"`
	Mode               string `json:"mode"`

	// WindowSeconds is the longest access can outlive its removal at the
	// provider: the session lifetime without push; with push, the two-minute
	// window every revocation has anyway (design 06 §3.1).
	WindowSeconds int64 `json:"window_seconds"`
}

Revocation is a provider's session behavior as it actually is on this install (R-047, R-050): the adapter's declaration, upgraded to push when a SCIM client can push, and the window that results.

type SCIMContext

type SCIMContext struct {
	Provider state.IdentityProvider
	Base     string
}

SCIMContext is who is pushing: the provider whose token was presented, and the address resources are reported at.

type SCIMError

type SCIMError struct {
	Status int
	Type   string
	Detail string
}

SCIMError is a SCIM error response (RFC 7644 §3.12).

func (*SCIMError) Body

func (e *SCIMError) Body() map[string]any

Body is the error as the client receives it.

func (*SCIMError) Error

func (e *SCIMError) Error() string

type Service

type Service struct {
	Providers   *state.IdentityProviders
	Credentials *state.AdapterCredentials
	Identities  *state.Identities
	Users       *state.Users
	Sessions    *state.Sessions
	Groups      *state.Groups
	Flows       *state.SSOFlows
	SCIMUsers   *state.SCIMUsers
	SCIMGroups  *state.SCIMGroups

	// Grants says whether an account holds an installation role, which an
	// email alone may not link a provider's identity to (R-080a). Nil links
	// no account by email.
	Grants interface {
		InstallRolesForUsers(ctx context.Context, userIDs []string) (map[string][]state.HeldInstallRole, error)
	}

	// Policy is host policy as in force, startup overlay included.
	Policy interface {
		Load(ctx context.Context) (policy.Document, error)
	}

	// Local is the local username-and-password adapter (R-041).
	Local api.IdentityAdapter

	// Kinds are the external kinds this build can run, by kind string.
	Kinds map[string]Kind

	// ExternalURL is PANDO_SERVER_EXTERNAL_URL. When set it is the only
	// address callbacks go to; when not, the address a request arrived at.
	ExternalURL *url.URL

	// Audit records an event. The HTTP layer supplies one that adds the
	// request ID.
	Audit func(ctx context.Context, e audit.Event)

	Clock clock.Clock
	// contains filtered or unexported fields
}

Service is external identity.

func (*Service) Adapter

func (s *Service) Adapter(ctx context.Context, providerID string) (api.IdentityAdapter, state.IdentityProvider, error)

Adapter returns a provider's adapter, configured from its stored settings and decrypted credentials.

Built on first use and rebuilt when the provider changes, so a provider an administrator adds or edits works on the next request — unlike other adapter categories, whose instances are fixed at startup (R-253). A sign-in page that needed a restart to show a new provider would be a restart in front of everyone who uses the install.

func (*Service) Callback

func (s *Service) Callback(ctx context.Context, providerID string, params url.Values, o Origin) string

Callback handles the provider's response and returns where to send the browser next. It never returns an error for the browser to see raw: a failure is recorded on the flow and the browser is sent to a page that explains it.

func (*Service) CheckProvider

func (s *Service) CheckProvider(ctx context.Context, providerID string) error

CheckProvider asks the provider whether it answers: its discovery document or its metadata.

func (*Service) Complete

func (s *Service) Complete(ctx context.Context, code, bind string, o Origin, userAgent, ip string) (Completed, error)

Complete spends a one-time code in the browser that started the flow.

func (*Service) CreateProvider

func (s *Service) CreateProvider(ctx context.Context, p authz.Principal, in ProviderInput, o Origin) (ProviderView, error)

CreateProvider adds a provider. Off unless enabled is set: a provider that appears on the sign-in page before anyone has tested it is a button that fails for everyone who presses it.

func (*Service) DeleteProvider

func (s *Service) DeleteProvider(ctx context.Context, p authz.Principal, providerID string) error

DeleteProvider removes a provider nobody has signed in through.

func (*Service) DisableSCIM

func (s *Service) DisableSCIM(ctx context.Context, p authz.Principal, providerID string) error

DisableSCIM removes a provider's SCIM token. Accounts and groups it pushed stay; sign-in claims own group membership again from the next sign-in.

func (*Service) Endpoints

func (s *Service) Endpoints(o Origin, providerID string) api.Endpoints

Endpoints are a provider's callback URL and entity ID. What an administrator registers with the provider, so they are stable — computed from the external URL and the provider's ID, never from anything a person types.

func (*Service) FailureMessage

func (s *Service) FailureMessage(ctx context.Context, flowID string) (Problem, bool, error)

FailureMessage is what the sign-in page shows for a failed flow. Only a sign-in's failure is shown, never a test's, and never anything but the message and remedy recorded when it failed.

func (*Service) IdentitiesFor

func (s *Service) IdentitiesFor(ctx context.Context, userID string) ([]state.Identity, error)

Identities lists the identities that reach an account.

func (*Service) KindInfos

func (s *Service) KindInfos() []api.KindInfo

KindInfos describes the external kinds, for forms.

func (*Service) LinkGroup

func (s *Service) LinkGroup(ctx context.Context, p authz.Principal, groupID, syncedGroupID string) error

LinkGroup makes everyone in a provider's group count as a member of a Pando-made group, live (R-079).

func (*Service) LinkIdentity

func (s *Service) LinkIdentity(ctx context.Context, p authz.Principal, userID, providerID, externalID string, replace bool) (string, error)

LinkIdentity attaches a provider's identity to an account. When it already reaches another account, it moves only with replace set, and that account becomes a suspended alias of this one — never deleted (design 02 §2.1).

func (*Service) ListProviders

func (s *Service) ListProviders(ctx context.Context, o Origin) ([]ProviderView, error)

ListProviders returns every provider with its endpoints and revocation.

func (*Service) Options

func (s *Service) Options(ctx context.Context) (SignInOptions, error)

Options answers the sign-in page.

func (*Service) PasswordSignInAllowed

func (s *Service) PasswordSignInAllowed(ctx context.Context) error

PasswordSignInAllowed is the gate on POST /sessions.

func (*Service) Provider

func (s *Service) Provider(ctx context.Context, o Origin, providerID string) (ProviderView, error)

Provider returns one provider's view.

func (*Service) RotateSCIMToken

func (s *Service) RotateSCIMToken(ctx context.Context, p authz.Principal, providerID string) (secret.Value, error)

RotateSCIMToken turns SCIM on for a provider, or replaces its token. The token is returned once and kept only as a digest: whoever holds it can create, suspend and regroup that provider's accounts.

func (*Service) SCIMAuthenticate

func (s *Service) SCIMAuthenticate(ctx context.Context, bearer string, o Origin) (SCIMContext, error)

SCIMAuthenticate resolves a bearer token to its provider.

func (*Service) SCIMBaseURL

func (s *Service) SCIMBaseURL(o Origin) string

SCIMBaseURL is what an administrator gives a SCIM client.

func (*Service) SCIMCreateGroup

func (s *Service) SCIMCreateGroup(ctx context.Context, c SCIMContext, body []byte) (map[string]any, error)

SCIMCreateGroup answers POST /Groups. The group has no access until an administrator gives it some, or links it to a Pando group (R-078).

func (*Service) SCIMCreateUser

func (s *Service) SCIMCreateUser(ctx context.Context, c SCIMContext, body []byte) (map[string]any, error)

SCIMCreateUser answers POST /Users.

An account the provider's sign-in already made (just-in-time) is adopted rather than refused: the client is telling Pando about someone it already knows, and a conflict would leave that person unmanaged.

func (*Service) SCIMDeleteGroup

func (s *Service) SCIMDeleteGroup(ctx context.Context, c SCIMContext, groupID string) error

SCIMDeleteGroup answers DELETE /Groups/{id}. The group goes, and every grant made to it with it — as when an administrator deletes one — unless that would leave nobody who can manage accounts (R-088).

func (*Service) SCIMDeleteUser

func (s *Service) SCIMDeleteUser(ctx context.Context, c SCIMContext, userID string) error

SCIMDeleteUser answers DELETE /Users/{id}: the account is suspended, not deleted (R-049), and the client forgets it.

func (*Service) SCIMGetGroup

func (s *Service) SCIMGetGroup(ctx context.Context, c SCIMContext, groupID string, excludeMembers bool) (map[string]any, error)

SCIMGetGroup answers GET /Groups/{id}.

func (*Service) SCIMGetUser

func (s *Service) SCIMGetUser(ctx context.Context, c SCIMContext, userID string) (map[string]any, error)

SCIMGetUser answers GET /Users/{id}.

func (*Service) SCIMListGroups

func (s *Service) SCIMListGroups(ctx context.Context, c SCIMContext, filter string, startIndex, count int, excludeMembers bool) (map[string]any, error)

SCIMListGroups answers GET /Groups.

func (*Service) SCIMListUsers

func (s *Service) SCIMListUsers(ctx context.Context, c SCIMContext, filter string, startIndex, count int) (map[string]any, error)

SCIMListUsers answers GET /Users.

func (*Service) SCIMPatchGroup

func (s *Service) SCIMPatchGroup(ctx context.Context, c SCIMContext, groupID string, body []byte) error

SCIMPatchGroup answers PATCH /Groups/{id}: membership changes, a rename. Answers 204, as Entra and Okta both accept, rather than recomputing a membership list the client did not ask to see.

func (*Service) SCIMPatchUser

func (s *Service) SCIMPatchUser(ctx context.Context, c SCIMContext, userID string, body []byte) (map[string]any, error)

SCIMPatchUser answers PATCH /Users/{id}.

func (*Service) SCIMReplaceGroup

func (s *Service) SCIMReplaceGroup(ctx context.Context, c SCIMContext, groupID string, body []byte) (map[string]any, error)

SCIMReplaceGroup answers PUT /Groups/{id}.

func (*Service) SCIMReplaceUser

func (s *Service) SCIMReplaceUser(ctx context.Context, c SCIMContext, userID string, body []byte) (map[string]any, error)

SCIMReplaceUser answers PUT /Users/{id}.

func (*Service) SCIMResourceTypes

func (s *Service) SCIMResourceTypes(c SCIMContext) map[string]any

SCIMResourceTypes answers GET /ResourceTypes.

func (*Service) SCIMSchemas

func (s *Service) SCIMSchemas(c SCIMContext) map[string]any

SCIMSchemas answers GET /Schemas with the attributes Pando reads. Others a client sends are kept and returned, but not described.

func (*Service) SCIMServiceProviderConfig

func (s *Service) SCIMServiceProviderConfig(c SCIMContext) map[string]any

SCIMServiceProviderConfig answers GET /ServiceProviderConfig.

func (*Service) ServiceMetadata

func (s *Service) ServiceMetadata(ctx context.Context, o Origin, providerID string) (*api.Metadata, error)

ServiceMetadata is Pando's metadata for a provider — SAML SP metadata. Public: a provider fetches it without credentials.

func (*Service) SessionLifetime

func (s *Service) SessionLifetime(ctx context.Context, providerID string) (time.Duration, error)

SessionLifetime is how long a session made through a provider lasts.

func (*Service) Start

func (s *Service) Start(ctx context.Context, req StartRequest) (Started, error)

Start begins a sign-in through a provider.

func (*Service) TestResult

func (s *Service) TestResult(ctx context.Context, p authz.Principal, providerID, flowID string) (TestReport, error)

TestResult returns a test sign-in's report, to the administrator who ran it.

func (*Service) UnlinkGroup

func (s *Service) UnlinkGroup(ctx context.Context, p authz.Principal, groupID, syncedGroupID string) error

UnlinkGroup removes such a link. Its members lose what the Pando group gave them, within the revocation window (design 06 §3.1).

func (*Service) UnlinkIdentity

func (s *Service) UnlinkIdentity(ctx context.Context, p authz.Principal, userID, providerID, externalID string) error

UnlinkIdentity removes a provider's identity from an account. The account stays; whoever signs in with that identity next is a stranger again.

func (*Service) UpdateProvider

func (s *Service) UpdateProvider(ctx context.Context, p authz.Principal, providerID string, in ProviderInput, o Origin) (ProviderView, error)

UpdateProvider changes a provider. Settings replace the stored ones whole when given; credentials change field by field.

func (*Service) ValidatePolicy

func (s *Service) ValidatePolicy(ctx context.Context, before, after bool) error

ValidatePolicy refuses turning password sign-in off while no external provider is on: the sign-in page would have nothing on it.

type SignInOptions

type SignInOptions struct {
	PasswordSignIn bool             `json:"password_sign_in"`
	Providers      []PublicProvider `json:"providers"`
}

SignInOptions is how people can sign in here.

type SignInOutcome

type SignInOutcome struct {
	Kind        string   `json:"kind"`
	UserID      string   `json:"user_id,omitempty"`
	Username    string   `json:"username,omitempty"`
	DisplayName string   `json:"display_name,omitempty"`
	Message     string   `json:"message,omitempty"`
	Remedy      string   `json:"remedy,omitempty"`
	Groups      []string `json:"groups,omitempty"`

	// GroupsFrom is where the account's groups at this provider come from:
	// "sign_in" (the groups claim, on every sign-in) or "scim".
	GroupsFrom string `json:"groups_from"`
	// contains filtered or unexported fields
}

SignInOutcome is which account a sign-in reaches, and how.

type StartRequest

type StartRequest struct {
	ProviderID string
	Origin     Origin
	Next       string

	// Test is an administrator's test sign-in: nobody is signed in, and the
	// report says what would have happened.
	Test        bool
	InitiatedBy authz.Principal
}

StartRequest begins a redirect sign-in.

type Started

type Started struct {
	RedirectURL string
	Bind        string
}

Started is where to send the browser, and the value of the cookie that binds the flow to it.

type SubjectView

type SubjectView struct {
	ExternalID    string   `json:"external_id"`
	Username      string   `json:"username,omitempty"`
	Email         string   `json:"email,omitempty"`
	EmailVerified bool     `json:"email_verified"`
	DisplayName   string   `json:"display_name,omitempty"`
	Groups        []string `json:"groups"`
}

SubjectView is a Subject as an administrator's test sign-in shows it.

type TestReport

type TestReport struct {
	OK         bool                `json:"ok"`
	Error      *Problem            `json:"error,omitempty"`
	Subject    *SubjectView        `json:"subject,omitempty"`
	Attributes map[string][]string `json:"attributes,omitempty"`
	Outcome    *SignInOutcome      `json:"outcome,omitempty"`
}

TestReport is what a test sign-in found: what the provider sent, and what Pando would have done with it.

Jump to

Keyboard shortcuts

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