ruby-saml 0.8.18 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of ruby-saml might be problematic. Click here for more details.
- 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 [![Build Status](https://secure.travis-ci.org/onelogin/ruby-saml.png)](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
|