├── .github └── workflows │ └── ruby.yml ├── .gitignore ├── .rspec ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.md ├── README.md ├── SECURITY.md ├── lib ├── omniauth-saml.rb ├── omniauth-saml │ └── version.rb └── omniauth │ └── strategies │ ├── saml.rb │ └── saml │ └── validation_error.rb ├── omniauth-saml.gemspec └── spec ├── omniauth └── strategies │ └── saml_spec.rb ├── spec_helper.rb └── support ├── custom_attributes.xml ├── digest_mismatch.xml ├── example_cert.pem ├── example_key.pem ├── example_logout_request.xml ├── example_logout_response.xml ├── example_response.xml ├── invalid_signature.xml └── no_name_id.xml /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake 2 | # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby 3 | 4 | name: Ruby 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | ruby-version: ['3.1', '3.2', '3.3', 'jruby-head'] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby-version }} 26 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 27 | - name: Run tests 28 | run: bundle exec rspec 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | coverage/ 3 | spec/support/example_private_key.pem 4 | /gemfiles/*.lock 5 | .idea/ 6 | .bundle/ 7 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ### v2.2.4 (2025-05-14) 3 | 4 | 5 | #### Bug Fixes 6 | 7 | * remove :idp_cert_fingerprint_validator ([c573690](/../../commit/c573690)) 8 | * Fix GHSA-cgp2-2cmh-pf7x 9 | 10 | 11 | ### v2.2.3 (2025-03-12) 12 | 13 | 14 | #### Features 15 | 16 | * new release 2.2.3 ([34eb354](/../../commit/34eb354)) 17 | 18 | 19 | #### Bug Fixes 20 | 21 | * bump ruby-saml to 1.18 ([7a348b4](/../../commit/7a348b4)) 22 | 23 | 24 | 25 | ### v2.2.2 (2025-03-04) 26 | 27 | 28 | #### Features 29 | 30 | * log errors on failed logout ([23ef364](/../../commit/23ef364)) 31 | 32 | 33 | 34 | ### v2.2.1 (2024-09-11) 35 | 36 | * Fix permission file permissions within published gem (#226) 37 | 38 | 39 | ### v2.2.0 (2024-09-10) 40 | 41 | This release fixes: 42 | 43 | * [GHSA-jw9c-mfg7-9rx2](https://github.com/SAML-Toolkits/ruby-saml/security/advisories/GHSA-jw9c-mfg7-9rx2) 44 | * [GHSA-cvp8-5r8g-fhvq](https://github.com/omniauth/omniauth-saml/security/advisories/GHSA-cvp8-5r8g-fhvq) 45 | 46 | #### Chores 47 | 48 | * use semantic versioning for ruby-saml as per gem build hints ([e17f460](/../../commit/e17f460)) 49 | 50 | 51 | ### v2.1.1 (2024-09-10) 52 | 53 | #### Chores 54 | 55 | * Add Ruby 3.1 to the CI matrix ([8954310](/../../commit/8954310)) 56 | * Add Ruby 3.2 to CI matrix ([9403366](/../../commit/9403366)) 57 | * Fix copy-pasteability of code example ([3eb8942](/../../commit/3eb8942)) 58 | * bump dependencies and remove ruby eol versions ([c6fc2db](/../../commit/c6fc2db)) 59 | * Remove old maintainer email from gemspec ([9f6daa](/../../commit/9f6daa)) 60 | 61 | 62 | ### v2.1.0 (2022-03-01) 63 | 64 | 65 | #### Refactor 66 | 67 | * Rename usage of deprecated SAML options ([74ed8df](/../../commit/74ed8df)) 68 | 69 | #### Chores 70 | 71 | * bump ruby-saml to 1.12 ([15c156a](/../../commit/15c156a)) 72 | 73 | 74 | ### v2.0.0 (2021-01-13) 75 | 76 | 77 | #### Chores 78 | 79 | * Allow OmniAuth 2.0.0 ([f7ec7ee](/../../commit/f7ec7ee)) 80 | 81 | 82 | 83 | ### v1.10.3 (2020-10-06) 84 | 85 | 86 | #### Bug Fixes 87 | 88 | * add options to logout_request initialization ([c271a37](/../../commit/c271a37)) 89 | 90 | 91 | 92 | ### v1.10.2 (2018-05-23) 93 | 94 | 95 | #### Features 96 | 97 | * **saml** 98 | * inherits allows response options from ruby-saml instead of whitelist ([a0eedd6](/../../commit/a0eedd6)) 99 | 100 | 101 | 102 | ### v1.10.1 (2018-06-07) 103 | 104 | 105 | #### Features 106 | 107 | * **saml-response** 108 | * whitelist more response options ([575198d](/../../commit/575198d)) 109 | 110 | 111 | 112 | ### v1.10.0 (2018-02-19) 113 | 114 | 115 | #### Bug Fixes 116 | 117 | * ambiguous path match in other phase ([1b465b9](/../../commit/1b465b9)) 118 | * Update ruby-saml gem to 1.7 or later to fix CVE-2017-11430 ([6bc28ad](/../../commit/6bc28ad)) 119 | 120 | 121 | 122 | ### v1.9.0 (2018-01-29) 123 | 124 | 125 | #### Bug Fixes 126 | 127 | * Update omniauth gem to 1.3.2 or later 1.3.x ([b6bb425](/../../commit/b6bb425)) 128 | 129 | 130 | 131 | ### v1.8.1 (2017-06-22) 132 | 133 | 134 | #### Bug Fixes 135 | 136 | * default assertion_consumer_service_url not set during callback ([4a2a5ef](/../../commit/4a2a5ef)) 137 | 138 | 139 | 140 | ### v1.8.0 (2017-06-07) 141 | 142 | 143 | #### Features 144 | 145 | * include SessionIndex in logout requests ([fb6ad86](/../../commit/fb6ad86)) 146 | * Support for configurable IdP SLO session destruction ([586bf89](/../../commit/586bf89)) 147 | * Add `uid_attribute` option to control the attribute used for the user id. ([eacc536](/../../commit/eacc536)) 148 | 149 | 150 | 151 | ### v1.7.0 (2016-10-19) 152 | 153 | #### Features 154 | 155 | * Support for Single Logout ([cd3fc43](/../../commit/cd3fc43)) 156 | * Add issuer information to the metadata endpoint, to allow IdPs to properly configure themselves. ([7bbbb67](/../../commit/7bbbb67)) 157 | * Added the response object to the extra['response_object'], so we can use the raw response object if we want to. ([76ed3d6](/../../commit/76ed3d6)) 158 | 159 | #### Chores 160 | 161 | * Update `ruby-saml` to 1.4.0 to address security fixes. ([638212](/../../commit/638212)) 162 | 163 | 164 | ### v1.6.0 (2016-06-27) 165 | * Ensure that subclasses of `OmniAuth::Stategies::SAML` are registered with OmniAuth as strategies (https://github.com/omniauth/omniauth-saml/pull/95) 166 | * Update ruby-saml to 1.3 to address [CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697) (Signature wrapping attacks) 167 | 168 | 169 | ### v1.5.0 (2016-02-25) 170 | 171 | * Initialize OneLogin::RubySaml::Response instance with settings 172 | * Adding "settings" to Response Class at initialization to handle signing verification 173 | * Support custom attributes 174 | * change URL from PracticallyGreen to omniauth 175 | * Add specs for ACS fallback URL behavior 176 | * Call validation earlier to get real error instead of 'response missing name_id' 177 | * Avoid mutation of the options hash during requests and callbacks 178 | 179 | 180 | ### v1.4.2 (2016-02-09) 181 | 182 | * update ruby-saml to 1.1 183 | 184 | 185 | ### v1.4.1 (2015-08-09) 186 | 187 | * Configurable attribute_consuming_service 188 | 189 | 190 | ### v1.4.0 (2015-07-23) 191 | 192 | * update ruby-saml to 1.0.0 193 | 194 | 195 | ### v1.3.1 (2015-02-26) 196 | 197 | * Added missing fingerprint key check 198 | * Expose fingerprint on the auth_hash 199 | 200 | 201 | ### v1.3.0 (2015-01-23) 202 | 203 | * add `idp_cert_fingerprint_validator` option 204 | 205 | 206 | ### v1.2.0 (2014-03-19) 207 | 208 | * provide SP metadata at `/auth/saml/metadata` 209 | 210 | 211 | ### v1.1.0 (2013-11-07) 212 | 213 | * no longer set a default `name_identifier_format` 214 | * pass strategy options to the underlying ruby-saml library 215 | * fallback to omniauth callback url if `assertion_consumer_service_url` is not set 216 | * add `idp_sso_target_url_runtime_params` option 217 | 218 | 219 | ### v1.0.0 (2012-11-12) 220 | 221 | * remove SAML code and port to ruby-saml gem 222 | * fix incompatibility with OmniAuth 1.1 223 | 224 | 225 | ### v0.9.2 (2012-03-30) 226 | 227 | * validate the SAML response 228 | * 100% test coverage 229 | * now requires ruby 1.9.2+ 230 | 231 | 232 | ### v0.9.1 (2012-02-23) 233 | 234 | * return first and last name in the info hash 235 | * no longer use LDAP OIDs for name and email selection 236 | * return SAML attributes as the omniauth raw_info hash 237 | 238 | 239 | ### v0.9.0 (2012-02-14) 240 | 241 | * initial release 242 | * extracts commits from omniauth 0-3-stable branch 243 | * port to omniauth 1.0 strategy format 244 | * update README with more documentation and license 245 | * package as the `omniauth-saml` gem 246 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | 4 | ## Workflow 5 | 6 | We are using the [Feature Branch Workflow (also known as GitHub Flow)](https://guides.github.com/introduction/flow/), 7 | and prefer delivery as pull requests. 8 | 9 | Our first line of defense is the [Travis CI](https://travis-ci.org/omniauth/omniauth-saml) build defined within [.travis.yml](.travis.yml) and triggered for every pull request. 10 | 11 | Create a feature branch: 12 | 13 | ```sh 14 | git checkout -B feat/contributing 15 | ``` 16 | 17 | ## Git Commit 18 | 19 | The cardinal rule for creating good commits is to ensure there is only one 20 | "logical change" per commit. Why is this an important rule? 21 | 22 | * The smaller the amount of code being changed, the quicker & easier it is to 23 | review & identify potential flaws. 24 | 25 | * If a change is found to be flawed later, it may be necessary to revert the 26 | broken commit. This is much easier to do if there are not other unrelated 27 | code changes entangled with the original commit. 28 | 29 | * When troubleshooting problems using Git's bisect capability, small well 30 | defined changes will aid in isolating exactly where the code problem was 31 | introduced. 32 | 33 | * When browsing history using Git annotate/blame, small well defined changes 34 | also aid in isolating exactly where & why a piece of code came from. 35 | 36 | Things to avoid when creating commits 37 | 38 | * Mixing whitespace changes with functional code changes. 39 | * Mixing two unrelated functional changes. 40 | * Sending large new features in a single giant commit. 41 | 42 | ## Git Commit Conventions 43 | 44 | We use git commit as per [Conventional Changelog](https://github.com/ajoslin/conventional-changelog): 45 | 46 | ```none 47 | (): 48 | ``` 49 | 50 | Allowed types: 51 | 52 | * **feat**: A new feature 53 | * **fix**: A bug fix 54 | * **docs**: Documentation only changes 55 | * **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, newline, line endings, etc) 56 | * **refactor**: A code change that neither fixes a bug or adds a feature 57 | * **perf**: A code change that improves performance 58 | * **test**: Adding missing tests 59 | * **chore**: Changes to the build process or auxiliary tools and libraries such as documentation generation 60 | 61 | You can add additional details after a new line to describe the change in detail or automatically close a issue on Github. 62 | 63 | ```none 64 | feat: create initial CONTRIBUTING.md 65 | 66 | This closes #73 67 | ``` 68 | 69 | ## Release process 70 | 71 | Example for version `v1.7.0` 72 | 73 | 1. Bump the version in `lib/omniauth-saml/version.rb` 74 | 1. Update [CHANGELOG.md](CHANGELOG.md) with `bundle exec conventional-changelog version=v1.7.0 since_version=v1.6.0` 75 | 1. Commit all your changes 76 | 1. Tag the latest commit with `git tag v1.7.0` 77 | 1. Contact the maintainers 78 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | Copyright © 2016 Omniauth-SAML maintainers 4 | 5 | Copyright © 2011-2014 [Practically Green, Inc.](http://www.practicallygreen.com/). 6 | 7 | All rights reserved. Released under the MIT license. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in 17 | all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OmniAuth SAML 2 | 3 | [![Gem Version](http://img.shields.io/gem/v/omniauth-saml.svg)][gem] 4 | [![Ruby](https://github.com/omniauth/omniauth-saml/actions/workflows/ruby.yml/badge.svg)](https://github.com/omniauth/omniauth-saml/actions/workflows/ruby.yml) 5 | [![Maintainability](https://api.codeclimate.com/v1/badges/749e17b553ea944522c1/maintainability)][codeclimate] 6 | [![Coverage Status](http://img.shields.io/coveralls/omniauth/omniauth-saml.svg)][coveralls] 7 | 8 | [gem]: https://rubygems.org/gems/omniauth-saml 9 | [codeclimate]: https://codeclimate.com/github/omniauth/omniauth-saml/maintainability 10 | [coveralls]: https://coveralls.io/r/omniauth/omniauth-saml 11 | 12 | A generic SAML strategy for OmniAuth available under the [MIT License](LICENSE.md) 13 | 14 | https://github.com/omniauth/omniauth-saml 15 | 16 | ## Requirements 17 | 18 | * [OmniAuth](http://www.omniauth.org/) 2.1+ 19 | * Ruby 3.1.x+ 20 | 21 | ## Versioning 22 | 23 | We tag and release gems according to the [Semantic Versioning](http://semver.org/) principle. In addition to the guidelines of Semantic Versioning, we follow a further guideline that otherwise backwards-compatible dependency upgrades for security reasons should generally be cause for a MINOR version upgrade as opposed to a PATCH version upgrade. Backwards-incompatible dependency upgrades for security reasons should still result in a MAJOR version upgrade for this library. 24 | 25 | ## Usage 26 | 27 | Use the SAML strategy as a middleware in your application: 28 | 29 | ```ruby 30 | require 'omniauth' 31 | use OmniAuth::Strategies::SAML, 32 | :assertion_consumer_service_url => "consumer_service_url", 33 | :sp_entity_id => "sp_entity_id", 34 | :idp_sso_service_url => "idp_sso_service_url", 35 | :idp_sso_service_url_runtime_params => {:original_request_param => :mapped_idp_param}, 36 | :idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", 37 | :idp_cert_multi => { 38 | :signing => ["-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", ...], 39 | :encryption => [] 40 | }, 41 | :idp_cert_fingerprint => "E7:91:B2:E1:...", 42 | :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" 43 | ``` 44 | 45 | or in your Rails application: 46 | 47 | in `Gemfile`: 48 | 49 | ```ruby 50 | gem 'omniauth-saml' 51 | ``` 52 | 53 | and in `config/initializers/omniauth.rb`: 54 | 55 | ```ruby 56 | Rails.application.config.middleware.use OmniAuth::Builder do 57 | provider :saml, 58 | :assertion_consumer_service_url => "consumer_service_url", 59 | :sp_entity_id => "rails-application", 60 | :idp_sso_service_url => "idp_sso_service_url", 61 | :idp_sso_service_url_runtime_params => {:original_request_param => :mapped_idp_param}, 62 | :idp_cert => "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", 63 | :idp_cert_multi => { 64 | :signing => ["-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", "-----BEGIN CERTIFICATE-----\n...-----END CERTIFICATE-----", ...], 65 | :encryption => [] 66 | }, 67 | :idp_cert_fingerprint => "E7:91:B2:E1:...", 68 | :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" 69 | end 70 | ``` 71 | 72 | For IdP-initiated SSO, users should directly access the IdP SSO service URL. Set the `href` of your application's login link to the value of `idp_sso_service_url`. For SP-initiated SSO, link to `/auth/saml`. 73 | 74 | A `OneLogin::RubySaml::Response` object is added to the `env['omniauth.auth']` extra attribute, so we can use it in the controller via `env['omniauth.auth'].extra.response_object` 75 | 76 | ## SP Metadata 77 | 78 | The service provider metadata used to ease configuration of the SAML SP in the IdP can be retrieved from `http://example.com/auth/saml/metadata`. Send this URL to the administrator of the IdP. 79 | 80 | Note that when [integrating with Devise](#devise-integration), the URL path will be scoped according to the name of the Devise resource. For example, if the app's user model calls `devise_for :users`, the metadata URL will be `http://example.com/users/auth/saml/metadata`. 81 | 82 | ## Options 83 | 84 | * `:assertion_consumer_service_url` - The URL at which the SAML assertion should be 85 | received. If not provided, defaults to the OmniAuth callback URL (typically 86 | `http://example.com/auth/saml/callback`). Optional. 87 | 88 | * `:sp_entity_id` - The name of your application. Some identity providers might need this 89 | to establish the identity of the service provider requesting the login. **Required**. 90 | 91 | * `:idp_sso_service_url` - The URL to which the authentication request should be sent. 92 | This would be on the identity provider. **Required**. 93 | 94 | * `:idp_slo_service_url` - The URL to which the single logout request and response should 95 | be sent. This would be on the identity provider. Optional. 96 | 97 | * `:idp_slo_session_destroy` - A proc that accepts up to two parameters (the rack environment, and the session), 98 | and performs whatever tasks are necessary to log out the current user from your application. 99 | See the example listed under "Single Logout." Defaults to calling `#clear` on the session. Optional. 100 | 101 | * `:slo_default_relay_state` - The value to use as default `RelayState` for single log outs. The 102 | value can be a string, or a `Proc` (or other object responding to `call`). The `request` 103 | instance will be passed to this callable if it has an arity of 1. If the value is a string, 104 | the string will be returned, when the `RelayState` is called. Optional. 105 | 106 | * `:idp_sso_service_url_runtime_params` - A dynamic mapping of request params that exist 107 | during the request phase of OmniAuth that should to be sent to the IdP after a specific 108 | mapping. So for example, a param `original_request_param` with value `original_param_value`, 109 | could be sent to the IdP on the login request as `mapped_idp_param` with value 110 | `original_param_value`. Optional. 111 | 112 | * `:idp_cert` - The identity provider's certificate in PEM format. Takes precedence 113 | over the fingerprint option below. This option or `:idp_cert_multi` or `:idp_cert_fingerprint` must 114 | be present. 115 | 116 | * `:idp_cert_multi` - Multiple identity provider certificates in PEM format. Takes precedence 117 | over the fingerprint option below. This option `:idp_cert` or `:idp_cert_fingerprint` must 118 | be present. 119 | 120 | * `:idp_cert_fingerprint` - The SHA1 fingerprint of the certificate, e.g. 121 | "90:CC:16:F0:8D:...". This is provided from the identity provider when setting up 122 | the relationship. This option or `:idp_cert` or `:idp_cert_multi` MUST be present. 123 | 124 | * `:name_identifier_format` - Used during SP-initiated SSO. Describes the format of 125 | the username required by this application. If you need the email address, use 126 | "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress". See 127 | http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf section 8.3 for 128 | other options. Note that the identity provider might not support all options. 129 | If not specified, the IdP is free to choose the name identifier format used 130 | in the response. Optional. 131 | 132 | * `:request_attributes` - Used to build the metadata file to inform the IdP to send certain attributes 133 | along with the SAMLResponse messages. Defaults to requesting `name`, `first_name`, `last_name` and `email` 134 | attributes. See the `OneLogin::RubySaml::AttributeService` class in the [Ruby SAML gem](https://github.com/onelogin/ruby-saml) for the available options for each attribute. Set to `{}` to disable this from metadata. 135 | 136 | * `:attribute_service_name` - Name for the attribute service. Defaults to `Required attributes`. 137 | 138 | * `:attribute_statements` - Used to map Attribute Names in a SAMLResponse to 139 | entries in the OmniAuth [info hash](https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later). 140 | For example, if your SAMLResponse contains an Attribute called 'EmailAddress', 141 | specify `{:email => ['EmailAddress']}` to map the Attribute to the 142 | corresponding key in the info hash. URI-named Attributes are also supported, e.g. 143 | `{:email => ['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']}`. 144 | *Note*: All attributes can also be found in an array under `auth_hash[:extra][:raw_info]`, 145 | so this setting should only be used to map attributes that are part of the OmniAuth info hash schema. 146 | 147 | * `:uid_attribute` - Attribute that uniquely identifies the user. If unset, the name identifier returned by the IdP is used. 148 | 149 | * See the `OneLogin::RubySaml::Settings` class in the [Ruby SAML gem](https://github.com/onelogin/ruby-saml) for additional supported options. 150 | 151 | ## IdP Metadata 152 | 153 | You can use the `OneLogin::RubySaml::IdpMetadataParser` to configure some options: 154 | 155 | ```ruby 156 | require 'omniauth' 157 | idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new 158 | idp_metadata = idp_metadata_parser.parse_remote_to_hash("http://idp.example.com/saml/metadata") 159 | 160 | # or, if you have the metadata in a String: 161 | # idp_metadata = idp_metadata_parser.parse_to_hash(idp_metadata_xml) 162 | 163 | use OmniAuth::Strategies::SAML, 164 | idp_metadata.merge( 165 | :assertion_consumer_service_url => "consumer_service_url", 166 | :sp_entity_id => "sp_entity_id" 167 | ) 168 | ``` 169 | 170 | See the [Ruby SAML gem's README](https://github.com/onelogin/ruby-saml#metadata-based-configuration) for more details. 171 | 172 | ## Devise Integration 173 | 174 | Straightforward integration with [Devise](https://github.com/plataformatec/devise), the widely-used authentication solution for Rails. 175 | 176 | In `config/initializers/devise.rb`: 177 | 178 | ```ruby 179 | Devise.setup do |config| 180 | config.omniauth :saml, 181 | idp_cert_fingerprint: 'fingerprint', 182 | idp_sso_service_url: 'idp_sso_service_url' 183 | end 184 | ``` 185 | 186 | Then follow Devise's general [OmniAuth tutorial](https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview), replacing references to `facebook` with `saml`. 187 | 188 | ## Single Logout 189 | 190 | Single Logout can be Service Provider initiated or Identity Provider initiated. 191 | 192 | For SP initiated logout, the `idp_slo_service_url` option must be set to the logout url on the IdP, 193 | and users directed to `user_saml_omniauth_authorize_path + '/spslo'` after logging out locally. For 194 | IdP initiated logout, logout requests from the IdP should go to `/auth/saml/slo` (this can be 195 | advertised in metadata by setting the `single_logout_service_url` config option). 196 | 197 | When using Devise as an authentication solution, the SP initiated flow can be integrated 198 | in the `SessionsController#destroy` action. 199 | 200 | For this to work it is important to preserve the `saml_uid` and `saml_session_index` value before Devise 201 | clears the session and redirect to the `/spslo` sub-path to initiate the single logout. 202 | 203 | Example `destroy` action in `sessions_controller.rb`: 204 | 205 | ```ruby 206 | class SessionsController < Devise::SessionsController 207 | # ... 208 | 209 | def destroy 210 | # Preserve the saml_uid and saml_session_index in the session 211 | saml_uid = session['saml_uid'] 212 | saml_session_index = session['saml_session_index'] 213 | super do 214 | session['saml_uid'] = saml_uid 215 | session['saml_session_index'] = saml_session_index 216 | end 217 | end 218 | 219 | # ... 220 | 221 | def after_sign_out_path_for(_) 222 | if session['saml_uid'] && session['saml_session_index'] && SAML_SETTINGS.idp_slo_service_url 223 | user_saml_omniauth_authorize_path + "/spslo" 224 | else 225 | super 226 | end 227 | end 228 | end 229 | ``` 230 | 231 | By default, omniauth-saml attempts to log the current user out of your application by clearing the session. 232 | This may not be enough for some authentication solutions (e.g. [Clearance](https://github.com/thoughtbot/clearance/)). 233 | Instead, you may set the `:idp_slo_session_destroy` option to a proc that performs the necessary logout tasks. 234 | 235 | Example `:idp_slo_session_destroy` setting for Clearance compatibility: 236 | 237 | ```ruby 238 | Rails.application.config.middleware.use OmniAuth::Builder do 239 | provider :saml, idp_slo_session_destroy: proc { |env, _session| env[:clearance].sign_out }, ... 240 | end 241 | ``` 242 | 243 | ## Authors 244 | 245 | Authored by [Rajiv Aaron Manglani](http://www.rajivmanglani.com/), Raecoo Cao, Todd W Saxton, Ryan Wilcox, Steven Anderson, Nikos Dimitrakopoulos, Rudolf Vriend and [Bruno Pedro](http://brunopedro.com/). 246 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 2.1.x | :white_check_mark: | 11 | | < 2.1.x | :x: | 12 | 13 | ## Reporting a Vulnerability 14 | 15 | You can report a vulnerability via https://github.com/omniauth/omniauth-saml/security. 16 | -------------------------------------------------------------------------------- /lib/omniauth-saml.rb: -------------------------------------------------------------------------------- 1 | require 'omniauth/strategies/saml' 2 | require 'omniauth/strategies/saml/validation_error' 3 | -------------------------------------------------------------------------------- /lib/omniauth-saml/version.rb: -------------------------------------------------------------------------------- 1 | module OmniAuth 2 | module SAML 3 | VERSION = '2.2.4' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/omniauth/strategies/saml.rb: -------------------------------------------------------------------------------- 1 | require 'omniauth' 2 | require 'ruby-saml' 3 | 4 | module OmniAuth 5 | module Strategies 6 | class SAML 7 | include OmniAuth::Strategy 8 | 9 | def self.inherited(subclass) 10 | OmniAuth::Strategy.included(subclass) 11 | end 12 | 13 | RUBYSAML_RESPONSE_OPTIONS = OneLogin::RubySaml::Response::AVAILABLE_OPTIONS 14 | 15 | option :name_identifier_format, nil 16 | option :idp_sso_service_url_runtime_params, {} 17 | option :request_attributes, [ 18 | { :name => 'email', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Email address' }, 19 | { :name => 'name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Full name' }, 20 | { :name => 'first_name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Given name' }, 21 | { :name => 'last_name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Family name' } 22 | ] 23 | option :attribute_service_name, 'Required attributes' 24 | option :attribute_statements, { 25 | name: ["name"], 26 | email: ["email", "mail"], 27 | first_name: ["first_name", "firstname", "firstName"], 28 | last_name: ["last_name", "lastname", "lastName"] 29 | } 30 | option :slo_default_relay_state 31 | option :uid_attribute 32 | option :idp_slo_session_destroy, proc { |_env, session| session.clear } 33 | 34 | def request_phase 35 | authn_request = OneLogin::RubySaml::Authrequest.new 36 | 37 | with_settings do |settings| 38 | redirect(authn_request.create(settings, additional_params_for_authn_request)) 39 | end 40 | end 41 | 42 | def callback_phase 43 | raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing") unless request.params["SAMLResponse"] 44 | 45 | with_settings do |settings| 46 | handle_response(request.params["SAMLResponse"], options_for_response_object, settings) do 47 | super 48 | end 49 | end 50 | rescue OmniAuth::Strategies::SAML::ValidationError 51 | fail!(:invalid_ticket, $!) 52 | rescue OneLogin::RubySaml::ValidationError 53 | fail!(:invalid_ticket, $!) 54 | end 55 | 56 | # Obtain an idp certificate fingerprint from the response. 57 | def response_fingerprint 58 | response = request.params["SAMLResponse"] 59 | response = (response =~ /^ 'http://www.w3.org/2000/09/xmldsig#' }) 62 | base64_cert = cert_element.text 63 | cert_text = Base64.decode64(base64_cert) 64 | cert = OpenSSL::X509::Certificate.new(cert_text) 65 | Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(':') 66 | end 67 | 68 | def other_phase 69 | if request_path_pattern.match(current_path) 70 | @env['omniauth.strategy'] ||= self 71 | setup_phase 72 | 73 | if on_subpath?(:metadata) 74 | other_phase_for_metadata 75 | elsif on_subpath?(:slo) 76 | other_phase_for_slo 77 | elsif on_subpath?(:spslo) 78 | other_phase_for_spslo 79 | else 80 | call_app! 81 | end 82 | else 83 | call_app! 84 | end 85 | end 86 | 87 | uid do 88 | if options.uid_attribute 89 | ret = find_attribute_by([options.uid_attribute]) 90 | if ret.nil? 91 | raise OmniAuth::Strategies::SAML::ValidationError.new("SAML response missing '#{options.uid_attribute}' attribute") 92 | end 93 | ret 94 | else 95 | @name_id 96 | end 97 | end 98 | 99 | info do 100 | found_attributes = options.attribute_statements.map do |key, values| 101 | attribute = find_attribute_by(values) 102 | [key, attribute] 103 | end 104 | 105 | Hash[found_attributes] 106 | end 107 | 108 | extra { { :raw_info => @attributes, :session_index => @session_index, :response_object => @response_object } } 109 | 110 | def find_attribute_by(keys) 111 | keys.each do |key| 112 | return @attributes[key] if @attributes[key] 113 | end 114 | 115 | nil 116 | end 117 | 118 | private 119 | 120 | def request_path_pattern 121 | @request_path_pattern ||= %r{\A#{Regexp.quote(request_path)}(/|\z)} 122 | end 123 | 124 | def on_subpath?(subpath) 125 | on_path?("#{request_path}/#{subpath}") 126 | end 127 | 128 | def handle_response(raw_response, opts, settings) 129 | response = OneLogin::RubySaml::Response.new(raw_response, opts.merge(settings: settings)) 130 | response.attributes["fingerprint"] = settings.idp_cert_fingerprint 131 | response.soft = false 132 | 133 | response.is_valid? 134 | @name_id = response.name_id 135 | @session_index = response.sessionindex 136 | @attributes = response.attributes 137 | @response_object = response 138 | 139 | session["saml_uid"] = @name_id 140 | session["saml_session_index"] = @session_index 141 | yield 142 | end 143 | 144 | def slo_relay_state 145 | if request.params.has_key?("RelayState") && request.params["RelayState"] != "" 146 | request.params["RelayState"] 147 | else 148 | slo_default_relay_state = options.slo_default_relay_state 149 | if slo_default_relay_state.respond_to?(:call) 150 | if slo_default_relay_state.arity == 1 151 | slo_default_relay_state.call(request) 152 | else 153 | slo_default_relay_state.call 154 | end 155 | else 156 | slo_default_relay_state 157 | end 158 | end 159 | end 160 | 161 | def handle_logout_response(raw_response, settings) 162 | # After sending an SP initiated LogoutRequest to the IdP, we need to accept 163 | # the LogoutResponse, verify it, then actually delete our session. 164 | 165 | logout_response = OneLogin::RubySaml::Logoutresponse.new(raw_response, settings, :matches_request_id => session["saml_transaction_id"]) 166 | logout_response.soft = false 167 | logout_response.validate 168 | 169 | session.delete("saml_uid") 170 | session.delete("saml_transaction_id") 171 | session.delete("saml_session_index") 172 | 173 | redirect(slo_relay_state) 174 | end 175 | 176 | def handle_logout_request(raw_request, settings) 177 | logout_request = OneLogin::RubySaml::SloLogoutrequest.new(raw_request, {}.merge(settings: settings).merge(get_params: @request.params)) 178 | 179 | if logout_request.is_valid? && 180 | logout_request.name_id == session["saml_uid"] 181 | 182 | # Actually log out this session 183 | options[:idp_slo_session_destroy].call @env, session 184 | 185 | # Generate a response to the IdP. 186 | logout_request_id = logout_request.id 187 | logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, RelayState: slo_relay_state) 188 | redirect(logout_response) 189 | else 190 | raise OmniAuth::Strategies::SAML::ValidationError.new("SAML failed to process LogoutRequest (#{logout_request.errors.join(', ')})") 191 | end 192 | end 193 | 194 | # Create a SP initiated SLO: https://github.com/onelogin/ruby-saml#single-log-out 195 | def generate_logout_request(settings) 196 | logout_request = OneLogin::RubySaml::Logoutrequest.new() 197 | 198 | # Since we created a new SAML request, save the transaction_id 199 | # to compare it with the response we get back 200 | session["saml_transaction_id"] = logout_request.uuid 201 | 202 | if settings.name_identifier_value.nil? 203 | settings.name_identifier_value = session["saml_uid"] 204 | end 205 | 206 | if settings.sessionindex.nil? 207 | settings.sessionindex = session["saml_session_index"] 208 | end 209 | 210 | logout_request.create(settings, RelayState: slo_relay_state) 211 | end 212 | 213 | def with_settings 214 | options[:assertion_consumer_service_url] ||= callback_url 215 | yield OneLogin::RubySaml::Settings.new(options) 216 | end 217 | 218 | def options_for_response_object 219 | # filter options to select only extra parameters 220 | opts = options.select {|k,_| RUBYSAML_RESPONSE_OPTIONS.include?(k.to_sym)} 221 | 222 | # symbolize keys without activeSupport/symbolize_keys (ruby-saml use symbols) 223 | opts.inject({}) do |new_hash, (key, value)| 224 | new_hash[key.to_sym] = value 225 | new_hash 226 | end 227 | end 228 | 229 | def other_phase_for_metadata 230 | with_settings do |settings| 231 | # omniauth does not set the strategy on the other_phase 232 | response = OneLogin::RubySaml::Metadata.new 233 | 234 | add_request_attributes_to(settings) if options.request_attributes.length > 0 235 | 236 | Rack::Response.new(response.generate(settings), 200, { "Content-Type" => "application/xml" }).finish 237 | end 238 | end 239 | 240 | def other_phase_for_slo 241 | with_settings do |settings| 242 | if request.params["SAMLResponse"] 243 | handle_logout_response(request.params["SAMLResponse"], settings) 244 | elsif request.params["SAMLRequest"] 245 | handle_logout_request(request.params["SAMLRequest"], settings) 246 | else 247 | raise OmniAuth::Strategies::SAML::ValidationError.new("SAML logout response/request missing") 248 | end 249 | end 250 | end 251 | 252 | def other_phase_for_spslo 253 | if options.idp_slo_service_url 254 | with_settings do |settings| 255 | redirect(generate_logout_request(settings)) 256 | end 257 | else 258 | Rack::Response.new("Not Implemented", 501, { "Content-Type" => "text/html" }).finish 259 | end 260 | end 261 | 262 | def add_request_attributes_to(settings) 263 | settings.attribute_consuming_service.service_name options.attribute_service_name 264 | settings.sp_entity_id = options.sp_entity_id 265 | 266 | options.request_attributes.each do |attribute| 267 | settings.attribute_consuming_service.add_attribute attribute 268 | end 269 | end 270 | 271 | def additional_params_for_authn_request 272 | {}.tap do |additional_params| 273 | runtime_request_parameters = options.delete(:idp_sso_service_url_runtime_params) 274 | 275 | if runtime_request_parameters 276 | runtime_request_parameters.each_pair do |request_param_key, mapped_param_key| 277 | additional_params[mapped_param_key] = request.params[request_param_key.to_s] if request.params.has_key?(request_param_key.to_s) 278 | end 279 | end 280 | end 281 | end 282 | end 283 | end 284 | end 285 | 286 | OmniAuth.config.add_camelization 'saml', 'SAML' 287 | -------------------------------------------------------------------------------- /lib/omniauth/strategies/saml/validation_error.rb: -------------------------------------------------------------------------------- 1 | module OmniAuth 2 | module Strategies 3 | class SAML 4 | class ValidationError < Exception 5 | end 6 | end 7 | end 8 | end -------------------------------------------------------------------------------- /omniauth-saml.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/omniauth-saml/version', __FILE__) 2 | 3 | Gem::Specification.new do |gem| 4 | gem.name = 'omniauth-saml' 5 | gem.version = OmniAuth::SAML::VERSION 6 | gem.summary = 'A generic SAML strategy for OmniAuth.' 7 | gem.description = 'A generic SAML strategy for OmniAuth.' 8 | gem.license = 'MIT' 9 | 10 | gem.authors = ['Raecoo Cao', 'Ryan Wilcox', 'Rajiv Aaron Manglani', 'Steven Anderson', 'Nikos Dimitrakopoulos', 'Rudolf Vriend', 'Bruno Pedro'] 11 | gem.homepage = 'https://github.com/omniauth/omniauth-saml' 12 | 13 | gem.required_ruby_version = '>= 3.1' 14 | 15 | gem.add_runtime_dependency 'omniauth', '~> 2.1' 16 | gem.add_runtime_dependency 'ruby-saml', '~> 1.18' 17 | 18 | gem.add_development_dependency 'rake', '~> 13.2' 19 | gem.add_development_dependency 'rspec', '~> 3.13' 20 | gem.add_development_dependency 'simplecov', '~> 0.10' 21 | gem.add_development_dependency 'rack-test', '~> 2.1' 22 | gem.add_development_dependency 'conventional-changelog', '~> 1.3' 23 | gem.add_development_dependency 'coveralls', '~> 0.8' 24 | 25 | gem.files = ['README.md', 'CHANGELOG.md', 'LICENSE.md'] + Dir['lib/**/*.rb'] 26 | gem.test_files = Dir['spec/**/*.rb'] 27 | gem.require_paths = ["lib"] 28 | end 29 | -------------------------------------------------------------------------------- /spec/omniauth/strategies/saml_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec::Matchers.define :fail_with do |message| 4 | match do |actual| 5 | actual.redirect? && /\?.*message=#{message}/ === actual.location 6 | end 7 | end 8 | 9 | def post_xml(xml = :example_response, opts = {}) 10 | post "/auth/saml/callback", opts.merge({'SAMLResponse' => load_xml(xml)}) 11 | end 12 | 13 | describe OmniAuth::Strategies::SAML, :type => :strategy do 14 | include OmniAuth::Test::StrategyTestCase 15 | 16 | let(:auth_hash){ last_request.env['omniauth.auth'] } 17 | let(:saml_options) do 18 | { 19 | :assertion_consumer_service_url => "http://localhost:9080/auth/saml/callback", 20 | :single_logout_service_url => "http://localhost:9080/auth/saml/slo", 21 | :idp_sso_service_url => "https://idp.sso.example.com/signon/29490", 22 | :idp_slo_service_url => "https://idp.sso.example.com/signoff/29490", 23 | :idp_cert_fingerprint => "C1:59:74:2B:E8:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB", 24 | :idp_sso_service_url_runtime_params => {:original_param_key => :mapped_param_key}, 25 | :name_identifier_format => "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", 26 | :request_attributes => [ 27 | { :name => 'email', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Email address' }, 28 | { :name => 'name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Full name' }, 29 | { :name => 'first_name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Given name' }, 30 | { :name => 'last_name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Family name' } 31 | ], 32 | :attribute_service_name => 'Required attributes' 33 | } 34 | end 35 | let(:strategy) { [OmniAuth::Strategies::SAML, saml_options] } 36 | 37 | describe 'POST /auth/saml' do 38 | context 'without idp runtime params present' do 39 | before do 40 | post '/auth/saml' 41 | end 42 | 43 | it 'should get authentication page' do 44 | expect(last_response).to be_redirect 45 | expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signon\/29490/ 46 | expect(last_response.location).to match /\?SAMLRequest=/ 47 | expect(last_response.location).not_to match /mapped_param_key/ 48 | expect(last_response.location).not_to match /original_param_key/ 49 | end 50 | end 51 | 52 | context 'with idp runtime params' do 53 | before do 54 | post '/auth/saml', 'original_param_key' => 'original_param_value', 'mapped_param_key' => 'mapped_param_value' 55 | end 56 | 57 | it 'should get authentication page' do 58 | expect(last_response).to be_redirect 59 | expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signon\/29490/ 60 | expect(last_response.location).to match /\?SAMLRequest=/ 61 | expect(last_response.location).to match /\&mapped_param_key=original_param_value/ 62 | expect(last_response.location).not_to match /original_param_key/ 63 | end 64 | end 65 | 66 | context "when the assertion_consumer_service_url is the default" do 67 | before :each do 68 | saml_options[:compress_request] = false 69 | saml_options.delete(:assertion_consumer_service_url) 70 | end 71 | 72 | it 'should send the current callback_url as the assertion_consumer_service_url' do 73 | %w(foo.example.com bar.example.com).each do |host| 74 | post "https://#{host}/auth/saml" 75 | 76 | expect(last_response).to be_redirect 77 | 78 | location = URI.parse(last_response.location) 79 | query = Rack::Utils.parse_query location.query 80 | expect(query).to have_key('SAMLRequest') 81 | 82 | request = REXML::Document.new(Base64.decode64(query['SAMLRequest'])) 83 | expect(request.root).not_to be_nil 84 | 85 | acs = request.root.attributes.get_attribute('AssertionConsumerServiceURL') 86 | expect(acs.to_s).to eq "https://#{host}/auth/saml/callback" 87 | end 88 | end 89 | end 90 | 91 | context 'when authn request signing is requested' do 92 | subject { post '/auth/saml' } 93 | 94 | let(:private_key) { OpenSSL::PKey::RSA.new 2048 } 95 | 96 | before do 97 | saml_options[:compress_request] = false 98 | 99 | saml_options[:private_key] = private_key.to_pem 100 | saml_options[:security] = { 101 | authn_requests_signed: true, 102 | signature_method: XMLSecurity::Document::RSA_SHA256 103 | } 104 | end 105 | 106 | it 'should sign the request' do 107 | is_expected.to be_redirect 108 | 109 | location = URI.parse(last_response.location) 110 | query = Rack::Utils.parse_query location.query 111 | expect(query).to have_key('SAMLRequest') 112 | expect(query).to have_key('Signature') 113 | expect(query).to have_key('SigAlg') 114 | 115 | expect(query['SigAlg']).to eq XMLSecurity::Document::RSA_SHA256 116 | end 117 | end 118 | end 119 | 120 | describe 'POST /auth/saml/callback' do 121 | subject { last_response } 122 | 123 | let(:xml) { :example_response } 124 | 125 | before :each do 126 | allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 20, 40, 00)) 127 | end 128 | 129 | context "when the response is valid" do 130 | before :each do 131 | post_xml 132 | end 133 | 134 | it "should set the uid to the nameID in the SAML response" do 135 | expect(auth_hash['uid']).to eq '_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23' 136 | end 137 | 138 | it "should set the raw info to all attributes" do 139 | expect(auth_hash['extra']['raw_info'].all.to_hash).to eq( 140 | 'first_name' => ['Rajiv'], 141 | 'last_name' => ['Manglani'], 142 | 'email' => ['user@example.com'], 143 | 'company_name' => ['Example Company'], 144 | 'fingerprint' => saml_options[:idp_cert_fingerprint] 145 | ) 146 | end 147 | 148 | it "should set the response_object to the response object from ruby_saml response" do 149 | expect(auth_hash['extra']['response_object']).to be_kind_of(OneLogin::RubySaml::Response) 150 | end 151 | end 152 | 153 | context "when the assertion_consumer_service_url is the default" do 154 | before :each do 155 | saml_options.delete(:assertion_consumer_service_url) 156 | OmniAuth.config.full_host = 'http://localhost:9080' 157 | post_xml 158 | end 159 | 160 | it { is_expected.not_to fail_with(:invalid_ticket) } 161 | end 162 | 163 | context "when there is no SAMLResponse parameter" do 164 | before :each do 165 | post '/auth/saml/callback' 166 | end 167 | 168 | it { is_expected.to fail_with(:invalid_ticket) } 169 | end 170 | 171 | context "when there is no name id in the XML" do 172 | before :each do 173 | allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 23, 55, 00)) 174 | post_xml :no_name_id 175 | end 176 | 177 | it { is_expected.to fail_with(:invalid_ticket) } 178 | end 179 | 180 | context "when the fingerprint is invalid" do 181 | before :each do 182 | saml_options[:idp_cert_fingerprint] = "00:00:00:00:00:0C:6C:A9:41:0F:6E:83:F6:D1:52:25:45:58:89:FB" 183 | post_xml 184 | end 185 | 186 | it { is_expected.to fail_with(:invalid_ticket) } 187 | end 188 | 189 | context "when the digest is invalid" do 190 | before :each do 191 | post_xml :digest_mismatch 192 | end 193 | 194 | it { is_expected.to fail_with(:invalid_ticket) } 195 | end 196 | 197 | context "when the signature is invalid" do 198 | before :each do 199 | post_xml :invalid_signature 200 | end 201 | 202 | it { is_expected.to fail_with(:invalid_ticket) } 203 | end 204 | 205 | context "when the response is stale" do 206 | before :each do 207 | allow(Time).to receive(:now).and_return(Time.utc(2012, 11, 8, 20, 45, 00)) 208 | end 209 | 210 | context "without :allowed_clock_drift option" do 211 | before { post_xml :example_response } 212 | 213 | it { is_expected.to fail_with(:invalid_ticket) } 214 | end 215 | 216 | context "with :allowed_clock_drift option" do 217 | before :each do 218 | saml_options[:allowed_clock_drift] = 60 219 | post_xml :example_response 220 | end 221 | 222 | it { is_expected.to_not fail_with(:invalid_ticket) } 223 | end 224 | end 225 | 226 | context "when response has custom attributes" do 227 | before :each do 228 | saml_options[:idp_cert_fingerprint] = "3B:82:F1:F5:54:FC:A8:FF:12:B8:4B:B8:16:61:1D:E4:8E:9B:E2:3C" 229 | saml_options[:attribute_statements] = { 230 | email: ["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"], 231 | first_name: ["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"], 232 | last_name: ["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"] 233 | } 234 | post_xml :custom_attributes 235 | end 236 | 237 | it "should obey attribute statements mapping" do 238 | expect(auth_hash[:info]).to eq( 239 | 'first_name' => 'Rajiv', 240 | 'last_name' => 'Manglani', 241 | 'email' => 'user@example.com', 242 | 'name' => nil 243 | ) 244 | end 245 | end 246 | 247 | context "when using custom user id attribute" do 248 | before :each do 249 | saml_options[:idp_cert_fingerprint] = "3B:82:F1:F5:54:FC:A8:FF:12:B8:4B:B8:16:61:1D:E4:8E:9B:E2:3C" 250 | saml_options[:uid_attribute] = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" 251 | post_xml :custom_attributes 252 | end 253 | 254 | it "should return user id attribute" do 255 | expect(auth_hash[:uid]).to eq("user@example.com") 256 | end 257 | end 258 | 259 | context "when using custom user id attribute, but it is missing" do 260 | before :each do 261 | saml_options[:uid_attribute] = "missing_attribute" 262 | post_xml 263 | end 264 | 265 | it "should fail to authenticate" do 266 | should fail_with(:invalid_ticket) 267 | expect(last_request.env['omniauth.error']).to be_instance_of(OmniAuth::Strategies::SAML::ValidationError) 268 | expect(last_request.env['omniauth.error'].message).to eq("SAML response missing 'missing_attribute' attribute") 269 | end 270 | end 271 | 272 | context "when response is a logout response" do 273 | before :each do 274 | saml_options[:sp_entity_id] = "https://idp.sso.example.com/metadata/29490" 275 | 276 | post "/auth/saml/slo", { 277 | SAMLResponse: load_xml(:example_logout_response), 278 | RelayState: "https://example.com/", 279 | }, "rack.session" => {"saml_transaction_id" => "_3fef1069-d0c6-418a-b68d-6f008a4787e9"} 280 | end 281 | it "should redirect to relaystate" do 282 | expect(last_response).to be_redirect 283 | expect(last_response.location).to match /https:\/\/example.com\// 284 | end 285 | end 286 | 287 | context "when request is a logout request" do 288 | subject { post "/auth/saml/slo", params, "rack.session" => { "saml_uid" => "username@example.com" } } 289 | 290 | before :each do 291 | saml_options[:sp_entity_id] = "https://idp.sso.example.com/metadata/29490" 292 | end 293 | 294 | let(:params) do 295 | { 296 | "SAMLRequest" => load_xml(:example_logout_request), 297 | "RelayState" => "https://example.com/", 298 | } 299 | end 300 | 301 | context "when logout request is valid" do 302 | before { subject } 303 | 304 | it "should redirect to logout response" do 305 | expect(last_response).to be_redirect 306 | expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signoff\/29490/ 307 | expect(last_response.location).to match /RelayState=https%3A%2F%2Fexample.com%2F/ 308 | end 309 | end 310 | 311 | context "when request is an invalid logout request" do 312 | before :each do 313 | allow_any_instance_of(OneLogin::RubySaml::SloLogoutrequest).to receive(:is_valid?).and_return(false) 314 | allow_any_instance_of(OneLogin::RubySaml::SloLogoutrequest).to receive(:errors).and_return(['Blank logout request']) 315 | end 316 | 317 | # TODO: Maybe this should not raise an exception, but return some 4xx error instead? 318 | it "should raise an exception" do 319 | expect { subject }. 320 | to raise_error(OmniAuth::Strategies::SAML::ValidationError, 'SAML failed to process LogoutRequest (Blank logout request)') 321 | end 322 | end 323 | 324 | context "when request is a logout request but the request param is missing" do 325 | let(:params) { {} } 326 | 327 | # TODO: Maybe this should not raise an exception, but return a 422 error instead? 328 | it 'should raise an exception' do 329 | expect { subject }. 330 | to raise_error(OmniAuth::Strategies::SAML::ValidationError, 'SAML logout response/request missing') 331 | end 332 | end 333 | end 334 | 335 | context "when sp initiated SLO" do 336 | def test_default_relay_state(static_default_relay_state = nil, &block_default_relay_state) 337 | saml_options["slo_default_relay_state"] = static_default_relay_state || block_default_relay_state 338 | post "/auth/saml/spslo" 339 | 340 | expect(last_response).to be_redirect 341 | expect(last_response.location).to match /https:\/\/idp.sso.example.com\/signoff\/29490/ 342 | expect(last_response.location).to match /RelayState=https%3A%2F%2Fexample.com%2F/ 343 | end 344 | 345 | it "should redirect to logout request" do 346 | test_default_relay_state("https://example.com/") 347 | end 348 | 349 | it "should redirect to logout request with a block" do 350 | test_default_relay_state do 351 | "https://example.com/" 352 | end 353 | end 354 | 355 | it "should redirect to logout request with a block with a request parameter" do 356 | test_default_relay_state do |request| 357 | "https://example.com/" 358 | end 359 | end 360 | 361 | it "should give not implemented without an idp_slo_service_url" do 362 | saml_options.delete(:idp_slo_service_url) 363 | post "/auth/saml/spslo" 364 | 365 | expect(last_response.status).to eq 501 366 | expect(last_response.body).to match /Not Implemented/ 367 | end 368 | end 369 | end 370 | 371 | describe 'POST /auth/saml/metadata' do 372 | before do 373 | saml_options[:sp_entity_id] = 'http://example.com/SAML' 374 | post '/auth/saml/metadata' 375 | end 376 | 377 | it 'should get SP metadata page' do 378 | expect(last_response.status).to eq 200 379 | expect(last_response.headers["Content-Type"]).to eq "application/xml" 380 | end 381 | 382 | it 'should configure attributes consuming service' do 383 | expect(last_response.body).to match /AttributeConsumingService/ 384 | expect(last_response.body).to match /first_name/ 385 | expect(last_response.body).to match /last_name/ 386 | expect(last_response.body).to match /Required attributes/ 387 | expect(last_response.body).to match /entityID/ 388 | expect(last_response.body).to match /http:\/\/example.com\/SAML/ 389 | end 390 | end 391 | 392 | context 'when hitting an unknown route in our sub path' do 393 | before { post '/auth/saml/unknown' } 394 | 395 | specify { expect(last_response.status).to eql 404 } 396 | end 397 | 398 | context 'when hitting a completely unknown route' do 399 | before { post '/unknown' } 400 | 401 | specify { expect(last_response.status).to eql 404 } 402 | end 403 | 404 | context 'when hitting a route that contains a substring match for the strategy name' do 405 | before { post '/auth/saml2/metadata' } 406 | 407 | it 'should not set the strategy' do 408 | expect(last_request.env['omniauth.strategy']).to be_nil 409 | expect(last_response.status).to eql 404 410 | end 411 | end 412 | 413 | describe 'subclass behavior' do 414 | it 'registers subclasses in OmniAuth.strategies' do 415 | subclass = Class.new(described_class) 416 | expect(OmniAuth.strategies).to include(described_class, subclass) 417 | end 418 | end 419 | end 420 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | 3 | if ENV['TRAVIS'] 4 | require 'coveralls' 5 | Coveralls.wear! 6 | end 7 | 8 | SimpleCov.start 9 | 10 | require 'omniauth-saml' 11 | require 'rack/test' 12 | require 'rexml/document' 13 | require 'rexml/xpath' 14 | require 'base64' 15 | 16 | TEST_LOGGER = Logger.new(StringIO.new) 17 | OneLogin::RubySaml::Logging.logger = TEST_LOGGER 18 | OmniAuth.config.logger = TEST_LOGGER 19 | OmniAuth.config.request_validation_phase = proc {} 20 | 21 | RSpec.configure do |config| 22 | config.include Rack::Test::Methods 23 | end 24 | 25 | def load_xml(filename=:example_response) 26 | filename = File.expand_path(File.join('..', 'support', "#{filename.to_s}.xml"), __FILE__) 27 | Base64.encode64(IO.read(filename)) 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/custom_attributes.xml: -------------------------------------------------------------------------------- 1 | 4 | http://localhost:9000/saml2/idp/metadata.php 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | f311FuR1PE2NXct21G5z8Ka/Gfo= 17 | 18 | 19 | 3vfxoQn2PLwcYp1ApVLzlaZKEcHGjNZwLCBHkJC8oHYRonoL8v25iJ+5NFlWWXxSRG0SUA15coH+1gLMm6cF41h1sqHL/3wtiHQARnJUogqRUM76hTePHkSiJMUpr+ZD+Kb/l0DFct9/gJYkW1RPny9v8vdGNsMOQ/qnmk2xtII= 20 | 21 | 22 | MIICWDCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBJMQswCQYDVQQGEwJmaTEQMA4GA1UECAwHVXVzaW1hYTERMA8GA1UECgwIRmxvd2RvY2sxFTATBgNVBAMMDGZsb3dkb2NrLmNvbTAeFw0xNTA5MTYwODUxMzdaFw0xNjA5MTUwODUxMzdaMEkxCzAJBgNVBAYTAmZpMRAwDgYDVQQIDAdVdXNpbWFhMREwDwYDVQQKDAhGbG93ZG9jazEVMBMGA1UEAwwMZmxvd2RvY2suY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 23 | gQDntqPTJ4pRMWb5d17e3vImfpOg6Hzr3PFtbsqEyM8uXZAL713Q4oASum+VlKkPp5ybzJKrFYeEeCl4NOdwyuabrOTUoJLE/x6CpGBgU6o+Iavku+4CkDM5scEIguZgroVabvkwoZRs/2TgVbLhNWXwtLD7n1OvVhLI0L9ycK+RNQIDAQABo1AwTjAdBgNVHQ4EFgQU9t1/AYExhABNzP1+hCsuImUpkXAwHwYDVR0jBBgwFoAU9t1/AYExhABNzP1+hCsuImUpkXAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQCoMeBcLW6JTOdmygPXhYtS+c8t9RCg6Ki/XENOkZN98NgBRS7mAw+DZDezw5KTSH6k0DNw04MFAVZ64gaP2/ad9wHnsktH3mvbfQ8RY6XefSqNy0SuKIt03q26Xf3/vi1jrxn2JgnJG4V+AVR3DVoiiAfQF1ijQW2qhnZR3WCnWQ== 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | http://localhost:9000/saml2/idp/metadata.php 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 20g3ohE5p7icP5ZQ3CSRkSpGaME= 46 | 47 | 48 | m9+Hq+RDNJyKWGsqCpqmkXt/6dz/NQUkdzeF5YHSezVuLFJajB+QC2aSeyic5H5Z0LBkQscjZ1sgme7Hyeo+ZvBgDrBejP6bZfMyaNrET6JTKXxXnrSI0txEL7oXGgnWLJX+oTUWLJgO+PHAUGeS9AgbKcBTQjaW7aW8uh4WtJg= 49 | 50 | 51 | MIICWDCCAcGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBJMQswCQYDVQQGEwJmaTEQMA4GA1UECAwHVXVzaW1hYTERMA8GA1UECgwIRmxvd2RvY2sxFTATBgNVBAMMDGZsb3dkb2NrLmNvbTAeFw0xNTA5MTYwODUxMzdaFw0xNjA5MTUwODUxMzdaMEkxCzAJBgNVBAYTAmZpMRAwDgYDVQQIDAdVdXNpbWFhMREwDwYDVQQKDAhGbG93ZG9jazEVMBMGA1UEAwwMZmxvd2RvY2suY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 52 | gQDntqPTJ4pRMWb5d17e3vImfpOg6Hzr3PFtbsqEyM8uXZAL713Q4oASum+VlKkPp5ybzJKrFYeEeCl4NOdwyuabrOTUoJLE/x6CpGBgU6o+Iavku+4CkDM5scEIguZgroVabvkwoZRs/2TgVbLhNWXwtLD7n1OvVhLI0L9ycK+RNQIDAQABo1AwTjAdBgNVHQ4EFgQU9t1/AYExhABNzP1+hCsuImUpkXAwHwYDVR0jBBgwFoAU9t1/AYExhABNzP1+hCsuImUpkXAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQCoMeBcLW6JTOdmygPXhYtS+c8t9RCg6Ki/XENOkZN98NgBRS7mAw+DZDezw5KTSH6k0DNw04MFAVZ64gaP2/ad9wHnsktH3mvbfQ8RY6XefSqNy0SuKIt03q26Xf3/vi1jrxn2JgnJG4V+AVR3DVoiiAfQF1ijQW2qhnZR3WCnWQ== 53 | 54 | 55 | 56 | 57 | _1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23 58 | 59 | 60 | 61 | 62 | 63 | 64 | sample-saml-strategy 65 | 66 | 67 | 68 | 69 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 70 | 71 | 72 | 73 | 74 | Rajiv 75 | 76 | 77 | Manglani 78 | 79 | 80 | user@example.com 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /spec/support/digest_mismatch.xml: -------------------------------------------------------------------------------- 1 | http://localhost:9000/saml2/idp/metadata.php 2 | 3 | 4 | AAAAAKooo1K7yYnKfXy88BRqgXM=N8G4Meh60EnU5U113JH3fHEr3nA+87kemKZDkqfEZnGHrfwfO2KhSbKEsU6M1ELq8ZCNDxYCFhbfwJOWij5+qkMD1gMYqvH2Hz169l5smEAfkmtovJwq+2lVO7AtVLez065rx2g+n2DmZx82H3ynrMV0vTDEQ2AohJPZjsRoNgY= 5 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=http://localhost:9000/saml2/idp/metadata.php 6 | 7 | 8 | 20g3ohE5p7icP5ZQ3CSRkSpGaME=m9+Hq+RDNJyKWGsqCpqmkXt/6dz/NQUkdzeF5YHSezVuLFJajB+QC2aSeyic5H5Z0LBkQscjZ1sgme7Hyeo+ZvBgDrBejP6bZfMyaNrET6JTKXxXnrSI0txEL7oXGgnWLJX+oTUWLJgO+PHAUGeS9AgbKcBTQjaW7aW8uh4WtJg= 9 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23sample-saml-strategyurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordRajivManglaniuser@example.comExample Company 10 | -------------------------------------------------------------------------------- /spec/support/example_cert.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 5 | 9a:bb:68:98:26:e0:37:bd 6 | Signature Algorithm: sha1WithRSAEncryption 7 | Issuer: C=US, ST=Some-State, O=Example Company, CN=OmniAuth SAML Testing Certificate 8 | Validity 9 | Not Before: Nov 8 20:29:51 2012 GMT 10 | Not After : Dec 8 20:29:51 2012 GMT 11 | Subject: C=US, ST=Some-State, O=Example Company, CN=OmniAuth SAML Testing Certificate 12 | Subject Public Key Info: 13 | Public Key Algorithm: rsaEncryption 14 | RSA Public Key: (1024 bit) 15 | Modulus (1024 bit): 16 | 00:cf:08:3a:45:a9:21:7f:a6:e8:87:91:36:ce:f7: 17 | bd:2e:9e:9d:4a:33:13:76:b2:61:15:0d:56:45:9c: 18 | 0c:39:f4:4e:ec:63:c3:f5:ce:d1:7a:d9:f1:b8:f6: 19 | e0:e8:fc:81:7d:27:45:a0:60:89:bc:cb:30:0c:93: 20 | 1a:ec:36:40:95:b8:2c:7f:55:b2:3b:0f:60:68:7c: 21 | e0:7f:88:fb:f5:79:45:a6:2b:37:5c:a2:57:f2:2d: 22 | 6f:53:1c:44:55:6e:e9:62:b5:13:25:4d:31:6d:9e: 23 | f9:97:70:17:b5:f9:c7:5e:7c:54:44:5d:1e:88:3f: 24 | ac:e7:59:ca:06:d9:d2:91:17 25 | Exponent: 65537 (0x10001) 26 | X509v3 extensions: 27 | X509v3 Subject Key Identifier: 28 | 2E:28:7A:8D:84:C9:DD:73:43:B3:9D:CA:49:5C:2D:E0:5F:46:D9:A8 29 | X509v3 Authority Key Identifier: 30 | keyid:2E:28:7A:8D:84:C9:DD:73:43:B3:9D:CA:49:5C:2D:E0:5F:46:D9:A8 31 | DirName:/C=US/ST=Some-State/O=Example Company/CN=OmniAuth SAML Testing Certificate 32 | serial:9A:BB:68:98:26:E0:37:BD 33 | 34 | X509v3 Basic Constraints: 35 | CA:TRUE 36 | Signature Algorithm: sha1WithRSAEncryption 37 | 83:ea:eb:c1:9a:28:a3:c0:c4:e3:b8:e6:68:15:fe:bd:42:41: 38 | 21:58:12:29:d0:78:e5:b0:e4:2f:b1:60:4d:c6:b6:87:5b:6c: 39 | 9a:05:0b:f6:71:72:be:9f:45:0c:44:3b:21:8c:39:e7:df:72: 40 | c2:1a:d0:7f:d1:9f:b0:86:77:ef:81:bb:89:ef:42:f0:9a:94: 41 | 45:b7:62:3f:8f:cc:32:f3:a3:57:54:73:cb:2a:57:07:27:da: 42 | 63:6d:2d:0f:08:33:e8:99:4b:b3:bb:27:87:b3:b7:b3:dd:c5: 43 | 2a:95:a1:0a:31:c4:e3:7e:02:27:06:b5:ab:99:c7:a8:d4:e0: 44 | 91:9c 45 | -----BEGIN CERTIFICATE----- 46 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV 47 | BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENv 48 | bXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0 49 | ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVT 50 | MRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkx 51 | KjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzAN 52 | BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJh 53 | FQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9g 54 | aHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s 55 | 51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G 56 | 2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJ 57 | BgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxl 58 | IENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZp 59 | Y2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA 60 | g+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7 61 | IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz 62 | 6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw= 63 | -----END CERTIFICATE----- 64 | -------------------------------------------------------------------------------- /spec/support/example_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDPCDpFqSF/puiHkTbO970unp1KMxN2smEVDVZFnAw59E7sY8P1 3 | ztF62fG49uDo/IF9J0WgYIm8yzAMkxrsNkCVuCx/VbI7D2BofOB/iPv1eUWmKzdc 4 | olfyLW9THERVbulitRMlTTFtnvmXcBe1+cdefFREXR6IP6znWcoG2dKRFwIDAQAB 5 | AoGBAItafWbARkUXQvNlgl/jj5qetz7njFVcEk7KUGTAedZUpP8m1BNTp9sqcjNP 6 | MeqBdGOamJowAOZsWiZMqlWO2v71qc2rQI+VmPR0xpmBvbBjL16Gc4BbTdXZ1o1T 7 | tPHQO90GG2JIIt8on4Tt5uVZ+h2cKAqn8k3phRWrfyaGndmhAkEA9BZnGP3Dh1gG 8 | dK8ZWi8KyJUW3BizNudkbfMW3e/cdaQ7DRidiJ8C2W9hVjwDMee3LM/la4lYzG8c 9 | iWVoovBBhwJBANki3CZQh5UTTHCHn2O5p6m+nHjQ8Io8jjDhBpQ9eJzlOyTMiYAB 10 | XMhqFMgtlkIlYGSaNysHlhCS9cW3Tw2gV/ECQAOhnbEKfXEzBw2PWVI1JvTq+ucV 11 | Wv0zHhRgrHNq0R3S7qn4NsfEjddMR+dvhyCj8N6yzRf3eCG6eXM11gOujVsCQQCF 12 | P+lN8fliOJeeLvxXXKVRe9HWKpKSopq30EATVK3hyqLC8GopDaz8qGzcc21UZk+D 13 | LUhRtbQXs7fzf4yZ1h5hAkBZoR45A2DNHLcAjR6XIg7B6/bZOj3wZLxyaoSYRgK3 14 | nLDigtGmCiMg+DXsaBihyjiQVRskiuPbDktmigbAyyOS 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /spec/support/example_logout_request.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://idp.sso.example.com/metadata/29490 4 | username@example.com 5 | 6 | -------------------------------------------------------------------------------- /spec/support/example_logout_response.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://idp.sso.example.com/metadata/29490 4 | 5 | 6 | Successfully logged out from service 7 | 8 | 9 | -------------------------------------------------------------------------------- /spec/support/example_response.xml: -------------------------------------------------------------------------------- 1 | http://localhost:9000/saml2/idp/metadata.php 2 | 3 | 4 | WSulGKooo1K7yYnKfXy88BRqgXM=N8G4Meh60EnU5U113JH3fHEr3nA+87kemKZDkqfEZnGHrfwfO2KhSbKEsU6M1ELq8ZCNDxYCFhbfwJOWij5+qkMD1gMYqvH2Hz169l5smEAfkmtovJwq+2lVO7AtVLez065rx2g+n2DmZx82H3ynrMV0vTDEQ2AohJPZjsRoNgY= 5 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=http://localhost:9000/saml2/idp/metadata.php 6 | 7 | 8 | 20g3ohE5p7icP5ZQ3CSRkSpGaME=m9+Hq+RDNJyKWGsqCpqmkXt/6dz/NQUkdzeF5YHSezVuLFJajB+QC2aSeyic5H5Z0LBkQscjZ1sgme7Hyeo+ZvBgDrBejP6bZfMyaNrET6JTKXxXnrSI0txEL7oXGgnWLJX+oTUWLJgO+PHAUGeS9AgbKcBTQjaW7aW8uh4WtJg= 9 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23sample-saml-strategyurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordRajivManglaniuser@example.comExample Company 10 | -------------------------------------------------------------------------------- /spec/support/invalid_signature.xml: -------------------------------------------------------------------------------- 1 | http://localhost:9000/saml2/idp/metadata.php 2 | 3 | 4 | WSulGKooo1K7yYnKfXy88BRqgXM=AAAAAeh60EnU5U113JH3fHEr3nA+87kemKZDkqfEZnGHrfwfO2KhSbKEsU6M1ELq8ZCNDxYCFhbfwJOWij5+qkMD1gMYqvH2Hz169l5smEAfkmtovJwq+2lVO7AtVLez065rx2g+n2DmZx82H3ynrMV0vTDEQ2AohJPZjsRoNgY= 5 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=http://localhost:9000/saml2/idp/metadata.php 6 | 7 | 8 | 20g3ohE5p7icP5ZQ3CSRkSpGaME=m9+Hq+RDNJyKWGsqCpqmkXt/6dz/NQUkdzeF5YHSezVuLFJajB+QC2aSeyic5H5Z0LBkQscjZ1sgme7Hyeo+ZvBgDrBejP6bZfMyaNrET6JTKXxXnrSI0txEL7oXGgnWLJX+oTUWLJgO+PHAUGeS9AgbKcBTQjaW7aW8uh4WtJg= 9 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=_1f6fcf6be5e13b08b1e3610e7ff59f205fbd814f23sample-saml-strategyurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordRajivManglaniuser@example.comExample Company 10 | -------------------------------------------------------------------------------- /spec/support/no_name_id.xml: -------------------------------------------------------------------------------- 1 | http://localhost:9000/saml2/idp/metadata.php 2 | 3 | 4 | kzbCl9Y1eWJhqW5Z1a0N1hlrVuI=bdRjpQ1SVr0P/2CRQYK66yIoZ025TRqNN0Gb3rfTu3TiEs5cjsbT+ZAt0qbEekKFmI59TwR890L+81bPb80yQx+pPbSuPB1ull9RYE/UxSR/9mRKRuxFJpCbKxSu3t64TKEfPZp+VMkNxBmJ1GDMaZu1zkB3jVTmeIcmPKYppyo= 5 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=http://localhost:9000/saml2/idp/metadata.php 6 | 7 | 8 | 4m3lMEXWyVKoEfMSk8RdwvR1pdQ=THArdS1Zpjj5nC5VgvTkGiqmlYewIgYFBGrxmMPiBo7z3vaDpa7indkSyJZiJXV9BbzFKclHk8l75lLEYuw7G5zDsE+eJ7OmA1P7vOQ25hMk3z7nyUwf6VITLWuvbfE2Tfi39jzyr1LWolKwPL3QQMEUhPJG+UKX2Mtr/FHT+iU= 9 | MIIDHDCCAoWgAwIBAgIJAJq7aJgm4De9MA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTAeFw0xMjExMDgyMDI5NTFaFw0xMjEyMDgyMDI5NTFaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzwg6Rakhf6boh5E2zve9Lp6dSjMTdrJhFQ1WRZwMOfRO7GPD9c7RetnxuPbg6PyBfSdFoGCJvMswDJMa7DZAlbgsf1WyOw9gaHzgf4j79XlFpis3XKJX8i1vUxxEVW7pYrUTJU0xbZ75l3AXtfnHXnxURF0eiD+s51nKBtnSkRcCAwEAAaOBzTCByjAdBgNVHQ4EFgQULih6jYTJ3XNDs53KSVwt4F9G2agwgZoGA1UdIwSBkjCBj4AULih6jYTJ3XNDs53KSVwt4F9G2aihbKRqMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMRgwFgYDVQQKEw9FeGFtcGxlIENvbXBhbnkxKjAoBgNVBAMTIU9tbmlBdXRoIFNBTUwgVGVzdGluZyBDZXJ0aWZpY2F0ZYIJAJq7aJgm4De9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAg+rrwZooo8DE47jmaBX+vUJBIVgSKdB45bDkL7FgTca2h1tsmgUL9nFyvp9FDEQ7IYw5599ywhrQf9GfsIZ374G7ie9C8JqURbdiP4/MMvOjV1RzyypXByfaY20tDwgz6JlLs7snh7O3s93FKpWhCjHE434CJwa1q5nHqNTgkZw=sample-saml-strategyurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordRajivManglaniuser@example.comExample Company 10 | --------------------------------------------------------------------------------