saml_idp 0.8.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +49 -47
- data/lib/saml_idp/assertion_builder.rb +28 -3
- data/lib/saml_idp/configurator.rb +6 -1
- data/lib/saml_idp/controller.rb +19 -11
- data/lib/saml_idp/encryptor.rb +0 -1
- data/lib/saml_idp/fingerprint.rb +19 -0
- data/lib/saml_idp/incoming_metadata.rb +18 -0
- data/lib/saml_idp/metadata_builder.rb +23 -8
- data/lib/saml_idp/persisted_metadata.rb +4 -0
- data/lib/saml_idp/request.rb +13 -6
- data/lib/saml_idp/response_builder.rb +26 -6
- data/lib/saml_idp/saml_response.rb +62 -28
- data/lib/saml_idp/service_provider.rb +1 -6
- data/lib/saml_idp/signable.rb +1 -2
- data/lib/saml_idp/version.rb +1 -1
- data/lib/saml_idp/xml_security.rb +1 -1
- data/lib/saml_idp.rb +2 -1
- data/saml_idp.gemspec +31 -31
- data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
- data/spec/lib/saml_idp/configurator_spec.rb +2 -0
- data/spec/lib/saml_idp/controller_spec.rb +24 -0
- data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
- data/spec/lib/saml_idp/incoming_metadata_spec.rb +20 -1
- data/spec/lib/saml_idp/metadata_builder_spec.rb +23 -0
- data/spec/lib/saml_idp/request_spec.rb +43 -9
- data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
- data/spec/lib/saml_idp/saml_response_spec.rb +122 -7
- data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
- data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
- data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
- data/spec/rails_app/config/application.rb +1 -0
- data/spec/rails_app/config/boot.rb +1 -1
- data/spec/rails_app/config/environments/development.rb +2 -0
- data/spec/spec_helper.rb +20 -1
- data/spec/support/certificates/sp_cert_req.csr +12 -0
- data/spec/support/certificates/sp_private_key.pem +16 -0
- data/spec/support/certificates/sp_x509_cert.crt +18 -0
- data/spec/support/saml_request_macros.rb +62 -3
- data/spec/support/security_helpers.rb +10 -0
- metadata +83 -61
- data/app/controllers/saml_idp/idp_controller.rb +0 -59
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
module SamlIdp
|
3
3
|
describe Request do
|
4
|
-
let(:
|
4
|
+
let(:issuer) { 'localhost:3000' }
|
5
|
+
let(:raw_authn_request) do
|
6
|
+
"<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'>#{issuer}</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>"
|
7
|
+
end
|
5
8
|
|
6
9
|
describe "deflated request" do
|
7
10
|
let(:deflated_request) { Base64.encode64(Zlib::Deflate.deflate(raw_authn_request, 9)[2..-5]) }
|
@@ -57,16 +60,47 @@ module SamlIdp
|
|
57
60
|
expect(subject.request['ID']).to eq(subject.request_id)
|
58
61
|
end
|
59
62
|
|
60
|
-
it
|
61
|
-
expect(subject.requested_authn_context).to eq(
|
63
|
+
it 'has a valid authn context' do
|
64
|
+
expect(subject.requested_authn_context).to eq('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
|
62
65
|
end
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
context 'the issuer is empty' do
|
68
|
+
let(:issuer) { nil }
|
69
|
+
let(:logger) { ->(msg) { puts msg } }
|
70
|
+
|
71
|
+
before do
|
72
|
+
allow(SamlIdp.config).to receive(:logger).and_return(logger)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'is invalid' do
|
76
|
+
expect(subject.issuer).to_not eq('')
|
77
|
+
expect(subject.issuer).to be_nil
|
78
|
+
expect(subject.valid?).to eq(false)
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'a Ruby Logger is configured' do
|
82
|
+
let(:logger) { Logger.new($stdout) }
|
83
|
+
|
84
|
+
before do
|
85
|
+
allow(logger).to receive(:info)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'logs an error message' do
|
89
|
+
expect(subject.valid?).to be false
|
90
|
+
expect(logger).to have_received(:info).with('Unable to find service provider for issuer ')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'a logger lambda is configured' do
|
95
|
+
let(:logger) { double }
|
96
|
+
|
97
|
+
before { allow(logger).to receive(:call) }
|
98
|
+
|
99
|
+
it 'logs an error message' do
|
100
|
+
expect(subject.valid?).to be false
|
101
|
+
expect(logger).to have_received(:call).with('Unable to find service provider for issuer ')
|
102
|
+
end
|
103
|
+
end
|
70
104
|
end
|
71
105
|
end
|
72
106
|
|
@@ -6,12 +6,14 @@ module SamlIdp
|
|
6
6
|
let(:saml_acs_url) { "http://sportngin.com" }
|
7
7
|
let(:saml_request_id) { "134" }
|
8
8
|
let(:assertion_and_signature) { "<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>" }
|
9
|
+
let(:algorithm) { :sha256 }
|
9
10
|
subject { described_class.new(
|
10
11
|
response_id,
|
11
12
|
issuer_uri,
|
12
13
|
saml_acs_url,
|
13
14
|
saml_request_id,
|
14
|
-
assertion_and_signature
|
15
|
+
assertion_and_signature,
|
16
|
+
algorithm
|
15
17
|
) }
|
16
18
|
|
17
19
|
before do
|
@@ -24,6 +24,10 @@ module SamlIdp
|
|
24
24
|
key_transport: 'rsa-oaep-mgf1p',
|
25
25
|
}
|
26
26
|
end
|
27
|
+
let(:signed_response_opts) { true }
|
28
|
+
let(:unsigned_response_opts) { false }
|
29
|
+
let(:signed_assertion_opts) { true }
|
30
|
+
let(:compress_opts) { false }
|
27
31
|
let(:subject_encrypted) { described_class.new(reference_id,
|
28
32
|
response_id,
|
29
33
|
issuer_uri,
|
@@ -35,7 +39,12 @@ module SamlIdp
|
|
35
39
|
authn_context_classref,
|
36
40
|
expiry,
|
37
41
|
encryption_opts,
|
38
|
-
session_expiry
|
42
|
+
session_expiry,
|
43
|
+
nil,
|
44
|
+
nil,
|
45
|
+
unsigned_response_opts,
|
46
|
+
signed_assertion_opts,
|
47
|
+
compress_opts
|
39
48
|
)
|
40
49
|
}
|
41
50
|
|
@@ -50,7 +59,12 @@ module SamlIdp
|
|
50
59
|
authn_context_classref,
|
51
60
|
expiry,
|
52
61
|
nil,
|
53
|
-
session_expiry
|
62
|
+
session_expiry,
|
63
|
+
nil,
|
64
|
+
nil,
|
65
|
+
signed_response_opts,
|
66
|
+
signed_assertion_opts,
|
67
|
+
compress_opts
|
54
68
|
)
|
55
69
|
}
|
56
70
|
|
@@ -66,14 +80,115 @@ module SamlIdp
|
|
66
80
|
expect(subject.build).to be_present
|
67
81
|
end
|
68
82
|
|
69
|
-
|
70
|
-
|
71
|
-
|
83
|
+
context "encrypted" do
|
84
|
+
it "builds encrypted" do
|
85
|
+
expect(subject_encrypted.build).to_not match(audience_uri)
|
86
|
+
encoded_xml = subject_encrypted.build
|
87
|
+
resp_settings = saml_settings(saml_acs_url)
|
88
|
+
resp_settings.private_key = Default::SECRET_KEY
|
89
|
+
resp_settings.issuer = audience_uri
|
90
|
+
saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
|
91
|
+
saml_resp.soft = false
|
92
|
+
expect(saml_resp.is_valid?).to eq(true)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "signed response" do
|
97
|
+
let(:resp_settings) do
|
98
|
+
resp_settings = saml_settings(saml_acs_url)
|
99
|
+
resp_settings.private_key = Default::SECRET_KEY
|
100
|
+
resp_settings.issuer = audience_uri
|
101
|
+
resp_settings
|
102
|
+
end
|
103
|
+
|
104
|
+
it "will build signed valid response" do
|
105
|
+
expect { subject.build }.not_to raise_error
|
106
|
+
signed_encoded_xml = subject.build
|
107
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
108
|
+
expect(
|
109
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
110
|
+
"//p:Response//ds:Signature",
|
111
|
+
{
|
112
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
113
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
114
|
+
}
|
115
|
+
)).to be_present
|
116
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
117
|
+
expect(saml_resp.is_valid?).to eq(true)
|
118
|
+
end
|
119
|
+
|
120
|
+
context "when signed_assertion_opts is true" do
|
121
|
+
it "builds a signed assertion" do
|
122
|
+
expect { subject.build }.not_to raise_error
|
123
|
+
signed_encoded_xml = subject.build
|
124
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
125
|
+
expect(
|
126
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
127
|
+
"//p:Response//a:Assertion//ds:Signature",
|
128
|
+
{
|
129
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
130
|
+
"a" => "urn:oasis:names:tc:SAML:2.0:assertion",
|
131
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
132
|
+
}
|
133
|
+
)).to be_present
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "when signed_assertion_opts is false" do
|
138
|
+
let(:signed_assertion_opts) { false }
|
139
|
+
|
140
|
+
it "builds a raw assertion" do
|
141
|
+
expect { subject.build }.not_to raise_error
|
142
|
+
signed_encoded_xml = subject.build
|
143
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
144
|
+
expect(
|
145
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
146
|
+
"//p:Response//a:Assertion",
|
147
|
+
{
|
148
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
149
|
+
"a" => "urn:oasis:names:tc:SAML:2.0:assertion"
|
150
|
+
}
|
151
|
+
)).to be_present
|
152
|
+
|
153
|
+
expect(
|
154
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
155
|
+
"//p:Response//Assertion//ds:Signature",
|
156
|
+
{
|
157
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
158
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
159
|
+
}
|
160
|
+
)).to_not be_present
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context "when compress opts is true" do
|
165
|
+
let(:compress_opts) { true }
|
166
|
+
it "will build a compressed valid response" do
|
167
|
+
expect { subject.build }.not_to raise_error
|
168
|
+
compressed_signed_encoded_xml = subject.build
|
169
|
+
saml_resp = OneLogin::RubySaml::Response.new(compressed_signed_encoded_xml, settings: resp_settings)
|
170
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
171
|
+
expect(saml_resp.is_valid?).to eq(true)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it "will build signed valid response" do
|
177
|
+
expect { subject.build }.not_to raise_error
|
178
|
+
signed_encoded_xml = subject.build
|
72
179
|
resp_settings = saml_settings(saml_acs_url)
|
73
180
|
resp_settings.private_key = Default::SECRET_KEY
|
74
181
|
resp_settings.issuer = audience_uri
|
75
|
-
saml_resp = OneLogin::RubySaml::Response.new(
|
76
|
-
|
182
|
+
saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
|
183
|
+
expect(
|
184
|
+
Nokogiri::XML(saml_resp.response).at_xpath(
|
185
|
+
"//p:Response//ds:Signature",
|
186
|
+
{
|
187
|
+
"p" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
188
|
+
"ds" => "http://www.w3.org/2000/09/xmldsig#"
|
189
|
+
}
|
190
|
+
)).to be_present
|
191
|
+
expect(saml_resp.send(:validate_signature)).to eq(true)
|
77
192
|
expect(saml_resp.is_valid?).to eq(true)
|
78
193
|
end
|
79
194
|
|
@@ -2,11 +2,7 @@ class SamlController < ApplicationController
|
|
2
2
|
|
3
3
|
def consume
|
4
4
|
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
|
5
|
-
|
6
|
-
render :text => response.name_id
|
7
|
-
else
|
8
|
-
render :plain => response.name_id
|
9
|
-
end
|
5
|
+
render :plain => response.name_id
|
10
6
|
end
|
11
7
|
|
12
8
|
end
|
@@ -1,9 +1,61 @@
|
|
1
|
-
class SamlIdpController <
|
1
|
+
class SamlIdpController < ApplicationController
|
2
|
+
include SamlIdp::Controller
|
3
|
+
|
4
|
+
if Rails::VERSION::MAJOR >= 4
|
5
|
+
before_action :add_view_path, only: [:new, :create, :logout]
|
6
|
+
before_action :validate_saml_request, only: [:new, :create, :logout]
|
7
|
+
else
|
8
|
+
before_filter :add_view_path, only: [:new, :create, :logout]
|
9
|
+
before_filter :validate_saml_request, only: [:new, :create, :logout]
|
10
|
+
end
|
11
|
+
|
12
|
+
def new
|
13
|
+
render template: "saml_idp/idp/new"
|
14
|
+
end
|
15
|
+
|
16
|
+
def show
|
17
|
+
render xml: SamlIdp.metadata.signed
|
18
|
+
end
|
19
|
+
|
20
|
+
def create
|
21
|
+
unless params[:email].blank? && params[:password].blank?
|
22
|
+
person = idp_authenticate(params[:email], params[:password])
|
23
|
+
if person.nil?
|
24
|
+
@saml_idp_fail_msg = "Incorrect email or password."
|
25
|
+
else
|
26
|
+
@saml_response = idp_make_saml_response(person)
|
27
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
28
|
+
return
|
29
|
+
end
|
30
|
+
end
|
31
|
+
render :template => "saml_idp/idp/new"
|
32
|
+
end
|
33
|
+
|
34
|
+
def logout
|
35
|
+
idp_logout
|
36
|
+
@saml_response = idp_make_saml_response(nil)
|
37
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
38
|
+
end
|
39
|
+
|
40
|
+
def idp_logout
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
private :idp_logout
|
44
|
+
|
2
45
|
def idp_authenticate(email, password)
|
3
46
|
{ :email => email }
|
4
47
|
end
|
48
|
+
protected :idp_authenticate
|
5
49
|
|
6
|
-
def idp_make_saml_response(
|
7
|
-
encode_response(
|
50
|
+
def idp_make_saml_response(person)
|
51
|
+
encode_response(person[:email])
|
8
52
|
end
|
53
|
+
protected :idp_make_saml_response
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def add_view_path
|
58
|
+
prepend_view_path("app/views")
|
59
|
+
end
|
60
|
+
|
9
61
|
end
|
@@ -1,22 +1,18 @@
|
|
1
1
|
<% if @saml_idp_fail_msg %>
|
2
2
|
<div id="saml_idp_fail_msg" class="flash error"><%= @saml_idp_fail_msg %></div>
|
3
3
|
<% end %>
|
4
|
-
|
5
4
|
<%= form_tag do %>
|
6
5
|
<%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
|
7
6
|
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
8
|
-
|
9
7
|
<p>
|
10
8
|
<%= label_tag :email %>
|
11
9
|
<%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
12
10
|
</p>
|
13
|
-
|
14
11
|
<p>
|
15
12
|
<%= label_tag :password %>
|
16
13
|
<%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
17
14
|
</p>
|
18
|
-
|
19
15
|
<p>
|
20
16
|
<%= submit_tag "Sign in", :class => "button big blueish" %>
|
21
17
|
</p>
|
22
|
-
<% end %>
|
18
|
+
<% end %>
|
@@ -18,6 +18,7 @@ module RailsApp
|
|
18
18
|
|
19
19
|
# Custom directories with classes and modules you want to be autoloadable.
|
20
20
|
# config.autoload_paths += %W(#{config.root}/extras)
|
21
|
+
config.autoload_paths += %w[app/controllers]
|
21
22
|
|
22
23
|
# Only load the plugins named here, in the order given (default is alphabetical).
|
23
24
|
# :all can be used as a placeholder for all plugins not explicitly named.
|
@@ -29,4 +29,6 @@ RailsApp::Application.configure do
|
|
29
29
|
# Log the query plan for queries taking more than this (works
|
30
30
|
# with SQLite, MySQL, and PostgreSQL)
|
31
31
|
#config.active_record.auto_explain_threshold_in_seconds = 0.5
|
32
|
+
|
33
|
+
config.hosts << "foo.example.com" if config.respond_to?(:hosts)
|
32
34
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -43,9 +43,28 @@ RSpec.configure do |config|
|
|
43
43
|
}
|
44
44
|
end
|
45
45
|
end
|
46
|
+
|
47
|
+
# To reset to default config
|
48
|
+
config.after do
|
49
|
+
SamlIdp.instance_variable_set(:@config, nil)
|
50
|
+
SamlIdp.configure do |c|
|
51
|
+
c.attributes = {
|
52
|
+
emailAddress: {
|
53
|
+
name: "email-address",
|
54
|
+
getter: ->(p) { "foo@example.com" }
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
c.name_id.formats = {
|
59
|
+
"1.1" => {
|
60
|
+
email_address: ->(p) { "foo@example.com" }
|
61
|
+
}
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
46
65
|
end
|
47
66
|
|
48
67
|
SamlIdp::Default::SERVICE_PROVIDER[:metadata_url] = 'https://example.com/meta'
|
49
68
|
SamlIdp::Default::SERVICE_PROVIDER[:response_hosts] = ['foo.example.com']
|
50
69
|
SamlIdp::Default::SERVICE_PROVIDER[:assertion_consumer_logout_service_url] = 'https://foo.example.com/saml/logout'
|
51
|
-
Capybara.default_host = "https://
|
70
|
+
Capybara.default_host = "https://foo.example.com"
|
@@ -0,0 +1,12 @@
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
2
|
+
MIIByTCCATICAQAwgYgxCzAJBgNVBAYTAmpwMQ4wDAYDVQQIDAVUb2t5bzELMAkG
|
3
|
+
A1UECgwCR1MxIDAeBgNVBAMMF2h0dHBzOi8vZm9vLmV4YW1wbGUuY29tMQwwCgYD
|
4
|
+
VQQHDANGb28xDDAKBgNVBAsMA0JvbzEeMBwGCSqGSIb3DQEJARYPZm9vQGV4YW1w
|
5
|
+
bGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8DVj2mVLQV7AjT+cn
|
6
|
+
Lv3kDnQFvAo3RdUeGGhplsYFacYByzNRD/jeguu1ahrvznDyZN8p3yB7OPbmt0r0
|
7
|
+
aGr+yYzPh6brgkf5u6FMtWTj94vLQuT/uyQGuzdBkiLb5mAWRMtm43oHXDK0v25J
|
8
|
+
tsG1PJnntkXfBDpFP1eWLO+jZwIDAQABoAAwDQYJKoZIhvcNAQENBQADgYEAd/J6
|
9
|
+
5zjrMhgjxuaMuWCiNN7IS4F9SKy+gEmhkpNVCpChbpggruaEIoERjDP/TkZn2dgL
|
10
|
+
VUeHTZB92t+wWfQbHNvEfbzqlV3XkuHkxewCwofnIV/k+8zG1Al5ELSKHehItxig
|
11
|
+
rnTuBrFYsd2j4HEVqLzm4NyCfL+xzn/D4U2ec50=
|
12
|
+
-----END CERTIFICATE REQUEST-----
|
@@ -0,0 +1,16 @@
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
2
|
+
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALwNWPaZUtBXsCNP
|
3
|
+
5ycu/eQOdAW8CjdF1R4YaGmWxgVpxgHLM1EP+N6C67VqGu/OcPJk3ynfIHs49ua3
|
4
|
+
SvRoav7JjM+HpuuCR/m7oUy1ZOP3i8tC5P+7JAa7N0GSItvmYBZEy2bjegdcMrS/
|
5
|
+
bkm2wbU8mee2Rd8EOkU/V5Ys76NnAgMBAAECgYEArwclVHCkebIECPnnxbqhKNCj
|
6
|
+
AGtifsuKbrZ9CDoDGSq31xeQLdTV6BSm2nVlmOnmilWEuG4qx0Xf2CGlrBI78kmv
|
7
|
+
vHCfFdaGnTxbmYnD0HN0u4RK2trsxWO+rEkJk14JE2eVD6ZRPrq1UOSMgGPrQSMb
|
8
|
+
SuwAHUu/j94eL8BXuhECQQD3jTlo3Y4VPWttP6XPNqKDP+jRYJs5G0Bch//S9Qy7
|
9
|
+
QzmU9/yAUk0BEOyqYcLxinjJhoq6bR2fiIibn+77z3jtAkEAwnhLwkGYOb7Nt3V6
|
10
|
+
dQLKx1BP9dnYH7qG/sCmAs7GHPv4LGluaz4zsh2pdEDF/Xar4gwTzUpxYo8FpkCH
|
11
|
+
rf4nIwJAVfWnGr/cR4nVVNFGHUcGdXbqvFHEdLb+yWK8NZ+79Qap5w2Zk2GAtb8P
|
12
|
+
vzZFQCRqPuhGIegj4jLB5PBLRwtLHQJBAJiWyWL4ExikRUhBTr/HXBL+Sm9u6i0j
|
13
|
+
L89unBQx6LNPZhB6/Z/6Y5fLvG2ycWgLGJ06usLnOYaLEHS9x3hXpp8CQQCdtQHw
|
14
|
+
xeLBPhRDpfWWbSmFr+bFxyD/4iQHTHToIs3kaecn6OJ4rczIFpGm2Bm7f4X7F3H3
|
15
|
+
DDy4jZ0R6iDqCcQD
|
16
|
+
-----END PRIVATE KEY-----
|
@@ -0,0 +1,18 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIC2DCCAkGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBiDELMAkGA1UEBhMCanAx
|
3
|
+
DjAMBgNVBAgMBVRva3lvMQswCQYDVQQKDAJHUzEgMB4GA1UEAwwXaHR0cHM6Ly9m
|
4
|
+
b28uZXhhbXBsZS5jb20xDDAKBgNVBAcMA0ZvbzEMMAoGA1UECwwDQm9vMR4wHAYJ
|
5
|
+
KoZIhvcNAQkBFg9mb29AZXhhbXBsZS5jb20wHhcNMjAwMTIzMDYyMzI5WhcNNDcw
|
6
|
+
NjA5MDYyMzI5WjCBiDELMAkGA1UEBhMCanAxDjAMBgNVBAgMBVRva3lvMQswCQYD
|
7
|
+
VQQKDAJHUzEgMB4GA1UEAwwXaHR0cHM6Ly9mb28uZXhhbXBsZS5jb20xDDAKBgNV
|
8
|
+
BAcMA0ZvbzEMMAoGA1UECwwDQm9vMR4wHAYJKoZIhvcNAQkBFg9mb29AZXhhbXBs
|
9
|
+
ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALwNWPaZUtBXsCNP5ycu
|
10
|
+
/eQOdAW8CjdF1R4YaGmWxgVpxgHLM1EP+N6C67VqGu/OcPJk3ynfIHs49ua3SvRo
|
11
|
+
av7JjM+HpuuCR/m7oUy1ZOP3i8tC5P+7JAa7N0GSItvmYBZEy2bjegdcMrS/bkm2
|
12
|
+
wbU8mee2Rd8EOkU/V5Ys76NnAgMBAAGjUDBOMB0GA1UdDgQWBBQMtOtrh2VS/mh4
|
13
|
+
awGbKA37vVnw+zAfBgNVHSMEGDAWgBQMtOtrh2VS/mh4awGbKA37vVnw+zAMBgNV
|
14
|
+
HRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAHjTTm4Hyx1rfzygknc6q1dYwpEv
|
15
|
+
/3AsPiTnF4AfH/5kGIIXNzwg0ADsziFMJYRRR9eMu97CHQbr8gHt99P8uaen6cmJ
|
16
|
+
4VCwJLP2N8gZrycssimA3M83DWRRVZbxZhpuUWNajtYIxwyUbB7eRSJgz3Tc0opF
|
17
|
+
933YwucWuFzKSqn3
|
18
|
+
-----END CERTIFICATE-----
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'saml_idp/logout_request_builder'
|
2
2
|
|
3
3
|
module SamlRequestMacros
|
4
|
-
def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume")
|
4
|
+
def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
|
5
5
|
auth_request = OneLogin::RubySaml::Authrequest.new
|
6
|
-
auth_url = auth_request.create(saml_settings(requested_saml_acs_url))
|
6
|
+
auth_url = auth_request.create(saml_settings(requested_saml_acs_url, enable_secure_options))
|
7
7
|
CGI.unescape(auth_url.split("=").last)
|
8
8
|
end
|
9
9
|
|
@@ -18,7 +18,12 @@ module SamlRequestMacros
|
|
18
18
|
Base64.strict_encode64(request_builder.signed)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def generate_sp_metadata(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
|
22
|
+
sp_metadata = OneLogin::RubySaml::Metadata.new
|
23
|
+
sp_metadata.generate(saml_settings(saml_acs_url, enable_secure_options), true)
|
24
|
+
end
|
25
|
+
|
26
|
+
def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
|
22
27
|
settings = OneLogin::RubySaml::Settings.new
|
23
28
|
settings.assertion_consumer_service_url = saml_acs_url
|
24
29
|
settings.issuer = "http://example.com/issuer"
|
@@ -26,9 +31,63 @@ module SamlRequestMacros
|
|
26
31
|
settings.assertion_consumer_logout_service_url = 'https://foo.example.com/saml/logout'
|
27
32
|
settings.idp_cert_fingerprint = SamlIdp::Default::FINGERPRINT
|
28
33
|
settings.name_identifier_format = SamlIdp::Default::NAME_ID_FORMAT
|
34
|
+
add_securty_options(settings) if enable_secure_options
|
29
35
|
settings
|
30
36
|
end
|
31
37
|
|
38
|
+
def add_securty_options(settings, authn_requests_signed: true,
|
39
|
+
embed_sign: true,
|
40
|
+
logout_requests_signed: true,
|
41
|
+
logout_responses_signed: true,
|
42
|
+
digest_method: XMLSecurity::Document::SHA256,
|
43
|
+
signature_method: XMLSecurity::Document::RSA_SHA256)
|
44
|
+
# Security section
|
45
|
+
settings.idp_cert = SamlIdp::Default::X509_CERTIFICATE
|
46
|
+
# Signed embedded singature
|
47
|
+
settings.security[:authn_requests_signed] = authn_requests_signed
|
48
|
+
settings.security[:embed_sign] = embed_sign
|
49
|
+
settings.security[:logout_requests_signed] = logout_requests_signed
|
50
|
+
settings.security[:logout_responses_signed] = logout_responses_signed
|
51
|
+
settings.security[:metadata_signed] = digest_method
|
52
|
+
settings.security[:digest_method] = digest_method
|
53
|
+
settings.security[:signature_method] = signature_method
|
54
|
+
settings.private_key = sp_pv_key
|
55
|
+
settings.certificate = sp_x509_cert
|
56
|
+
end
|
57
|
+
|
58
|
+
def idp_configure(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
|
59
|
+
SamlIdp.configure do |config|
|
60
|
+
config.x509_certificate = SamlIdp::Default::X509_CERTIFICATE
|
61
|
+
config.secret_key = SamlIdp::Default::SECRET_KEY
|
62
|
+
config.password = nil
|
63
|
+
config.algorithm = :sha256
|
64
|
+
config.organization_name = 'idp.com'
|
65
|
+
config.organization_url = 'http://idp.com'
|
66
|
+
config.base_saml_location = 'http://idp.com/saml/idp'
|
67
|
+
config.single_logout_service_post_location = 'http://idp.com/saml/idp/logout'
|
68
|
+
config.single_logout_service_redirect_location = 'http://idp.com/saml/idp/logout'
|
69
|
+
config.attribute_service_location = 'http://idp.com/saml/idp/attribute'
|
70
|
+
config.single_service_post_location = 'http://idp.com/saml/idp/sso'
|
71
|
+
config.name_id.formats = SamlIdp::Default::NAME_ID_FORMAT
|
72
|
+
config.service_provider.metadata_persister = lambda { |_identifier, _service_provider|
|
73
|
+
raw_metadata = generate_sp_metadata(saml_acs_url, enable_secure_options)
|
74
|
+
SamlIdp::IncomingMetadata.new(raw_metadata).to_h
|
75
|
+
}
|
76
|
+
config.service_provider.persisted_metadata_getter = lambda { |_identifier, _settings|
|
77
|
+
raw_metadata = generate_sp_metadata(saml_acs_url, enable_secure_options)
|
78
|
+
SamlIdp::IncomingMetadata.new(raw_metadata).to_h
|
79
|
+
}
|
80
|
+
config.service_provider.finder = lambda { |_issuer_or_entity_id|
|
81
|
+
{
|
82
|
+
response_hosts: [URI(saml_acs_url).host],
|
83
|
+
acs_url: saml_acs_url,
|
84
|
+
cert: sp_x509_cert,
|
85
|
+
fingerprint: SamlIdp::Fingerprint.certificate_digest(sp_x509_cert)
|
86
|
+
}
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
32
91
|
def print_pretty_xml(xml_string)
|
33
92
|
doc = REXML::Document.new xml_string
|
34
93
|
outbuf = ""
|
@@ -58,4 +58,14 @@ module SecurityHelpers
|
|
58
58
|
def r1_signature_2
|
59
59
|
@signature2 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'r1_certificate2_base64'))
|
60
60
|
end
|
61
|
+
|
62
|
+
# Generated by SAML tool https://www.samltool.com/self_signed_certs.php
|
63
|
+
def sp_pv_key
|
64
|
+
@sp_pv_key ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'sp_private_key.pem'))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Generated by SAML tool https://www.samltool.com/self_signed_certs.php, expired date is 9999
|
68
|
+
def sp_x509_cert
|
69
|
+
@sp_x509_cert ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'sp_x509_cert.crt'))
|
70
|
+
end
|
61
71
|
end
|