ruby-saml 0.8.18 → 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -6
- data/Gemfile +2 -12
- data/README.md +363 -35
- data/Rakefile +14 -0
- data/changelog.md +22 -9
- data/lib/onelogin/ruby-saml/attribute_service.rb +34 -0
- data/lib/onelogin/ruby-saml/attributes.rb +26 -64
- data/lib/onelogin/ruby-saml/authrequest.rb +47 -93
- data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +87 -0
- data/lib/onelogin/ruby-saml/logoutrequest.rb +36 -100
- data/lib/onelogin/ruby-saml/logoutresponse.rb +25 -35
- data/lib/onelogin/ruby-saml/metadata.rb +46 -16
- data/lib/onelogin/ruby-saml/response.rb +63 -373
- data/lib/onelogin/ruby-saml/saml_message.rb +78 -0
- data/lib/onelogin/ruby-saml/settings.rb +54 -122
- data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +25 -71
- data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +37 -102
- data/lib/onelogin/ruby-saml/utils.rb +32 -199
- data/lib/onelogin/ruby-saml/version.rb +1 -1
- data/lib/ruby-saml.rb +5 -2
- data/lib/schemas/{saml20assertion_schema.xsd → saml-schema-assertion-2.0.xsd} +283 -283
- data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
- data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
- data/lib/schemas/saml-schema-metadata-2.0.xsd +339 -0
- data/lib/schemas/{saml20protocol_schema.xsd → saml-schema-protocol-2.0.xsd} +302 -302
- data/lib/schemas/sstc-metadata-attr.xsd +35 -0
- data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
- data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
- data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
- data/lib/schemas/{xenc_schema.xsd → xenc-schema.xsd} +1 -11
- data/lib/schemas/xml.xsd +287 -0
- data/lib/schemas/{xmldsig_schema.xsd → xmldsig-core-schema.xsd} +0 -9
- data/lib/xml_security.rb +83 -235
- data/ruby-saml.gemspec +1 -0
- data/test/idp_metadata_parser_test.rb +54 -0
- data/test/logoutrequest_test.rb +68 -155
- data/test/logoutresponse_test.rb +43 -32
- data/test/metadata_test.rb +87 -0
- data/test/request_test.rb +102 -99
- data/test/response_test.rb +181 -495
- data/test/responses/idp_descriptor.xml +3 -0
- data/test/responses/logoutresponse_fixtures.rb +7 -8
- data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
- data/test/responses/response_with_multiple_attribute_values.xml +1 -1
- data/test/responses/slo_request.xml +4 -0
- data/test/settings_test.rb +25 -112
- data/test/slo_logoutrequest_test.rb +40 -50
- data/test/slo_logoutresponse_test.rb +86 -185
- data/test/test_helper.rb +27 -102
- data/test/xml_security_test.rb +114 -337
- metadata +30 -81
- data/lib/onelogin/ruby-saml/setting_error.rb +0 -6
- data/test/certificates/certificate.der +0 -0
- data/test/certificates/formatted_certificate +0 -14
- data/test/certificates/formatted_chained_certificate +0 -42
- data/test/certificates/formatted_private_key +0 -12
- data/test/certificates/formatted_rsa_private_key +0 -12
- data/test/certificates/invalid_certificate1 +0 -1
- data/test/certificates/invalid_certificate2 +0 -1
- data/test/certificates/invalid_certificate3 +0 -12
- data/test/certificates/invalid_chained_certificate1 +0 -1
- data/test/certificates/invalid_private_key1 +0 -1
- data/test/certificates/invalid_private_key2 +0 -1
- data/test/certificates/invalid_private_key3 +0 -10
- data/test/certificates/invalid_rsa_private_key1 +0 -1
- data/test/certificates/invalid_rsa_private_key2 +0 -1
- data/test/certificates/invalid_rsa_private_key3 +0 -10
- data/test/certificates/ruby-saml-2.crt +0 -15
- data/test/requests/logoutrequest_fixtures.rb +0 -47
- data/test/responses/encrypted_new_attack.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +0 -1
- data/test/responses/invalids/invalid_issuer_message.xml.base64 +0 -1
- data/test/responses/invalids/multiple_signed.xml.base64 +0 -1
- data/test/responses/invalids/no_signature.xml.base64 +0 -1
- data/test/responses/invalids/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/invalids/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/invalids/signature_wrapping_attack.xml.base64 +0 -1
- data/test/responses/response_node_text_attack.xml.base64 +0 -1
- data/test/responses/response_with_concealed_signed_assertion.xml +0 -51
- data/test/responses/response_with_doubled_signed_assertion.xml +0 -49
- data/test/responses/response_with_multiple_attribute_statements.xml +0 -72
- data/test/responses/response_with_signed_assertion_3.xml +0 -30
- data/test/responses/response_with_signed_message_and_assertion.xml +0 -34
- data/test/responses/response_with_undefined_recipient.xml.base64 +0 -1
- data/test/responses/response_wrapped.xml.base64 +0 -150
- data/test/responses/valid_response.xml.base64 +0 -1
- data/test/responses/valid_response_without_x509certificate.xml.base64 +0 -1
- data/test/utils_test.rb +0 -231
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a74680083b8b3cd4e4145de5e3358addac4fce7
|
|
4
|
+
data.tar.gz: 7fc603140154d73be347544795a7616ba1fb246e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fbdd9922622ff8c2e62ab0956d319db1bc13311da52c969ff5307d53d2d2ed5eb6c2a6900452a3976478b552b7085af6b9338db2bcea4958be0dd809f412f34
|
|
7
|
+
data.tar.gz: 071f1f8f248e724b60b9870d5c1a4c7c3b9f2630cb289f762b11911392aa0d9d471c1f1fbf5c2e0cfe4e1d5eaf8c29a82eeee7a52845f1bcc77c10e2d75941d4
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -5,19 +5,9 @@ source 'http://rubygems.org'
|
|
|
5
5
|
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
if RUBY_VERSION < '1.9'
|
|
9
|
-
gem 'nokogiri', '~> 1.5.0'
|
|
10
|
-
gem 'minitest', '~> 5.5', '<= 5.11.3'
|
|
11
|
-
elsif RUBY_VERSION < '2.1'
|
|
12
|
-
gem 'nokogiri', '>= 1.5.0', '<= 1.6.8.1'
|
|
13
|
-
gem 'minitest', '~> 5.5'
|
|
14
|
-
else
|
|
15
|
-
gem 'nokogiri', '>= 1.5.0'
|
|
16
|
-
gem 'minitest', '~> 5.5'
|
|
17
|
-
end
|
|
18
|
-
|
|
19
8
|
group :test do
|
|
20
9
|
if RUBY_VERSION < '1.9'
|
|
10
|
+
gem 'nokogiri', '~> 1.5.0'
|
|
21
11
|
gem 'ruby-debug', '~> 0.10.4'
|
|
22
12
|
elsif RUBY_VERSION < '2.0'
|
|
23
13
|
gem 'debugger-linecache', '~> 1.2.0'
|
|
@@ -32,6 +22,6 @@ group :test do
|
|
|
32
22
|
gem 'rake', '~> 10'
|
|
33
23
|
gem 'shoulda', '~> 2.11'
|
|
34
24
|
gem 'systemu', '~> 2'
|
|
35
|
-
gem 'test-unit', '~> 3
|
|
25
|
+
gem 'test-unit', '~> 3'
|
|
36
26
|
gem 'timecop', '<= 0.6.0'
|
|
37
27
|
end
|
data/README.md
CHANGED
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
# Ruby SAML [](http://travis-ci.org/onelogin/ruby-saml)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Version `0.
|
|
5
|
-
|
|
6
|
-
# Updating from 0.8.7 to 0.8.8
|
|
7
|
-
Version `0.8.8` adds support for ForceAuthn and Subjects on AuthNRequests by the new name_identifier_value_requested setting
|
|
8
|
-
|
|
9
|
-
## Note on versions 0.8.6 and 0.8.7
|
|
10
|
-
Version `0.8.6` introduced an incompatibility with regards to manipulating the `OneLogin::RubySaml::Response#attributes` property; in this version
|
|
11
|
-
the `#attributes` property is a class (`OneLogin::RubySaml::Attributes`) which implements the `Enumerator` module, thus any non-overriden Hash method
|
|
12
|
-
will throw a NoMethodError.
|
|
13
|
-
Version `0.8.7` overrides the behavior of class `OneLogin::RubySaml::Attributes` by making any method not found on the class be tested on the
|
|
14
|
-
internal hash structure, thus all methods available in the `Hash` class are now available in `OneLogin::RubySaml::Response#attributes`.
|
|
3
|
+
## Updating from 0.8.x to 0.9
|
|
4
|
+
Version `0.9` adds many new features and improvements. It is a recommended update for all Ruby SAML users. For more details, please review [the changelog](changelog.md)
|
|
15
5
|
|
|
16
6
|
## Updating from 0.7.x to 0.8.x
|
|
17
7
|
Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
|
|
@@ -22,7 +12,47 @@ The Ruby SAML library is for implementing the client side of a SAML authorizatio
|
|
|
22
12
|
|
|
23
13
|
SAML authorization is a two step process and you are expected to implement support for both.
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
We created a demo project for Rails4 that uses the latest version of this library: [ruby-saml-example](https://github.com/onelogin/ruby-saml-example)
|
|
16
|
+
|
|
17
|
+
## Adding Features, Pull Requests
|
|
18
|
+
* Fork the repository
|
|
19
|
+
* Make your feature addition or bug fix
|
|
20
|
+
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
|
|
21
|
+
* Ensure all tests pass.
|
|
22
|
+
* Do not change rakefile, version, or history.
|
|
23
|
+
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
|
|
24
|
+
|
|
25
|
+
## Getting Started
|
|
26
|
+
In order to use the toolkit you will need to install the gem (either manually or using Bundler), and require the library in your Ruby application:
|
|
27
|
+
|
|
28
|
+
Using `Gemfile`
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
# latest stable
|
|
32
|
+
gem 'ruby-saml', '~> 0.9'
|
|
33
|
+
|
|
34
|
+
# or track master for bleeding-edge
|
|
35
|
+
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Using Bundler
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
gem install ruby-saml
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When requiring the gem, you can add the whole toolkit
|
|
45
|
+
```ruby
|
|
46
|
+
require 'onelogin/ruby-saml'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
or just the required components individually:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require 'onelogin/ruby-saml/authrequest'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## The Initialization Phase
|
|
26
56
|
|
|
27
57
|
This is the first request you will get from the identity provider. It will hit your application at a specific URL (that you've announced as being your SAML initialization point). The response to this initialization, is a redirect back to the identity provider, which can look something like this (ignore the saml_settings method call for now):
|
|
28
58
|
|
|
@@ -40,10 +70,13 @@ def consume
|
|
|
40
70
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
|
41
71
|
response.settings = saml_settings
|
|
42
72
|
|
|
43
|
-
if
|
|
44
|
-
|
|
73
|
+
# We validate the SAML Response and check if the user already exists in the system
|
|
74
|
+
if response.is_valid?
|
|
75
|
+
# authorize_success, log the user
|
|
76
|
+
session[:userid] = response.name_id
|
|
77
|
+
session[:attributes] = response.attributes
|
|
45
78
|
else
|
|
46
|
-
authorize_failure
|
|
79
|
+
authorize_failure # This method shows an error message
|
|
47
80
|
end
|
|
48
81
|
end
|
|
49
82
|
```
|
|
@@ -55,13 +88,21 @@ def saml_settings
|
|
|
55
88
|
settings = OneLogin::RubySaml::Settings.new
|
|
56
89
|
|
|
57
90
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize"
|
|
58
|
-
settings.
|
|
59
|
-
settings.idp_sso_target_url = "https://app.onelogin.com/saml/
|
|
91
|
+
settings.issuer = request.host
|
|
92
|
+
settings.idp_sso_target_url = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
|
93
|
+
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
|
|
94
|
+
settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
|
|
95
|
+
settings.idp_slo_target_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
|
|
60
96
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
|
61
97
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
98
|
+
|
|
62
99
|
# Optional for most SAML IdPs
|
|
63
100
|
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
|
64
101
|
|
|
102
|
+
# Optional bindings (defaults to Redirect for logout POST for acs)
|
|
103
|
+
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
|
104
|
+
settings.single_logout_service_url_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
|
105
|
+
|
|
65
106
|
settings
|
|
66
107
|
end
|
|
67
108
|
```
|
|
@@ -80,10 +121,13 @@ class SamlController < ApplicationController
|
|
|
80
121
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
|
81
122
|
response.settings = saml_settings
|
|
82
123
|
|
|
83
|
-
if
|
|
84
|
-
|
|
124
|
+
# We validate the SAML Response and check if the user already exists in the system
|
|
125
|
+
if response.is_valid?
|
|
126
|
+
# authorize_success, log the user
|
|
127
|
+
session[:userid] = response.name_id
|
|
128
|
+
session[:attributes] = response.attributes
|
|
85
129
|
else
|
|
86
|
-
authorize_failure
|
|
130
|
+
authorize_failure # This method shows an error message
|
|
87
131
|
end
|
|
88
132
|
end
|
|
89
133
|
|
|
@@ -93,20 +137,57 @@ class SamlController < ApplicationController
|
|
|
93
137
|
settings = OneLogin::RubySaml::Settings.new
|
|
94
138
|
|
|
95
139
|
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
|
96
|
-
settings.
|
|
140
|
+
settings.issuer = request.host
|
|
97
141
|
settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}"
|
|
98
142
|
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
|
|
99
143
|
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
144
|
+
|
|
100
145
|
# Optional for most SAML IdPs
|
|
101
146
|
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
|
102
147
|
|
|
148
|
+
# Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
|
|
149
|
+
settings.attributes_index = 5
|
|
150
|
+
# Optional. Describe an attribute consuming service for support of additional attributes.
|
|
151
|
+
settings.attribute_consuming_service.configure do
|
|
152
|
+
service_name "Service"
|
|
153
|
+
service_index 5
|
|
154
|
+
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
|
|
155
|
+
end
|
|
156
|
+
|
|
103
157
|
settings
|
|
104
158
|
end
|
|
105
159
|
end
|
|
106
160
|
```
|
|
161
|
+
## Metadata Based Configuration
|
|
107
162
|
|
|
108
|
-
|
|
109
|
-
|
|
163
|
+
The method above requires a little extra work to manually specify attributes about the IdP. (And your SP application) There's an easier method -- use a metadata exchange. Metadata is just an XML file that defines the capabilities of both the IdP and the SP application. It also contains the X.509 public
|
|
164
|
+
key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata.
|
|
165
|
+
|
|
166
|
+
Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado.
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
def saml_settings
|
|
170
|
+
|
|
171
|
+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
|
|
172
|
+
# Returns OneLogin::RubySaml::Settings prepopulated with idp metadata
|
|
173
|
+
settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata")
|
|
174
|
+
|
|
175
|
+
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
|
|
176
|
+
settings.issuer = request.host
|
|
177
|
+
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
178
|
+
# Optional for most SAML IdPs
|
|
179
|
+
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
|
180
|
+
|
|
181
|
+
settings
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
The following attributes are set:
|
|
185
|
+
* id_sso_target_url
|
|
186
|
+
* idp_slo_target_url
|
|
187
|
+
* id_cert_fingerpint
|
|
188
|
+
|
|
189
|
+
If are using saml:AttributeStatement to transfer metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key the one/more saml:AttributeValue as value. The value returned depends on the value of the
|
|
190
|
+
`single_value_compatibility` (when activate, only one value returned, the first one)
|
|
110
191
|
|
|
111
192
|
```ruby
|
|
112
193
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
|
@@ -115,13 +196,253 @@ response.settings = saml_settings
|
|
|
115
196
|
response.attributes[:username]
|
|
116
197
|
```
|
|
117
198
|
|
|
199
|
+
Imagine this saml:AttributeStatement
|
|
200
|
+
|
|
201
|
+
```xml
|
|
202
|
+
<saml:AttributeStatement>
|
|
203
|
+
<saml:Attribute Name="uid">
|
|
204
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">demo</saml:AttributeValue>
|
|
205
|
+
</saml:Attribute>
|
|
206
|
+
<saml:Attribute Name="another_value">
|
|
207
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value1</saml:AttributeValue>
|
|
208
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value2</saml:AttributeValue>
|
|
209
|
+
</saml:Attribute>
|
|
210
|
+
<saml:Attribute Name="role">
|
|
211
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role1</saml:AttributeValue>
|
|
212
|
+
</saml:Attribute>
|
|
213
|
+
<saml:Attribute Name="role">
|
|
214
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role2</saml:AttributeValue>
|
|
215
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role3</saml:AttributeValue>
|
|
216
|
+
</saml:Attribute>
|
|
217
|
+
<saml:Attribute Name="attribute_with_nil_value">
|
|
218
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
219
|
+
</saml:Attribute>
|
|
220
|
+
<saml:Attribute Name="attribute_with_nils_and_empty_strings">
|
|
221
|
+
<saml:AttributeValue/>
|
|
222
|
+
<saml:AttributeValue>valuePresent</saml:AttributeValue>
|
|
223
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
|
|
224
|
+
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
|
|
225
|
+
</saml:Attribute>
|
|
226
|
+
</saml:AttributeStatement>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
|
|
231
|
+
# => @attributes=
|
|
232
|
+
{"uid"=>["demo"],
|
|
233
|
+
"another_value"=>["value1", "value2"],
|
|
234
|
+
"role"=>["role1", "role2", "role3"],
|
|
235
|
+
"attribute_with_nil_value"=>[nil],
|
|
236
|
+
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]}>
|
|
237
|
+
|
|
238
|
+
# Active single_value_compatibility
|
|
239
|
+
OneLogin::RubySaml::Attributes.single_value_compatibility = true
|
|
240
|
+
|
|
241
|
+
pp(response.attributes[:uid])
|
|
242
|
+
# => "demo"
|
|
243
|
+
|
|
244
|
+
pp(response.attributes[:role])
|
|
245
|
+
# => "role1"
|
|
246
|
+
|
|
247
|
+
pp(response.attributes.single(:role))
|
|
248
|
+
# => "role1"
|
|
249
|
+
|
|
250
|
+
pp(response.attributes.multi(:role))
|
|
251
|
+
# => ["role1", "role2", "role3"]
|
|
252
|
+
|
|
253
|
+
pp(response.attributes[:attribute_with_nil_value])
|
|
254
|
+
# => nil
|
|
255
|
+
|
|
256
|
+
pp(response.attributes[:attribute_with_nils_and_empty_strings])
|
|
257
|
+
# => ""
|
|
258
|
+
|
|
259
|
+
pp(response.attributes[:not_exists])
|
|
260
|
+
# => nil
|
|
261
|
+
|
|
262
|
+
pp(response.attributes.single(:not_exists))
|
|
263
|
+
# => nil
|
|
264
|
+
|
|
265
|
+
pp(response.attributes.multi(:not_exists))
|
|
266
|
+
# => nil
|
|
267
|
+
|
|
268
|
+
# Deactive single_value_compatibility
|
|
269
|
+
OneLogin::RubySaml::Attributes.single_value_compatibility = false
|
|
270
|
+
|
|
271
|
+
pp(response.attributes[:uid])
|
|
272
|
+
# => ["demo"]
|
|
273
|
+
|
|
274
|
+
pp(response.attributes[:role])
|
|
275
|
+
# => ["role1", "role2", "role3"]
|
|
276
|
+
|
|
277
|
+
pp(response.attributes.single(:role))
|
|
278
|
+
# => "role1"
|
|
279
|
+
|
|
280
|
+
pp(response.attributes.multi(:role))
|
|
281
|
+
# => ["role1", "role2", "role3"]
|
|
282
|
+
|
|
283
|
+
pp(response.attributes[:attribute_with_nil_value])
|
|
284
|
+
# => [nil]
|
|
285
|
+
|
|
286
|
+
pp(response.attributes[:attribute_with_nils_and_empty_strings])
|
|
287
|
+
# => ["", "valuePresent", nil, nil]
|
|
288
|
+
|
|
289
|
+
pp(response.attributes[:not_exists])
|
|
290
|
+
# => nil
|
|
291
|
+
|
|
292
|
+
pp(response.attributes.single(:not_exists))
|
|
293
|
+
# => nil
|
|
294
|
+
|
|
295
|
+
pp(response.attributes.multi(:not_exists))
|
|
296
|
+
# => nil
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The saml:AuthnContextClassRef of the AuthNRequest can be provided by `settings.authn_context` , possible values are described at [SAMLAuthnCxt]. The comparison method can be set using the parameter `settings.authn_context_comparison` (the possible values are: 'exact', 'better', 'maximum' and 'minimum'), 'exact' is the default value.
|
|
300
|
+
If we want to add a saml:AuthnContextDeclRef, define a `settings.authn_context_decl_ref`.
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
## Signing
|
|
304
|
+
|
|
305
|
+
The Ruby Toolkit supports 2 different kinds of signature: Embeded and as GET parameter
|
|
306
|
+
|
|
307
|
+
In order to be able to sign we need first to define the private key and the public cert of the service provider
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
settings.certificate = "CERTIFICATE TEXT WITH HEADS"
|
|
311
|
+
settings.private_key = "PRIVATE KEY TEXT WITH HEADS"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The settings related to sign are stored in the `security` attribute of the settings:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
|
|
318
|
+
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
|
|
319
|
+
settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
|
|
320
|
+
|
|
321
|
+
settings.security[:digest_method] = XMLSecurity::Document::SHA1
|
|
322
|
+
settings.security[:signature_method] = XMLSecurity::Document::SHA1
|
|
323
|
+
|
|
324
|
+
settings.security[:embed_sign] = false # Embeded signature or HTTP GET parameter Signature
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
## Single Log Out
|
|
329
|
+
|
|
330
|
+
The Ruby Toolkit supports SP-initiated Single Logout and IdP-Initiated Single Logout.
|
|
331
|
+
|
|
332
|
+
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
# Create a SP initiated SLO
|
|
336
|
+
def sp_logout_request
|
|
337
|
+
# LogoutRequest accepts plain browser requests w/o paramters
|
|
338
|
+
settings = saml_settings
|
|
339
|
+
|
|
340
|
+
if settings.idp_slo_target_url.nil?
|
|
341
|
+
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
|
|
342
|
+
delete_session
|
|
343
|
+
else
|
|
344
|
+
|
|
345
|
+
# Since we created a new SAML request, save the transaction_id
|
|
346
|
+
# to compare it with the response we get back
|
|
347
|
+
logout_request = OneLogin::RubySaml::Logoutrequest.new()
|
|
348
|
+
session[:transaction_id] = logout_request.uuid
|
|
349
|
+
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{session[:transaction_id]}'"
|
|
350
|
+
|
|
351
|
+
if settings.name_identifier_value.nil?
|
|
352
|
+
settings.name_identifier_value = session[:userid]
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
relayState = url_for controller: 'saml', action: 'index'
|
|
356
|
+
redirect_to(logout_request.create(settings, :RelayState => relayState))
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
and this method process the SAML Logout Response sent by the IdP as reply of the SAML Logout Request
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
|
|
365
|
+
# the LogoutResponse, verify it, then actually delete our session.
|
|
366
|
+
def process_logout_response
|
|
367
|
+
settings = Account.get_saml_settings
|
|
368
|
+
|
|
369
|
+
if session.has_key? :transation_id
|
|
370
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transation_id])
|
|
371
|
+
else
|
|
372
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
logger.info "LogoutResponse is: #{logout_response.to_s}"
|
|
376
|
+
|
|
377
|
+
# Validate the SAML Logout Response
|
|
378
|
+
if not logout_response.validate
|
|
379
|
+
logger.error "The SAML Logout Response is invalid"
|
|
380
|
+
else
|
|
381
|
+
# Actually log out this session
|
|
382
|
+
if logout_response.success?
|
|
383
|
+
logger.info "Delete session for '#{session[:userid]}'"
|
|
384
|
+
delete_session
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
# Delete a user's session.
|
|
390
|
+
def delete_session
|
|
391
|
+
session[:userid] = nil
|
|
392
|
+
session[:attributes] = nil
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply a SAML Logout Response to the IdP
|
|
397
|
+
|
|
398
|
+
```ruby
|
|
399
|
+
# Method to handle IdP initiated logouts
|
|
400
|
+
def idp_logout_request
|
|
401
|
+
settings = Account.get_saml_settings
|
|
402
|
+
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest])
|
|
403
|
+
if !logout_request.is_valid?
|
|
404
|
+
logger.error "IdP initiated LogoutRequest was not valid!"
|
|
405
|
+
render :inline => logger.error
|
|
406
|
+
end
|
|
407
|
+
logger.info "IdP initiated Logout for #{logout_request.name_id}"
|
|
408
|
+
|
|
409
|
+
# Actually log out this session
|
|
410
|
+
delete_session
|
|
411
|
+
|
|
412
|
+
# Generate a response to the IdP.
|
|
413
|
+
logout_request_id = logout_request.id
|
|
414
|
+
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, :RelayState => params[:RelayState])
|
|
415
|
+
redirect_to logout_response
|
|
416
|
+
end
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
All the mentioned methods could be handled in a unique view:
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
# Trigger SP and IdP initiated Logout requests
|
|
423
|
+
def logout
|
|
424
|
+
# If we're given a logout request, handle it in the IdP logout initiated method
|
|
425
|
+
if params[:SAMLRequest]
|
|
426
|
+
return idp_logout_request
|
|
427
|
+
# We've been given a response back from the IdP, process it
|
|
428
|
+
elsif params[:SAMLResponse]
|
|
429
|
+
return process_logout_response
|
|
430
|
+
# Initiate SLO (send Logout Request)
|
|
431
|
+
else
|
|
432
|
+
return sp_logout_request
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
118
439
|
## Service Provider Metadata
|
|
119
440
|
|
|
120
441
|
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
|
|
121
442
|
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
|
|
122
443
|
|
|
123
|
-
The class OneLogin::RubySaml::Metadata takes care of this by reading the Settings and returning XML. All
|
|
124
|
-
|
|
444
|
+
The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator.
|
|
445
|
+
|
|
125
446
|
The metdata will be polled by the IdP every few minutes, so updating your settings should propagate
|
|
126
447
|
to the IdP settings.
|
|
127
448
|
|
|
@@ -131,7 +452,7 @@ class SamlController < ApplicationController
|
|
|
131
452
|
def metadata
|
|
132
453
|
settings = Account.get_saml_settings
|
|
133
454
|
meta = OneLogin::RubySaml::Metadata.new
|
|
134
|
-
render :xml => meta.generate(settings)
|
|
455
|
+
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
|
|
135
456
|
end
|
|
136
457
|
end
|
|
137
458
|
```
|
|
@@ -145,16 +466,23 @@ First, ensure that both systems synchronize their clocks, using for example the
|
|
|
145
466
|
Even then you may experience intermittent issues though, because the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift you can initialize the response passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example:
|
|
146
467
|
|
|
147
468
|
```ruby
|
|
148
|
-
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1)
|
|
469
|
+
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second)
|
|
149
470
|
```
|
|
150
471
|
|
|
151
472
|
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
|
|
152
473
|
|
|
153
|
-
##
|
|
474
|
+
## Attribute Service
|
|
154
475
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
476
|
+
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
settings = OneLogin::RubySaml::Settings.new
|
|
480
|
+
|
|
481
|
+
settings.attributes_index = 5
|
|
482
|
+
settings.attribute_consuming_service.configure do
|
|
483
|
+
service_name "Service"
|
|
484
|
+
service_index 5
|
|
485
|
+
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
|
|
486
|
+
add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value"
|
|
487
|
+
end
|
|
488
|
+
```
|
data/Rakefile
CHANGED
|
@@ -25,3 +25,17 @@ end
|
|
|
25
25
|
task :test
|
|
26
26
|
|
|
27
27
|
task :default => :test
|
|
28
|
+
|
|
29
|
+
# require 'rake/rdoctask'
|
|
30
|
+
# Rake::RDocTask.new do |rdoc|
|
|
31
|
+
# if File.exist?('VERSION')
|
|
32
|
+
# version = File.read('VERSION')
|
|
33
|
+
# else
|
|
34
|
+
# version = ""
|
|
35
|
+
# end
|
|
36
|
+
|
|
37
|
+
# rdoc.rdoc_dir = 'rdoc'
|
|
38
|
+
# rdoc.title = "ruby-saml #{version}"
|
|
39
|
+
# rdoc.rdoc_files.include('README*')
|
|
40
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
|
41
|
+
#end
|
data/changelog.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# RubySaml Changelog
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* Fix
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
### 0.9 (Jan 26, 2015)
|
|
3
|
+
* [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false
|
|
4
|
+
* [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious
|
|
5
|
+
* [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError
|
|
6
|
+
* [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata
|
|
7
|
+
* [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement
|
|
8
|
+
* [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99
|
|
9
|
+
* [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec
|
|
10
|
+
* [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation.
|
|
11
|
+
* [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug
|
|
12
|
+
* [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures
|
|
13
|
+
* [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time
|
|
14
|
+
* [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata
|
|
15
|
+
* [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues
|
|
16
|
+
* [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging"
|
|
17
|
+
* [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec
|
|
18
|
+
* [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest
|
|
19
|
+
* [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml
|
|
20
|
+
* [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner
|
|
21
|
+
* [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml
|
|
22
|
+
|
|
23
|
+
### 0.8.2 (Jan 26, 2015)
|
|
11
24
|
* [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution.
|
|
12
25
|
|
|
13
26
|
### 0.8.0 (Feb 21, 2014)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module OneLogin
|
|
2
|
+
module RubySaml
|
|
3
|
+
class AttributeService
|
|
4
|
+
attr_reader :attributes
|
|
5
|
+
attr_reader :name
|
|
6
|
+
attr_reader :index
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@index = "1"
|
|
10
|
+
@attributes = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def configure(&block)
|
|
14
|
+
instance_eval &block
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configured?
|
|
18
|
+
@attributes.length > 0 && !@name.nil?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def service_name(name)
|
|
22
|
+
@name = name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def service_index(index)
|
|
26
|
+
@index = index
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_attribute(options={})
|
|
30
|
+
attributes << options
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|