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
- func ApplyPatch(resource map[string]any, ops []PatchOp) error
- func Digest(v string) string
- func Paging(startIndex, count int) (offset, limit int)
- func SafeNext(next string) string
- type Completed
- type Filter
- type Kind
- type Origin
- type PatchOp
- type PatchRequest
- type Problem
- type ProviderInput
- type ProviderView
- type PublicProvider
- type Revocation
- type SCIMContext
- type SCIMError
- type Service
- func (s *Service) Adapter(ctx context.Context, providerID string) (api.IdentityAdapter, state.IdentityProvider, error)
- func (s *Service) Callback(ctx context.Context, providerID string, params url.Values, o Origin) string
- func (s *Service) CheckProvider(ctx context.Context, providerID string) error
- func (s *Service) Complete(ctx context.Context, code, bind string, o Origin, userAgent, ip string) (Completed, error)
- func (s *Service) CreateProvider(ctx context.Context, p authz.Principal, in ProviderInput, o Origin) (ProviderView, error)
- func (s *Service) DeleteProvider(ctx context.Context, p authz.Principal, providerID string) error
- func (s *Service) DisableSCIM(ctx context.Context, p authz.Principal, providerID string) error
- func (s *Service) Endpoints(o Origin, providerID string) api.Endpoints
- func (s *Service) FailureMessage(ctx context.Context, flowID string) (Problem, bool, error)
- func (s *Service) IdentitiesFor(ctx context.Context, userID string) ([]state.Identity, error)
- func (s *Service) KindInfos() []api.KindInfo
- func (s *Service) LinkGroup(ctx context.Context, p authz.Principal, groupID, syncedGroupID string) error
- func (s *Service) LinkIdentity(ctx context.Context, p authz.Principal, userID, providerID, externalID string, ...) (string, error)
- func (s *Service) ListProviders(ctx context.Context, o Origin) ([]ProviderView, error)
- func (s *Service) LoginURL(o Origin, providerID string) string
- func (s *Service) Options(ctx context.Context) (SignInOptions, error)
- func (s *Service) PasswordSignInAllowed(ctx context.Context) error
- func (s *Service) Provider(ctx context.Context, o Origin, providerID string) (ProviderView, error)
- func (s *Service) RotateSCIMToken(ctx context.Context, p authz.Principal, providerID string) (secret.Value, error)
- func (s *Service) SCIMAuthenticate(ctx context.Context, bearer string, o Origin) (SCIMContext, error)
- func (s *Service) SCIMBaseURL(o Origin) string
- func (s *Service) SCIMCreateGroup(ctx context.Context, c SCIMContext, body []byte) (map[string]any, error)
- func (s *Service) SCIMCreateUser(ctx context.Context, c SCIMContext, body []byte) (map[string]any, error)
- func (s *Service) SCIMDeleteGroup(ctx context.Context, c SCIMContext, groupID string) error
- func (s *Service) SCIMDeleteUser(ctx context.Context, c SCIMContext, userID string) error
- func (s *Service) SCIMGetGroup(ctx context.Context, c SCIMContext, groupID string, excludeMembers bool) (map[string]any, error)
- func (s *Service) SCIMGetUser(ctx context.Context, c SCIMContext, userID string) (map[string]any, error)
- func (s *Service) SCIMListGroups(ctx context.Context, c SCIMContext, filter string, startIndex, count int, ...) (map[string]any, error)
- func (s *Service) SCIMListUsers(ctx context.Context, c SCIMContext, filter string, startIndex, count int) (map[string]any, error)
- func (s *Service) SCIMPatchGroup(ctx context.Context, c SCIMContext, groupID string, body []byte) error
- func (s *Service) SCIMPatchUser(ctx context.Context, c SCIMContext, userID string, body []byte) (map[string]any, error)
- func (s *Service) SCIMReplaceGroup(ctx context.Context, c SCIMContext, groupID string, body []byte) (map[string]any, error)
- func (s *Service) SCIMReplaceUser(ctx context.Context, c SCIMContext, userID string, body []byte) (map[string]any, error)
- func (s *Service) SCIMResourceTypes(c SCIMContext) map[string]any
- func (s *Service) SCIMSchemas(c SCIMContext) map[string]any
- func (s *Service) SCIMServiceProviderConfig(c SCIMContext) map[string]any
- func (s *Service) ServiceMetadata(ctx context.Context, o Origin, providerID string) (*api.Metadata, error)
- func (s *Service) SessionLifetime(ctx context.Context, providerID string) (time.Duration, error)
- func (s *Service) Start(ctx context.Context, req StartRequest) (Started, error)
- func (s *Service) TestResult(ctx context.Context, p authz.Principal, providerID, flowID string) (TestReport, error)
- func (s *Service) UnlinkGroup(ctx context.Context, p authz.Principal, groupID, syncedGroupID string) error
- func (s *Service) UnlinkIdentity(ctx context.Context, p authz.Principal, userID, providerID, externalID string) error
- func (s *Service) UpdateProvider(ctx context.Context, p authz.Principal, providerID string, in ProviderInput, ...) (ProviderView, error)
- func (s *Service) ValidatePolicy(ctx context.Context, before, after bool) error
- type SignInOptions
- type SignInOutcome
- type StartRequest
- type Started
- type SubjectView
- type TestReport
Constants ¶
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.
const ( LoginPath = "/.pando/login" CompletePath = "/.pando/api/v1/auth/complete" )
Paths on every hostname Pando serves (R-172).
const ( OutcomeExisting = "existing" OutcomeLinked = "linked_by_email" OutcomeCreated = "created" OutcomeRefused = "refused" )
What a sign-in resolves to.
const FlowLifetime = 10 * time.Minute
FlowLifetime is how long a person has at the provider.
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 ¶
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 ¶
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 SafeNext ¶
SafeNext keeps a post-sign-in destination on the same host: a path, not a URL. It agrees with console/src/auth/return-to.ts.
A prefix test is not enough, because the browser parses the value, not Pando: it drops tabs and newlines anywhere in a URL and reads a backslash as a slash, so a value that starts with one slash can still reach it as a protocol-relative URL. So a control character refuses the value outright, a backslash is read as the browser reads it, the result is resolved against this host, and anything that lands elsewhere is refused. The path returned is rebuilt from the parse with its leading slashes collapsed to one, so a same-host path whose dot segments resolve to two slashes cannot become protocol-relative either.
Types ¶
type Filter ¶
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.
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 ¶
Origin is where a request came from, as scheme and host — the address a browser is sent back to when a flow finishes.
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.
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"`
// LoginURL is where an OIDC provider sends a browser to start a sign-in
// from its own dashboard: OIDC's initiate login URI. It begins the same
// sign-in the button on Pando's sign-in page does, so the sign-in is still
// bound to the browser that started it (R-043).
LoginURL string `json:"login_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 ¶
SCIMError is a SCIM error response (RFC 7644 §3.12).
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 ¶
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 ¶
DeleteProvider removes a provider nobody has signed in through.
func (*Service) DisableSCIM ¶
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 ¶
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 ¶
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 ¶
Identities lists the identities that reach an account.
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, confirm 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). Suspending the last active owner of an app that way needs confirm (R-409).
func (*Service) ListProviders ¶
ListProviders returns every provider with its endpoints and revocation.
func (*Service) LoginURL ¶ added in v0.14.0
LoginURL is where a provider starts a sign-in from its own dashboard: the start of an ordinary redirect sign-in, which the provider's iss parameter does not change.
func (*Service) Options ¶
func (s *Service) Options(ctx context.Context) (SignInOptions, error)
Options answers the sign-in page.
func (*Service) PasswordSignInAllowed ¶
PasswordSignInAllowed is the gate on POST /sessions.
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 ¶
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 ¶
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 ¶
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 ¶
SessionLifetime is how long a session made through a provider lasts.
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.
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 ¶
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.