Super Identity

Gem Version Build Status Dependency Status Code Climate Coverage Status

AAF Identity Enhancement (IdE) client library for Ruby applications.

Copyright 2016, Australian Access Federation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Installation

Add the super-identity dependency to your application's Gemfile:

gem 'super-identity'

Use Bundler to install the dependency:

bundle install

Querying IdE from your application

AAF typically uses this library along with rapid-rack or shib-rack, although it can be used anywhere that you have access to a user's auEduPersonSharedToken. This example shows a simple SubjectReceiver class for rapid-rack which is using SuperIdentity::Client to query IdE:

class SubjectReceiver
  # Include the client mixin, which provides the `identity_enhancements` and
  # `entitlements` methods. We'll be using `entitlements`.
  include SuperIdentity::Client

  # Map attributes from the attribute claim, as normal. In particular note we
  # have the 'auedupersonsharedtoken' here, which is needed when querying IdE.
  def map_attributes(_env, attrs)
    {
      shared_token: attrs['auedupersonsharedtoken'],
      display_name: attrs['displayname'],
      mail: attrs['mail']
    }
  end

  # Provision a Subject based on the attributes, and call the `assign_roles`
  # method defined below.
  def subject(_env, attrs)
    subject = Subject.find_or_initialize_by(shared_token: attrs[:shared_token])
    assign_roles(subject)
    subject.update!(attrs)
  end

  # Query IdE, and set the admin flag on the Subject based on the presence of
  # the entitlement we're using for authorization.
  def assign_roles(subject)
    # Here, `values` is an Array of String values which represent the
    # entitlements that were returned from IdE.
    values = entitlements(subject.shared_token)

    # Usually you'd do something more sophisticated, but boolean access control
    # works fine in a README :)
    subject.admin = values.include?('urn:mace:aaf.edu.au:ide:test-only')
  end

  # SuperIdentity::Client requires that this method be defined on the including
  # class. To access IdE, it needs the hostname and an client keypair. The
  # certificate must be issued by https://certs.aaf.edu.au and granted access to
  # the ide[.test].aaf.edu.au, otherwise requests will fail.
  def ide_config
    {
      host: 'ide.test.aaf.edu.au',
      cert: 'config/api-client.crt',
      key: 'config/api-client.key'
    }
  end
end

Testing code which queries IdE

A test helper is provided which uses webmock to return a set of entitlements which are provided by the test cases.

Example setup in RSpec:

# spec/spec_helper.rb

require 'webmock/rspec'

RSpec.configure do |config|
  # Include the `TestStub` mixin, which provides the `stub_ide` method for use
  # by your test cases.
  config.include SuperIdentity::TestStub
end

The complete test code:

# spec/lib/subject_receiver_spec.rb

RSpec.describe SubjectReceiver do
  # The `cert` and `key` files provided here should actually exist in your
  # project, though they shouldn't be a real keypair, since you'd typically
  # commit these to source control. A self-signed certificate is recommended
  # here (see below for more information).
  let(:ide_config) do
    { host: 'ide.example.edu', cert: 'spec/api.crt', key: 'spec/api.key' }
  end

  # Override the real `ide_config` in the class under test.
  before { allow(subject).to receive(:ide_config).and_return(ide_config) }

  # Empty Rack env, because the code under test isn't using it.
  let(:env) { {} }

  # User attributes
  let(:attrs) do
    {
      shared_token: shared_token,
      display_name: 'Super Identity Test',
      mail: '[email protected]'
    }
  end

  context 'when the expected entitlement is present' do
    # Define the shared_token and entitlements which will be returned from the
    # stubbed web request.
    let(:shared_token) { SecureRandom.urlsafe_base64(20) }
    let(:entitlements) { ['urn:mace:aaf.edu.au:ide:test-only'] }

    before do
      # This `stub_ide` method is provided by the `SuperIdentity::TestStub`
      # mixin. It takes `shared_token` and `entitlements` keyword arguments, and
      # sets up the response payload in webmock.
      stub_ide(shared_token: shared_token, entitlements: entitlements)
    end

    it 'assigns an administrator' do
      # Call the `SubjectReceiver`
      new_subject = subject.subject(env, attrs)

      # The `admin?` predicate will be true if the expected entitlement was
      # returned from IdE.
      expect(new_subject).to be_admin
    end
  end
end

If you aren't familiar with generating certiicates, here's a suggested method of creating the spec/api.crt and spec/api.key files required for this test case.

openssl genrsa -out spec/api.key 512
openssl req -new -x509 -key spec/api.key -out spec/api.crt -subj '/CN=test_api_client/'

Contributing

Refer to GitHub Flow for help contributing to this project.