icn_saml_idp 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +238 -0
- data/app/controllers/saml_idp/idp_controller.rb +53 -0
- data/app/views/saml_idp/idp/new.html.erb +22 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +14 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +172 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +48 -0
- data/lib/saml_idp/controller.rb +128 -0
- data/lib/saml_idp/default.rb +49 -0
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/engine.rb +5 -0
- data/lib/saml_idp/hashable.rb +26 -0
- data/lib/saml_idp/incoming_metadata.rb +144 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +34 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +160 -0
- data/lib/saml_idp/name_id_formatter.rb +68 -0
- data/lib/saml_idp/persisted_metadata.rb +10 -0
- data/lib/saml_idp/request.rb +180 -0
- data/lib/saml_idp/response_builder.rb +62 -0
- data/lib/saml_idp/saml_response.rb +79 -0
- data/lib/saml_idp/service_provider.rb +76 -0
- data/lib/saml_idp/signable.rb +131 -0
- data/lib/saml_idp/signature_builder.rb +42 -0
- data/lib/saml_idp/signed_info_builder.rb +88 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +181 -0
- data/saml_idp.gemspec +65 -0
- data/spec/acceptance/acceptance_helper.rb +9 -0
- data/spec/acceptance/idp_controller_spec.rb +16 -0
- data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
- data/spec/lib/saml_idp/assertion_builder_spec.rb +106 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +53 -0
- data/spec/lib/saml_idp/configurator_spec.rb +43 -0
- data/spec/lib/saml_idp/controller_spec.rb +94 -0
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +42 -0
- data/spec/lib/saml_idp/request_spec.rb +106 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +42 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +68 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +27 -0
- data/spec/lib/saml_idp/signable_spec.rb +77 -0
- data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
- data/spec/rails_app/.gitignore +15 -0
- data/spec/rails_app/README.rdoc +261 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +60 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +37 -0
- data/spec/rails_app/config/environments/production.rb +67 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +6 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/doc/README_FOR_APP +2 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/rails_app/log/.gitkeep +0 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/test/fixtures/.gitkeep +0 -0
- data/spec/rails_app/test/functional/.gitkeep +0 -0
- data/spec/rails_app/test/integration/.gitkeep +0 -0
- data/spec/rails_app/test/performance/browsing_test.rb +12 -0
- data/spec/rails_app/test/test_helper.rb +13 -0
- data/spec/rails_app/test/unit/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/certificates/certificate1 +12 -0
- data/spec/support/certificates/r1_certificate2_base64 +1 -0
- data/spec/support/responses/adfs_response_sha1.xml +46 -0
- data/spec/support/responses/adfs_response_sha256.xml +46 -0
- data/spec/support/responses/adfs_response_sha384.xml +46 -0
- data/spec/support/responses/adfs_response_sha512.xml +46 -0
- data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
- data/spec/support/responses/no_signature_ns.xml +48 -0
- data/spec/support/responses/open_saml_response.xml +56 -0
- data/spec/support/responses/r1_response6.xml.base64 +1 -0
- data/spec/support/responses/response1.xml.base64 +1 -0
- data/spec/support/responses/response2.xml.base64 +79 -0
- data/spec/support/responses/response3.xml.base64 +66 -0
- data/spec/support/responses/response4.xml.base64 +93 -0
- data/spec/support/responses/response5.xml.base64 +102 -0
- data/spec/support/responses/response_with_ampersands.xml +139 -0
- data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
- data/spec/support/responses/simple_saml_php.xml +71 -0
- data/spec/support/responses/starfield_response.xml.base64 +1 -0
- data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
- data/spec/support/saml_request_macros.rb +38 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +137 -0
- metadata +465 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'saml_idp/logout_request_builder'
|
3
|
+
|
4
|
+
module SamlIdp
|
5
|
+
describe LogoutRequestBuilder do
|
6
|
+
before do
|
7
|
+
Timecop.freeze(Time.local(1990))
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Timecop.return
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:response_id) { 'some_response_id' }
|
15
|
+
let(:issuer_uri) { 'http://example.com' }
|
16
|
+
let(:saml_slo_url) { 'http://localhost:3000/saml/logout' }
|
17
|
+
let(:name_id) { 'some_name_id' }
|
18
|
+
let(:algorithm) { OpenSSL::Digest::SHA256 }
|
19
|
+
|
20
|
+
subject do
|
21
|
+
described_class.new(
|
22
|
+
response_id,
|
23
|
+
issuer_uri,
|
24
|
+
saml_slo_url,
|
25
|
+
name_id,
|
26
|
+
algorithm
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is a valid SloLogoutrequest" do
|
31
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
32
|
+
slo_request = OneLogin::RubySaml::SloLogoutrequest.new(
|
33
|
+
subject.encoded,
|
34
|
+
settings: saml_settings('localhost:3000')
|
35
|
+
)
|
36
|
+
slo_request.soft = false
|
37
|
+
expect(slo_request.is_valid?).to eq true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'saml_idp/logout_response_builder'
|
3
|
+
|
4
|
+
module SamlIdp
|
5
|
+
describe LogoutResponseBuilder do
|
6
|
+
before do
|
7
|
+
Timecop.freeze(Time.local(1990))
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Timecop.return
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:response_id) { 'some_response_id' }
|
15
|
+
let(:issuer_uri) { 'http://example.com' }
|
16
|
+
let(:saml_slo_url) { 'http://localhost:3000/saml/logout' }
|
17
|
+
let(:request_id) { 'some_request_id' }
|
18
|
+
let(:algorithm) { OpenSSL::Digest::SHA256 }
|
19
|
+
|
20
|
+
subject do
|
21
|
+
described_class.new(
|
22
|
+
response_id,
|
23
|
+
issuer_uri,
|
24
|
+
saml_slo_url,
|
25
|
+
request_id,
|
26
|
+
algorithm
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is a valid LogoutResponse" do
|
31
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
32
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(
|
33
|
+
subject.encoded,
|
34
|
+
saml_settings('localhost:3000')
|
35
|
+
)
|
36
|
+
logout_response.soft = false
|
37
|
+
expect(logout_response.validate).to eq true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe MetadataBuilder do
|
4
|
+
it "has a valid fresh" do
|
5
|
+
subject.fresh.should_not be_empty
|
6
|
+
end
|
7
|
+
|
8
|
+
it "signs valid xml" do
|
9
|
+
Saml::XML::Document.parse(subject.signed).valid_signature?(Default::FINGERPRINT).should be_truthy
|
10
|
+
end
|
11
|
+
|
12
|
+
it "includes logout element" do
|
13
|
+
subject.configurator.single_logout_service_post_location = 'https://example.com/saml/logout'
|
14
|
+
subject.fresh.should match(
|
15
|
+
'<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/logout"/>'
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe NameIdFormatter do
|
4
|
+
subject { described_class.new list }
|
5
|
+
|
6
|
+
describe "with one item" do
|
7
|
+
let(:list) { { email_address: ->() { "foo@example.com" } } }
|
8
|
+
|
9
|
+
it "has a valid all" do
|
10
|
+
subject.all.should == ["urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress"]
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "with hash describing versions" do
|
16
|
+
let(:list) {
|
17
|
+
{
|
18
|
+
"1.1" => { email_address: -> {} },
|
19
|
+
"2.0" => { undefined: -> {} },
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
it "has a valid all" do
|
24
|
+
subject.all.should == [
|
25
|
+
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
26
|
+
"urn:oasis:names:tc:SAML:2.0:nameid-format:undefined",
|
27
|
+
]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "with actual list" do
|
32
|
+
let(:list) { [:email_address, :undefined] }
|
33
|
+
|
34
|
+
it "has a valid all" do
|
35
|
+
subject.all.should == [
|
36
|
+
"urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress",
|
37
|
+
"urn:oasis:names:tc:SAML:2.0:nameid-format:undefined",
|
38
|
+
]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe Request do
|
4
|
+
let(:raw_authn_request) { "<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>localhost:3000</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>" }
|
5
|
+
|
6
|
+
describe "deflated request" do
|
7
|
+
let(:deflated_request) { Base64.encode64(Zlib::Deflate.deflate(raw_authn_request, 9)[2..-5]) }
|
8
|
+
|
9
|
+
subject { described_class.from_deflated_request deflated_request }
|
10
|
+
|
11
|
+
it "inflates" do
|
12
|
+
subject.request_id.should == "_af43d1a0-e111-0130-661a-3c0754403fdb"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "handles invalid SAML" do
|
16
|
+
req = described_class.from_deflated_request "bang!"
|
17
|
+
req.valid?.should == false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "authn request" do
|
22
|
+
subject { described_class.new raw_authn_request }
|
23
|
+
|
24
|
+
it "has a valid request_id" do
|
25
|
+
subject.request_id.should == "_af43d1a0-e111-0130-661a-3c0754403fdb"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has a valid acs_url" do
|
29
|
+
subject.acs_url.should == "http://localhost:3000/saml/consume"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "has a valid service_provider" do
|
33
|
+
subject.service_provider.should be_a ServiceProvider
|
34
|
+
end
|
35
|
+
|
36
|
+
it "has a valid service_provider" do
|
37
|
+
subject.service_provider.should be_truthy
|
38
|
+
end
|
39
|
+
|
40
|
+
it "has a valid issuer" do
|
41
|
+
subject.issuer.should == "localhost:3000"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "has a valid valid_signature" do
|
45
|
+
subject.valid_signature?.should be_truthy
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return acs_url for response_url" do
|
49
|
+
subject.response_url.should == subject.acs_url
|
50
|
+
end
|
51
|
+
|
52
|
+
it "is a authn request" do
|
53
|
+
subject.authn_request?.should == true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "fetches internal request" do
|
57
|
+
subject.request['ID'].should == subject.request_id
|
58
|
+
end
|
59
|
+
|
60
|
+
it "has a valid authn context" do
|
61
|
+
subject.requested_authn_context.should == "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not permit empty issuer" do
|
65
|
+
raw_req = raw_authn_request.gsub('localhost:3000', '')
|
66
|
+
authn_request = described_class.new raw_req
|
67
|
+
authn_request.issuer.should_not == ''
|
68
|
+
authn_request.issuer.should == nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "logout request" do
|
73
|
+
let(:raw_logout_request) { "<LogoutRequest ID='_some_response_id' Version='2.0' IssueInstant='2010-06-01T13:00:00Z' Destination='http://localhost:3000/saml/logout' xmlns='urn:oasis:names:tc:SAML:2.0:protocol'><Issuer xmlns='urn:oasis:names:tc:SAML:2.0:assertion'>http://example.com</Issuer><NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' Format='urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'>some_name_id</NameID><SessionIndex>abc123index</SessionIndex></LogoutRequest>" }
|
74
|
+
|
75
|
+
subject { described_class.new raw_logout_request }
|
76
|
+
|
77
|
+
it "has a valid request_id" do
|
78
|
+
subject.request_id.should == '_some_response_id'
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be flagged as a logout_request" do
|
82
|
+
subject.logout_request?.should == true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should have a valid name_id" do
|
86
|
+
subject.name_id.should == 'some_name_id'
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should have a session index" do
|
90
|
+
subject.session_index.should == 'abc123index'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should have a valid issuer" do
|
94
|
+
subject.issuer.should == 'http://example.com'
|
95
|
+
end
|
96
|
+
|
97
|
+
it "fetches internal request" do
|
98
|
+
subject.request['ID'].should == subject.request_id
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should return logout_url for response_url" do
|
102
|
+
subject.response_url.should == subject.logout_url
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe ResponseBuilder do
|
4
|
+
let(:response_id) { "abc" }
|
5
|
+
let(:issuer_uri) { "http://example.com" }
|
6
|
+
let(:saml_acs_url) { "http://sportngin.com" }
|
7
|
+
let(:saml_request_id) { "134" }
|
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
|
+
subject { described_class.new(
|
10
|
+
response_id,
|
11
|
+
issuer_uri,
|
12
|
+
saml_acs_url,
|
13
|
+
saml_request_id,
|
14
|
+
assertion_and_signature
|
15
|
+
) }
|
16
|
+
|
17
|
+
before do
|
18
|
+
Timecop.freeze(Time.local(1990))
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
Timecop.return
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
it "builds a legit raw XML file" do
|
27
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
28
|
+
subject.raw.should == "<samlp:Response ID=\"_abc\" Version=\"2.0\" IssueInstant=\"2010-06-01T13:00:00Z\" Destination=\"http://sportngin.com\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" InResponseTo=\"134\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://example.com</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><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></samlp:Response>"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "No request ID" do
|
33
|
+
let(:saml_request_id) { nil }
|
34
|
+
|
35
|
+
it "builds a legit raw XML file without a request ID" do
|
36
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
37
|
+
subject.raw.should == "<samlp:Response ID=\"_abc\" Version=\"2.0\" IssueInstant=\"2010-06-01T13:00:00Z\" Destination=\"http://sportngin.com\" Consent=\"urn:oasis:names:tc:SAML:2.0:consent:unspecified\" xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://example.com</Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><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></samlp:Response>"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe SamlResponse do
|
4
|
+
let(:reference_id) { "123" }
|
5
|
+
let(:response_id) { "abc" }
|
6
|
+
let(:issuer_uri) { "localhost" }
|
7
|
+
let(:name_id) { "name" }
|
8
|
+
let(:audience_uri) { "localhost/audience" }
|
9
|
+
let(:saml_request_id) { "abc123" }
|
10
|
+
let(:saml_acs_url) { "localhost/acs" }
|
11
|
+
let(:algorithm) { :sha1 }
|
12
|
+
let(:secret_key) { Default::SECRET_KEY }
|
13
|
+
let(:x509_certificate) { Default::X509_CERTIFICATE }
|
14
|
+
let(:xauthn) { Default::X509_CERTIFICATE }
|
15
|
+
let(:authn_context_classref) {
|
16
|
+
Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD
|
17
|
+
}
|
18
|
+
let(:expiry) { 3 * 60 * 60 }
|
19
|
+
let (:encryption_opts) do
|
20
|
+
{
|
21
|
+
cert: Default::X509_CERTIFICATE,
|
22
|
+
block_encryption: 'aes256-cbc',
|
23
|
+
key_transport: 'rsa-oaep-mgf1p',
|
24
|
+
}
|
25
|
+
end
|
26
|
+
let(:subject_encrypted) { described_class.new(reference_id,
|
27
|
+
response_id,
|
28
|
+
issuer_uri,
|
29
|
+
name_id,
|
30
|
+
audience_uri,
|
31
|
+
saml_request_id,
|
32
|
+
saml_acs_url,
|
33
|
+
algorithm,
|
34
|
+
authn_context_classref,
|
35
|
+
expiry,
|
36
|
+
encryption_opts
|
37
|
+
)
|
38
|
+
}
|
39
|
+
|
40
|
+
subject { described_class.new(reference_id,
|
41
|
+
response_id,
|
42
|
+
issuer_uri,
|
43
|
+
name_id,
|
44
|
+
audience_uri,
|
45
|
+
saml_request_id,
|
46
|
+
saml_acs_url,
|
47
|
+
algorithm,
|
48
|
+
authn_context_classref,
|
49
|
+
expiry
|
50
|
+
)
|
51
|
+
}
|
52
|
+
|
53
|
+
it "has a valid build" do
|
54
|
+
subject.build.should be_present
|
55
|
+
end
|
56
|
+
|
57
|
+
it "builds encrypted" do
|
58
|
+
subject_encrypted.build.should_not match(audience_uri)
|
59
|
+
encoded_xml = subject_encrypted.build
|
60
|
+
resp_settings = saml_settings(saml_acs_url)
|
61
|
+
resp_settings.private_key = Default::SECRET_KEY
|
62
|
+
resp_settings.issuer = audience_uri
|
63
|
+
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
|
64
|
+
saml_resp.soft = false
|
65
|
+
saml_resp.is_valid?.should == true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
module SamlIdp
|
3
|
+
describe ServiceProvider do
|
4
|
+
subject { described_class.new attributes }
|
5
|
+
let(:attributes) { {} }
|
6
|
+
|
7
|
+
it { should respond_to :fingerprint }
|
8
|
+
it { should respond_to :metadata_url }
|
9
|
+
it { should_not be_valid }
|
10
|
+
|
11
|
+
describe "with attributes" do
|
12
|
+
let(:attributes) { { fingerprint: fingerprint, metadata_url: metadata_url } }
|
13
|
+
let(:fingerprint) { Default::FINGERPRINT }
|
14
|
+
let(:metadata_url) { "http://localhost:3000/metadata" }
|
15
|
+
|
16
|
+
it "has a valid fingerprint" do
|
17
|
+
subject.fingerprint.should == fingerprint
|
18
|
+
end
|
19
|
+
|
20
|
+
it "has a valid metadata_url" do
|
21
|
+
subject.metadata_url.should == metadata_url
|
22
|
+
end
|
23
|
+
|
24
|
+
it { should be_valid }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
class MockSignable
|
4
|
+
include SamlIdp::Signable
|
5
|
+
|
6
|
+
def raw
|
7
|
+
builder = Builder::XmlMarkup.new
|
8
|
+
builder.body do |body|
|
9
|
+
sign body
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def reference_id
|
14
|
+
"abc"
|
15
|
+
end
|
16
|
+
|
17
|
+
def digest
|
18
|
+
algorithm.digest raw
|
19
|
+
end
|
20
|
+
|
21
|
+
def algorithm
|
22
|
+
OpenSSL::Digest::SHA1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module SamlIdp
|
27
|
+
describe MockSignable do
|
28
|
+
let(:signature_regex) { %r{<ds:Signature xmlns:ds=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#\">} }
|
29
|
+
let(:info_regex) { %r{<ds:SignedInfo xmlns:ds=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#\">} }
|
30
|
+
let(:canon) do
|
31
|
+
%r{<ds:CanonicalizationMethod Algorithm=\"http:\/\/www.w3.org\/2001\/10\/xml-exc-c14n#\"><\/ds:CanonicalizationMethod>}
|
32
|
+
end
|
33
|
+
let(:sig_method) do
|
34
|
+
%r{<ds:SignatureMethod Algorithm=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#rsa-sha1\"><\/ds:SignatureMethod>}
|
35
|
+
end
|
36
|
+
let(:reference) { %r{<ds:Reference URI=\"#_abc\">} }
|
37
|
+
let(:transforms) { %r{<ds:Transforms>} }
|
38
|
+
let(:enveloped) { %r{<ds:Transform Algorithm=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#enveloped-signature\"><\/ds:Transform>} }
|
39
|
+
let(:c14n) { %r{<ds:Transform Algorithm=\"http:\/\/www.w3.org\/2001\/10\/xml-exc-c14n#\"><\/ds:Transform>} }
|
40
|
+
let(:end_transforms) { %r{<\/ds:Transforms>} }
|
41
|
+
let(:digest_method) { %r{<ds:DigestMethod Algorithm=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#sha1\"><\/ds:DigestMethod>} }
|
42
|
+
let(:digest_value) { %r{<ds:DigestValue>\S+<\/ds:DigestValue>} }
|
43
|
+
let(:end_reference) { %r{<\/ds:Reference>} }
|
44
|
+
let(:end_info) { %r{<\/ds:SignedInfo>} }
|
45
|
+
let(:sig_val) { %r{<ds:SignatureValue>\S+<\/ds:SignatureValue>} }
|
46
|
+
let(:key_info) { %r{<KeyInfo xmlns=\"http:\/\/www.w3.org\/2000\/09\/xmldsig#\">} }
|
47
|
+
let(:x509) { %r{<ds:X509Data><ds:X509Certificate>\S+<\/ds:X509Certificate><\/ds:X509Data>} }
|
48
|
+
let(:end_rest) { %r{<\/KeyInfo><\/ds:Signature>} }
|
49
|
+
|
50
|
+
let(:all_regex) do
|
51
|
+
Regexp.new [
|
52
|
+
signature_regex,
|
53
|
+
info_regex,
|
54
|
+
canon,
|
55
|
+
sig_method,
|
56
|
+
reference,
|
57
|
+
transforms,
|
58
|
+
enveloped,
|
59
|
+
c14n,
|
60
|
+
end_transforms,
|
61
|
+
digest_method,
|
62
|
+
digest_value,
|
63
|
+
end_reference,
|
64
|
+
end_info,
|
65
|
+
sig_val,
|
66
|
+
key_info,
|
67
|
+
x509,
|
68
|
+
end_rest,
|
69
|
+
].map(&:to_s).join(".*")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "has a valid signed" do
|
73
|
+
subject.signed.should match all_regex
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|