saml_idp 0.7.2 → 1.0.0
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 +5 -5
- data/Gemfile +1 -1
- data/README.md +71 -55
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +9 -3
- data/lib/saml_idp/controller.rb +27 -16
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +31 -1
- data/lib/saml_idp/metadata_builder.rb +25 -9
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +103 -13
- data/lib/saml_idp/response_builder.rb +26 -6
- data/lib/saml_idp/saml_response.rb +62 -28
- data/lib/saml_idp/service_provider.rb +16 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/signature_builder.rb +2 -1
- data/lib/saml_idp/signed_info_builder.rb +2 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +20 -15
- data/lib/saml_idp.rb +4 -3
- data/saml_idp.gemspec +46 -42
- data/spec/acceptance/idp_controller_spec.rb +5 -4
- data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
- data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
- data/spec/lib/saml_idp/configurator_spec.rb +45 -7
- data/spec/lib/saml_idp/controller_spec.rb +86 -25
- data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +134 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
- data/spec/lib/saml_idp/request_spec.rb +153 -64
- data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
- data/spec/lib/saml_idp/saml_response_spec.rb +146 -12
- data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
- data/spec/lib/saml_idp/signable_spec.rb +1 -1
- data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
- data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +3 -4
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -6
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -5
- data/spec/rails_app/config/environments/production.rb +1 -0
- data/spec/rails_app/config/environments/test.rb +1 -0
- data/spec/spec_helper.rb +23 -1
- data/spec/support/certificates/sp_cert_req.csr +12 -0
- data/spec/support/certificates/sp_private_key.pem +16 -0
- data/spec/support/certificates/sp_x509_cert.crt +18 -0
- data/spec/support/saml_request_macros.rb +107 -5
- data/spec/support/security_helpers.rb +12 -2
- data/spec/xml_security_spec.rb +19 -15
- metadata +146 -80
- data/app/controllers/saml_idp/idp_controller.rb +0 -59
- data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4a0fd46437c04823ad86f269552282b0252cb888294345984c5e16b88bcfa9cf
|
|
4
|
+
data.tar.gz: b7c56c2516f7aaf392fe5a999e98f67af0b79f799374757f3724724605616fc4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 558df23c6ffefed06a205df4ea72cc530f7a963c64741e0bb86cae16f7ffd26a822299793a6b4d7a587addf49428a71e3449f46650d1a26990ec563b690a6cac
|
|
7
|
+
data.tar.gz: ee6eec4ee019c3e01443bbc6d383e4eb6dcd422e3eb5dddcbefd10666799d9604e094e9dec22b213780a415f9efaaebfe54f8b512fef90322f6ce3cc89e4d751
|
data/Gemfile
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
source "
|
|
1
|
+
source "https://rubygems.org"
|
|
2
2
|
gemspec
|
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Ruby SAML Identity Provider (IdP)
|
|
2
|
-
Forked from https://github.com/lawrencepit/ruby-saml-idp
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Forked from <https://github.com/lawrencepit/ruby-saml-idp>
|
|
4
|
+
|
|
5
|
+
[](http://badge.fury.io/rb/saml_idp)
|
|
6
6
|
|
|
7
7
|
The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows
|
|
8
8
|
your application to act as an IdP (Identity Provider) using the
|
|
@@ -12,62 +12,53 @@ protocol. It provides a means for managing authentication requests and confirmat
|
|
|
12
12
|
This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
|
|
13
13
|
SAML IDP implementation.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Installation and Usage
|
|
16
16
|
|
|
17
17
|
Add this to your Gemfile:
|
|
18
18
|
|
|
19
|
+
```ruby
|
|
19
20
|
gem 'saml_idp'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Not using rails?
|
|
20
24
|
|
|
21
|
-
## Not using rails?
|
|
22
25
|
Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
|
|
23
26
|
|
|
24
|
-
Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
|
25
|
-
`saml_acs_url` to determine the source for which you need to authenticate a user.
|
|
26
|
-
|
|
27
|
+
Basically, you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
|
28
|
+
`saml_acs_url` to determine the source for which you need to authenticate a user.
|
|
29
|
+
If the signature (`Signature`) and signing algorithm (`SigAlg`) are provided as external parameters in the request,
|
|
30
|
+
you can pass those parameters as `decode_request(params[:SAMLRequest], params[:Signature], params[:SigAlg], params[:RelayState])`.
|
|
31
|
+
Then, you can verify the request signature with the `valid?` method.
|
|
32
|
+
|
|
33
|
+
How you authenticate a user is entirely up to you.
|
|
27
34
|
|
|
28
|
-
Once a user has successfully authenticated on your system send the Service Provider a
|
|
35
|
+
Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
|
|
29
36
|
posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
|
|
30
37
|
`encode_response(user_email)`.
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
Add to your `routes.rb` file, for example:
|
|
39
|
+
### Using rails?
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
get '/saml/metadata' => 'saml_idp#show'
|
|
38
|
-
post '/saml/auth' => 'saml_idp#create'
|
|
39
|
-
match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
|
|
40
|
-
```
|
|
41
|
+
Check out our Wiki page for Rails integration
|
|
42
|
+
[Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
### Configuration
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
class SamlIdpController < SamlIdp::IdpController
|
|
46
|
-
def idp_authenticate(email, password) # not using params intentionally
|
|
47
|
-
user = User.by_email(email).first
|
|
48
|
-
user && user.valid_password?(password) ? user : nil
|
|
49
|
-
end
|
|
50
|
-
private :idp_authenticate
|
|
51
|
-
|
|
52
|
-
def idp_make_saml_response(found_user) # not using params intentionally
|
|
53
|
-
# NOTE encryption is optional
|
|
54
|
-
encode_response found_user, encryption: {
|
|
55
|
-
cert: saml_request.service_provider.cert,
|
|
56
|
-
block_encryption: 'aes256-cbc',
|
|
57
|
-
key_transport: 'rsa-oaep-mgf1p'
|
|
58
|
-
}
|
|
59
|
-
end
|
|
60
|
-
private :idp_make_saml_response
|
|
46
|
+
#### Signed assertions and Signed Response
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
user.logout
|
|
65
|
-
end
|
|
66
|
-
private :idp_logout
|
|
67
|
-
end
|
|
68
|
-
```
|
|
48
|
+
By default SAML Assertion will be signed with an algorithm which defined to `config.algorithm`, because SAML assertions contain secure information used for authentication such as NameID.
|
|
49
|
+
Besides that, signing assertions could be optional and can be defined with `config.signed_assertion` option. Setting this configuration flag to `false` will add raw assertions on the response instead of signed ones. If the response is encrypted the `config.signed_assertion` will be ignored and all assertions will be signed.
|
|
69
50
|
|
|
70
|
-
|
|
51
|
+
Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
|
|
52
|
+
For that, you can enable it with `signed_message: true` option for `encode_response(user_email, signed_message: true)` method. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
|
|
53
|
+
|
|
54
|
+
#### Signing algorithm
|
|
55
|
+
|
|
56
|
+
Following algorithms you can set in your response signing algorithm
|
|
57
|
+
:sha1 - RSA-SHA1 default value but not recommended to production environment
|
|
58
|
+
Highly recommended to use one of following algorithm, suit with your computing power.
|
|
59
|
+
:sha256 - RSA-SHA256
|
|
60
|
+
:sha384 - RSA-SHA384
|
|
61
|
+
:sha512 - RSA-SHA512
|
|
71
62
|
|
|
72
63
|
Be sure to load a file like this during your app initialization:
|
|
73
64
|
|
|
@@ -87,19 +78,29 @@ KEY DATA
|
|
|
87
78
|
-----END RSA PRIVATE KEY-----
|
|
88
79
|
CERT
|
|
89
80
|
|
|
81
|
+
# x509_certificate, secret_key, and password may also be set from within a proc, for example:
|
|
82
|
+
# config.x509_certificate = -> { File.read("cert.pem") }
|
|
83
|
+
# config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
|
|
84
|
+
# config.password = -> { "password" }
|
|
85
|
+
|
|
90
86
|
# config.password = "secret_key_password"
|
|
91
|
-
# config.algorithm = :sha256
|
|
87
|
+
# config.algorithm = :sha256 # Default: sha1 only for development.
|
|
92
88
|
# config.organization_name = "Your Organization"
|
|
93
89
|
# config.organization_url = "http://example.com"
|
|
94
90
|
# config.base_saml_location = "#{base}/saml"
|
|
95
|
-
# config.reference_id_generator # Default: -> {
|
|
91
|
+
# config.reference_id_generator # Default: -> { SecureRandom.uuid }
|
|
92
|
+
# config.single_logout_service_post_location = "#{base}/saml/logout"
|
|
93
|
+
# config.single_logout_service_redirect_location = "#{base}/saml/logout"
|
|
96
94
|
# config.attribute_service_location = "#{base}/saml/attributes"
|
|
97
95
|
# config.single_service_post_location = "#{base}/saml/auth"
|
|
98
96
|
# config.session_expiry = 86400 # Default: 0 which means never
|
|
97
|
+
# config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
|
|
98
|
+
# config.compress = true # Default: false which means the SAML Response is not being compressed
|
|
99
|
+
# config.logger = ::Logger.new($stdout) # Default: if in Rails context - Rails.logger, else ->(msg) { puts msg }. Works with either a Ruby Logger or a lambda
|
|
99
100
|
|
|
100
101
|
# Principal (e.g. User) is passed in when you `encode_response`
|
|
101
102
|
#
|
|
102
|
-
# config.name_id.formats
|
|
103
|
+
# config.name_id.formats =
|
|
103
104
|
# { # All 2.0
|
|
104
105
|
# email_address: -> (principal) { principal.email_address },
|
|
105
106
|
# transient: -> (principal) { principal.id },
|
|
@@ -169,7 +170,11 @@ CERT
|
|
|
169
170
|
service_providers = {
|
|
170
171
|
"some-issuer-url.com/saml" => {
|
|
171
172
|
fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
|
|
172
|
-
metadata_url: "http://some-issuer-url.com/saml/metadata"
|
|
173
|
+
metadata_url: "http://some-issuer-url.com/saml/metadata",
|
|
174
|
+
|
|
175
|
+
# We now validate AssertionConsumerServiceURL will match the MetadataURL set above.
|
|
176
|
+
# *If* it's not going to match your Metadata URL's Host, then set this so we can validate the host using this list
|
|
177
|
+
response_hosts: ["foo.some-issuer-url.com"]
|
|
173
178
|
},
|
|
174
179
|
}
|
|
175
180
|
|
|
@@ -177,7 +182,7 @@ CERT
|
|
|
177
182
|
# settings is an IncomingMetadata object which has a to_h method that needs to be persisted
|
|
178
183
|
config.service_provider.metadata_persister = ->(identifier, settings) {
|
|
179
184
|
fname = identifier.to_s.gsub(/\/|:/,"_")
|
|
180
|
-
|
|
185
|
+
FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
|
|
181
186
|
File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
|
|
182
187
|
Marshal.dump settings.to_h, f
|
|
183
188
|
end
|
|
@@ -188,7 +193,7 @@ CERT
|
|
|
188
193
|
# `service_provider` you should return the settings.to_h from above
|
|
189
194
|
config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
|
|
190
195
|
fname = identifier.to_s.gsub(/\/|:/,"_")
|
|
191
|
-
|
|
196
|
+
FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
|
|
192
197
|
full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
|
|
193
198
|
if File.file?(full_filename)
|
|
194
199
|
File.open full_filename, "rb" do |f|
|
|
@@ -204,7 +209,8 @@ CERT
|
|
|
204
209
|
end
|
|
205
210
|
```
|
|
206
211
|
|
|
207
|
-
|
|
212
|
+
## Keys and Secrets
|
|
213
|
+
|
|
208
214
|
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
|
209
215
|
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
|
210
216
|
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
|
@@ -214,22 +220,32 @@ and `SamlIdp.config.secret_key` properties.
|
|
|
214
220
|
|
|
215
221
|
The fingerprint to use, if you use the default X.509 certificate of this gem, is:
|
|
216
222
|
|
|
223
|
+
```bash
|
|
224
|
+
9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
|
|
217
225
|
```
|
|
218
|
-
|
|
226
|
+
|
|
227
|
+
## Fingerprint
|
|
228
|
+
|
|
229
|
+
The gem provides an helper to generate a fingerprint for a X.509 certificate.
|
|
230
|
+
The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
|
|
219
234
|
```
|
|
220
235
|
|
|
236
|
+
## Service Providers
|
|
221
237
|
|
|
222
|
-
# Service Providers
|
|
223
238
|
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
|
|
224
239
|
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
|
|
225
240
|
|
|
241
|
+
## Author
|
|
226
242
|
|
|
227
|
-
|
|
228
|
-
Jon Phenow, me@jphenow.com
|
|
243
|
+
Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
|
|
229
244
|
|
|
230
245
|
Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
|
|
231
246
|
|
|
232
|
-
|
|
247
|
+
## Copyright
|
|
248
|
+
|
|
233
249
|
Copyright (c) 2012 Sport Ngin.
|
|
234
250
|
Portions Copyright (c) 2010 OneLogin, LLC
|
|
235
251
|
Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
|
|
@@ -16,10 +16,26 @@ module SamlIdp
|
|
|
16
16
|
attr_accessor :expiry
|
|
17
17
|
attr_accessor :encryption_opts
|
|
18
18
|
attr_accessor :session_expiry
|
|
19
|
+
attr_accessor :name_id_formats_opts
|
|
20
|
+
attr_accessor :asserted_attributes_opts
|
|
19
21
|
|
|
20
22
|
delegate :config, to: :SamlIdp
|
|
21
23
|
|
|
22
|
-
def initialize(
|
|
24
|
+
def initialize(
|
|
25
|
+
reference_id,
|
|
26
|
+
issuer_uri,
|
|
27
|
+
principal,
|
|
28
|
+
audience_uri,
|
|
29
|
+
saml_request_id,
|
|
30
|
+
saml_acs_url,
|
|
31
|
+
raw_algorithm,
|
|
32
|
+
authn_context_classref,
|
|
33
|
+
expiry=60*60,
|
|
34
|
+
encryption_opts=nil,
|
|
35
|
+
session_expiry=nil,
|
|
36
|
+
name_id_formats_opts = nil,
|
|
37
|
+
asserted_attributes_opts = nil
|
|
38
|
+
)
|
|
23
39
|
self.reference_id = reference_id
|
|
24
40
|
self.issuer_uri = issuer_uri
|
|
25
41
|
self.principal = principal
|
|
@@ -31,6 +47,8 @@ module SamlIdp
|
|
|
31
47
|
self.expiry = expiry
|
|
32
48
|
self.encryption_opts = encryption_opts
|
|
33
49
|
self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
|
|
50
|
+
self.name_id_formats_opts = name_id_formats_opts
|
|
51
|
+
self.asserted_attributes_opts = asserted_attributes_opts
|
|
34
52
|
end
|
|
35
53
|
|
|
36
54
|
def fresh
|
|
@@ -98,7 +116,9 @@ module SamlIdp
|
|
|
98
116
|
end
|
|
99
117
|
|
|
100
118
|
def asserted_attributes
|
|
101
|
-
if
|
|
119
|
+
if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
|
|
120
|
+
asserted_attributes_opts
|
|
121
|
+
elsif principal.respond_to?(:asserted_attributes)
|
|
102
122
|
principal.send(:asserted_attributes)
|
|
103
123
|
elsif !config.attributes.nil? && !config.attributes.empty?
|
|
104
124
|
config.attributes
|
|
@@ -139,10 +159,15 @@ module SamlIdp
|
|
|
139
159
|
private :name_id_getter
|
|
140
160
|
|
|
141
161
|
def name_id_format
|
|
142
|
-
@name_id_format ||= NameIdFormatter.new(
|
|
162
|
+
@name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
|
|
143
163
|
end
|
|
144
164
|
private :name_id_format
|
|
145
165
|
|
|
166
|
+
def name_id_formats
|
|
167
|
+
@name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
|
|
168
|
+
end
|
|
169
|
+
private :name_id_formats
|
|
170
|
+
|
|
146
171
|
def reference_string
|
|
147
172
|
"_#{reference_id}"
|
|
148
173
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# encoding: utf-8
|
|
2
2
|
require 'ostruct'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
3
5
|
module SamlIdp
|
|
4
6
|
class Configurator
|
|
5
7
|
attr_accessor :x509_certificate
|
|
@@ -13,23 +15,27 @@ module SamlIdp
|
|
|
13
15
|
attr_accessor :reference_id_generator
|
|
14
16
|
attr_accessor :attribute_service_location
|
|
15
17
|
attr_accessor :single_service_post_location
|
|
18
|
+
attr_accessor :single_service_redirect_location
|
|
16
19
|
attr_accessor :single_logout_service_post_location
|
|
17
20
|
attr_accessor :single_logout_service_redirect_location
|
|
18
21
|
attr_accessor :attributes
|
|
19
22
|
attr_accessor :service_provider
|
|
23
|
+
attr_accessor :assertion_consumer_service_hosts
|
|
20
24
|
attr_accessor :session_expiry
|
|
25
|
+
attr_accessor :logger
|
|
21
26
|
|
|
22
27
|
def initialize
|
|
23
|
-
self.x509_certificate = Default::X509_CERTIFICATE
|
|
24
|
-
self.secret_key = Default::SECRET_KEY
|
|
28
|
+
self.x509_certificate = -> { Default::X509_CERTIFICATE }
|
|
29
|
+
self.secret_key = -> { Default::SECRET_KEY }
|
|
25
30
|
self.algorithm = :sha1
|
|
26
|
-
self.reference_id_generator = ->() {
|
|
31
|
+
self.reference_id_generator = ->() { SecureRandom.uuid }
|
|
27
32
|
self.service_provider = OpenStruct.new
|
|
28
33
|
self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
|
|
29
34
|
self.service_provider.metadata_persister = ->(id, settings) { }
|
|
30
35
|
self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
|
|
31
36
|
self.session_expiry = 0
|
|
32
37
|
self.attributes = {}
|
|
38
|
+
self.logger = (defined?(::Rails) && Rails.respond_to?(:logger)) ? Rails.logger : ->(msg) { puts msg }
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
# formats
|
data/lib/saml_idp/controller.rb
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
# encoding: utf-8
|
|
2
1
|
require 'openssl'
|
|
3
2
|
require 'base64'
|
|
4
3
|
require 'time'
|
|
5
|
-
require '
|
|
4
|
+
require 'securerandom'
|
|
6
5
|
require 'saml_idp/request'
|
|
7
6
|
require 'saml_idp/logout_response_builder'
|
|
8
7
|
module SamlIdp
|
|
@@ -34,18 +33,18 @@ module SamlIdp
|
|
|
34
33
|
end
|
|
35
34
|
|
|
36
35
|
def validate_saml_request(raw_saml_request = params[:SAMLRequest])
|
|
37
|
-
decode_request(raw_saml_request)
|
|
38
|
-
|
|
39
|
-
if Rails::VERSION::MAJOR >= 4
|
|
40
|
-
head :forbidden
|
|
41
|
-
else
|
|
42
|
-
render nothing: true, status: :forbidden
|
|
43
|
-
end
|
|
44
|
-
end
|
|
36
|
+
decode_request(raw_saml_request, params[:Signature], params[:SigAlg], params[:RelayState])
|
|
37
|
+
valid_saml_request?
|
|
45
38
|
end
|
|
46
39
|
|
|
47
|
-
def decode_request(raw_saml_request)
|
|
48
|
-
@saml_request = Request.from_deflated_request(
|
|
40
|
+
def decode_request(raw_saml_request, signature, sig_algorithm, relay_state)
|
|
41
|
+
@saml_request = Request.from_deflated_request(
|
|
42
|
+
raw_saml_request,
|
|
43
|
+
saml_request: raw_saml_request,
|
|
44
|
+
signature: signature,
|
|
45
|
+
sig_algorithm: sig_algorithm,
|
|
46
|
+
relay_state: relay_state
|
|
47
|
+
)
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
def authn_context_classref
|
|
@@ -62,6 +61,13 @@ module SamlIdp
|
|
|
62
61
|
expiry = opts[:expiry] || 60*60
|
|
63
62
|
session_expiry = opts[:session_expiry]
|
|
64
63
|
encryption_opts = opts[:encryption] || nil
|
|
64
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
|
65
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
|
66
|
+
signed_message_opts = opts[:signed_message] || false
|
|
67
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
|
68
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
|
69
|
+
signed_assertion_opts = opts[:signed_assertion].nil? ? true : opts[:signed_assertion]
|
|
70
|
+
compress_opts = opts[:compress] || false
|
|
65
71
|
|
|
66
72
|
SamlResponse.new(
|
|
67
73
|
reference_id,
|
|
@@ -75,11 +81,16 @@ module SamlIdp
|
|
|
75
81
|
my_authn_context_classref,
|
|
76
82
|
expiry,
|
|
77
83
|
encryption_opts,
|
|
78
|
-
session_expiry
|
|
84
|
+
session_expiry,
|
|
85
|
+
name_id_formats_opts,
|
|
86
|
+
asserted_attributes_opts,
|
|
87
|
+
signed_message_opts,
|
|
88
|
+
signed_assertion_opts,
|
|
89
|
+
compress_opts
|
|
79
90
|
).build
|
|
80
91
|
end
|
|
81
92
|
|
|
82
|
-
def encode_logout_response(
|
|
93
|
+
def encode_logout_response(_principal, opts = {})
|
|
83
94
|
SamlIdp::LogoutResponseBuilder.new(
|
|
84
95
|
get_saml_response_id,
|
|
85
96
|
(opts[:issuer_uri] || issuer_uri),
|
|
@@ -122,11 +133,11 @@ module SamlIdp
|
|
|
122
133
|
end
|
|
123
134
|
|
|
124
135
|
def get_saml_response_id
|
|
125
|
-
|
|
136
|
+
SecureRandom.uuid
|
|
126
137
|
end
|
|
127
138
|
|
|
128
139
|
def get_saml_reference_id
|
|
129
|
-
|
|
140
|
+
SecureRandom.uuid
|
|
130
141
|
end
|
|
131
142
|
|
|
132
143
|
def default_algorithm
|
data/lib/saml_idp/encryptor.rb
CHANGED
|
@@ -61,7 +61,6 @@ module SamlIdp
|
|
|
61
61
|
key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
|
|
62
62
|
enc_key.EncryptionMethod Algorithm: key_transport_ns
|
|
63
63
|
enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
|
|
64
|
-
key_info2.tag! 'ds:KeyName'
|
|
65
64
|
key_info2.tag! 'ds:X509Data' do |x509_data|
|
|
66
65
|
x509_data.tag! 'ds:X509Certificate' do |x509_cert|
|
|
67
66
|
x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '')
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SamlIdp
|
|
2
|
+
module Fingerprint
|
|
3
|
+
def self.certificate_digest(cert, sha_size = nil)
|
|
4
|
+
sha_size ||= SamlIdp.config.algorithm
|
|
5
|
+
digest_sha_class(sha_size).hexdigest(OpenSSL::X509::Certificate.new(cert).to_der).scan(/../).join(':')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.digest_sha_class(sha_size)
|
|
9
|
+
case sha_size
|
|
10
|
+
when :sha256
|
|
11
|
+
Digest::SHA256
|
|
12
|
+
when :sha512
|
|
13
|
+
Digest::SHA512
|
|
14
|
+
else
|
|
15
|
+
raise ArgumentError, "Unsupported sha size parameter: #{sha_size}"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -16,16 +16,37 @@ module SamlIdp
|
|
|
16
16
|
@document ||= Saml::XML::Document.parse raw
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
def entity_id
|
|
20
|
+
xpath('//md:EntityDescriptor/@entityID', md: metadata_namespace).first.try(:content).to_s
|
|
21
|
+
end
|
|
22
|
+
hashable :entity_id
|
|
23
|
+
|
|
19
24
|
def sign_assertions
|
|
20
25
|
doc = xpath(
|
|
21
26
|
"//md:SPSSODescriptor",
|
|
22
27
|
ds: signature_namespace,
|
|
23
28
|
md: metadata_namespace
|
|
24
29
|
).first
|
|
25
|
-
doc
|
|
30
|
+
if (doc && !doc['WantAssertionsSigned'].nil?)
|
|
31
|
+
return doc['WantAssertionsSigned'].strip.downcase == 'true'
|
|
32
|
+
end
|
|
33
|
+
return false
|
|
26
34
|
end
|
|
27
35
|
hashable :sign_assertions
|
|
28
36
|
|
|
37
|
+
def sign_authn_request
|
|
38
|
+
doc = xpath(
|
|
39
|
+
"//md:SPSSODescriptor",
|
|
40
|
+
ds: signature_namespace,
|
|
41
|
+
md: metadata_namespace
|
|
42
|
+
).first
|
|
43
|
+
if (doc && !doc['AuthnRequestsSigned'].nil?)
|
|
44
|
+
return doc['AuthnRequestsSigned'].strip.downcase == 'true'
|
|
45
|
+
end
|
|
46
|
+
return false
|
|
47
|
+
end
|
|
48
|
+
hashable :sign_authn_request
|
|
49
|
+
|
|
29
50
|
def display_name
|
|
30
51
|
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
|
|
31
52
|
end
|
|
@@ -42,6 +63,15 @@ module SamlIdp
|
|
|
42
63
|
end
|
|
43
64
|
hashable :contact_person
|
|
44
65
|
|
|
66
|
+
def unspecified_certificate
|
|
67
|
+
xpath(
|
|
68
|
+
"//md:SPSSODescriptor/md:KeyDescriptor[not(@use)]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
|
69
|
+
ds: signature_namespace,
|
|
70
|
+
md: metadata_namespace
|
|
71
|
+
).first.try(:content).to_s
|
|
72
|
+
end
|
|
73
|
+
hashable :unspecified_certificate
|
|
74
|
+
|
|
45
75
|
def signing_certificate
|
|
46
76
|
xpath(
|
|
47
77
|
"//md:SPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
|
@@ -24,13 +24,15 @@ module SamlIdp
|
|
|
24
24
|
|
|
25
25
|
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
|
26
26
|
build_key_descriptor descriptor
|
|
27
|
-
descriptor
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
build_endpoint descriptor, [
|
|
28
|
+
{ tag: 'SingleLogoutService', url: single_logout_service_post_location, bind: 'HTTP-POST' },
|
|
29
|
+
{ tag: 'SingleLogoutService', url: single_logout_service_redirect_location, bind: 'HTTP-Redirect'}
|
|
30
|
+
]
|
|
31
31
|
build_name_id_formats descriptor
|
|
32
|
-
descriptor
|
|
33
|
-
|
|
32
|
+
build_endpoint descriptor, [
|
|
33
|
+
{ tag: 'SingleSignOnService', url: single_service_post_location, bind: 'HTTP-POST' },
|
|
34
|
+
{ tag: 'SingleSignOnService', url: single_service_redirect_location, bind: 'HTTP-Redirect'}
|
|
35
|
+
]
|
|
34
36
|
build_attribute descriptor
|
|
35
37
|
end
|
|
36
38
|
|
|
@@ -38,8 +40,9 @@ module SamlIdp
|
|
|
38
40
|
build_key_descriptor authority_descriptor
|
|
39
41
|
build_organization authority_descriptor
|
|
40
42
|
build_contact authority_descriptor
|
|
41
|
-
authority_descriptor
|
|
42
|
-
|
|
43
|
+
build_endpoint authority_descriptor, [
|
|
44
|
+
{ tag: 'AttributeService', url: attribute_service_location, bind: 'HTTP-Redirect' }
|
|
45
|
+
]
|
|
43
46
|
build_name_id_formats authority_descriptor
|
|
44
47
|
build_attribute authority_descriptor
|
|
45
48
|
end
|
|
@@ -69,6 +72,17 @@ module SamlIdp
|
|
|
69
72
|
end
|
|
70
73
|
private :build_name_id_formats
|
|
71
74
|
|
|
75
|
+
def build_endpoint(el, end_points)
|
|
76
|
+
end_points.each do |ep|
|
|
77
|
+
next unless ep[:url].present?
|
|
78
|
+
|
|
79
|
+
el.tag! ep[:tag],
|
|
80
|
+
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:#{ep[:bind]}",
|
|
81
|
+
Location: ep[:url]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
private :build_endpoint
|
|
85
|
+
|
|
72
86
|
def build_attribute(el)
|
|
73
87
|
attributes.each do |attribute|
|
|
74
88
|
el.tag! "saml:Attribute",
|
|
@@ -138,7 +152,8 @@ module SamlIdp
|
|
|
138
152
|
private :raw_algorithm
|
|
139
153
|
|
|
140
154
|
def x509_certificate
|
|
141
|
-
SamlIdp.config.x509_certificate
|
|
155
|
+
certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
|
|
156
|
+
certificate
|
|
142
157
|
.to_s
|
|
143
158
|
.gsub(/-----BEGIN CERTIFICATE-----/,"")
|
|
144
159
|
.gsub(/-----END CERTIFICATE-----/,"")
|
|
@@ -151,6 +166,7 @@ module SamlIdp
|
|
|
151
166
|
organization_url
|
|
152
167
|
attribute_service_location
|
|
153
168
|
single_service_post_location
|
|
169
|
+
single_service_redirect_location
|
|
154
170
|
single_logout_service_post_location
|
|
155
171
|
single_logout_service_redirect_location
|
|
156
172
|
technical_contact
|