IntegrationPal

This engine is meant to contain the elements of big_sis that can be shared across sis integrations such as

  • access token
  • job starting
  • job monitoring
  • error capturing
  • Okta SAML2 authentication

Usage

1) Create an active job bin/rails generate job example_job

Installation

  1. Add integration_pal to your application's Gemfile:

    gem 'integration_pal'
    

    And install the bundle:

    $ bundle install
    

    Or install the Gem manually:

    $ gem install integration_pal
    
  2. Define Environment variables

    ENCRYPTION_KEY=... # must be 32 chars
    SALT_KEY=... # must be 32 chars
    
    OKTA_APP=okta_app_key
    OKTA_ID=xxxxxxxxxxx # Usually Base64
    SAML_DOMAIN=https://example-app-test.herokuapp.com/
    SAML_AUDIENCE=https://example-app.herokuapp.com/login # If using a single Okta app for all environments, this should be set to the prod instance + /login
    
  3. Generate template files for your project:

    $ bundle exec rails g integration_pal:install
    
  4. Set active job config.active_job.queue_adapter = :sidekiq # If you are using sidekiq

  5. Configure app. Include sp_metadata.xml[.erb] in config/saml:

    <?xml version="1.0"?>
    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="<%= ENV['SAML_AUDIENCE'] || URI.join(ENV['SAML_DOMAIN'], saml2_meta_path) %>">
      <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:AssertionConsumerService Location="<%= URI.join(ENV['SAML_DOMAIN'], saml2_login_path) %>" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" index="0"/>
      </md:SPSSODescriptor>
    </md:EntityDescriptor>
    

    Also include idp_metadata.xml[.erb] in config/saml:

    <?xml version="1.0" encoding="UTF-8"?>
    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://www.okta.com/<%= ENV['OKTA_ID'] %>">
        <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
            <md:KeyDescriptor use="signing">
                <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                    <ds:X509Data>
                        <ds:X509Certificate>
    {{ INSERT OKTA CERTIFICATE HERE }}
                        </ds:X509Certificate>
                    </ds:X509Data>
                </ds:KeyInfo>
            </md:KeyDescriptor>
            <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
            <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
            <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://instructure.okta.com/app/<%= ENV['OKTA_APP'] %>/<%= ENV['OKTA_ID'] %>/sso/saml"/>
            <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://instructure.okta.com/app/<%= ENV['OKTA_APP'] %>/<%= ENV['OKTA_ID'] %>/sso/saml"/>
        </md:IDPSSODescriptor>
    </md:EntityDescriptor>
    

    Metadata file names can be overriden from within an initializer:

    IntegrationPal. = 'idp_metadata.xml'
    IntegrationPal. = 'sp_metadata.xml'
    

    To validate or begin Okta authentication within a controller, use:

    session[:saml_username] ||= ENV['USER'] if Rails.env.in?(%w(development test))
    redirect_to  unless session[:saml_username]
    

Deploying to Heroku

The SAML2 Authentication library that this tool uses requires libxmlsec1-dev. In order to ensure that it is installed on the Heroku dyno, add https://github.com/ABASystems/heroku-buildpack-apt as the first buildpack in Heroku, followed by heroku/ruby. Also include an Aptfile in the root of your project:

libxmlsec1-dev

! - Double check this if Heroku fails to build Gem native extensions for nokogiri-xmlsec-instructure during deployment.

License

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

Example API Request

Note how we sign the request using ApiAuth

require "uri"
require "net/https"
require "time"
require "rubygems"
require "api_auth"
require 'active_record'

JOB_URL = "http://localhost:3000/api/v1/jobs"

access_key_id = "test"
secret_access_key = "test"

uri = URI.parse(JOB_URL)
http = Net::HTTP.new(uri.host, uri.port)

request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json')
request.body = {
  job: {
    worker_id: 1,
    job_params: {
      start_date: "2016-05-25T19:57:16Z",
      end_date: "2017-05-25T13:57:59-06:00"
    }
  }
}.to_json

ApiAuth.sign!(request, access_key_id, secret_access_key)

response = http.request(request)
puts response.body