saml-kit 0.2.1 → 0.2.2

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.
@@ -8,56 +8,7 @@ module Saml
8
8
  super(xml, name: "LogoutResponse")
9
9
  end
10
10
 
11
- private
12
-
13
- class Builder
14
- attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
15
- attr_reader :request
16
-
17
- def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
18
- @user = user
19
- @now = Time.now.utc
20
- @request = request
21
- @id = SecureRandom.uuid
22
- @version = "2.0"
23
- @status_code = Namespaces::SUCCESS
24
- @sign = sign
25
- @issuer = configuration.issuer
26
- provider = configuration.registry.metadata_for(@issuer)
27
- if provider
28
- @destination = provider.single_logout_service_for(binding: :http_post).try(:location)
29
- end
30
- end
31
-
32
- def to_xml
33
- Signature.sign(sign: sign) do |xml, signature|
34
- xml.LogoutResponse logout_response_options do
35
- xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
36
- signature.template(id)
37
- xml.Status do
38
- xml.StatusCode Value: status_code
39
- end
40
- end
41
- end
42
- end
43
-
44
- def build
45
- LogoutResponse.new(to_xml, request_id: request.id)
46
- end
47
-
48
- private
49
-
50
- def logout_response_options
51
- {
52
- xmlns: Namespaces::PROTOCOL,
53
- ID: "_#{id}",
54
- Version: version,
55
- IssueInstant: now.utc.iso8601,
56
- Destination: destination,
57
- InResponseTo: request.id,
58
- }
59
- end
60
- end
11
+ Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutResponse::Builder', 'Saml::Kit::Builders::LogoutResponse')
61
12
  end
62
13
  end
63
14
  end
@@ -3,6 +3,7 @@ module Saml
3
3
  class Metadata
4
4
  include ActiveModel::Validations
5
5
  include XsdValidatable
6
+ include Buildable
6
7
  METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
7
8
 
8
9
  validates_presence_of :metadata
@@ -63,6 +64,14 @@ module Saml
63
64
  service_for(binding: binding, type: 'SingleLogoutService')
64
65
  end
65
66
 
67
+ def logout_request_for(user, binding: :http_post, relay_state: nil)
68
+ builder = Saml::Kit::LogoutRequest.builder(user) do |x|
69
+ yield x if block_given?
70
+ end
71
+ request_binding = single_logout_service_for(binding: binding)
72
+ request_binding.serialize(builder, relay_state: relay_state)
73
+ end
74
+
66
75
  def matches?(fingerprint, use: :signing)
67
76
  certificates.find do |certificate|
68
77
  certificate.for?(use) && certificate.fingerprint == fingerprint
@@ -20,11 +20,16 @@ module Saml
20
20
  end
21
21
 
22
22
  def attributes
23
- @attributes ||= Hash[
24
- assertion.fetch('AttributeStatement', {}).fetch('Attribute', []).map do |item|
25
- [item['Name'].to_sym, item['AttributeValue']]
23
+ @attributes ||=
24
+ begin
25
+ attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
26
+ items = if attrs.is_a? Hash
27
+ [[attrs["Name"], attrs["AttributeValue"]]]
28
+ else
29
+ attrs.map { |item| [item['Name'], item['AttributeValue']] }
30
+ end
31
+ Hash[items].with_indifferent_access
26
32
  end
27
- ].with_indifferent_access
28
33
  end
29
34
 
30
35
  def started_at
@@ -51,7 +56,7 @@ module Saml
51
56
  @assertion =
52
57
  begin
53
58
  if encrypted?
54
- decrypted = Cryptography.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
59
+ decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
55
60
  Saml::Kit.logger.debug(decrypted)
56
61
  Hash.from_xml(decrypted)['Assertion']
57
62
  else
@@ -99,182 +104,7 @@ module Saml
99
104
  Time.at(0).to_datetime
100
105
  end
101
106
 
