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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93be89334cf44650af8cc55f114a3d873af44cf5
4
- data.tar.gz: 0802b704af21d4403221a1aa5087bde77592e104
3
+ metadata.gz: 33d61d6767a79ea9158672fd6ef93c08c70bf4e6
4
+ data.tar.gz: a2160be6f4da871e04811e6568695328c2e226e9
5
5
  SHA512:
6
- metadata.gz: d62633f5d8e338639b7dfa346ebbbcf5ed280382addb9e3db170effe19a87c5df797f5284268cd8b52d77c1b1f8408ed6ff40fc1c09225f83bb19f212095f3ea
7
- data.tar.gz: 21354ec9146654b28138a44dc92db871331c95f38d41e383edc945ded85530af7719456b55a0ca205250618ab1b8a8854c0310d6baf119b76a53be5bd1b6e350
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 `prepend_before_filter`
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
- before_filter :validate_saml_request, only: [:new, :create]
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
- confirmation.SubjectConfirmationData "", InResponseTo: saml_request_id,
46
- NotOnOrAfter: not_on_or_after_subject,
47
- Recipient: saml_acs_url
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
@@ -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
- self.saml_request = Request.from_deflated_request(raw_saml_request)
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
- saml_acs_url,
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
- UUID.generate
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
- self.response_id = response_id
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
- self.response_id = response_id
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
- self.algorithm = algorithm
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
- %w[company given_name sur_name telephone mail_to_string].each do |section|
98
- section_value = technical_contact.public_send(section)
99
- contact.Company section_value if section_value.present?
100
- end
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
@@ -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
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module SamlIdp
3
- VERSION = '0.5.0'
3
+ VERSION = '0.6.0'
4
4
  end
@@ -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><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>"
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><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion>"
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
- it "should create a SAML Response" do
37
- saml_response = encode_response(principal)
38
- response = OneLogin::RubySaml::Response.new(saml_response)
39
- response.name_id.should == "foo@example.com"
40
- response.issuers.first.should == "http://example.com"
41
- response.settings = saml_settings
42
- response.is_valid?.should be_truthy
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
- it "should create a SAML Logout Response" do
46
- params[:SAMLRequest] = make_saml_logout_request
47
- validate_saml_request
48
- expect(saml_request.logout_request?).to eq true
49
- saml_response = encode_response(principal)
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
- [:sha1, :sha256, :sha384, :sha512].each do |algorithm_name|
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 encrypt SAML Response assertion" do
67
- self.algorithm = algorithm_name
68
- saml_response = encode_response(principal, encryption: encryption_opts)
69
- resp_settings = saml_settings
70
- resp_settings.private_key = SamlIdp::Default::SECRET_KEY
71
- response = OneLogin::RubySaml::Response.new(saml_response, settings: resp_settings)
72
- response.document.to_s.should_not match("foo@example.com")
73
- response.decrypted_document.to_s.should match("foo@example.com")
74
- response.name_id.should == "foo@example.com"
75
- response.issuers.first.should == "http://example.com"
76
- response.is_valid?.should be_truthy
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(
@@ -66,6 +66,7 @@ module SamlIdp
66
66
  authn_request = described_class.new raw_req
67
67
  authn_request.issuer.should_not == ''
68
68
  authn_request.issuer.should == nil
69
+ authn_request.valid?.should == false
69
70
  end
70
71
  end
71
72
 
@@ -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
@@ -46,4 +46,3 @@ RSpec.configure do |config|
46
46
  end
47
47
 
48
48
  Capybara.default_host = "https://app.example.com"
49
-
@@ -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.5.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-21 00:00:00.000000000 Z
11
+ date: 2017-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport