saml_idp 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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