Skip to content
Landon Gray edited this page Nov 3, 2022 · 11 revisions

ScimRails

This Gem is a fork of https://github.com/lessonly/scim_rails and enables rails apps to interface with Okta via the SCIM standard. As such this documentation may have changes that aren't in the original forked repo. As such this documentation serves the purposes of developers at Hendrick.

Installation

Add this line to your application's Gemfile:

gem "scim_rails", git: "https://github.com/Hendrick/scim_rails.git"

And then execute:

$ bundle

Generate the config file with:

$ rails generate scim_rails config

The config file will be located at:

config/initializers/scim_rails_config.rb

Please update the config file with the models and attributes of your app. See example

Remove the following line from configuration config.user_schema block unless there is an active method is defined in Users model. Similar to atlas.

active: :active?

Mount the gem in your routes file :

Application.routes.draw do
  mount ScimRails::Engine => "/"
end

This will enable the following routes for the Gem to use:

Request Route
get 'scim/v2/Users'
post 'scim/v2/Users'
get 'scim/v2/Users/:id'
put 'scim/v2/Users/:id'
patch 'scim/v2/Users/:id'

Note: This Gem can be mounted to any path. For example:

https://scim.example.com/scim/v2/Users
https://www.example.com/scim/v2/Users
https://example.com/example/scim/v2/Users

Generate a migration that creates a company table and adds a foreign key referencing company to users

def change

  create_table :companies do |t|
    t.string :name
    t.string :subdomain
    t.string :api_token

    t.timestamps
  end
    
  add_reference :users, :company, foreign_key: true
end

Add a company model

class Company < ApplicationRecord
  has_many :users
end

Create a company and associate users with that company. I'd recommend making a migration for this.

def change
  company = Company.create!(
    name: "Hendrick Automotive Group",
    subdomain: "hendrick",
    api_token: SecureRandom.hex(10)
  )
  User.update_all(company_id: company.id)
end

Run the app locally and ensure that the following command work with the correct username and passowrd

$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users'

Usage

Content-Type

When sending requests to the server the Content-Type should be set to application/scim+json but will also respond to application/json.

All responses will be sent with a Content-Type of application/scim+json.

Authentication

The original gem claims to support both basic and OAuth bearer authentication. But I've only been able to successfully use the Basic Auth method

Basic Auth
Username

The config setting basic_auth_model_searchable_attribute is the model attribute used to authenticate as the username. It defaults to :subdomain.

Ensure it is unique to the model records.

Password

The config setting basic_auth_model_authenticatable_attribute is the model attribute used to authenticate as password. Defaults to :api_token.

Assuming the attribute is :api_token, generate the password using:

token = ScimRails::Encoder.encode(company)
# use the token as password for requests
company.api_token = token # required
company.save! # don't forget to persist the company record

This is necessary irrespective of your authentication choice(s) - basic auth, oauth bearer or both.

Sample Request
$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users'
OAuth Bearer

If you wish to further delve into this method please explore the original guide: https://github.com/lessonly/scim_rails

List

All

Sample request:

$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users'
Pagination

This Gem provides two pagination filters; startIndex and count.

startIndex is the positional number you would like to start at. This parameter can accept any integer but anything less than 1 will be interpreted as 1. If you visualize an array with all your user records in the array, startIndex is basically what element you would like to start at. If you are familiar with SQL this parameter is directly correlated to the query offset. The default value for this filter is 1.

count is the number of records you would like present in the response. The default value for this filter is 100.

Sample request:

$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users?startIndex=38&count=44'

Pagination only really works with a determinate order. What that means is, every time you call the database you need to get the results in the exact same order. So the 4th record is always the 4th record and never appears in a different position. If there is no order then records might show up on multiple pages. The default order is by id but this can be configured with scim_users_list_order.

The pagination filters may be used on their own or in addition to the query filters listed in the next section.

Querying

Currently the only filter supported is a single level eq. More operators can be added fairly easily in future releases. The SCIM RFC documents nested querying which is something we would like to implement in the future.

Queryable attributes can be mapped in the configuration file.

Supported filters:

filter=email eq [email protected]
fitler=userName eq [email protected]
filter=formattedName eq Test User
filter=id eq 1