102
- class Builder
103
- attr_reader :user, :request
104
- attr_accessor :id, :reference_id, :now
105
- attr_accessor :version, :status_code
106
- attr_accessor :issuer, :sign, :destination, :encrypt
107
-
108
- def initialize(user, request)
109
- @user = user
110
- @request = request
111
- @id = SecureRandom.uuid
112
- @reference_id = SecureRandom.uuid
113
- @now = Time.now.utc
114
- @version = "2.0"
115
- @status_code = Namespaces::SUCCESS
116
- @issuer = configuration.issuer
117
- @destination = destination_for(request)
118
- @sign = want_assertions_signed
119
- @encrypt = false
120
- end
121
-
122
- def want_assertions_signed
123
- request.provider.want_assertions_signed
124
- rescue => error
125
- Saml::Kit.logger.error(error)
126
- true
127
- end
128
-
129
- def to_xml
130
- Signature.sign(sign: sign) do |xml, signature|
131
- xml.Response response_options do
132
- xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
133
- signature.template(id)
134
- xml.Status do
135
- xml.StatusCode Value: status_code
136
- end
137
- assertion(xml, signature)
138
- end
139
- end
140
- end
141
-
142
- def build
143
- Response.new(to_xml, request_id: request.id)
144
- end
145
-
146
- private
147
-
148
- def assertion(xml, signature)
149
- with_encryption(xml) do |xml|
150
- xml.Assertion(assertion_options) do
151
- xml.Issuer issuer
152
- signature.template(reference_id) unless encrypt
153
- xml.Subject do
154
- xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
155
- xml.SubjectConfirmation Method: Namespaces::BEARER do
156
- xml.SubjectConfirmationData "", subject_confirmation_data_options
157
- end
158
- end
159
- xml.Conditions conditions_options do
160
- xml.AudienceRestriction do
161
- xml.Audience request.issuer
162
- end
163
- end
164
- xml.AuthnStatement authn_statement_options do
165
- xml.AuthnContext do
166
- xml.AuthnContextClassRef Namespaces::PASSWORD
167
- end
168
- end
169
- assertion_attributes = user.assertion_attributes_for(request)
170
- if assertion_attributes.any?
171
- xml.AttributeStatement do
172
- assertion_attributes.each do |key, value|
173
- xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
174
- xml.AttributeValue value.to_s
175
- end
176
- end
177
- end
178
- end
179
- end
180
- end
181
- end
182
-
183
- def with_encryption(xml)
184
- if encrypt
185
- temp = ::Builder::XmlMarkup.new
186
- yield temp
187
- raw_xml_to_encrypt = temp.target!
188
-
189
- encryption_certificate = request.provider.encryption_certificates.first
190
- public_key = encryption_certificate.public_key
191
-
192
- cipher = OpenSSL::Cipher.new('AES-256-CBC')
193
- cipher.encrypt
194
- key = cipher.random_key
195
- iv = cipher.random_iv
196
- encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
197
-
198
- Saml::Kit.logger.debug ['+iv', iv].inspect
199
- Saml::Kit.logger.debug ['+key', key].inspect
200
-
201
- xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
202
- xml.EncryptedData xmlns: Namespaces::XMLENC do
203
- xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
204
- xml.KeyInfo xmlns: Namespaces::XMLDSIG do
205
- xml.EncryptedKey xmlns: Namespaces::XMLENC do
206
- xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
207
- xml.CipherData do
208
- xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
209
- end
210
- end
211
- end
212
- xml.CipherData do
213
- xml.CipherValue Base64.encode64(iv + encrypted)
214
- end
215
- end
216
- end
217
- else
218
- yield xml
219
- end
220
- end
221
-
222
- def destination_for(request)
223
- if request.signed? && request.trusted?
224
- request.acs_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
225
- else
226
- request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
227
- end
228
- end
229
-
230
- def configuration
231
- Saml::Kit.configuration
232
- end
233
-
234
- def response_options
235
- {
236
- ID: id.present? ? "_#{id}" : nil,
237
- Version: version,
238
- IssueInstant: now.iso8601,
239
- Destination: destination,
240
- Consent: Namespaces::UNSPECIFIED,
241
- InResponseTo: request.id,
242
- xmlns: Namespaces::PROTOCOL,
243
- }
244
- end
245
-
246
- def assertion_options
247
- {
248
- ID: "_#{reference_id}",
249
- IssueInstant: now.iso8601,
250
- Version: "2.0",
251
- xmlns: Namespaces::ASSERTION,
252
- }
253
- end
254
-
255
- def subject_confirmation_data_options
256
- {
257
- InResponseTo: request.id,
258
- NotOnOrAfter: 3.hours.since(now).utc.iso8601,
259
- Recipient: request.acs_url,
260
- }
261
- end
262
-
263
- def conditions_options
264
- {
265
- NotBefore: now.utc.iso8601,
266
- NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
267
- }
268
- end
269
-
270
- def authn_statement_options
271
- {
272
- AuthnInstant: now.iso8601,
273
- SessionIndex: assertion_options[:ID],
274
- SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
275
- }
276
- end
277
- end
107
+ Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
278
108
  end
279
109
  end
280
110
  end
@@ -1,6 +1,8 @@
1
1
  module Saml
2
2
  module Kit
3
3
  class SelfSignedCertificate
4
+ SUBJECT="/C=CA/ST=Alberta/L=Calgary/O=SamlKit/OU=SamlKit/CN=SamlKit"
5
+
4
6
  def initialize(password)
5
7
  @password = password
6
8
  end
@@ -9,20 +11,25 @@ module Saml
9
11
  rsa_key = OpenSSL::PKey::RSA.new(2048)
10
12
  public_key = rsa_key.public_key
11
13
  certificate = OpenSSL::X509::Certificate.new
12
- certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse("/C=CA/ST=Alberta/L=Calgary/O=Xsig/OU=Xsig/CN=Xsig")
14
+ certificate.subject = certificate.issuer = OpenSSL::X509::Name.parse(SUBJECT)
13
15
  certificate.not_before = DateTime.now.beginning_of_day
14
- certificate.not_after = 1.year.from_now.end_of_day
16
+ certificate.not_after = 30.days.from_now
15
17
  certificate.public_key = public_key
