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

scim

package
v1.9.0 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 42 Imported by: 0

README

scim

SCIM 2.0 provisioning, so a customer's identity provider can create, update and deactivate users in your product automatically.

An enterprise admin who removes someone in Okta expects them gone from every connected app within minutes. SCIM is how that message gets delivered. The plugin exposes the standard /Users and /Groups resources plus the discovery endpoints identity providers probe on setup, and authenticates callers with a bearer token you issue per connection.

It ships disabled. Turn scim.enabled on deliberately, because these endpoints can create and delete users.

Use it when

  • A customer's security review asks for automated deprovisioning
  • Manual user management does not scale past a few hundred seats per tenant
  • You already run sso and users are being created on first login with no way to remove them

Skip it when

  • Your customers are small enough to manage users by hand
  • You cannot honour a deactivation quickly. An admin who sees the user removed in Okta will assume they lost access to you too, and stop checking
  • You have not decided whether a SCIM delete means soft-suspend or hard-delete in your data model

Wiring

authsome.WithPlugin(scim.New(scim.Config{
    BasePath:      "/scim/v2",
    MaxLogEntries: 1000,
}))

Then set scim.enabled to true per app once a connection is configured.

Config

Field Type Default What it does
BasePath string /scim/v2 Where SCIM endpoints are mounted
TokenLength int 32 Random bytes in a generated bearer token
MaxLogEntries int 1000 Provision log entries kept per config

Settings

Key Default
scim.enabled false
scim.auto_create_users true
scim.auto_suspend_users true
scim.group_sync false
scim.default_role member
scim.token_expiry_days 365

Endpoints

Mounted under BasePath, /scim/v2 by default.

Method Path Purpose
GET /Users List and filter users
POST /Users Create a user
GET /Users/:userId Read a user
PUT /Users/:userId Replace a user
PATCH /Users/:userId Partially update a user
DELETE /Users/:userId Deprovision a user
GET /Groups List groups
POST /Groups Create a group
GET /Groups/:groupId Read a group
PUT /Groups/:groupId Replace a group
PATCH /Groups/:groupId Partially update a group
DELETE /Groups/:groupId Delete a group
GET /ServiceProviderConfig Advertise supported features
GET /ResourceTypes Advertise resource types
GET /Schemas Advertise schemas

Lifecycle hooks

None. It is a RouteProvider, a SettingsProvider and a MigrationProvider.

sso handles authentication for the same customers. organization supplies the tenant that a SCIM connection belongs to, and group sync maps onto its teams.

Documentation

Overview

contract.go: Wires the scim plugin into the forge-dashboard contract surface via plugin.ContractContributor.

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"
	SchemaListResponse  = "urn:ietf:params:scim:api:messages:2.0:ListResponse"
	SchemaError         = "urn:ietf:params:scim:api:messages:2.0:Error"
	SchemaPatchOp       = "urn:ietf:params:scim:api:messages:2.0:PatchOp"
	SchemaServiceConfig = "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
	SchemaResourceType  = "urn:ietf:params:scim:schemas:core:2.0:ResourceType"
	SchemaSchema        = "urn:ietf:params:scim:schemas:core:2.0:Schema"
)

SCIM schema URIs.

View Source
const (
	LogStatusSuccess = "success"
	LogStatusError   = "error"
	LogStatusSkipped = "skipped"
)

Provision log statuses.

View Source
const (
	ActionCreateUser   = "create_user"
	ActionUpdateUser   = "update_user"
	ActionDeleteUser   = "delete_user"
	ActionSuspendUser  = "suspend_user"
	ActionCreateGroup  = "create_group"
	ActionUpdateGroup  = "update_group"
	ActionDeleteGroup  = "delete_group"
	ActionAddMember    = "add_member"
	ActionRemoveMember = "remove_member"
)

Provision log actions.

Variables

