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

idp

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: AGPL-3.0 Imports: 23 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 {
	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

	// 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