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