saml_idp 0.2.1 → 0.3.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.
- data/README.md +16 -2
- data/app/controllers/saml_idp/idp_controller.rb +11 -0
- data/lib/saml_idp.rb +2 -2
- data/lib/saml_idp/assertion_builder.rb +11 -1
- data/lib/saml_idp/configurator.rb +1 -0
- data/lib/saml_idp/controller.rb +39 -4
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +36 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +3 -0
- data/lib/saml_idp/request.rb +94 -10
- data/lib/saml_idp/response_builder.rb +1 -1
- data/lib/saml_idp/saml_response.rb +12 -4
- data/lib/saml_idp/service_provider.rb +7 -2
- data/lib/saml_idp/signable.rb +1 -1
- data/lib/saml_idp/signed_info_builder.rb +1 -1
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +15 -2
- data/saml_idp.gemspec +7 -1
- data/spec/lib/saml_idp/assertion_builder_spec.rb +24 -0
- data/spec/lib/saml_idp/configurator_spec.rb +1 -0
- data/spec/lib/saml_idp/controller_spec.rb +32 -2
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +43 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +7 -0
- data/spec/lib/saml_idp/request_spec.rb +77 -19
- data/spec/lib/saml_idp/saml_response_spec.rb +32 -0
- data/spec/support/saml_request_macros.rb +20 -0
- data/spec/xml_security_spec.rb +2 -1
- metadata +40 -6
@@ -15,6 +15,7 @@ module SamlIdp
|
|
15
15
|
attr_accessor :x509_certificate
|
16
16
|
attr_accessor :authn_context_classref
|
17
17
|
attr_accessor :expiry
|
18
|
+
attr_accessor :encryption_opts
|
18
19
|
|
19
20
|
def initialize(reference_id,
|
20
21
|
response_id,
|
@@ -25,7 +26,8 @@ module SamlIdp
|
|
25
26
|
saml_acs_url,
|
26
27
|
algorithm,
|
27
28
|
authn_context_classref,
|
28
|
-
expiry=60*60
|
29
|
+
expiry=60*60,
|
30
|
+
encryption_opts=nil
|
29
31
|
)
|
30
32
|
self.reference_id = reference_id
|
31
33
|
self.response_id = response_id
|
@@ -39,6 +41,7 @@ module SamlIdp
|
|
39
41
|
self.x509_certificate = x509_certificate
|
40
42
|
self.authn_context_classref = authn_context_classref
|
41
43
|
self.expiry = expiry
|
44
|
+
self.encryption_opts = encryption_opts
|
42
45
|
end
|
43
46
|
|
44
47
|
def build
|
@@ -46,9 +49,13 @@ module SamlIdp
|
|
46
49
|
end
|
47
50
|
|
48
51
|
def signed_assertion
|
49
|
-
|
52
|
+
if encryption_opts
|
53
|
+
assertion_builder.encrypt(sign: true)
|
54
|
+
else
|
55
|
+
assertion_builder.signed
|
56
|
+
end
|
50
57
|
end
|
51
|
-
private
|
58
|
+
private :signed_assertion
|
52
59
|
|
53
60
|
def response_builder
|
54
61
|
ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
|
@@ -64,7 +71,8 @@ module SamlIdp
|
|
64
71
|
saml_acs_url,
|
65
72
|
algorithm,
|
66
73
|
authn_context_classref,
|
67
|
-
expiry
|
74
|
+
expiry,
|
75
|
+
encryption_opts
|
68
76
|
end
|
69
77
|
private :assertion_builder
|
70
78
|
end
|
@@ -6,10 +6,12 @@ module SamlIdp
|
|
6
6
|
class ServiceProvider
|
7
7
|
include Attributeable
|
8
8
|
attribute :identifier
|
9
|
+
attribute :cert
|
9
10
|
attribute :fingerprint
|
10
11
|
attribute :metadata_url
|
11
12
|
attribute :validate_signature
|
12
13
|
attribute :acs_url
|
14
|
+
attribute :assertion_consumer_logout_service_url
|
13
15
|
|
14
16
|
delegate :config, to: :SamlIdp
|
15
17
|
|
@@ -17,9 +19,12 @@ module SamlIdp
|
|
17
19
|
attributes.present?
|
18
20
|
end
|
19
21
|
|
20
|
-
def valid_signature?(doc)
|
21
|
-
|
22
|
+
def valid_signature?(doc, require_signature = false)
|
23
|
+
if require_signature || should_validate_signature?
|
22
24
|
doc.valid_signature?(fingerprint)
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
def should_validate_signature?
|
data/lib/saml_idp/signable.rb
CHANGED
data/lib/saml_idp/version.rb
CHANGED
@@ -52,15 +52,28 @@ module SamlIdp
|
|
52
52
|
cert = OpenSSL::X509::Certificate.new(cert_text)
|
53
53
|
|
54
54
|
# check cert matches registered idp cert
|
55
|
-
fingerprint =
|
55
|
+
fingerprint = fingerprint_cert(cert)
|
56
|
+
sha1_fingerprint = fingerprint_cert_sha1(cert)
|
57
|
+
plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
|
56
58
|
|
57
|
-
if fingerprint !=
|
59
|
+
if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
|
58
60
|
return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
|
59
61
|
end
|
60
62
|
|
61
63
|
validate_doc(base64_cert, soft)
|
62
64
|
end
|
63
65
|
|
66
|
+
def fingerprint_cert(cert)
|
67
|
+
# pick algorithm based on the doc's digest algorithm
|
68
|
+
ref_elem = REXML::XPath.first(self, "//ds:Reference", {"ds"=>DSIG})
|
69
|
+
digest_algorithm = algorithm(REXML::XPath.first(ref_elem, "//ds:DigestMethod"))
|
70
|
+
digest_algorithm.hexdigest(cert.to_der)
|
71
|
+
end
|
72
|
+
|
73
|
+
def fingerprint_cert_sha1(cert)
|
74
|
+
OpenSSL::Digest::SHA1.hexdigest(cert.to_der)
|
75
|
+
end
|
76
|
+
|
64
77
|
def validate_doc(base64_cert, soft = true)
|
65
78
|
# validate references
|
66
79
|
|
data/saml_idp.gemspec
CHANGED
@@ -34,6 +34,11 @@ If you just need to see the certificate `bundle open saml_idp` and go to
|
|
34
34
|
|
35
35
|
Similarly, please see the README about certificates - you should avoid using the
|
36
36
|
defaults in a Production environment. Post any issues you to github.
|
37
|
+
|
38
|
+
** New in Version 0.3.0 **
|
39
|
+
|
40
|
+
Encrypted Assertions require the xmlenc gem. See the example in the Controller
|
41
|
+
section of the README.
|
37
42
|
INST
|
38
43
|
|
39
44
|
s.add_dependency('activesupport')
|
@@ -45,9 +50,10 @@ defaults in a Production environment. Post any issues you to github.
|
|
45
50
|
s.add_development_dependency "rake"
|
46
51
|
s.add_development_dependency "simplecov"
|
47
52
|
s.add_development_dependency "rspec", "~> 2.5"
|
48
|
-
s.add_development_dependency "ruby-saml", "~>
|
53
|
+
s.add_development_dependency "ruby-saml", "~> 1.2"
|
49
54
|
s.add_development_dependency("rails", "~> 3.2")
|
50
55
|
s.add_development_dependency("capybara")
|
51
56
|
s.add_development_dependency("timecop")
|
57
|
+
s.add_development_dependency("xmlenc", ">= 0.6.4")
|
52
58
|
end
|
53
59
|
|
@@ -12,6 +12,13 @@ module SamlIdp
|
|
12
12
|
Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD
|
13
13
|
}
|
14
14
|
let(:expiry) { 3*60*60 }
|
15
|
+
let (:encryption_opts) do
|
16
|
+
{
|
17
|
+
cert: Default::X509_CERTIFICATE,
|
18
|
+
block_encryption: 'aes256-cbc',
|
19
|
+
key_transport: 'rsa-oaep-mgf1p',
|
20
|
+
}
|
21
|
+
end
|
15
22
|
subject { described_class.new(
|
16
23
|
reference_id,
|
17
24
|
issuer_uri,
|
@@ -29,5 +36,22 @@ module SamlIdp
|
|
29
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>"
|
30
37
|
end
|
31
38
|
end
|
39
|
+
|
40
|
+
it "builds encrypted XML" do
|
41
|
+
builder = described_class.new(
|
42
|
+
reference_id,
|
43
|
+
issuer_uri,
|
44
|
+
name_id,
|
45
|
+
audience_uri,
|
46
|
+
saml_request_id,
|
47
|
+
saml_acs_url,
|
48
|
+
algorithm,
|
49
|
+
authn_context_classref,
|
50
|
+
expiry,
|
51
|
+
encryption_opts
|
52
|
+
)
|
53
|
+
encrypted_xml = builder.encrypt
|
54
|
+
encrypted_xml.should_not match(audience_uri)
|
55
|
+
end
|
32
56
|
end
|
33
57
|
end
|
@@ -10,6 +10,7 @@ module SamlIdp
|
|
10
10
|
it { should respond_to :reference_id_generator }
|
11
11
|
it { should respond_to :attribute_service_location }
|
12
12
|
it { should respond_to :single_service_post_location }
|
13
|
+
it { should respond_to :single_logout_service_post_location }
|
13
14
|
it { should respond_to :name_id }
|
14
15
|
it { should respond_to :attributes }
|
15
16
|
it { should respond_to :service_provider }
|
@@ -25,26 +25,56 @@ describe SamlIdp::Controller do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
let(:principal) { double email_address: "foo@example.com" }
|
28
|
+
let (:encryption_opts) do
|
29
|
+
{
|
30
|
+
cert: SamlIdp::Default::X509_CERTIFICATE,
|
31
|
+
block_encryption: 'aes256-cbc',
|
32
|
+
key_transport: 'rsa-oaep-mgf1p',
|
33
|
+
}
|
34
|
+
end
|
28
35
|
|
29
36
|
it "should create a SAML Response" do
|
30
37
|
saml_response = encode_response(principal)
|
31
38
|
response = OneLogin::RubySaml::Response.new(saml_response)
|
32
39
|
response.name_id.should == "foo@example.com"
|
33
|
-
response.
|
40
|
+
response.issuers.first.should == "http://example.com"
|
34
41
|
response.settings = saml_settings
|
35
42
|
response.is_valid?.should be_truthy
|
36
43
|
end
|
37
44
|
|
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
|
54
|
+
|
38
55
|
[:sha1, :sha256, :sha384, :sha512].each do |algorithm_name|
|
39
56
|
it "should create a SAML Response using the #{algorithm_name} algorithm" do
|
40
57
|
self.algorithm = algorithm_name
|
41
58
|
saml_response = encode_response(principal)
|
42
59
|
response = OneLogin::RubySaml::Response.new(saml_response)
|
43
60
|
response.name_id.should == "foo@example.com"
|
44
|
-
response.
|
61
|
+
response.issuers.first.should == "http://example.com"
|
45
62
|
response.settings = saml_settings
|
46
63
|
response.is_valid?.should be_truthy
|
47
64
|
end
|
65
|
+
|
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
|
77
|
+
end
|
48
78
|
end
|
49
79
|
end
|
50
80
|
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'saml_idp/encryptor'
|
4
|
+
|
5
|
+
module SamlIdp
|
6
|
+
describe Encryptor do
|
7
|
+
let (:encryption_opts) do
|
8
|
+
{
|
9
|
+
cert: Default::X509_CERTIFICATE,
|
10
|
+
block_encryption: 'aes256-cbc',
|
11
|
+
key_transport: 'rsa-oaep-mgf1p',
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { described_class.new encryption_opts }
|
16
|
+
|
17
|
+
it "encrypts XML" do
|
18
|
+
raw_xml = '<foo>bar</foo>'
|
19
|
+
encrypted_xml = subject.encrypt(raw_xml)
|
20
|
+
encrypted_xml.should_not match 'bar'
|
21
|
+
encrypted_doc = Nokogiri::XML::Document.parse(encrypted_xml)
|
22
|
+
encrypted_data = Xmlenc::EncryptedData.new(encrypted_doc.at_xpath('//xenc:EncryptedData', Xmlenc::NAMESPACES))
|
23
|
+
decrypted_xml = encrypted_data.decrypt(subject.encryption_key)
|
24
|
+
decrypted_xml.should == raw_xml
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'saml_idp/logout_request_builder'
|
3
|
+
|
4
|
+
module SamlIdp
|
5
|
+
describe LogoutRequestBuilder do
|
6
|
+
before do
|
7
|
+
Timecop.freeze(Time.local(1990))
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Timecop.return
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:response_id) { 'some_response_id' }
|
15
|
+
let(:issuer_uri) { 'http://example.com' }
|
16
|
+
let(:saml_slo_url) { 'http://localhost:3000/saml/logout' }
|
17
|
+
let(:name_id) { 'some_name_id' }
|
18
|
+
let(:session_index) { 'abc123index' }
|
19
|
+
let(:algorithm) { OpenSSL::Digest::SHA256 }
|
20
|
+
|
21
|
+
subject do
|
22
|
+
described_class.new(
|
23
|
+
response_id,
|
24
|
+
issuer_uri,
|
25
|
+
saml_slo_url,
|
26
|
+
name_id,
|
27
|
+
session_index,
|
28
|
+
algorithm
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is a valid SloLogoutrequest" do
|
33
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
34
|
+
slo_request = OneLogin::RubySaml::SloLogoutrequest.new(
|
35
|
+
subject.encoded,
|
36
|
+
settings: saml_settings('localhost:3000')
|
37
|
+
)
|
38
|
+
slo_request.soft = false
|
39
|
+
expect(slo_request.is_valid?).to eq true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'saml_idp/logout_response_builder'
|
3
|
+
|
4
|
+
module SamlIdp
|
5
|
+
describe LogoutResponseBuilder do
|
6
|
+
before do
|
7
|
+
Timecop.freeze(Time.local(1990))
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
Timecop.return
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:response_id) { 'some_response_id' }
|
15
|
+
let(:issuer_uri) { 'http://example.com' }
|
16
|
+
let(:saml_slo_url) { 'http://localhost:3000/saml/logout' }
|
17
|
+
let(:request_id) { 'some_request_id' }
|
18
|
+
let(:algorithm) { OpenSSL::Digest::SHA256 }
|
19
|
+
|
20
|
+
subject do
|
21
|
+
described_class.new(
|
22
|
+
response_id,
|
23
|
+
issuer_uri,
|
24
|
+
saml_slo_url,
|
25
|
+
request_id,
|
26
|
+
algorithm
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is a valid LogoutResponse" do
|
31
|
+
Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
|
32
|
+
logout_response = OneLogin::RubySaml::Logoutresponse.new(
|
33
|
+
subject.encoded,
|
34
|
+
saml_settings('localhost:3000')
|
35
|
+
)
|
36
|
+
logout_response.soft = false
|
37
|
+
expect(logout_response.validate).to eq true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -8,5 +8,12 @@ module SamlIdp
|
|
8
8
|
it "signs valid xml" do
|
9
9
|
Saml::XML::Document.parse(subject.signed).valid_signature?(Default::FINGERPRINT).should be_truthy
|
10
10
|
end
|
11
|
+
|
12
|
+
it "includes logout element" do
|
13
|
+
subject.configurator.single_logout_service_post_location = 'https://example.com/saml/logout'
|
14
|
+
subject.fresh.should match(
|
15
|
+
'<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/logout"/>'
|
16
|
+
)
|
17
|
+
end
|
11
18
|
end
|
12
19
|
end
|
@@ -1,32 +1,90 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
module SamlIdp
|
3
3
|
describe Request do
|
4
|
-
|
5
|
-
|
4
|
+
describe "authn request" do
|
5
|
+
let(:raw_authn_request) { "<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>localhost:3000</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>" }
|
6
|
+
subject { described_class.new raw_authn_request }
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
it "has a valid request_id" do
|
9
|
+
subject.request_id.should == "_af43d1a0-e111-0130-661a-3c0754403fdb"
|
10
|
+
end
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
it "has a valid acs_url" do
|
13
|
+
subject.acs_url.should == "http://localhost:3000/saml/consume"
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
it "has a valid service_provider" do
|
17
|
+
subject.service_provider.should be_a ServiceProvider
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
it "has a valid service_provider" do
|
21
|
+
subject.service_provider.should be_truthy
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
it "has a valid issuer" do
|
25
|
+
subject.issuer.should == "localhost:3000"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "has a valid valid_signature" do
|
29
|
+
subject.valid_signature?.should be_truthy
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return acs_url for response_url" do
|
33
|
+
subject.response_url.should == subject.acs_url
|
34
|
+
end
|
35
|
+
|
36
|
+
it "is a authn request" do
|
37
|
+
subject.authn_request?.should == true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "fetches internal request" do
|
41
|
+
subject.request['ID'].should == subject.request_id
|
42
|
+
end
|
43
|
+
|
44
|
+
it "has a valid authn context" do
|
45
|
+
subject.requested_authn_context.should == "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
46
|
+
end
|
26
47
|
|
27
|
-
|
28
|
-
|
48
|
+
it "does not permit empty issuer" do
|
49
|
+
raw_req = raw_authn_request.gsub('localhost:3000', '')
|
50
|
+
authn_request = described_class.new raw_req
|
51
|
+
authn_request.issuer.should_not == ''
|
52
|
+
authn_request.issuer.should == nil
|
53
|
+
end
|
29
54
|
end
|
30
55
|
|
56
|
+
describe "logout request" do
|
57
|
+
let(:raw_logout_request) { "<LogoutRequest ID='_some_response_id' Version='2.0' IssueInstant='2010-06-01T13:00:00Z' Destination='http://localhost:3000/saml/logout' xmlns='urn:oasis:names:tc:SAML:2.0:protocol'><Issuer xmlns='urn:oasis:names:tc:SAML:2.0:assertion'>http://example.com</Issuer><NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' Format='urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'>some_name_id</NameID><SessionIndex>abc123index</SessionIndex></LogoutRequest>" }
|
58
|
+
|
59
|
+
subject { described_class.new raw_logout_request }
|
60
|
+
|
61
|
+
it "has a valid request_id" do
|
62
|
+
subject.request_id.should == '_some_response_id'
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should be flagged as a logout_request" do
|
66
|
+
subject.logout_request?.should == true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have a valid name_id" do
|
70
|
+
subject.name_id.should == 'some_name_id'
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have a session index" do
|
74
|
+
subject.session_index.should == 'abc123index'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should have a valid issuer" do
|
78
|
+
subject.issuer.should == 'http://example.com'
|
79
|
+
end
|
80
|
+
|
81
|
+
it "fetches internal request" do
|
82
|
+
subject.request['ID'].should == subject.request_id
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return logout_url for response_url" do
|
86
|
+
subject.response_url.should == subject.logout_url
|
87
|
+
end
|
88
|
+
end
|
31
89
|
end
|
32
90
|
end
|