Unsupported filter:

filter=(email eq [email protected]) or (userName eq [email protected])

Sample request:

$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users?filter=formattedName%20eq%20%22Test%20User%22'

Show

This response can be modified in the configuration file. The user_schema configuration supports any JSON structure and will transform any values by calling symbols against the user model. A sample SCIM compliant response looks like:

{
  schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
  id: "1",
  userName: "[email protected]",
  name: {
    givenName: "Test",
    familyName: "User"
  },
  emails: [
    {
      value: "[email protected]"
    },
  ],
  active: "true"
}

Sample request:

$ curl -X GET 'http://username:password@localhost:3000/scim/v2/Users/1'

Create

The create request can receive any SCIM compliant JSON but can only be parsed with the configuration schema provided. What that means is that if your app receives a request to modify an attribute that is not listed in your mutable_user_attributes configuration it will ignore the parameter. In addition to needing to be included in the mutable attributes it also requires mutable_user_attributes_schema which defines where the Gem should look for a given attribute.

Do not include attributes that you do not want modified such as id. Any attributes can be provided in the user_schema configuration to be returned as part of the response but if they are not part of the mutable_user_attributes_schema then they cannot be modified.

Sample request:

$ curl -X POST 'http://username:password@localhost:3000/scim/v2/Users/' -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"[email protected]","name":{"givenName":"Test","familyName":"User"},"emails":[{"primary":true,"value":"[email protected]","type":"work"}],"displayName":"Test User","active":true}' -H 'Content-Type: application/scim+json'

Update

Update requests follow the same guidelines as create requests. The request is parsed for the mutable attributes provided in the configuration file and sent to the user model to update those attributes. This request expects a full representation of the object and any missing mutable attributes will send nil to the user model. If the attribute cannot be blank and sends a validation error, that error will be rescued and the response will be an appropriate SCIM error.

Sample request:

$ curl -X PUT 'http://username:password@localhost:3000/scim/v2/Users/1' -d '{"schemas":["urn:ietf:params:scim:schemas:core:2.0:User"],"userName":"[email protected]","name":{"givenName":"Test","familyName":"User"},"emails":[{"primary":true,"value":"[email protected]","type":"work"}],"displayName":"Test User","active":true}' -H 'Content-Type: application/scim+json'

Deprovision / Reprovision

The PATCH request was implemented to work with Okta. Okta updates profiles with PUT and deprovisions / reprovisions with PATCH. This implementation of PATCH is not SCIM compliant as it does not update a single attribute on the user profile but instead only sends a status update request to the record.

We would like to implement PATCH to be fully SCIM compliant in future releases.

Sample request:

$ curl -X PATCH 'http://username:password@localhost:3000/scim/v2/Users/1' -d '{"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{"op": "replace", "value": { "active": false }}]}' -H 'Content-Type: application/scim+json'

Error Handling

By default, scim_rails will output any unhandled exceptions to your configured rails logs.

If you would like, you can supply a custom handler for exceptions in the initializer. The only requirement is that the value you supply responds to #call.

For example, you might want to notify Honeybadger:

ScimRails.configure do |config|
  config.on_error = ->(e) { Honeybadger.notify(e) }
end

Contributing

Pull Requests

Pull requests are welcome and encouraged! Please follow the default template format.

How to create a pull request from a fork.

Getting Started

Clone (or fork) the project.

Navigate to the top level of the project directory in your console and run bundle install.

Proceed to setting up the dummy app.

Dummy App

This Gem contains a fully functional Rails application that lives in /spec/dummy.

In the console, navigate to the dummy app at /spec/dummy.

Next run bin/setup to setup the app. This will set up the gems and build the databases. The databases are local to the project.

Last run bundle exec rails server.

If you wish you may send CURL requests to the dummy server or send requests to it via Postman.

Specs

Specs can be run with rspec at the top level of the project (if you run rspec and it shows zero specs try running rspec from a different directory).

All specs should be passing. (The dummy app will need to be setup first.)

Current Maintainers

Maintainers:

  • Are active contributors

  • Help set project direction

  • Merge contributions from contributors

  • @wernull

  • @rreinhardt9

License

The gem is available as open source under the terms of the MIT License.