saml_idp 0.5.0 → 0.6.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 +2 -4
- data/app/controllers/saml_idp/idp_controller.rb +7 -1
- data/lib/saml_idp/assertion_builder.rb +11 -8
- data/lib/saml_idp/controller.rb +19 -3
- data/lib/saml_idp/incoming_metadata.rb +1 -1
- data/lib/saml_idp/logout_builder.rb +18 -10
- data/lib/saml_idp/logout_request_builder.rb +1 -8
- data/lib/saml_idp/logout_response_builder.rb +5 -12
- data/lib/saml_idp/metadata_builder.rb +6 -5
- data/lib/saml_idp/request.rb +2 -1
- data/lib/saml_idp/response_builder.rb +10 -8
- data/lib/saml_idp/version.rb +1 -1
- data/spec/acceptance/idp_controller_spec.rb +0 -2
- data/spec/lib/saml_idp/assertion_builder_spec.rb +12 -2
- data/spec/lib/saml_idp/controller_spec.rb +49 -36
- data/spec/lib/saml_idp/metadata_builder_spec.rb +37 -0
- data/spec/lib/saml_idp/request_spec.rb +1 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +10 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +0 -2
- data/spec/spec_helper.rb +0 -1
- data/spec/support/saml_request_macros.rb +0 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33d61d6767a79ea9158672fd6ef93c08c70bf4e6
|
4
|
+
data.tar.gz: a2160be6f4da871e04811e6568695328c2e226e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3675637b4e2c170f3d2c14a9920e61584b95cdfbaf32d8e93492a45c59bd3df32d2c546e6b819b6712c8c039906f527facad0ecff511a7379b4167a2d28e1104
|
7
|
+
data.tar.gz: ba1f8db3b4f1e994b8b07ceb455c9bfc76de28b476ef727d6eecf59418d7df3c089a328fdac0c42dfbe94fcdeea27022a2999dad7341e25ad08cda60faf8cf88
|
data/README.md
CHANGED
@@ -42,9 +42,7 @@ match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
|
|
42
42
|
Create a controller that looks like this, customize to your own situation:
|
43
43
|
|
44
44
|
``` ruby
|
45
|
-
class SamlIdpController
|
46
|
-
include SamlIdp::IdpController
|
47
|
-
|
45
|
+
class SamlIdpController < SamlIdp::IdpController
|
48
46
|
def idp_authenticate(email, password) # not using params intentionally
|
49
47
|
user = User.by_email(email).first
|
50
48
|
user && user.valid_password?(password) ? user : nil
|
@@ -209,7 +207,7 @@ end
|
|
209
207
|
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
210
208
|
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
211
209
|
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
212
|
-
within the controller set the properties `x509_certificate` and `secret_key` using a `
|
210
|
+
within the controller set the properties `x509_certificate` and `secret_key` using a `prepend_before_action`
|
213
211
|
callback within the current request context or set them globally via the `SamlIdp.config.x509_certificate`
|
214
212
|
and `SamlIdp.config.secret_key` properties.
|
215
213
|
|
@@ -1,11 +1,17 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
|
2
3
|
module SamlIdp
|
3
4
|
class IdpController < ActionController::Base
|
4
5
|
include SamlIdp::Controller
|
5
6
|
|
6
7
|
unloadable unless Rails::VERSION::MAJOR >= 4
|
7
8
|
protect_from_forgery
|
8
|
-
|
9
|
+
|
10
|
+
if Rails::VERSION::MAJOR >= 4
|
11
|
+
before_action :validate_saml_request, only: [:new, :create]
|
12
|
+
else
|
13
|
+
before_filter :validate_saml_request, only: [:new, :create]
|
14
|
+
end
|
9
15
|
|
10
16
|
def new
|
11
17
|
render template: "saml_idp/idp/new"
|
@@ -42,9 +42,12 @@ module SamlIdp
|
|
42
42
|
assertion.Subject do |subject|
|
43
43
|
subject.NameID name_id, Format: name_id_format[:name]
|
44
44
|
subject.SubjectConfirmation Method: Saml::XML::Namespaces::Methods::BEARER do |confirmation|
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
confirmation_hash = {}
|
46
|
+
confirmation_hash[:InResponseTo] = saml_request_id unless saml_request_id.nil?
|
47
|
+
confirmation_hash[:NotOnOrAfter] = not_on_or_after_subject
|
48
|
+
confirmation_hash[:Recipient] = saml_acs_url
|
49
|
+
|
50
|
+
confirmation.SubjectConfirmationData "", confirmation_hash
|
48
51
|
end
|
49
52
|
end
|
50
53
|
assertion.Conditions NotBefore: not_before, NotOnOrAfter: not_on_or_after_condition do |conditions|
|
@@ -52,6 +55,11 @@ module SamlIdp
|
|
52
55
|
restriction.Audience audience_uri
|
53
56
|
end
|
54
57
|
end
|
58
|
+
assertion.AuthnStatement AuthnInstant: now_iso, SessionIndex: reference_string do |statement|
|
59
|
+
statement.AuthnContext do |context|
|
60
|
+
context.AuthnContextClassRef authn_context_classref
|
61
|
+
end
|
62
|
+
end
|
55
63
|
if asserted_attributes
|
56
64
|
assertion.AttributeStatement do |attr_statement|
|
57
65
|
asserted_attributes.each do |friendly_name, attrs|
|
@@ -67,11 +75,6 @@ module SamlIdp
|
|
67
75
|
end
|
68
76
|
end
|
69
77
|
end
|
70
|
-
assertion.AuthnStatement AuthnInstant: now_iso, SessionIndex: reference_string do |statement|
|
71
|
-
statement.AuthnContext do |context|
|
72
|
-
context.AuthnContextClassRef authn_context_classref
|
73
|
-
end
|
74
|
-
end
|
75
78
|
end
|
76
79
|
end
|
77
80
|
alias_method :raw, :fresh
|
data/lib/saml_idp/controller.rb
CHANGED
@@ -14,17 +14,32 @@ module SamlIdp
|
|
14
14
|
end
|
15
15
|
|
16
16
|
attr_accessor :algorithm
|
17
|
-
attr_accessor :saml_request
|
18
17
|
|
19
18
|
protected
|
20
19
|
|
20
|
+
def saml_request
|
21
|
+
@saml_request ||= Struct.new(:request_id) do
|
22
|
+
def authn_request?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def issuer
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def acs_url
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end.new(nil)
|
34
|
+
end
|
35
|
+
|
21
36
|
def validate_saml_request(raw_saml_request = params[:SAMLRequest])
|
22
37
|
decode_request(raw_saml_request)
|
23
38
|
render nothing: true, status: :forbidden unless valid_saml_request?
|
24
39
|
end
|
25
40
|
|
26
41
|
def decode_request(raw_saml_request)
|
27
|
-
|
42
|
+
@saml_request = Request.from_deflated_request(raw_saml_request)
|
28
43
|
end
|
29
44
|
|
30
45
|
def authn_context_classref
|
@@ -37,6 +52,7 @@ module SamlIdp
|
|
37
52
|
audience_uri = opts[:audience_uri] || saml_request.issuer || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
|
38
53
|
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
|
39
54
|
my_authn_context_classref = opts[:authn_context_classref] || authn_context_classref
|
55
|
+
acs_url = opts[:acs_url] || saml_acs_url
|
40
56
|
expiry = opts[:expiry] || 60*60
|
41
57
|
encryption_opts = opts[:encryption] || nil
|
42
58
|
|
@@ -47,7 +63,7 @@ module SamlIdp
|
|
47
63
|
principal,
|
48
64
|
audience_uri,
|
49
65
|
saml_request_id,
|
50
|
-
|
66
|
+
acs_url,
|
51
67
|
(opts[:algorithm] || algorithm || default_algorithm),
|
52
68
|
my_authn_context_classref,
|
53
69
|
expiry,
|
@@ -128,7 +128,7 @@ module SamlIdp
|
|
128
128
|
end
|
129
129
|
|
130
130
|
def contact_person_document
|
131
|
-
@contact_person_document ||= xpath("//md:ContactPerson", md: metadata_namespace).first
|
131
|
+
@contact_person_document ||= (xpath("//md:ContactPerson", md: metadata_namespace).first || Saml::XML::Document.new)
|
132
132
|
end
|
133
133
|
|
134
134
|
def metadata_namespace
|
@@ -3,35 +3,43 @@ module SamlIdp
|
|
3
3
|
class LogoutBuilder
|
4
4
|
include Signable
|
5
5
|
|
6
|
+
attr_accessor :response_id
|
7
|
+
attr_accessor :issuer_uri
|
8
|
+
attr_accessor :saml_slo_url
|
9
|
+
attr_accessor :algorithm
|
10
|
+
|
11
|
+
def initialize(response_id, issuer_uri, saml_slo_url, algorithm)
|
12
|
+
self.response_id = response_id
|
13
|
+
self.issuer_uri = issuer_uri
|
14
|
+
self.saml_slo_url = saml_slo_url
|
15
|
+
self.algorithm = algorithm
|
16
|
+
end
|
17
|
+
|
6
18
|
# this is an abstract base class.
|
7
19
|
def build
|
8
20
|
raise "#{self.class} must implement build method"
|
9
21
|
end
|
10
22
|
|
11
23
|
def reference_id
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def digest
|
16
|
-
algorithm.hexdigest raw
|
24
|
+
self.response_id
|
17
25
|
end
|
18
26
|
|
19
27
|
def encoded
|
20
28
|
@encoded ||= encode
|
21
|
-
end
|
29
|
+
end
|
22
30
|
|
23
|
-
def raw
|
31
|
+
def raw
|
24
32
|
build
|
25
|
-
end
|
33
|
+
end
|
26
34
|
|
27
35
|
def encode
|
28
36
|
Base64.strict_encode64(raw)
|
29
|
-
end
|
37
|
+
end
|
30
38
|
private :encode
|
31
39
|
|
32
40
|
def response_id_string
|
33
41
|
"_#{response_id}"
|
34
|
-
end
|
42
|
+
end
|
35
43
|
private :response_id_string
|
36
44
|
|
37
45
|
def now_iso
|
@@ -1,18 +1,11 @@
|
|
1
1
|
require 'saml_idp/logout_builder'
|
2
2
|
module SamlIdp
|
3
3
|
class LogoutRequestBuilder < LogoutBuilder
|
4
|
-
attr_accessor :response_id
|
5
|
-
attr_accessor :issuer_uri
|
6
|
-
attr_accessor :saml_slo_url
|
7
4
|
attr_accessor :name_id
|
8
|
-
attr_accessor :algorithm
|
9
5
|
|
10
6
|
def initialize(response_id, issuer_uri, saml_slo_url, name_id, algorithm)
|
11
|
-
|
12
|
-
self.issuer_uri = issuer_uri
|
13
|
-
self.saml_slo_url = saml_slo_url
|
7
|
+
super(response_id, issuer_uri, saml_slo_url, algorithm)
|
14
8
|
self.name_id = name_id
|
15
|
-
self.algorithm = algorithm
|
16
9
|
end
|
17
10
|
|
18
11
|
def build
|
@@ -1,19 +1,12 @@
|
|
1
1
|
require 'saml_idp/logout_builder'
|
2
2
|
module SamlIdp
|
3
3
|
class LogoutResponseBuilder < LogoutBuilder
|
4
|
-
attr_accessor :response_id
|
5
|
-
attr_accessor :issuer_uri
|
6
|
-
attr_accessor :saml_slo_url
|
7
4
|
attr_accessor :saml_request_id
|
8
|
-
attr_accessor :algorithm
|
9
5
|
|
10
6
|
def initialize(response_id, issuer_uri, saml_slo_url, saml_request_id, algorithm)
|
11
|
-
|
12
|
-
self.issuer_uri = issuer_uri
|
13
|
-
self.saml_slo_url = saml_slo_url
|
7
|
+
super(response_id, issuer_uri, saml_slo_url, algorithm)
|
14
8
|
self.saml_request_id = saml_request_id
|
15
|
-
|
16
|
-
end
|
9
|
+
end
|
17
10
|
|
18
11
|
def build
|
19
12
|
builder = Builder::XmlMarkup.new
|
@@ -26,9 +19,9 @@ module SamlIdp
|
|
26
19
|
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
27
20
|
sign response
|
28
21
|
response.Status xmlns: Saml::XML::Namespaces::PROTOCOL do |status|
|
29
|
-
status.StatusCode Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
30
|
-
end
|
31
|
-
end
|
22
|
+
status.StatusCode Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
23
|
+
end
|
24
|
+
end
|
32
25
|
end
|
33
26
|
private :build
|
34
27
|
end
|
@@ -24,11 +24,11 @@ module SamlIdp
|
|
24
24
|
|
25
25
|
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
26
26
|
build_key_descriptor descriptor
|
27
|
-
build_name_id_formats descriptor
|
28
27
|
descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
29
28
|
Location: single_logout_service_post_location
|
30
29
|
descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
31
30
|
Location: single_logout_service_redirect_location
|
31
|
+
build_name_id_formats descriptor
|
32
32
|
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
33
33
|
Location: single_service_post_location
|
34
34
|
build_attribute descriptor
|
@@ -94,10 +94,11 @@ module SamlIdp
|
|
94
94
|
|
95
95
|
def build_contact(el)
|
96
96
|
el.ContactPerson contactType: "technical" do |contact|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
contact.Company technical_contact.company if technical_contact.company
|
98
|
+
contact.GivenName technical_contact.given_name if technical_contact.given_name
|
99
|
+
contact.SurName technical_contact.sur_name if technical_contact.sur_name
|
100
|
+
contact.EmailAddress technical_contact.mail_to_string if technical_contact.mail_to_string
|
101
|
+
contact.TelephoneNumber technical_contact.telephone if technical_contact.telephone
|
101
102
|
end
|
102
103
|
end
|
103
104
|
private :build_contact
|
data/lib/saml_idp/request.rb
CHANGED
@@ -115,10 +115,11 @@ module SamlIdp
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def service_provider?
|
118
|
-
service_provider.valid?
|
118
|
+
service_provider && service_provider.valid?
|
119
119
|
end
|
120
120
|
|
121
121
|
def service_provider
|
122
|
+
return unless issuer.present?
|
122
123
|
@_service_provider ||= ServiceProvider.new((service_provider_finder[issuer] || {}).merge(identifier: issuer))
|
123
124
|
end
|
124
125
|
|
@@ -29,15 +29,17 @@ module SamlIdp
|
|
29
29
|
private :encode
|
30
30
|
|
31
31
|
def build
|
32
|
+
resp_options = {}
|
33
|
+
resp_options[:ID] = response_id_string
|
34
|
+
resp_options[:Version] = "2.0"
|
35
|
+
resp_options[:IssueInstant] = now_iso
|
36
|
+
resp_options[:Destination] = saml_acs_url
|
37
|
+
resp_options[:Consent] = Saml::XML::Namespaces::Consents::UNSPECIFIED
|
38
|
+
resp_options[:InResponseTo] = saml_request_id unless saml_request_id.nil?
|
39
|
+
resp_options["xmlns:samlp"] = Saml::XML::Namespaces::PROTOCOL
|
40
|
+
|
32
41
|
builder = Builder::XmlMarkup.new
|
33
|
-
builder.tag! "samlp:Response",
|
34
|
-
ID: response_id_string,
|
35
|
-
Version: "2.0",
|
36
|
-
IssueInstant: now_iso,
|
37
|
-
Destination: saml_acs_url,
|
38
|
-
Consent: Saml::XML::Namespaces::Consents::UNSPECIFIED,
|
39
|
-
InResponseTo: saml_request_id,
|
40
|
-
"xmlns:samlp" => Saml::XML::Namespaces::PROTOCOL do |response|
|
42
|
+
builder.tag! "samlp:Response", resp_options do |response|
|
41
43
|
response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
|
42
44
|
response.tag! "samlp:Status" do |status|
|
43
45
|
status.tag! "samlp:StatusCode", Value: Saml::XML::Namespaces::Statuses::SUCCESS
|
data/lib/saml_idp/version.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/acceptance_helper')
|
2
2
|
|
3
3
|
feature 'IdpController' do
|
4
|
-
|
5
4
|
scenario 'Login via default signup page' do
|
6
5
|
saml_request = make_saml_request("http://foo.example.com/saml/consume")
|
7
6
|
visit "/saml/auth?SAMLRequest=#{CGI.escape(saml_request)}"
|
@@ -12,5 +11,4 @@ feature 'IdpController' do
|
|
12
11
|
current_url.should == 'http://foo.example.com/saml/consume'
|
13
12
|
page.should have_content "foo@example.com"
|
14
13
|
end
|
15
|
-
|
16
14
|
end
|
@@ -31,9 +31,19 @@ module SamlIdp
|
|
31
31
|
expiry
|
32
32
|
) }
|
33
33
|
|
34
|
+
context "No Request ID" do
|
35
|
+
let(:saml_request_id) { nil }
|
36
|
+
|
37
|
+
it "builds a legit raw XML file" do
|
38
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
39
|
+
subject.raw.should == "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
34
44
|
it "builds a legit raw XML file" do
|
35
45
|
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
36
|
-
subject.raw.should == "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement
|
46
|
+
subject.raw.should == "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"email-address\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
|
37
47
|
end
|
38
48
|
end
|
39
49
|
|
@@ -71,7 +81,7 @@ module SamlIdp
|
|
71
81
|
expiry
|
72
82
|
)
|
73
83
|
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
74
|
-
builder.raw.should == "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement
|
84
|
+
builder.raw.should == "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>"
|
75
85
|
end
|
76
86
|
end
|
77
87
|
end
|
@@ -19,11 +19,6 @@ describe SamlIdp::Controller do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
context "SAML Responses" do
|
22
|
-
before(:each) do
|
23
|
-
params[:SAMLRequest] = make_saml_request
|
24
|
-
validate_saml_request
|
25
|
-
end
|
26
|
-
|
27
22
|
let(:principal) { double email_address: "foo@example.com" }
|
28
23
|
let (:encryption_opts) do
|
29
24
|
{
|
@@ -33,28 +28,24 @@ describe SamlIdp::Controller do
|
|
33
28
|
}
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
31
|
+
context "unsolicited Response" do
|
32
|
+
it "should create a SAML Response" do
|
33
|
+
saml_response = encode_response(principal, { audience_uri: 'http://example.com/issuer', issuer_uri: 'http://example.com', acs_url: 'https://foo.example.com/saml/consume' })
|
34
|
+
response = OneLogin::RubySaml::Response.new(saml_response)
|
35
|
+
response.name_id.should == "foo@example.com"
|
36
|
+
response.issuers.first.should == "http://example.com"
|
37
|
+
response.settings = saml_settings
|
38
|
+
response.is_valid?.should be_truthy
|
39
|
+
end
|
43
40
|
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
|
51
|
-
response.validate.should == true
|
52
|
-
response.issuer.should == "http://example.com"
|
53
|
-
end
|
42
|
+
context "solicited Response" do
|
43
|
+
before(:each) do
|
44
|
+
params[:SAMLRequest] = make_saml_request
|
45
|
+
validate_saml_request
|
46
|
+
end
|
54
47
|
|
55
|
-
|
56
|
-
it "should create a SAML Response using the #{algorithm_name} algorithm" do
|
57
|
-
self.algorithm = algorithm_name
|
48
|
+
it "should create a SAML Response" do
|
58
49
|
saml_response = encode_response(principal)
|
59
50
|
response = OneLogin::RubySaml::Response.new(saml_response)
|
60
51
|
response.name_id.should == "foo@example.com"
|
@@ -63,19 +54,41 @@ describe SamlIdp::Controller do
|
|
63
54
|
response.is_valid?.should be_truthy
|
64
55
|
end
|
65
56
|
|
66
|
-
it "should
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
response = OneLogin::RubySaml::
|
72
|
-
response.
|
73
|
-
response.
|
74
|
-
|
75
|
-
|
76
|
-
|
57
|
+
it "should create a SAML Logout Response" do
|
58
|
+
params[:SAMLRequest] = make_saml_logout_request
|
59
|
+
validate_saml_request
|
60
|
+
expect(saml_request.logout_request?).to eq true
|
61
|
+
saml_response = encode_response(principal)
|
62
|
+
response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
|
63
|
+
response.validate.should == true
|
64
|
+
response.issuer.should == "http://example.com"
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
[:sha1, :sha256, :sha384, :sha512].each do |algorithm_name|
|
69
|
+
it "should create a SAML Response using the #{algorithm_name} algorithm" do
|
70
|
+
self.algorithm = algorithm_name
|
71
|
+
saml_response = encode_response(principal)
|
72
|
+
response = OneLogin::RubySaml::Response.new(saml_response)
|
73
|
+
response.name_id.should == "foo@example.com"
|
74
|
+
response.issuers.first.should == "http://example.com"
|
75
|
+
response.settings = saml_settings
|
76
|
+
response.is_valid?.should be_truthy
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should encrypt SAML Response assertion" do
|
80
|
+
self.algorithm = algorithm_name
|
81
|
+
saml_response = encode_response(principal, encryption: encryption_opts)
|
82
|
+
resp_settings = saml_settings
|
83
|
+
resp_settings.private_key = SamlIdp::Default::SECRET_KEY
|
84
|
+
response = OneLogin::RubySaml::Response.new(saml_response, settings: resp_settings)
|
85
|
+
response.document.to_s.should_not match("foo@example.com")
|
86
|
+
response.decrypted_document.to_s.should match("foo@example.com")
|
87
|
+
response.name_id.should == "foo@example.com"
|
88
|
+
response.issuers.first.should == "http://example.com"
|
89
|
+
response.is_valid?.should be_truthy
|
90
|
+
end
|
77
91
|
end
|
78
92
|
end
|
79
93
|
end
|
80
|
-
|
81
94
|
end
|
@@ -15,6 +15,43 @@ module SamlIdp
|
|
15
15
|
'<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/logout"/>'
|
16
16
|
)
|
17
17
|
end
|
18
|
+
|
19
|
+
context "technical contact" do
|
20
|
+
before do
|
21
|
+
subject.configurator.technical_contact.company = nil
|
22
|
+
subject.configurator.technical_contact.given_name = nil
|
23
|
+
subject.configurator.technical_contact.sur_name = nil
|
24
|
+
subject.configurator.technical_contact.telephone = nil
|
25
|
+
subject.configurator.technical_contact.email_address = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "all fields" do
|
29
|
+
subject.configurator.technical_contact.company = "ACME Corporation"
|
30
|
+
subject.configurator.technical_contact.given_name = "Road"
|
31
|
+
subject.configurator.technical_contact.sur_name = "Runner"
|
32
|
+
subject.configurator.technical_contact.telephone = "1-800-555-5555"
|
33
|
+
subject.configurator.technical_contact.email_address = "acme@example.com"
|
34
|
+
|
35
|
+
subject.fresh.should match(
|
36
|
+
'<ContactPerson contactType="technical"><Company>ACME Corporation</Company><GivenName>Road</GivenName><SurName>Runner</SurName><EmailAddress>mailto:acme@example.com</EmailAddress><TelephoneNumber>1-800-555-5555</TelephoneNumber></ContactPerson>'
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "no fields" do
|
41
|
+
subject.fresh.should match(
|
42
|
+
'<ContactPerson contactType="technical"></ContactPerson>'
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "just email" do
|
47
|
+
subject.configurator.technical_contact.email_address = "acme@example.com"
|
48
|
+
subject.fresh.should match(
|
49
|
+
'<ContactPerson contactType="technical"><EmailAddress>mailto:acme@example.com</EmailAddress></ContactPerson>'
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
18
55
|
it "includes logout element as HTTP Redirect" do
|
19
56
|
subject.configurator.single_logout_service_redirect_location = 'https://example.com/saml/logout'
|
20
57
|
subject.fresh.should match(
|
@@ -28,5 +28,15 @@ module SamlIdp
|
|
28
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
29
|
end
|
30
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
|
31
41
|
end
|
32
42
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
class SamlIdpController < SamlIdp::IdpController
|
2
|
-
|
3
2
|
def idp_authenticate(email, password)
|
4
3
|
{ :email => email }
|
5
4
|
end
|
@@ -7,5 +6,4 @@ class SamlIdpController < SamlIdp::IdpController
|
|
7
6
|
def idp_make_saml_response(user)
|
8
7
|
encode_response(user[:email])
|
9
8
|
end
|
10
|
-
|
11
9
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'saml_idp/logout_request_builder'
|
2
2
|
|
3
3
|
module SamlRequestMacros
|
4
|
-
|
5
4
|
def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume")
|
6
5
|
auth_request = OneLogin::RubySaml::Authrequest.new
|
7
6
|
auth_url = auth_request.create(saml_settings(requested_saml_acs_url))
|
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.6.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: 2017-06-
|
11
|
+
date: 2017-06-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|