ExScimEcto.StorageAdapter (ex_scim_ecto v0.2.1)

Copy Markdown View Source

Ecto-based implementation of ExScim.Storage.Adapter.

Expects the following in your application config:

config :ex_scim,
  storage_repo: MyApp.Repo,
  user_model: MyApp.Accounts.User,
  group_model: MyApp.Groups.Group

To preload associations:

config :ex_scim,
  storage_repo: MyApp.Repo,
  user_model: {MyApp.Accounts.User, preload: [:roles, :organizations]},
  group_model: {MyApp.Groups.Group, preload: [:members]}

To configure a custom lookup key (defaults to :id):

config :ex_scim,
  user_model: {MyApp.Accounts.User, lookup_key: :resource_id},
  group_model: {MyApp.Groups.Group, preload: [:members], lookup_key: :uuid}

To map SCIM complex attribute paths to DB columns:

config :ex_scim,
  user_model: {MyApp.Accounts.User,
    filter_mapping: %{
      "emails.value" => :email,
      "name.givenName" => :given_name
    }}

To map SCIM paths to association fields (for has_many/has_one relations):

config :ex_scim,
  user_model: {MyApp.Accounts.User,
    preload: [:user_emails],
    filter_mapping: %{
      "emails.value" => {:assoc, :user_emails, :value},
      "emails.type" => {:assoc, :user_emails, :type}
    }}

The association tuple format is {:assoc, assoc_name, field_name} where:

  • assoc_name is the association name as defined in your schema
  • field_name is the column in the associated table to filter on

When filtering on associations, a LEFT JOIN is automatically added and results are made DISTINCT to avoid duplicate root records.

To enable multi-tenant scoping via a discriminator column:

config :ex_scim,
  user_model: {MyApp.Accounts.User, lookup_key: :id, tenant_key: :organization_id},
  group_model: {MyApp.Groups.Group, tenant_key: :organization_id}

When tenant_key is configured and scope.tenant_id is not nil, all queries include a WHERE clause on the tenant column, and creates inject the tenant_id.

To map domain fields to DB columns with value transformation:

config :ex_scim,
  user_model: {MyApp.Accounts.User,
    field_mapping: %{
      active: {:status,
        fn true -> "active"; false -> "inactive" end,
        fn "active" -> true; _ -> false end}
    }}

Each field_mapping entry is domain_field => {db_field, to_storage_fn, from_storage_fn}:

  • domain_field - the field name used in the domain/SCIM layer (atom)
  • db_field - the column name in the Ecto schema (atom)
  • to_storage_fn - (domain_value -> db_value), applied on writes and filter queries
  • from_storage_fn - (db_value -> domain_value), applied on reads

When field_mapping is configured, read operations return a plain map instead of an Ecto struct (since the domain key may not exist on the struct). Your mapper's to_scim/2 should accept a map rather than pattern-matching on the struct (e.g., use def to_scim(user, ...) instead of def to_scim(%User{} = user, ...)).

field_mapping works together with filter_mapping: filter_mapping resolves SCIM attribute paths to domain field atoms, and field_mapping then resolves domain field atoms to DB columns with value transformation.

See also ExScim.Resources.Resource.