├── .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]
4 | [](https://github.com/omniauth/omniauth-saml/actions/workflows/ruby.yml)
5 | [][codeclimate]
6 | [][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 =~ /^) ? response : Base64.decode64(response)
60 | document = XMLSecurity::SignedDocument::new(response)
61 | cert_element = REXML::XPath.first(document, "//ds:X509Certificate", { "ds"=> '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 |
--------------------------------------------------------------------------------