View Source
var (
	// SettingSCIMEnabled controls whether SCIM provisioning is enabled.
	SettingSCIMEnabled = settings.Define("scim.enabled", false,
		settings.WithDisplayName("Enable SCIM Provisioning"),
		settings.WithDescription("Enable SCIM 2.0 endpoints for automated user and group provisioning"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithOrder(10),
	)

	// SettingAutoCreateUsers controls whether users are auto-created on SCIM push.
	SettingAutoCreateUsers = settings.Define("scim.auto_create_users", true,
		settings.WithDisplayName("Auto-Create Users"),
		settings.WithDescription("Automatically create new users when provisioned via SCIM"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithVisibleWhen("scim.enabled", "true", "eq"),
		settings.WithOrder(20),
	)

	// SettingAutoSuspendUsers controls auto-suspension on SCIM deactivation.
	SettingAutoSuspendUsers = settings.Define("scim.auto_suspend_users", true,
		settings.WithDisplayName("Auto-Suspend Deprovisioned Users"),
		settings.WithDescription("Automatically suspend users when deactivated via SCIM"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithVisibleWhen("scim.enabled", "true", "eq"),
		settings.WithOrder(30),
	)

	// SettingGroupSync controls whether SCIM Groups are synced to teams.
	SettingGroupSync = settings.Define("scim.group_sync", false,
		settings.WithDisplayName("Sync SCIM Groups to Teams"),
		settings.WithDescription("Map SCIM Group resources to organization teams"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithVisibleWhen("scim.enabled", "true", "eq"),
		settings.WithOrder(40),
	)

	// SettingDefaultRole is the default org role for SCIM-provisioned users.
	SettingDefaultRole = settings.Define("scim.default_role", "member",
		settings.WithDisplayName("Default Member Role"),
		settings.WithDescription("Default organization role assigned to SCIM-provisioned users"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithInputType(formconfig.FieldSelect),
		settings.WithOptions(
			formconfig.SelectOption{Label: "Member", Value: "member"},
			formconfig.SelectOption{Label: "Admin", Value: "admin"},
		),
		settings.WithVisibleWhen("scim.enabled", "true", "eq"),
		settings.WithOrder(50),
	)

	// SettingTokenExpiryDays controls default token expiry.
	SettingTokenExpiryDays = settings.Define("scim.token_expiry_days", 365,
		settings.WithDisplayName("Token Expiry (days)"),
		settings.WithDescription("Default expiry period for new SCIM bearer tokens (0 = no expiry)"),
		settings.WithCategory("SCIM"),
		settings.WithScopes(settings.ScopeGlobal, settings.ScopeApp),
		settings.WithInputType(formconfig.FieldNumber),
		settings.WithUIValidation(formconfig.Validation{Min: intPtr(0), Max: intPtr(3650)}),
		settings.WithHelpText("Set to 0 for tokens that never expire. Default: 365"),
		settings.WithVisibleWhen("scim.enabled", "true", "eq"),
		settings.WithOrder(60),
	)
)

Dynamic setting definitions for the SCIM plugin.

View Source
var (
	ErrConfigNotFound = errors.New("scim: config not found")
	ErrTokenNotFound  = errors.New("scim: token not found")
)

Store defines the persistence interface for SCIM entities. Sentinel errors every store returns for a missing row, so callers and the conformance suite can tell absence from failure.

View Source
var ErrOutOfScope = errors.New("scim: resource outside configuration scope")

ErrOutOfScope marks a row that exists but lies outside the SCIM configuration's app or organization. Handlers answer 404 so a token for one tenant cannot probe another.

View Source
var PostgresMigrations = migrate.NewGroup("authsome-scim", migrate.DependsOn("authsome"))

PostgresMigrations is the postgres migration group for the SCIM plugin.

View Source
var SqliteMigrations = migrate.NewGroup("authsome-scim", migrate.DependsOn("authsome"))

SqliteMigrations is the SQLite migration group for the SCIM plugin.

Functions

This section is empty.

Types

type Config

type Config struct {
	// BasePath is the HTTP prefix for SCIM endpoints (default: "/scim/v2").
	BasePath string

	// TokenLength is the number of random bytes for generated bearer tokens (default: 32).
	TokenLength int

	// MaxLogEntries is the maximum number of provision logs to keep per config (default: 1000).
	MaxLogEntries int
}

Config holds plugin-level configuration for the SCIM plugin.

type Email

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

Email represents a SCIM email address.

type GroupRef

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

GroupRef is a reference to a SCIM group.

type GroupResource

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

GroupResource represents a SCIM 2.0 Group resource.

func TeamToSCIMGroup

func TeamToSCIMGroup(t *organization.Team, members []*organization.Member, baseURL string) *GroupResource

TeamToSCIMGroup converts an AuthSome team to a SCIM Group resource.

type ListResponse

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

ListResponse is a SCIM list response (RFC 7644 Section 3.4.2).

type MemberRef

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

MemberRef is a member reference within a SCIM Group.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore is a thread-safe in-memory implementation of the SCIM Store.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore creates a new in-memory SCIM store.

func (*MemoryStore) CountAllLogsByStatus

func (s *MemoryStore) CountAllLogsByStatus(_ context.Context, appID string) (success, errors, skipped int, err error)

func (*MemoryStore) CountLogsByStatus

func (s *MemoryStore) CountLogsByStatus(_ context.Context, configID id.SCIMConfigID) (success, errors, skipped int, err error)

func (*MemoryStore) CreateConfig

func (s *MemoryStore) CreateConfig(_ context.Context, c *SCIMConfig) error

func (*MemoryStore) CreateLog

func (s *MemoryStore) CreateLog(_ context.Context, l *ProvisionLog) error

func (*MemoryStore) CreateToken

func (s *MemoryStore) CreateToken(_ context.Context, t *Token) error

func (*MemoryStore) DeleteConfig

func (s *MemoryStore) DeleteConfig(_ context.Context, configID id.SCIMConfigID) error

func (*MemoryStore) DeleteToken

func (s *MemoryStore) DeleteToken(_ context.Context, tokenID id.SCIMTokenID) error

func (*MemoryStore) FindTokenByPlaintext

func (s *MemoryStore) FindTokenByPlaintext(_ context.Context, plaintext string) (*Token, *SCIMConfig, error)

FindTokenByPlaintext linearly scans stored tokens and bcrypt-compares each one against the presented plaintext. Bcrypt digests are salted, so an index lookup is not possible; a production store would want a lookup key (for example a token prefix column) to narrow the scan first.

func (*MemoryStore) GetConfig

func (s *MemoryStore) GetConfig(_ context.Context, configID id.SCIMConfigID) (*SCIMConfig, error)

func (*MemoryStore) GetToken

func (s *MemoryStore) GetToken(_ context.Context, tokenID id.SCIMTokenID) (*Token, error)

func (*MemoryStore) ListAllLogs

func (s *MemoryStore) ListAllLogs(_ context.Context, appID string, limit int) ([]*ProvisionLog, error)

func (*MemoryStore) ListConfigs

func (s *MemoryStore) ListConfigs(_ context.Context, appID string) ([]*SCIMConfig, error)

func (*MemoryStore) ListConfigsByOrg

func (s *MemoryStore) ListConfigsByOrg(_ context.Context, orgID id.OrgID) ([]*SCIMConfig, error)

func (*MemoryStore) ListLogs

func (s *MemoryStore) ListLogs(_ context.Context, configID id.SCIMConfigID, limit int) ([]*ProvisionLog, error)

func (*MemoryStore) ListTokens

func (s *MemoryStore) ListTokens(_ context.Context, configID id.SCIMConfigID) ([]*Token, error)

func (*MemoryStore) UpdateConfig

func (s *MemoryStore) UpdateConfig(_ context.Context, c *SCIMConfig) error

func (*MemoryStore) UpdateToken

func (s *MemoryStore) UpdateToken(_ context.Context, t *Token) error

type Meta

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

Meta holds SCIM resource metadata.

type MongoStore

type MongoStore struct {
	// contains filtered or unexported fields
}

MongoStore implements Store on MongoDB. The plugin has no mongo migration group, so the store installs its own indexes before its first write.

func NewMongoStore

func NewMongoStore(db *grove.DB) *MongoStore

NewMongoStore creates a SCIM store on the mongo driver.

func (*MongoStore) CountAllLogsByStatus

func (s *MongoStore) CountAllLogsByStatus(ctx context.Context, appID string) (success, errs, skipped int, err error)

func (*MongoStore) CountLogsByStatus

func (s *MongoStore) CountLogsByStatus(ctx context.Context, configID id.SCIMConfigID) (success, errs, skipped int, err error)

func (*MongoStore) CreateConfig

func (s *MongoStore) CreateConfig(ctx context.Context, c *SCIMConfig) error

func (*MongoStore) CreateLog

func (s *MongoStore) CreateLog(ctx context.Context, l *ProvisionLog) error

func (*MongoStore) CreateToken

func (s *MongoStore) CreateToken(ctx context.Context, t *Token) error

func (*MongoStore) DeleteConfig

func (s *MongoStore) DeleteConfig(ctx context.Context, configID id.SCIMConfigID) error

func (*MongoStore) DeleteToken

func (s *MongoStore) DeleteToken(ctx context.Context, tokenID id.SCIMTokenID) error

func (*MongoStore) FindTokenByPlaintext

func (s *MongoStore) FindTokenByPlaintext(ctx context.Context, plaintext string) (*Token, *SCIMConfig, error)

FindTokenByPlaintext resolves by the lookup digest and bcrypt-compares that document; documents without a lookup are compared one by one and upgraded on a match.

func (*MongoStore) GetConfig

func (s *MongoStore) GetConfig(ctx context.Context, configID id.SCIMConfigID) (*SCIMConfig, error)

func (*MongoStore) GetToken

func (s *MongoStore) GetToken(ctx context.Context, tokenID id.SCIMTokenID) (*Token, error)

func (*MongoStore) ListAllLogs

func (s *MongoStore) ListAllLogs(ctx context.Context, appID string, limit int) ([]*ProvisionLog, error)

func (*MongoStore) ListConfigs

func (s *MongoStore) ListConfigs(ctx context.Context, appID string) ([]*SCIMConfig, error)

func (*MongoStore) ListConfigsByOrg

func (s *MongoStore) ListConfigsByOrg(ctx context.Context, orgID id.OrgID) ([]*SCIMConfig, error)

func (*MongoStore) ListLogs

func (s *MongoStore) ListLogs(ctx context.Context, configID id.SCIMConfigID, limit int) ([]*ProvisionLog, error)

func (*MongoStore) ListTokens

func (s *MongoStore) ListTokens(ctx context.Context, configID id.SCIMConfigID) ([]*Token, error)

func (*MongoStore) UpdateConfig

func (s *MongoStore) UpdateConfig(ctx context.Context, c *SCIMConfig) error

func (*MongoStore) UpdateToken

func (s *MongoStore) UpdateToken(ctx context.Context, t *Token) error

type Name

type Name struct {
	GivenName  string `json:"givenName"`
	FamilyName string `json:"familyName"`
	Formatted  string `json:"formatted,omitempty"`
}

Name represents a SCIM name component.

type Operation

type Operation struct {
	Op    string `json:"op"`
	Path  string `json:"path,omitempty"`
	Value any    `json:"value,omitempty"`
}

Operation is a single SCIM PATCH operation.

type PatchOp

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

PatchOp represents a SCIM PATCH request (RFC 7644 Section 3.5.2).

type Plugin

type Plugin struct {
	// contains filtered or unexported fields
}

Plugin is the SCIM 2.0 provisioning plugin for authsome.

func New

func New(cfg ...Config) *Plugin

New creates a new SCIM plugin with the given configuration.

func (*Plugin) DeclareSettings

func (p *Plugin) DeclareSettings(m *settings.Manager) error

DeclareSettings registers SCIM settings with the settings manager.

func (*Plugin) MigrationGroups

func (p *Plugin) MigrationGroups(driverName string) []*migrate.Group

MigrationGroups returns SCIM-specific database migrations.

func (*Plugin) Name

func (p *Plugin) Name() string

Name returns the plugin name.

func (*Plugin) OnAfterOrgDelete

func (p *Plugin) OnAfterOrgDelete(ctx context.Context, orgID id.OrgID) error

OnAfterOrgDelete removes every SCIM configuration scoped to the deleted organization so we don't leave orphaned provisioning targets behind. Failures are logged and the hook returns nil so other delete-cascade listeners still run.

func (*Plugin) OnInit

func (p *Plugin) OnInit(_ context.Context, engine plugin.Engine) error

OnInit captures bridge and engine references.

func (*Plugin) RegisterContract

func (p *Plugin) RegisterContract(
	d *dispatcher.Dispatcher,
	reg contract.Registry,
	wreg contract.WardenRegistry,
	engine plugin.Engine,
) error

func (*Plugin) RegisterRoutes

func (p *Plugin) RegisterRoutes(router forge.Router) error

RegisterRoutes registers SCIM 2.0 API routes on a forge.Router.

func (*Plugin) SetSCIMStore

func (p *Plugin) SetSCIMStore(s Store)

SetSCIMStore overrides the store the plugin uses; call it before OnInit.

type ProvisionLog

type ProvisionLog struct {
	ID           id.SCIMLogID    `json:"id"`
	ConfigID     id.SCIMConfigID `json:"config_id"`
	Action       string          `json:"action"`        // "create_user", "update_user", "delete_user", "create_group", etc.
	ResourceType string          `json:"resource_type"` // "User" or "Group"
	ExternalID   string          `json:"external_id"`   // SCIM externalId
	InternalID   string          `json:"internal_id"`   // authsome user/team ID
	Status       string          `json:"status"`        // "success", "error", "skipped"
	Detail       string          `json:"detail"`        // error message or summary
	CreatedAt    time.Time       `json:"created_at"`
}

ProvisionLog records a single SCIM provisioning action for audit purposes.

type ResourceTypeList

type ResourceTypeList map[string]any

ResourceTypeList is the /ResourceTypes document: the resource types on offer and the endpoint each one lives at.

type SCIMConfig

type SCIMConfig struct {
	ID          id.SCIMConfigID   `json:"id"`
	AppID       id.AppID          `json:"app_id"`
	OrgID       id.OrgID          `json:"org_id,omitempty"` // empty = app-level
	Name        string            `json:"name"`
	Enabled     bool              `json:"enabled"`
	AutoCreate  bool              `json:"auto_create"`  // auto-create users on SCIM push
	AutoSuspend bool              `json:"auto_suspend"` // auto-suspend on SCIM deactivate
	GroupSync   bool              `json:"group_sync"`   // sync SCIM Groups to teams
	DefaultRole string            `json:"default_role"` // default org role for provisioned users
	Metadata    map[string]string `json:"metadata,omitempty"`
	CreatedAt   time.Time         `json:"created_at"`
	UpdatedAt   time.Time         `json:"updated_at"`
}

SCIMConfig represents a SCIM 2.0 provisioning configuration. Each config provides a unique SCIM endpoint with its own bearer tokens, and can be scoped to an application or a specific organization.

type SCIMError

type SCIMError struct {
	Schemas  []string `json:"schemas"`
	Detail   string   `json:"detail"`
	Status   string   `json:"status"`
	ScimType string   `json:"scimType,omitempty"`
}

SCIMError represents a SCIM error response (RFC 7644 Section 3.12).

func NewSCIMError

func NewSCIMError(status int, detail string) *SCIMError

NewSCIMError creates a SCIM error response.

type SQLStore

type SQLStore struct {
	// contains filtered or unexported fields
}

SQLStore implements Store over postgres or sqlite.

func NewPostgresStore

func NewPostgresStore(db *grove.DB) *SQLStore

NewPostgresStore creates a SCIM store on the postgres driver.

func NewSqliteStore

func NewSqliteStore(db *grove.DB) *SQLStore

NewSqliteStore creates a SCIM store on the sqlite driver.

func (*SQLStore) CountAllLogsByStatus

func (s *SQLStore) CountAllLogsByStatus(ctx context.Context, appID string) (success, errs, skipped int, err error)

func (*SQLStore) CountLogsByStatus

func (s *SQLStore) CountLogsByStatus(ctx context.Context, configID id.SCIMConfigID) (success, errs, skipped int, err error)

func (*SQLStore) CreateConfig

func (s *SQLStore) CreateConfig(ctx context.Context, c *SCIMConfig) error

func (*SQLStore) CreateLog

func (s *SQLStore) CreateLog(ctx context.Context, l *ProvisionLog) error

func (*SQLStore) CreateToken

func (s *SQLStore) CreateToken(ctx context.Context, t *Token) error

func (*SQLStore) DeleteConfig

func (s *SQLStore) DeleteConfig(ctx context.Context, configID id.SCIMConfigID) error

func (*SQLStore) DeleteToken

func (s *SQLStore) DeleteToken(ctx context.Context, tokenID id.SCIMTokenID) error

func (*SQLStore) FindTokenByPlaintext

func (s *SQLStore) FindTokenByPlaintext(ctx context.Context, plaintext string) (*Token, *SCIMConfig, error)

FindTokenByPlaintext resolves the presented token by its SHA-256 lookup column and bcrypt-compares that one row. Rows from before the column existed carry no lookup and are found the old way, by comparing each; a match there is rewritten with its lookup so the scan is paid once.

func (*SQLStore) GetConfig

func (s *SQLStore) GetConfig(ctx context.Context, configID id.SCIMConfigID) (*SCIMConfig, error)

func (*SQLStore) GetToken

func (s *SQLStore) GetToken(ctx context.Context, tokenID id.SCIMTokenID) (*Token, error)

func (*SQLStore) ListAllLogs

func (s *SQLStore) ListAllLogs(ctx context.Context, appID string, limit int) ([]*ProvisionLog, error)

func (*SQLStore) ListConfigs

func (s *SQLStore) ListConfigs(ctx context.Context, appID string) ([]*SCIMConfig, error)

func (*SQLStore) ListConfigsByOrg

func (s *SQLStore) ListConfigsByOrg(ctx context.Context, orgID id.OrgID) ([]*SCIMConfig, error)

func (*SQLStore) ListLogs

func (s *SQLStore) ListLogs(ctx context.Context, configID id.SCIMConfigID, limit int) ([]*ProvisionLog, error)

func (*SQLStore) ListTokens

func (s *SQLStore) ListTokens(ctx context.Context, configID id.SCIMConfigID) ([]*Token, error)

func (*SQLStore) UpdateConfig

func (s *SQLStore) UpdateConfig(ctx context.Context, c *SCIMConfig) error

func (*SQLStore) UpdateToken

func (s *SQLStore) UpdateToken(ctx context.Context, t *Token) error

type SchemaList

type SchemaList map[string]any

SchemaList is the /Schemas document: the resource schemas on offer.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service encapsulates the SCIM business logic.

func (*Service) ChangeUserName

func (s *Service) ChangeUserName(ctx context.Context, cfg *SCIMConfig, u *user.User, email string) error

ChangeUserName moves a provisioned user to a new primary address through the email-record API rather than by overwriting the user's email field: the address becomes a verified email record owned by the user and is then made primary, so uniqueness inside the environment holds and every place that reads email records sees the change. An address another account owns is refused as out of scope; the user's own current address is a no-op.

func (*Service) CountAllLogsByStatus

func (s *Service) CountAllLogsByStatus(ctx context.Context, appID string) (success, failed, skipped int, err error)

CountAllLogsByStatus returns log counts across all configs for an app.

func (*Service) CountLogsByStatus

func (s *Service) CountLogsByStatus(ctx context.Context, configID id.SCIMConfigID) (success, failed, skipped int, err error)

CountLogsByStatus returns log counts grouped by status for a config.

func (*Service) CreateConfig

func (s *Service) CreateConfig(ctx context.Context, c *SCIMConfig) error

CreateConfig creates a new SCIM configuration.

func (*Service) DeactivateUser

func (s *Service) DeactivateUser(ctx context.Context, cfg *SCIMConfig, userID id.UserID) error

DeactivateUser suspends a user by setting Active=false.

func (*Service) DeleteConfig

func (s *Service) DeleteConfig(ctx context.Context, configID id.SCIMConfigID) error

DeleteConfig deletes a SCIM configuration.

func (*Service) GenerateToken

func (s *Service) GenerateToken(ctx context.Context, configID id.SCIMConfigID, name string, expiresAt *time.Time) (string, *Token, error)

GenerateToken creates a new bearer token for a SCIM config. Returns the plaintext token (shown once) and the stored token record.

func (*Service) GetConfig

func (s *Service) GetConfig(ctx context.Context, configID id.SCIMConfigID) (*SCIMConfig, error)

GetConfig returns a SCIM configuration by ID.

func (*Service) ListAllLogs

func (s *Service) ListAllLogs(ctx context.Context, appID string, limit int) ([]*ProvisionLog, error)

ListAllLogs returns provision logs across all configs for an app.

func (*Service) ListConfigs

func (s *Service) ListConfigs(ctx context.Context, appID string) ([]*SCIMConfig, error)

ListConfigs returns all SCIM configurations for an app.

func (*Service) ListConfigsByOrg

func (s *Service) ListConfigsByOrg(ctx context.Context, orgID id.OrgID) ([]*SCIMConfig, error)

ListConfigsByOrg returns all SCIM configurations for an organization.

func (*Service) ListLogs

func (s *Service) ListLogs(ctx context.Context, configID id.SCIMConfigID, limit int) ([]*ProvisionLog, error)

ListLogs returns provision logs for a config.

func (*Service) ListTokens

func (s *Service) ListTokens(ctx context.Context, configID id.SCIMConfigID) ([]*Token, error)

ListTokens returns all tokens for a SCIM configuration.

func (*Service) LoadTeam

func (s *Service) LoadTeam(ctx context.Context, cfg *SCIMConfig, raw string) (*organization.Team, error)

LoadTeam resolves a SCIM group id to a team inside cfg's organization.

func (*Service) LoadUser

func (s *Service) LoadUser(ctx context.Context, cfg *SCIMConfig, raw string) (*user.User, error)

LoadUser resolves a SCIM user id to a user inside cfg's scope. Any miss, including a well-formed id in another tenant, is ErrOutOfScope.

func (*Service) ProvisionGroup

func (s *Service) ProvisionGroup(ctx context.Context, cfg *SCIMConfig, scimGroup *GroupResource) (*organization.Team, string, error)

ProvisionGroup creates or updates a team from SCIM Group data.

func (*Service) ProvisionUser

func (s *Service) ProvisionUser(ctx context.Context, cfg *SCIMConfig, scimUser *UserResource) (*user.User, string, error)

ProvisionUser creates or updates a user from SCIM data.

func (*Service) RecordLog

func (s *Service) RecordLog(ctx context.Context, configID id.SCIMConfigID, action, resourceType, externalID, internalID, status, detail string)

RecordLog records a SCIM provisioning action.

func (*Service) ReplaceUser

func (s *Service) ReplaceUser(ctx context.Context, cfg *SCIMConfig, target *user.User, scimUser *UserResource) error

ReplaceUser applies a SCIM PUT to target, which the caller resolved by path id. The body's email must be the target's own address: an address that resolves to another account is out of scope, so a PUT can never re-point one user's record at another.

func (*Service) RevokeToken

func (s *Service) RevokeToken(ctx context.Context, tokenID id.SCIMTokenID) error

RevokeToken deletes a SCIM bearer token.

func (*Service) RotateToken

func (s *Service) RotateToken(ctx context.Context, oldTokenID id.SCIMTokenID, newName string, grace time.Duration) (string, *Token, error)

RotateToken issues a fresh bearer token for the same SCIM config and schedules the old token for expiry after a grace window. During the grace window both tokens authenticate so in-flight provisioning runs don't fail; once the grace expires the old token is rejected by ValidateToken (via Token.IsExpired). The new token's plaintext is returned to the caller exactly once — operators must persist it before discarding the response.

Grace must be ≥ 1 minute and ≤ 30 days; values outside that range are clamped to the bound, so a misconfigured rotation can't either instantly invalidate the old token (locking out a long-running job) nor leave it valid indefinitely.

If the old token's existing ExpiresAt is already sooner than now + grace, it is left unchanged — rotation never extends the lifetime of a token that was already on a tighter expiry.

func (*Service) UpdateConfig

func (s *Service) UpdateConfig(ctx context.Context, c *SCIMConfig) error

UpdateConfig updates a SCIM configuration.

func (*Service) ValidateToken

func (s *Service) ValidateToken(ctx context.Context, plaintext string) (*Token, *SCIMConfig, error)

ValidateToken checks a bearer token against stored hashes. Returns the matching token and its associated config, or an error.

type ServiceProviderConfig

type ServiceProviderConfig map[string]any

ServiceProviderConfig is the /ServiceProviderConfig document: which parts of SCIM this server implements.

type Store

type Store interface {
	// Config CRUD
	CreateConfig(ctx context.Context, c *SCIMConfig) error
	GetConfig(ctx context.Context, configID id.SCIMConfigID) (*SCIMConfig, error)
	UpdateConfig(ctx context.Context, c *SCIMConfig) error
	DeleteConfig(ctx context.Context, configID id.SCIMConfigID) error
	ListConfigs(ctx context.Context, appID string) ([]*SCIMConfig, error)
	ListConfigsByOrg(ctx context.Context, orgID id.OrgID) ([]*SCIMConfig, error)

	// Token CRUD
	CreateToken(ctx context.Context, t *Token) error
	GetToken(ctx context.Context, tokenID id.SCIMTokenID) (*Token, error)
	UpdateToken(ctx context.Context, t *Token) error
	ListTokens(ctx context.Context, configID id.SCIMConfigID) ([]*Token, error)
	DeleteToken(ctx context.Context, tokenID id.SCIMTokenID) error
	// FindTokenByPlaintext resolves a presented bearer token to its stored
	// record and owning config. Token hashes are salted bcrypt digests, so the
	// plaintext cannot be hashed and looked up directly; implementations must
	// scan candidates and bcrypt-compare. Expiry is not checked here.
	FindTokenByPlaintext(ctx context.Context, plaintext string) (*Token, *SCIMConfig, error)

	// Provision logs
	CreateLog(ctx context.Context, l *ProvisionLog) error
	ListLogs(ctx context.Context, configID id.SCIMConfigID, limit int) ([]*ProvisionLog, error)
	ListAllLogs(ctx context.Context, appID string, limit int) ([]*ProvisionLog, error)
	CountLogsByStatus(ctx context.Context, configID id.SCIMConfigID) (success, errors, skipped int, err error)
	CountAllLogsByStatus(ctx context.Context, appID string) (success, errors, skipped int, err error)
}

type Token

type Token struct {
	ID        id.SCIMTokenID  `json:"id"`
	ConfigID  id.SCIMConfigID `json:"config_id"`
	Name      string          `json:"name"`
	TokenHash string          `json:"-"`
	// TokenLookup is the hex SHA-256 of the plaintext, the indexed column a
	// presented token is resolved by; TokenHash, a salted bcrypt digest, is
	// then compared on that single row. Empty on rows written before the
	// column existed, which are found by scanning and upgraded on use.
	TokenLookup string     `json:"-"`
	LastUsedAt  *time.Time `json:"last_used_at,omitempty"`
	ExpiresAt   *time.Time `json:"expires_at,omitempty"`
	CreatedAt   time.Time  `json:"created_at"`
}

Token represents a bearer token for authenticating SCIM API requests.

func (*Token) IsExpired

func (t *Token) IsExpired() bool

IsExpired reports whether the token has expired.

type UserResource

type UserResource struct {
	Schemas    []string   `json:"schemas"`
	ID         string     `json:"id,omitempty"`
	ExternalID string     `json:"externalId,omitempty"`
	UserName   string     `json:"userName"`
	Name       Name       `json:"name"`
	Emails     []Email    `json:"emails,omitempty"`
	Active     bool       `json:"active"`
	Meta       *Meta      `json:"meta,omitempty"`
	Groups     []GroupRef `json:"groups,omitempty"`
}

UserResource represents a SCIM 2.0 User resource.

func UserToSCIM

func UserToSCIM(u *user.User, baseURL string) *UserResource

UserToSCIM converts an AuthSome user to a SCIM User resource.

func (*UserResource) PrimaryEmail

func (u *UserResource) PrimaryEmail() string

PrimaryEmail returns the primary email from the emails list, falling back to UserName.

Directories

Path Synopsis
Package contract wires the scim plugin's deep-link settings page into the forge-dashboard contract registry.
Package contract wires the scim plugin's deep-link settings page into the forge-dashboard contract registry.
Package scimtest is the store contract every SCIM store implementation must satisfy.
Package scimtest is the store contract every SCIM store implementation must satisfy.

Jump to

Keyboard shortcuts

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