saml_idp 0.9.0 → 0.10.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 +4 -4
- data/README.md +18 -1
- data/lib/saml_idp/controller.rb +3 -1
- data/lib/saml_idp/request.rb +1 -0
- data/lib/saml_idp/response_builder.rb +19 -5
- data/lib/saml_idp/saml_response.rb +15 -3
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/version.rb +1 -1
- data/saml_idp.gemspec +1 -1
- data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
- data/spec/lib/saml_idp/saml_response_spec.rb +25 -2
- metadata +18 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a69c8d8c945c47e87cb526d4dfcf5f30c2f687d03b33e99013eef63acbe4377
|
4
|
+
data.tar.gz: 4933af3eb5cb686111307cd86d074ab45cc879a253f7a0833aaaa3262add74db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61d5e37801af0e41b71cdf0bdbac6cac61e0b86969de75c27519b74ede55e3b9c2ec7c2dfc8bff9ef82ec1fa1a870c9483fe31e65e17d645fdf6cab5205ac2d1
|
7
|
+
data.tar.gz: a1594a0f1da0694a93c478259dcece4d38c30863564aa653b585bdae9ef6834699b784ff151b7569e7cccee22b41e0b5a37f0a5151a89b664feda92005f3f21f
|
data/README.md
CHANGED
@@ -72,6 +72,22 @@ end
|
|
72
72
|
|
73
73
|
## Configuration
|
74
74
|
|
75
|
+
#### Signed assertions and Signed Response
|
76
|
+
|
77
|
+
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.
|
78
|
+
|
79
|
+
Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
|
80
|
+
For that, you can enable it with `config.signed_message` option. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
|
81
|
+
|
82
|
+
#### Signing algorithm
|
83
|
+
|
84
|
+
Following algorithms you can set in your response signing algorithm
|
85
|
+
:sha1 - RSA-SHA1 default value but not recommended to production environment
|
86
|
+
Highly recommended to use one of following algorithm, suit with your computing power.
|
87
|
+
:sha256 - RSA-SHA256
|
88
|
+
:sha384 - RSA-SHA384
|
89
|
+
:sha512 - RSA-SHA512
|
90
|
+
|
75
91
|
Be sure to load a file like this during your app initialization:
|
76
92
|
|
77
93
|
```ruby
|
@@ -91,7 +107,7 @@ KEY DATA
|
|
91
107
|
CERT
|
92
108
|
|
93
109
|
# config.password = "secret_key_password"
|
94
|
-
# config.algorithm = :sha256
|
110
|
+
# config.algorithm = :sha256 # Default: sha1 only for development.
|
95
111
|
# config.organization_name = "Your Organization"
|
96
112
|
# config.organization_url = "http://example.com"
|
97
113
|
# config.base_saml_location = "#{base}/saml"
|
@@ -101,6 +117,7 @@ CERT
|
|
101
117
|
# config.attribute_service_location = "#{base}/saml/attributes"
|
102
118
|
# config.single_service_post_location = "#{base}/saml/auth"
|
103
119
|
# config.session_expiry = 86400 # Default: 0 which means never
|
120
|
+
# config.signed_message = true # Default: false which means unsigned SAML Response
|
104
121
|
|
105
122
|
# Principal (e.g. User) is passed in when you `encode_response`
|
106
123
|
#
|
data/lib/saml_idp/controller.rb
CHANGED
@@ -64,6 +64,7 @@ module SamlIdp
|
|
64
64
|
expiry = opts[:expiry] || 60*60
|
65
65
|
session_expiry = opts[:session_expiry]
|
66
66
|
encryption_opts = opts[:encryption] || nil
|
67
|
+
signed_message_opts = opts[:signed_message] || false
|
67
68
|
|
68
69
|
SamlResponse.new(
|
69
70
|
reference_id,
|
@@ -77,7 +78,8 @@ module SamlIdp
|
|
77
78
|
my_authn_context_classref,
|
78
79
|
expiry,
|
79
80
|
encryption_opts,
|
80
|
-
session_expiry
|
81
|
+
session_expiry,
|
82
|
+
signed_message_opts
|
81
83
|
).build
|
82
84
|
end
|
83
85
|
|
data/lib/saml_idp/request.rb
CHANGED
@@ -106,6 +106,7 @@ module SamlIdp
|
|
106
106
|
end
|
107
107
|
|
108
108
|
if !service_provider.acceptable_response_hosts.include?(response_host)
|
109
|
+
log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
|
109
110
|
log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
|
110
111
|
return false
|
111
112
|
end
|
@@ -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)
|
27
|
+
@encoded ||= signed_message ? encode_signed_message : encode_raw_message
|
20
28
|
end
|
21
29
|
|
22
30
|
def raw
|
23
31
|
build
|
24
32
|
end
|
25
33
|
|
26
|
-
def
|
34
|
+
def encode_raw_message
|
27
35
|
Base64.strict_encode64(raw)
|
28
36
|
end
|
29
|
-
private :
|
37
|
+
private :encode_raw_message
|
38
|
+
|
39
|
+
def encode_signed_message
|
40
|
+
Base64.strict_encode64(signed)
|
41
|
+
end
|
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
|
@@ -17,6 +17,7 @@ module SamlIdp
|
|
17
17
|
attr_accessor :expiry
|
18
18
|
attr_accessor :encryption_opts
|
19
19
|
attr_accessor :session_expiry
|
20
|
+
attr_accessor :signed_message_opts
|
20
21
|
|
21
22
|
def initialize(reference_id,
|
22
23
|
response_id,
|
@@ -29,7 +30,8 @@ module SamlIdp
|
|
29
30
|
authn_context_classref,
|
30
31
|
expiry=60*60,
|
31
32
|
encryption_opts=nil,
|
32
|
-
session_expiry=0
|
33
|
+
session_expiry=0,
|
34
|
+
signed_message_opts
|
33
35
|
)
|
34
36
|
self.reference_id = reference_id
|
35
37
|
self.response_id = response_id
|
@@ -45,10 +47,11 @@ module SamlIdp
|
|
45
47
|
self.expiry = expiry
|
46
48
|
self.encryption_opts = encryption_opts
|
47
49
|
self.session_expiry = session_expiry
|
50
|
+
self.signed_message_opts = signed_message_opts
|
48
51
|
end
|
49
52
|
|
50
53
|
def build
|
51
|
-
@built ||=
|
54
|
+
@built ||= encoded_message
|
52
55
|
end
|
53
56
|
|
54
57
|
def signed_assertion
|
@@ -60,8 +63,17 @@ module SamlIdp
|
|
60
63
|
end
|
61
64
|
private :signed_assertion
|
62
65
|
|
66
|
+
def encoded_message
|
67
|
+
if signed_message_opts
|
68
|
+
response_builder.encoded(signed_message: true)
|
69
|
+
else
|
70
|
+
response_builder.encoded(signed_message: false)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
private :encoded_message
|
74
|
+
|
63
75
|
def response_builder
|
64
|
-
ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
|
76
|
+
ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion, algorithm)
|
65
77
|
end
|
66
78
|
private :response_builder
|
67
79
|
|
data/lib/saml_idp/signable.rb
CHANGED
@@ -108,8 +108,7 @@ module SamlIdp
|
|
108
108
|
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
109
109
|
canon_hashed_element = noko_raw.canonicalize(canon_algorithm, inclusive_namespaces)
|
110
110
|
digest_algorithm = get_algorithm
|
111
|
-
|
112
|
-
hash = digest_algorithm.digest(canon_hashed_element)
|
111
|
+
hash = digest_algorithm.digest(canon_hashed_element)
|
113
112
|
Base64.strict_encode64(hash).gsub(/\n/, '')
|
114
113
|
end
|
115
114
|
private :digest
|
data/lib/saml_idp/version.rb
CHANGED
data/saml_idp.gemspec
CHANGED
@@ -6,12 +6,14 @@ module SamlIdp
|
|
6
6
|
let(:saml_acs_url) { "http://sportngin.com" }
|
7
7
|
let(:saml_request_id) { "134" }
|
8
8
|
let(:assertion_and_signature) { "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2013-07-31T05:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><signature>stuff</signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">jon.phenow@sportngin.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2013-07-31T05:03:00Z\" Recipient=\"http://saml.acs.url\"/></SubjectConfirmation></Subject><Conditions NotBefore=\"2013-07-31T04:59:55Z\" NotOnOrAfter=\"2013-07-31T06:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>jon.phenow@sportngin.com</AttributeValue></Attribute></AttributeStatement><AuthnStatment AuthnInstant=\"2013-07-31T05:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatment></Assertion>" }
|
9
|
+
let(:algorithm) { :sha256 }
|
9
10
|
subject { described_class.new(
|
10
11
|
response_id,
|
11
12
|
issuer_uri,
|
12
13
|
saml_acs_url,
|
13
14
|
saml_request_id,
|
14
|
-
assertion_and_signature
|
15
|
+
assertion_and_signature,
|
16
|
+
algorithm
|
15
17
|
) }
|
16
18
|
|
17
19
|
before do
|
@@ -24,6 +24,8 @@ module SamlIdp
|
|
24
24
|
key_transport: 'rsa-oaep-mgf1p',
|
25
25
|
}
|
26
26
|
end
|
27
|
+
let(:signed_response_opts) { true }
|
28
|
+
let(:unsigned_response_opts) { false }
|
27
29
|
let(:subject_encrypted) { described_class.new(reference_id,
|
28
30
|
response_id,
|
29
31
|
issuer_uri,
|
@@ -35,7 +37,8 @@ module SamlIdp
|
|
35
37
|
authn_context_classref,
|
36
38
|
expiry,
|
37
39
|
encryption_opts,
|
38
|
-
session_expiry
|
40
|
+
session_expiry,
|
41
|
+
unsigned_response_opts
|
39
42
|
)
|
40
43
|
}
|
41
44
|
|
@@ -50,7 +53,8 @@ module SamlIdp
|
|
50
53
|
authn_context_classref,
|
51
54
|
expiry,
|
52
55
|
nil,
|
53
|
-
session_expiry
|
56
|
+
session_expiry,
|
57
|
+
signed_response_opts
|
54
58
|
)
|
55
59
|
}
|
56
60
|
|
@@ -77,6 +81,25 @@ module SamlIdp
|
|
77
81
|
expect(saml_resp.is_valid?).to eq(true)
|
78
82
|
end
|
79
83
|
|
84
|
+
it "will build signed valid response" do
|
85
|
+
expect { subject.build }.not_to raise_error
|
86
|
+
signed_encoded_xml = subject.build
|
87
|
+
resp_settings = saml_settings(saml_acs_url)
|
88
|
+
resp_settings.private_key = Default::SECRET_KEY
|
89
|
+
resp_settings.issuer = audience_uri
|
90
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
91
|
+
expect(
|
92
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
93
|
+
"//p:Response//ds:Signature",
|
94
|
+
{
|
95
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
96
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
97
|
+
}
|
98
|
+
)).to be_present
|
99
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
100
|
+
expect(saml_resp.is_valid?).to eq(true)
|
101
|
+
end
|
102
|
+
|
80
103
|
it "sets session expiration" do
|
81
104
|
saml_resp = OneLogin::RubySaml::Response.new(subject.build)
|
82
105
|
expect(saml_resp.session_expires_at).to eq Time.local(1990, "jan", 2).iso8601
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saml_idp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Phenow
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - ">="
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: byebug
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
209
223
|
description: SAML IdP (Identity Provider) Library for Ruby
|
210
224
|
email: jon.phenow@sportngin.com
|
211
225
|
executables: []
|
@@ -347,7 +361,7 @@ metadata:
|
|
347
361
|
homepage_uri: https://github.com/saml-idp/saml_idp
|
348
362
|
source_code_uri: https://github.com/saml-idp/saml_idp
|
349
363
|
bug_tracker_uri: https://github.com/saml-idp/saml_idp/issues
|
350
|
-
documentation_uri: http://rdoc.info/gems/saml_idp/0.
|
364
|
+
documentation_uri: http://rdoc.info/gems/saml_idp/0.10.0
|
351
365
|
post_install_message: |
|
352
366
|
If you're just recently updating saml_idp - please be aware we've changed the default
|
353
367
|
certificate. See the PR and a description of why we've done this here:
|
@@ -378,8 +392,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
378
392
|
- !ruby/object:Gem::Version
|
379
393
|
version: '0'
|
380
394
|
requirements: []
|
381
|
-
|
382
|
-
rubygems_version: 2.7.6
|
395
|
+
rubygems_version: 3.1.2
|
383
396
|
signing_key:
|
384
397
|
specification_version: 4
|
385
398
|
summary: SAML Indentity Provider for Ruby
|