16
18
  certificate.serial = 0x0
17
19
  certificate.version = 2
18
20
  factory = OpenSSL::X509::ExtensionFactory.new
19
21
  factory.subject_certificate = factory.issuer_certificate = certificate
20
- certificate.extensions = [ factory.create_extension("basicConstraints","CA:TRUE", true), factory.create_extension("subjectKeyIdentifier", "hash"), ]
21
- certificate.add_extension(factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always"))
22
+ certificate.extensions = [
23
+ factory.create_extension("basicConstraints","CA:TRUE", true),
24
+ factory.create_extension("subjectKeyIdentifier", "hash"),
25
+ ]
26
+ certificate.add_extension(
27
+ factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
28
+ )
22
29
  certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
23
30
  [
24
31
  certificate.to_pem,
25
- rsa_key.to_pem(OpenSSL::Cipher::Cipher.new('des3'), @password)
32
+ rsa_key.to_pem(OpenSSL::Cipher.new('AES-256-CBC'), @password)
26
33
  ]
27
34
  end
28
35
  end
@@ -15,92 +15,15 @@ module Saml
15
15
 
16
16
  def want_assertions_signed
17
17
  attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
18
+ return true if attribute.nil?
18
19
  attribute.text.downcase == "true"
19
20
  end
20
21
 
21
- class Builder
22
- attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
23
- attr_accessor :want_assertions_signed
24
-
25
- def initialize(configuration = Saml::Kit.configuration)
26
- @id = SecureRandom.uuid
27
- @configuration = configuration
28
- @entity_id = configuration.issuer
29
- @acs_urls = []
30
- @logout_urls = []
31
- @name_id_formats = [Namespaces::PERSISTENT]
32
- @sign = true
33
- @want_assertions_signed = true
34
- end
35
-
36
- def add_assertion_consumer_service(url, binding: :http_post)
37
- @acs_urls.push(location: url, binding: Bindings.binding_for(binding))
38
- end
39
-
40
- def add_single_logout_service(url, binding: :http_post)
41
- @logout_urls.push(location: url, binding: Bindings.binding_for(binding))
42
- end
43
-
44
- def to_xml
45
- Signature.sign(sign: sign) do |xml, signature|
46
- xml.instruct!
47
- xml.EntityDescriptor entity_descriptor_options do
48
- signature.template(id)
49
- xml.SPSSODescriptor descriptor_options do
50
- if @configuration.signing_certificate_pem.present?
51
- xml.KeyDescriptor use: "signing" do
52
- xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
53
- xml.X509Data do
54
- xml.X509Certificate @configuration.stripped_signing_certificate
55
- end
56
- end
57
- end
58
- end
59
- if @configuration.encryption_certificate_pem.present?
60
- xml.KeyDescriptor use: "encryption" do
61
- xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
62
- xml.X509Data do
63
- xml.X509Certificate @configuration.stripped_encryption_certificate
64
- end
65
- end
66
- end
67
- end
68
- logout_urls.each do |item|
69
- xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
70
- end
71
- name_id_formats.each do |format|
72
- xml.NameIDFormat format
73
- end
74
- acs_urls.each_with_index do |item, index|
75
- xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
76
- end
77
- end
78
- end
79
- end
80
- end
81
-
82
- def build
83
- ServiceProviderMetadata.new(to_xml)
84
- end
85
-
86
- private
87
-
88
- def entity_descriptor_options
89
- {
90
- 'xmlns': Namespaces::METADATA,
91
- ID: "_#{id}",
92
- entityID: entity_id,
93
- }
94
- end
95
-
96
- def descriptor_options
97
- {
98
- AuthnRequestsSigned: sign,
99
- WantAssertionsSigned: want_assertions_signed,
100
- protocolSupportEnumeration: Namespaces::PROTOCOL,
101
- }
102
- end
22
+ def self.builder_class
23
+ Saml::Kit::Builders::ServiceProviderMetadata
103
24
  end
25
+
26
+ Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::ServiceProviderMetadata::Builder', 'Saml::Kit::Builders::ServiceProviderMetadata')
104
27
  end
105
28
  end
106
29
  end
@@ -34,7 +34,7 @@ module Saml
34
34
  xml.SignedInfo do
35
35
  xml.CanonicalizationMethod Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
36
36
  xml.SignatureMethod Algorithm: SIGNATURE_METHODS[configuration.signature_method]
37
- xml.Reference URI: "#_#{reference_id}" do
37
+ xml.Reference URI: "##{reference_id}" do
38
38
  xml.Transforms do
39
39
  xml.Transform Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
40
40
  xml.Transform Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#"
@@ -30,11 +30,7 @@ module Saml
30
30
  end
31
31
 
32
32
  def provider
33
- registry.metadata_for(issuer)
34
- end
35
-
36
- def registry
37
- Saml::Kit.configuration.registry
33
+ Saml::Kit.registry.metadata_for(issuer)
38
34
  end
39
35
 
40
36
  def signature_verified!