saml_idp 0.8.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -47
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +6 -1
- data/lib/saml_idp/controller.rb +19 -11
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +18 -0
- data/lib/saml_idp/metadata_builder.rb +23 -8
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +13 -6
- 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 +1 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +1 -1
- data/lib/saml_idp.rb +2 -1
- data/saml_idp.gemspec +31 -31
- data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
- data/spec/lib/saml_idp/configurator_spec.rb +2 -0
- data/spec/lib/saml_idp/controller_spec.rb +24 -0
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +20 -1
- data/spec/lib/saml_idp/metadata_builder_spec.rb +23 -0
- data/spec/lib/saml_idp/request_spec.rb +43 -9
- data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
- data/spec/lib/saml_idp/saml_response_spec.rb +122 -7
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
- 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 +1 -5
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -0
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -0
- data/spec/spec_helper.rb +20 -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 +62 -3
- data/spec/support/security_helpers.rb +10 -0
- metadata +83 -61
- data/app/controllers/saml_idp/idp_controller.rb +0 -59
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4da444f25fd4d8cb2b53d847ee3ffa44adab3b2c4b64be57a6935c0922acf1a8
|
4
|
+
data.tar.gz: ff0beb64e76c37a0bbcb098f0bd5a50b4d15ff124d63d9c01d421f4693f6fa2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1fe91e27e817106e66738c73c670ce064c18b18e9528f7aef3c2a4dc87658c9262877b7a62f491c29ff371d39e0306721bc1f97af7ec3fb6fd1d23b8550b32ce
|
7
|
+
data.tar.gz: d6ee196976da4fe1af818bca3183632372ef2e1e3059891e75a13dc39caa9fb86c3d312c384d926877f9e14cfbd751d566d517742e72e0bb77e276e446a88aed
|
data/README.md
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# Ruby SAML Identity Provider (IdP)
|
2
|
-
|
2
|
+
|
3
|
+
Forked from <https://github.com/lawrencepit/ruby-saml-idp>
|
3
4
|
|
4
5
|
[![Build Status](https://travis-ci.org/saml-idp/saml_idp.svg)](https://travis-ci.org/saml-idp/saml_idp)
|
5
6
|
[![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)
|
@@ -12,62 +13,49 @@ protocol. It provides a means for managing authentication requests and confirmat
|
|
12
13
|
This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
|
13
14
|
SAML IDP implementation.
|
14
15
|
|
15
|
-
|
16
|
+
## Installation and Usage
|
16
17
|
|
17
18
|
Add this to your Gemfile:
|
18
19
|
|
20
|
+
```ruby
|
19
21
|
gem 'saml_idp'
|
22
|
+
```
|
23
|
+
|
24
|
+
### Not using rails?
|
20
25
|
|
21
|
-
## Not using rails?
|
22
26
|
Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
|
23
27
|
|
24
28
|
Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
25
29
|
`saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
|
26
30
|
a user is entirely up to you.
|
27
31
|
|
28
|
-
Once a user has successfully authenticated on your system send the Service Provider a
|
32
|
+
Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
|
29
33
|
posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
|
30
34
|
`encode_response(user_email)`.
|
31
35
|
|
32
|
-
|
33
|
-
Add to your `routes.rb` file, for example:
|
36
|
+
### Using rails?
|
34
37
|
|
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
|
-
```
|
38
|
+
Check out our Wiki page for Rails integration
|
39
|
+
[Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
|
41
40
|
|
42
|
-
|
41
|
+
### Configuration
|
43
42
|
|
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
|
43
|
+
#### Signed assertions and Signed Response
|
61
44
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
45
|
+
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.
|
46
|
+
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.
|
47
|
+
|
48
|
+
Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
|
49
|
+
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)
|
50
|
+
|
51
|
+
#### Signing algorithm
|
69
52
|
|
70
|
-
|
53
|
+
Following algorithms you can set in your response signing algorithm
|
54
|
+
:sha1 - RSA-SHA1 default value but not recommended to production environment
|
55
|
+
Highly recommended to use one of following algorithm, suit with your computing power.
|
56
|
+
:sha256 - RSA-SHA256
|
57
|
+
:sha384 - RSA-SHA384
|
58
|
+
:sha512 - RSA-SHA512
|
71
59
|
|
72
60
|
Be sure to load a file like this during your app initialization:
|
73
61
|
|
@@ -88,20 +76,23 @@ KEY DATA
|
|
88
76
|
CERT
|
89
77
|
|
90
78
|
# config.password = "secret_key_password"
|
91
|
-
# config.algorithm = :sha256
|
79
|
+
# config.algorithm = :sha256 # Default: sha1 only for development.
|
92
80
|
# config.organization_name = "Your Organization"
|
93
81
|
# config.organization_url = "http://example.com"
|
94
82
|
# config.base_saml_location = "#{base}/saml"
|
95
|
-
# config.reference_id_generator # Default: -> {
|
83
|
+
# config.reference_id_generator # Default: -> { SecureRandom.uuid }
|
96
84
|
# config.single_logout_service_post_location = "#{base}/saml/logout"
|
97
85
|
# config.single_logout_service_redirect_location = "#{base}/saml/logout"
|
98
86
|
# config.attribute_service_location = "#{base}/saml/attributes"
|
99
87
|
# config.single_service_post_location = "#{base}/saml/auth"
|
100
88
|
# config.session_expiry = 86400 # Default: 0 which means never
|
89
|
+
# config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
|
90
|
+
# config.compress = true # Default: false which means the SAML Response is not being compressed
|
91
|
+
# 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
|
101
92
|
|
102
93
|
# Principal (e.g. User) is passed in when you `encode_response`
|
103
94
|
#
|
104
|
-
# config.name_id.formats
|
95
|
+
# config.name_id.formats =
|
105
96
|
# { # All 2.0
|
106
97
|
# email_address: -> (principal) { principal.email_address },
|
107
98
|
# transient: -> (principal) { principal.id },
|
@@ -210,7 +201,8 @@ CERT
|
|
210
201
|
end
|
211
202
|
```
|
212
203
|
|
213
|
-
|
204
|
+
## Keys and Secrets
|
205
|
+
|
214
206
|
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
215
207
|
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
216
208
|
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
@@ -220,22 +212,32 @@ and `SamlIdp.config.secret_key` properties.
|
|
220
212
|
|
221
213
|
The fingerprint to use, if you use the default X.509 certificate of this gem, is:
|
222
214
|
|
215
|
+
```bash
|
216
|
+
9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
|
223
217
|
```
|
224
|
-
|
218
|
+
|
219
|
+
## Fingerprint
|
220
|
+
|
221
|
+
The gem provides an helper to generate a fingerprint for a X.509 certificate.
|
222
|
+
The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
|
225
226
|
```
|
226
227
|
|
228
|
+
## Service Providers
|
227
229
|
|
228
|
-
# Service Providers
|
229
230
|
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
|
230
231
|
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
|
231
232
|
|
233
|
+
## Author
|
232
234
|
|
233
|
-
|
234
|
-
Jon Phenow, me@jphenow.com
|
235
|
+
Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
|
235
236
|
|
236
237
|
Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
|
237
238
|
|
238
|
-
|
239
|
+
## Copyright
|
240
|
+
|
239
241
|
Copyright (c) 2012 Sport Ngin.
|
240
242
|
Portions Copyright (c) 2010 OneLogin, LLC
|
241
243
|
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,24 +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
|
20
23
|
attr_accessor :assertion_consumer_service_hosts
|
21
24
|
attr_accessor :session_expiry
|
25
|
+
attr_accessor :logger
|
22
26
|
|
23
27
|
def initialize
|
24
28
|
self.x509_certificate = Default::X509_CERTIFICATE
|
25
29
|
self.secret_key = Default::SECRET_KEY
|
26
30
|
self.algorithm = :sha1
|
27
|
-
self.reference_id_generator = ->() {
|
31
|
+
self.reference_id_generator = ->() { SecureRandom.uuid }
|
28
32
|
self.service_provider = OpenStruct.new
|
29
33
|
self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
|
30
34
|
self.service_provider.metadata_persister = ->(id, settings) { }
|
31
35
|
self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
|
32
36
|
self.session_expiry = 0
|
33
37
|
self.attributes = {}
|
38
|
+
self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
|
34
39
|
end
|
35
40
|
|
36
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
|
@@ -36,11 +35,8 @@ module SamlIdp
|
|
36
35
|
def validate_saml_request(raw_saml_request = params[:SAMLRequest])
|
37
36
|
decode_request(raw_saml_request)
|
38
37
|
return true if valid_saml_request?
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
render nothing: true, status: :forbidden
|
43
|
-
end
|
38
|
+
|
39
|
+
head :forbidden if defined?(::Rails)
|
44
40
|
false
|
45
41
|
end
|
46
42
|
|
@@ -62,6 +58,13 @@ module SamlIdp
|
|
62
58
|
expiry = opts[:expiry] || 60*60
|
63
59
|
session_expiry = opts[:session_expiry]
|
64
60
|
encryption_opts = opts[:encryption] || nil
|
61
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
62
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
63
|
+
signed_message_opts = opts[:signed_message] || false
|
64
|
+
name_id_formats_opts = opts[:name_id_formats] || nil
|
65
|
+
asserted_attributes_opts = opts[:attributes] || nil
|
66
|
+
signed_assertion_opts = opts[:signed_assertion] || true
|
67
|
+
compress_opts = opts[:compress] || false
|
65
68
|
|
66
69
|
SamlResponse.new(
|
67
70
|
reference_id,
|
@@ -75,11 +78,16 @@ module SamlIdp
|
|
75
78
|
my_authn_context_classref,
|
76
79
|
expiry,
|
77
80
|
encryption_opts,
|
78
|
-
session_expiry
|
81
|
+
session_expiry,
|
82
|
+
name_id_formats_opts,
|
83
|
+
asserted_attributes_opts,
|
84
|
+
signed_assertion_opts,
|
85
|
+
signed_message_opts,
|
86
|
+
compress_opts
|
79
87
|
).build
|
80
88
|
end
|
81
89
|
|
82
|
-
def encode_logout_response(
|
90
|
+
def encode_logout_response(_principal, opts = {})
|
83
91
|
SamlIdp::LogoutResponseBuilder.new(
|
84
92
|
get_saml_response_id,
|
85
93
|
(opts[:issuer_uri] || issuer_uri),
|
@@ -122,11 +130,11 @@ module SamlIdp
|
|
122
130
|
end
|
123
131
|
|
124
132
|
def get_saml_response_id
|
125
|
-
|
133
|
+
SecureRandom.uuid
|
126
134
|
end
|
127
135
|
|
128
136
|
def get_saml_reference_id
|
129
|
-
|
137
|
+
SecureRandom.uuid
|
130
138
|
end
|
131
139
|
|
132
140
|
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,6 +16,11 @@ 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",
|
@@ -29,6 +34,19 @@ module SamlIdp
|
|
29
34
|
end
|
30
35
|
hashable :sign_assertions
|
31
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
|
+
|
32
50
|
def display_name
|
33
51
|
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
|
34
52
|
end
|
@@ -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",
|
@@ -151,6 +165,7 @@ module SamlIdp
|
|
151
165
|
organization_url
|
152
166
|
attribute_service_location
|
153
167
|
single_service_post_location
|
168
|
+
single_service_redirect_location
|
154
169
|
single_logout_service_post_location
|
155
170
|
single_logout_service_redirect_location
|
156
171
|
technical_contact
|
data/lib/saml_idp/request.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'saml_idp/xml_security'
|
2
2
|
require 'saml_idp/service_provider'
|
3
|
+
require 'logger'
|
3
4
|
module SamlIdp
|
4
5
|
class Request
|
5
6
|
def self.from_deflated_request(raw)
|
@@ -77,10 +78,10 @@ module SamlIdp
|
|
77
78
|
end
|
78
79
|
|
79
80
|
def log(msg)
|
80
|
-
if
|
81
|
-
|
81
|
+
if config.logger.class <= ::Logger
|
82
|
+
config.logger.info msg
|
82
83
|
else
|
83
|
-
|
84
|
+
config.logger.call msg
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -106,6 +107,7 @@ module SamlIdp
|
|
106
107
|
end
|
107
108
|
|
108
109
|
if !service_provider.acceptable_response_hosts.include?(response_host)
|
110
|
+
log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
|
109
111
|
log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
|
110
112
|
return false
|
111
113
|
end
|
@@ -114,9 +116,14 @@ module SamlIdp
|
|
114
116
|
end
|
115
117
|
|
116
118
|
def valid_signature?
|
117
|
-
# Force signatures for logout requests because there is no other
|
118
|
-
#
|
119
|
-
service_provider.
|
119
|
+
# Force signatures for logout requests because there is no other protection against a cross-site DoS.
|
120
|
+
# Validate signature when metadata specify AuthnRequest should be signed
|
121
|
+
metadata = service_provider.current_metadata
|
122
|
+
if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
|
123
|
+
document.valid_signature?(service_provider.fingerprint)
|
124
|
+
else
|
125
|
+
true
|
126
|
+
end
|
120
127
|
end
|
121
128
|
|
122
129
|
def service_provider?
|
@@ -1,32 +1,45 @@
|
|
1
1
|
require 'builder'
|
2
|
+
require 'saml_idp/algorithmable'
|
3
|
+
require 'saml_idp/signable'
|
2
4
|
module SamlIdp
|
3
5
|
class ResponseBuilder
|
6
|
+
include Algorithmable
|
7
|
+
include Signable
|
4
8
|
attr_accessor :response_id
|
5
9
|
attr_accessor :issuer_uri
|
6
10
|
attr_accessor :saml_acs_url
|
7
11
|
attr_accessor :saml_request_id
|
8
12
|
attr_accessor :assertion_and_signature
|
13
|
+
attr_accessor :raw_algorithm
|
9
14
|
|
10
|
-
|
15
|
+
alias_method :reference_id, :response_id
|
16
|
+
|
17
|
+
def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature, raw_algorithm)
|
11
18
|
self.response_id = response_id
|
12
19
|
self.issuer_uri = issuer_uri
|
13
20
|
self.saml_acs_url = saml_acs_url
|
14
21
|
self.saml_request_id = saml_request_id
|
15
22
|
self.assertion_and_signature = assertion_and_signature
|
23
|
+
self.raw_algorithm = raw_algorithm
|
16
24
|
end
|
17
25
|
|
18
|
-
def encoded
|
19
|
-
@encoded ||=
|
26
|
+
def encoded(signed_message: false, compress: false)
|
27
|
+
@encoded ||= signed_message ? encode_signed_message(compress) : encode_raw_message(compress)
|
20
28
|
end
|
21
29
|
|
22
30
|
def raw
|
23
31
|
build
|
24
32
|
end
|
25
33
|
|
26
|
-
def
|
27
|
-
Base64.strict_encode64(raw)
|
34
|
+
def encode_raw_message(compress)
|
35
|
+
Base64.strict_encode64(compress ? deflate(raw) : raw)
|
36
|
+
end
|
37
|
+
private :encode_raw_message
|
38
|
+
|
39
|
+
def encode_signed_message(compress)
|
40
|
+
Base64.strict_encode64(compress ? deflate(signed) : signed)
|
28
41
|
end
|
29
|
-
private :
|
42
|
+
private :encode_signed_message
|
30
43
|
|
31
44
|
def build
|
32
45
|
resp_options = {}
|
@@ -41,6 +54,7 @@ module SamlIdp
|
|
41
54
|
builder = Builder::XmlMarkup.new
|
42
55
|
builder.tag! "samlp:Response", resp_options do |response|
|
43
56
|
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
57
|
+
sign response
|
44
58
|
response.tag! "samlp:Status" do |status|
|
45
59
|
status.tag! "samlp:StatusCode", Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
46
60
|
end
|
@@ -52,11 +66,17 @@ module SamlIdp
|
|
52
66
|
def response_id_string
|
53
67
|
"_#{response_id}"
|
54
68
|
end
|
69
|
+
alias_method :reference_id, :response_id
|
55
70
|
private :response_id_string
|
56
71
|
|
57
72
|
def now_iso
|
58
73
|
Time.now.utc.iso8601
|
59
74
|
end
|
60
75
|
private :now_iso
|
76
|
+
|
77
|
+
def deflate(inflated)
|
78
|
+
Zlib::Deflate.deflate(inflated, 9)[2..-5]
|
79
|
+
end
|
80
|
+
private :deflate
|
61
81
|
end
|
62
82
|
end
|