saml-kit 0.2.3 → 0.2.4
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/.rspec +1 -1
- data/exe/saml-kit-create-self-signed-certificate +1 -0
- data/lib/saml/kit.rb +1 -0
- data/lib/saml/kit/assertion.rb +73 -0
- data/lib/saml/kit/authentication_request.rb +2 -2
- data/lib/saml/kit/bindings/http_redirect.rb +1 -1
- data/lib/saml/kit/bindings/url_builder.rb +9 -5
- data/lib/saml/kit/buildable.rb +2 -4
- data/lib/saml/kit/builders/assertion.rb +2 -2
- data/lib/saml/kit/builders/authentication_request.rb +2 -3
- data/lib/saml/kit/builders/identity_provider_metadata.rb +2 -3
- data/lib/saml/kit/builders/logout_request.rb +3 -4
- data/lib/saml/kit/builders/logout_response.rb +2 -3
- data/lib/saml/kit/builders/response.rb +11 -17
- data/lib/saml/kit/builders/service_provider_metadata.rb +3 -4
- data/lib/saml/kit/builders/templates/assertion.builder +23 -21
- data/lib/saml/kit/builders/templates/identity_provider_metadata.builder +4 -4
- data/lib/saml/kit/builders/templates/nil_class.builder +0 -0
- data/lib/saml/kit/builders/templates/response.builder +1 -3
- data/lib/saml/kit/builders/templates/service_provider_metadata.builder +4 -4
- data/lib/saml/kit/builders/xml_signature.rb +2 -3
- data/lib/saml/kit/composite_metadata.rb +13 -8
- data/lib/saml/kit/configuration.rb +36 -11
- data/lib/saml/kit/document.rb +3 -2
- data/lib/saml/kit/locales/en.yml +3 -0
- data/lib/saml/kit/logout_request.rb +2 -2
- data/lib/saml/kit/metadata.rb +2 -2
- data/lib/saml/kit/response.rb +9 -62
- data/lib/saml/kit/self_signed_certificate.rb +0 -9
- data/lib/saml/kit/signature.rb +8 -21
- data/lib/saml/kit/signatures.rb +6 -12
- data/lib/saml/kit/templatable.rb +8 -2
- data/lib/saml/kit/template.rb +2 -1
- data/lib/saml/kit/trustable.rb +1 -1
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml.rb +1 -0
- data/lib/saml/kit/xml_decryption.rb +2 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13c5ee20e28acd63c6ec3dde99cd1fea092be3db550dca1bd129a4b0e1fb83d3
|
4
|
+
data.tar.gz: 0337c7d33845d4fbfb9dd6cc817cfde1dc620c3745cfd83b96f246e29ec30dc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc09a60391991a964b3f6114e6c039da70eb917ba268787abe8940bc00a8883449f5e751aeb2365b9cae5f693d177ded5fd2d9d152ae766569a9feb5a8198ad2
|
7
|
+
data.tar.gz: e6cd00b9f963348a4b9cce442d09fe7b5819c0b970334ccff01c6fcfcce54e60cfff7692878f5603a6e54061c2b75b0df61364e2eaa1717547fdc079efbe588c
|
data/.rspec
CHANGED
data/lib/saml/kit.rb
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
class Assertion
|
4
|
+
def initialize(xml_hash, configuration:)
|
5
|
+
@xml_hash = xml_hash
|
6
|
+
@configuration = configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
def name_id
|
10
|
+
assertion.fetch('Subject', {}).fetch('NameID', nil)
|
11
|
+
end
|
12
|
+
|
13
|
+
def signed?
|
14
|
+
assertion.fetch('Signature', nil).present?
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
@attributes ||=
|
19
|
+
begin
|
20
|
+
attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
|
21
|
+
items = if attrs.is_a? Hash
|
22
|
+
[[attrs["Name"], attrs["AttributeValue"]]]
|
23
|
+
else
|
24
|
+
attrs.map { |item| [item['Name'], item['AttributeValue']] }
|
25
|
+
end
|
26
|
+
Hash[items].with_indifferent_access
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def started_at
|
31
|
+
parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
|
32
|
+
end
|
33
|
+
|
34
|
+
def expired_at
|
35
|
+
parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
|
36
|
+
end
|
37
|
+
|
38
|
+
def certificate
|
39
|
+
assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
|
40
|
+
end
|
41
|
+
|
42
|
+
def audiences
|
43
|
+
Array(assertion['Conditions']['AudienceRestriction']['Audience'])
|
44
|
+
rescue => error
|
45
|
+
Saml::Kit.logger.error(error)
|
46
|
+
[]
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def encrypted?
|
52
|
+
@xml_hash.fetch('Response', {}).fetch('EncryptedAssertion', nil).present?
|
53
|
+
end
|
54
|
+
|
55
|
+
def assertion
|
56
|
+
if encrypted?
|
57
|
+
decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
|
58
|
+
Saml::Kit.logger.debug(decrypted)
|
59
|
+
Hash.from_xml(decrypted)['Assertion']
|
60
|
+
else
|
61
|
+
@xml_hash.fetch('Response', {}).fetch('Assertion', {})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_date(value)
|
66
|
+
DateTime.parse(value)
|
67
|
+
rescue => error
|
68
|
+
Saml::Kit.logger.error(error)
|
69
|
+
Time.at(0).to_datetime
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -3,8 +3,8 @@ module Saml
|
|
3
3
|
class AuthenticationRequest < Document
|
4
4
|
include Requestable
|
5
5
|
|
6
|
-
def initialize(xml)
|
7
|
-
super(xml, name: "AuthnRequest")
|
6
|
+
def initialize(xml, configuration: Saml::Kit.configuration)
|
7
|
+
super(xml, name: "AuthnRequest", configuration: configuration)
|
8
8
|
end
|
9
9
|
|
10
10
|
def assertion_consumer_service_url
|
@@ -12,7 +12,7 @@ module Saml
|
|
12
12
|
builder.sign = false
|
13
13
|
builder.destination = location
|
14
14
|
document = builder.build
|
15
|
-
[UrlBuilder.new.build(document, relay_state: relay_state), {}]
|
15
|
+
[UrlBuilder.new(configuration: builder.configuration).build(document, relay_state: relay_state), {}]
|
16
16
|
end
|
17
17
|
|
18
18
|
def deserialize(params)
|
@@ -3,21 +3,25 @@ module Saml
|
|
3
3
|
module Bindings
|
4
4
|
class UrlBuilder
|
5
5
|
include Serializable
|
6
|
+
attr_reader :configuration
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
8
|
+
def initialize(configuration: Saml::Kit.configuration)
|
9
|
+
@configuration = configuration
|
9
10
|
end
|
10
11
|
|
11
12
|
def build(saml_document, relay_state: nil)
|
12
13
|
payload = canonicalize(saml_document, relay_state)
|
13
|
-
|
14
|
+
if configuration.sign?
|
15
|
+
"#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
|
16
|
+
else
|
17
|
+
"#{saml_document.destination}?#{payload}"
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
21
|
private
|
17
22
|
|
18
|
-
attr_reader :private_key
|
19
|
-
|
20
23
|
def signature_for(payload)
|
24
|
+
private_key = configuration.private_keys(use: :signing).sample
|
21
25
|
encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
|
22
26
|
end
|
23
27
|
|
data/lib/saml/kit/buildable.rb
CHANGED
@@ -5,7 +5,7 @@ module Saml
|
|
5
5
|
include Templatable
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
-
def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version
|
8
|
+
def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination, :encryption_certificate
|
9
9
|
|
10
10
|
def initialize(response_builder)
|
11
11
|
@response_builder = response_builder
|
@@ -38,7 +38,7 @@ module Saml
|
|
38
38
|
{
|
39
39
|
InResponseTo: request.id,
|
40
40
|
NotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
41
|
-
Recipient:
|
41
|
+
Recipient: destination,
|
42
42
|
}
|
43
43
|
end
|
44
44
|
|
@@ -3,17 +3,16 @@ module Saml
|
|
3
3
|
module Builders
|
4
4
|
class AuthenticationRequest
|
5
5
|
include Saml::Kit::Templatable
|
6
|
-
attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :
|
6
|
+
attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
|
7
7
|
attr_accessor :version
|
8
8
|
attr_reader :configuration
|
9
9
|
|
10
|
-
def initialize(configuration: Saml::Kit.configuration
|
10
|
+
def initialize(configuration: Saml::Kit.configuration)
|
11
11
|
@configuration = configuration
|
12
12
|
@id = Id.generate
|
13
13
|
@issuer = configuration.issuer
|
14
14
|
@name_id_format = Namespaces::PERSISTENT
|
15
15
|
@now = Time.now.utc
|
16
|
-
@sign = sign
|
17
16
|
@version = "2.0"
|
18
17
|
end
|
19
18
|
|
@@ -4,18 +4,17 @@ module Saml
|
|
4
4
|
class IdentityProviderMetadata
|
5
5
|
include Saml::Kit::Templatable
|
6
6
|
attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
|
7
|
-
attr_accessor :want_authn_requests_signed
|
7
|
+
attr_accessor :want_authn_requests_signed
|
8
8
|
attr_reader :logout_urls, :single_sign_on_urls
|
9
9
|
attr_reader :configuration
|
10
10
|
|
11
|
-
def initialize(configuration
|
11
|
+
def initialize(configuration: Saml::Kit.configuration)
|
12
12
|
@attributes = []
|
13
13
|
@configuration = configuration
|
14
14
|
@entity_id = configuration.issuer
|
15
15
|
@id = Id.generate
|
16
16
|
@logout_urls = []
|
17
17
|
@name_id_formats = [Namespaces::PERSISTENT]
|
18
|
-
@sign = true
|
19
18
|
@single_sign_on_urls = []
|
20
19
|
@want_authn_requests_signed = true
|
21
20
|
end
|
@@ -4,10 +4,10 @@ module Saml
|
|
4
4
|
class LogoutRequest
|
5
5
|
include Saml::Kit::Templatable
|
6
6
|
attr_accessor :id, :destination, :issuer, :name_id_format, :now
|
7
|
-
attr_accessor :
|
7
|
+
attr_accessor :version
|
8
8
|
attr_reader :user, :configuration
|
9
9
|
|
10
|
-
def initialize(user, configuration: Saml::Kit.configuration
|
10
|
+
def initialize(user, configuration: Saml::Kit.configuration)
|
11
11
|
@configuration = configuration
|
12
12
|
@user = user
|
13
13
|
@id = "_#{SecureRandom.uuid}"
|
@@ -15,11 +15,10 @@ module Saml
|
|
15
15
|
@name_id_format = Saml::Kit::Namespaces::PERSISTENT
|
16
16
|
@now = Time.now.utc
|
17
17
|
@version = "2.0"
|
18
|
-
@sign = sign
|
19
18
|
end
|
20
19
|
|
21
20
|
def build
|
22
|
-
Saml::Kit::LogoutRequest.new(to_xml)
|
21
|
+
Saml::Kit::LogoutRequest.new(to_xml, configuration: configuration)
|
23
22
|
end
|
24
23
|
|
25
24
|
private
|
@@ -3,17 +3,16 @@ module Saml
|
|
3
3
|
module Builders
|
4
4
|
class LogoutResponse
|
5
5
|
include Saml::Kit::Templatable
|
6
|
-
attr_accessor :id, :issuer, :version, :status_code, :
|
6
|
+
attr_accessor :id, :issuer, :version, :status_code, :now, :destination
|
7
7
|
attr_reader :request
|
8
8
|
attr_reader :configuration
|
9
9
|
|
10
|
-
def initialize(user, request, configuration: Saml::Kit.configuration
|
10
|
+
def initialize(user, request, configuration: Saml::Kit.configuration)
|
11
11
|
@configuration = configuration
|
12
12
|
@id = Id.generate
|
13
13
|
@issuer = configuration.issuer
|
14
14
|
@now = Time.now.utc
|
15
15
|
@request = request
|
16
|
-
@sign = sign
|
17
16
|
@status_code = Namespaces::SUCCESS
|
18
17
|
@user = user
|
19
18
|
@version = "2.0"
|
@@ -6,7 +6,7 @@ module Saml
|
|
6
6
|
attr_reader :user, :request
|
7
7
|
attr_accessor :id, :reference_id, :now
|
8
8
|
attr_accessor :version, :status_code
|
9
|
-
attr_accessor :issuer, :
|
9
|
+
attr_accessor :issuer, :destination, :encrypt
|
10
10
|
attr_reader :configuration
|
11
11
|
|
12
12
|
def initialize(user, request, configuration: Saml::Kit.configuration)
|
@@ -18,9 +18,8 @@ module Saml
|
|
18
18
|
@version = "2.0"
|
19
19
|
@status_code = Namespaces::SUCCESS
|
20
20
|
@issuer = configuration.issuer
|
21
|
-
@destination = destination_for(request)
|
22
21
|
@sign = want_assertions_signed
|
23
|
-
@encrypt =
|
22
|
+
@encrypt = encryption_certificate.present?
|
24
23
|
@configuration = configuration
|
25
24
|
end
|
26
25
|
|
@@ -28,29 +27,24 @@ module Saml
|
|
28
27
|
request.provider.want_assertions_signed
|
29
28
|
rescue => error
|
30
29
|
Saml::Kit.logger.error(error)
|
31
|
-
|
30
|
+
nil
|
32
31
|
end
|
33
32
|
|
34
33
|
def build
|
35
|
-
Saml::Kit::Response.new(to_xml, request_id: request.id)
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def assertion
|
41
|
-
@assertion ||= Saml::Kit::Builders::Assertion.new(self)
|
34
|
+
Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
|
42
35
|
end
|
43
36
|
|
44
37
|
def encryption_certificate
|
45
38
|
request.provider.encryption_certificates.first
|
39
|
+
rescue => error
|
40
|
+
Saml::Kit.logger.error(error)
|
41
|
+
nil
|
46
42
|
end
|
47
43
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
|
53
|
-
end
|
44
|
+
private
|
45
|
+
|
46
|
+
def assertion
|
47
|
+
@assertion ||= Saml::Kit::Builders::Assertion.new(self)
|
54
48
|
end
|
55
49
|
|
56
50
|
def response_options
|
@@ -3,19 +3,18 @@ module Saml
|
|
3
3
|
module Builders
|
4
4
|
class ServiceProviderMetadata
|
5
5
|
include Saml::Kit::Templatable
|
6
|
-
attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats
|
6
|
+
attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats
|
7
7
|
attr_accessor :organization_name, :organization_url, :contact_email
|
8
8
|
attr_accessor :want_assertions_signed
|
9
9
|
attr_reader :configuration
|
10
10
|
|
11
|
-
def initialize(configuration
|
11
|
+
def initialize(configuration: Saml::Kit.configuration)
|
12
12
|
@acs_urls = []
|
13
13
|
@configuration = configuration
|
14
14
|
@entity_id = configuration.issuer
|
15
15
|
@id = Id.generate
|
16
16
|
@logout_urls = []
|
17
17
|
@name_id_formats = [Namespaces::PERSISTENT]
|
18
|
-
@sign = true
|
19
18
|
@want_assertions_signed = true
|
20
19
|
end
|
21
20
|
|
@@ -43,7 +42,7 @@ module Saml
|
|
43
42
|
|
44
43
|
def descriptor_options
|
45
44
|
{
|
46
|
-
AuthnRequestsSigned: sign
|
45
|
+
AuthnRequestsSigned: sign?,
|
47
46
|
WantAssertionsSigned: want_assertions_signed,
|
48
47
|
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
49
48
|
}
|
@@ -1,27 +1,29 @@
|
|
1
|
-
xml
|
2
|
-
xml.
|
3
|
-
|
4
|
-
|
5
|
-
xml.
|
6
|
-
|
7
|
-
xml.
|
1
|
+
encryption_for(xml: xml) do |xml|
|
2
|
+
xml.Assertion(assertion_options) do
|
3
|
+
xml.Issuer issuer
|
4
|
+
signature_for(reference_id: reference_id, xml: xml) unless encrypt
|
5
|
+
xml.Subject do
|
6
|
+
xml.NameID name_id, Format: name_id_format
|
7
|
+
xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
|
8
|
+
xml.SubjectConfirmationData "", subject_confirmation_data_options
|
9
|
+
end
|
8
10
|
end
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
xml.Conditions conditions_options do
|
12
|
+
xml.AudienceRestriction do
|
13
|
+
xml.Audience request.issuer
|
14
|
+
end
|
13
15
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
xml.AuthnStatement authn_statement_options do
|
17
|
+
xml.AuthnContext do
|
18
|
+
xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
|
19
|
+
end
|
18
20
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
if assertion_attributes.any?
|
22
|
+
xml.AttributeStatement do
|
23
|
+
assertion_attributes.each do |key, value|
|
24
|
+
xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
|
25
|
+
xml.AttributeValue value.to_s
|
26
|
+
end
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -2,11 +2,11 @@ xml.instruct!
|
|
2
2
|
xml.EntityDescriptor entity_descriptor_options do
|
3
3
|
signature_for(reference_id: id, xml: xml)
|
4
4
|
xml.IDPSSODescriptor idp_sso_descriptor_options do
|
5
|
-
|
6
|
-
render
|
5
|
+
configuration.certificates(use: :signing).each do |certificate|
|
6
|
+
render certificate, xml: xml
|
7
7
|
end
|
8
|
-
|
9
|
-
render
|
8
|
+
configuration.certificates(use: :encryption).each do |certificate|
|
9
|
+
render certificate, xml: xml
|
10
10
|
end
|
11
11
|
logout_urls.each do |item|
|
12
12
|
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
File without changes
|
@@ -2,11 +2,11 @@ xml.instruct!
|
|
2
2
|
xml.EntityDescriptor entity_descriptor_options do
|
3
3
|
signature_for(reference_id: id, xml: xml)
|
4
4
|
xml.SPSSODescriptor descriptor_options do
|
5
|
-
|
6
|
-
render
|
5
|
+
configuration.certificates(use: :signing).each do |certificate|
|
6
|
+
render certificate, xml: xml
|
7
7
|
end
|
8
|
-
|
9
|
-
render
|
8
|
+
configuration.certificates(use: :encryption).each do |certificate|
|
9
|
+
render certificate, xml: xml
|
10
10
|
end
|
11
11
|
logout_urls.each do |item|
|
12
12
|
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
@@ -21,11 +21,10 @@ module Saml
|
|
21
21
|
attr_reader :reference_id
|
22
22
|
attr_reader :x509_certificate
|
23
23
|
|
24
|
-
def initialize(reference_id, configuration
|
24
|
+
def initialize(reference_id, configuration:)
|
25
25
|
@configuration = configuration
|
26
26
|
@reference_id = reference_id
|
27
|
-
@
|
28
|
-
@x509_certificate = configuration.signing_certificate.stripped
|
27
|
+
@x509_certificate = configuration.certificates(use: :signing).sample.stripped
|
29
28
|
end
|
30
29
|
|
31
30
|
def signature_method
|
@@ -1,16 +1,19 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
class CompositeMetadata < Metadata
|
4
|
+
include Enumerable
|
4
5
|
attr_reader :service_provider, :identity_provider
|
5
6
|
|
6
7
|
def initialize(xml)
|
7
8
|
super("IDPSSODescriptor", xml)
|
8
|
-
@
|
9
|
-
|
9
|
+
@metadatum = [
|
10
|
+
Saml::Kit::ServiceProviderMetadata.new(xml),
|
11
|
+
Saml::Kit::IdentityProviderMetadata.new(xml),
|
12
|
+
]
|
10
13
|
end
|
11
14
|
|
12
15
|
def services(type)
|
13
|
-
xpath = "//md:EntityDescriptor/md
|
16
|
+
xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join("|")
|
14
17
|
document.find_all(xpath).map do |item|
|
15
18
|
binding = item.attribute("Binding").value
|
16
19
|
location = item.attribute("Location").value
|
@@ -19,14 +22,16 @@ module Saml
|
|
19
22
|
end
|
20
23
|
|
21
24
|
def certificates
|
22
|
-
|
25
|
+
flat_map(&:certificates)
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
@metadatum.each(&block)
|
23
30
|
end
|
24
31
|
|
25
32
|
def method_missing(name, *args)
|
26
|
-
if
|
27
|
-
|
28
|
-
elsif service_provider.respond_to?(name)
|
29
|
-
service_provider.public_send(name, *args)
|
33
|
+
if target = find { |x| x.respond_to?(name) }
|
34
|
+
target.public_send(name, *args)
|
30
35
|
else
|
31
36
|
super
|
32
37
|
end
|
@@ -3,37 +3,62 @@ module Saml
|
|
3
3
|
class Configuration
|
4
4
|
attr_accessor :issuer
|
5
5
|
attr_accessor :signature_method, :digest_method
|
6
|
-
attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
|
7
|
-
attr_accessor :encryption_certificate_pem, :encryption_private_key_pem, :encryption_private_key_password
|
8
6
|
attr_accessor :registry, :session_timeout
|
9
7
|
attr_accessor :logger
|
10
8
|
|
11
9
|
def initialize
|
12
10
|
@signature_method = :SHA256
|
13
11
|
@digest_method = :SHA256
|
14
|
-
@signing_private_key_password = SecureRandom.uuid
|
15
|
-
@encryption_private_key_password = SecureRandom.uuid
|
16
|
-
@signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
|
17
|
-
@encryption_certificate_pem, @encryption_private_key_pem = SelfSignedCertificate.new(@encryption_private_key_password).create
|
18
12
|
@registry = DefaultRegistry.new
|
19
13
|
@session_timeout = 3.hours
|
20
14
|
@logger = Logger.new(STDOUT)
|
15
|
+
yield self if block_given?
|
21
16
|
end
|
22
17
|
|
23
|
-
def
|
24
|
-
|
18
|
+
def add_key_pair(certificate, private_key, password:, use: :signing)
|
19
|
+
key_pairs.push({
|
20
|
+
certificate: Saml::Kit::Certificate.new(certificate, use: use),
|
21
|
+
private_key: OpenSSL::PKey::RSA.new(private_key, password)
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_key_pair_for(use:, password: SecureRandom.uuid)
|
26
|
+
certificate, private_key = SelfSignedCertificate.new(password).create
|
27
|
+
add_key_pair(certificate, private_key, password: password, use: use)
|
28
|
+
end
|
29
|
+
|
30
|
+
def certificates(use: nil)
|
31
|
+
certificates = key_pairs.map { |x| x[:certificate] }
|
32
|
+
use.present? ? certificates.find_all { |x| x.for?(use) } : certificates
|
33
|
+
end
|
34
|
+
|
35
|
+
def private_keys(use: :signing)
|
36
|
+
key_pairs.find_all { |x| x[:certificate].for?(use) }.map { |x| x[:private_key] }
|
25
37
|
end
|
26
38
|
|
27
39
|
def encryption_certificate
|
28
|
-
Saml::Kit
|
40
|
+
Saml::Kit.deprecate("encryption_certificate is deprecated. Use certificates(use: :encryption) instead")
|
41
|
+
certificates(use: :encryption).last
|
29
42
|
end
|
30
43
|
|
31
44
|
def signing_private_key
|
32
|
-
|
45
|
+
Saml::Kit.deprecate("signing_private_key is deprecated. Use private_keys(use: :signing) instead")
|
46
|
+
private_keys(use: :signing).last
|
33
47
|
end
|
34
48
|
|
35
49
|
def encryption_private_key
|
36
|
-
|
50
|
+
Saml::Kit.deprecate("encryption_private_key is deprecated. Use private_keys(use: :encryption) instead")
|
51
|
+
private_keys(use: :encryption).last
|
52
|
+
end
|
53
|
+
|
54
|
+
def sign?
|
55
|
+
certificates(use: :signing).any?
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def key_pairs
|
61
|
+
@key_pairs ||= []
|
37
62
|
end
|
38
63
|
end
|
39
64
|
end
|
data/lib/saml/kit/document.rb
CHANGED
@@ -12,9 +12,10 @@ module Saml
|
|
12
12
|
validate :must_be_expected_type
|
13
13
|
validate :must_be_valid_version
|
14
14
|
|
15
|
-
attr_reader :content, :name
|
15
|
+
attr_reader :content, :name, :configuration
|
16
16
|
|
17
|
-
def initialize(xml, name:)
|
17
|
+
def initialize(xml, name:, configuration: Saml::Kit.configuration)
|
18
|
+
@configuration = configuration
|
18
19
|
@content = xml
|
19
20
|
@name = name
|
20
21
|
@xml_hash = Hash.from_xml(xml) || {}
|
data/lib/saml/kit/locales/en.yml
CHANGED
@@ -11,6 +11,9 @@ en:
|
|
11
11
|
invalid_signature: "invalid signature."
|
12
12
|
InvalidDocument:
|
13
13
|
invalid: "must contain valid SAMLRequest"
|
14
|
+
LogoutRequest:
|
15
|
+
invalid_fingerprint: "does not match."
|
16
|
+
unregistered: "is unregistered."
|
14
17
|
LogoutResponse:
|
15
18
|
unregistered: "is unregistered."
|
16
19
|
Response:
|
@@ -4,8 +4,8 @@ module Saml
|
|
4
4
|
include Requestable
|
5
5
|
validates_presence_of :single_logout_service, if: :expected_type?
|
6
6
|
|
7
|
-
def initialize(xml)
|
8
|
-
super(xml, name: "LogoutRequest")
|
7
|
+
def initialize(xml, configuration: Saml::Kit.configuration)
|
8
|
+
super(xml, name: "LogoutRequest", configuration: configuration)
|
9
9
|
end
|
10
10
|
|
11
11
|
def name_id
|
data/lib/saml/kit/metadata.rb
CHANGED
@@ -91,8 +91,8 @@ module Saml
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def verify(algorithm, signature, data)
|
94
|
-
signing_certificates.find do |
|
95
|
-
|
94
|
+
signing_certificates.find do |certificate|
|
95
|
+
certificate.public_key.verify(algorithm, signature, data)
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
data/lib/saml/kit/response.rb
CHANGED
@@ -2,42 +2,16 @@ module Saml
|
|
2
2
|
module Kit
|
3
3
|
class Response < Document
|
4
4
|
include Respondable
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :assertion, :name_id, :[], :attributes, :started_at, :expired_at, :audiences
|
5
8
|
|
6
9
|
validate :must_be_active_session
|
7
10
|
validate :must_match_issuer
|
8
11
|
|
9
|
-
def initialize(xml, request_id: nil)
|
12
|
+
def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
|
10
13
|
@request_id = request_id
|
11
|
-
super(xml, name: "Response")
|
12
|
-
end
|
13
|
-
|
14
|
-
def name_id
|
15
|
-
assertion.fetch('Subject', {}).fetch('NameID', nil)
|
16
|
-
end
|
17
|
-
|
18
|
-
def [](key)
|
19
|
-
attributes[key]
|
20
|
-
end
|
21
|
-
|
22
|
-
def attributes
|
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
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def started_at
|
36
|
-
parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
|
37
|
-
end
|
38
|
-
|
39
|
-
def expired_at
|
40
|
-
parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
|
14
|
+
super(xml, name: "Response", configuration: configuration)
|
41
15
|
end
|
42
16
|
|
43
17
|
def expired?
|
@@ -48,29 +22,16 @@ module Saml
|
|
48
22
|
Time.current > started_at && !expired?
|
49
23
|
end
|
50
24
|
|
51
|
-
def encrypted?
|
52
|
-
to_h[name]['EncryptedAssertion'].present?
|
53
|
-
end
|
54
|
-
|
55
25
|
def assertion
|
56
|
-
@assertion =
|
57
|
-
begin
|
58
|
-
if encrypted?
|
59
|
-
decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
|
60
|
-
Saml::Kit.logger.debug(decrypted)
|
61
|
-
Hash.from_xml(decrypted)['Assertion']
|
62
|
-
else
|
63
|
-
to_h.fetch(name, {}).fetch('Assertion', {})
|
64
|
-
end
|
65
|
-
end
|
26
|
+
@assertion = Saml::Kit::Assertion.new(to_h, configuration: @configuration)
|
66
27
|
end
|
67
28
|
|
68
29
|
def signed?
|
69
|
-
super || assertion.
|
30
|
+
super || assertion.signed?
|
70
31
|
end
|
71
32
|
|
72
33
|
def certificate
|
73
|
-
super || assertion.
|
34
|
+
super || assertion.certificate
|
74
35
|
end
|
75
36
|
|
76
37
|
private
|
@@ -85,25 +46,11 @@ module Saml
|
|
85
46
|
return unless expected_type?
|
86
47
|
return unless success?
|
87
48
|
|
88
|
-
unless audiences.include?(
|
49
|
+
unless audiences.include?(configuration.issuer)
|
89
50
|
errors[:audience] << error_message(:must_match_issuer)
|
90
51
|
end
|
91
52
|
end
|
92
53
|
|
93
|
-
def audiences
|
94
|
-
Array(assertion['Conditions']['AudienceRestriction']['Audience'])
|
95
|
-
rescue => error
|
96
|
-
Saml::Kit.logger.error(error)
|
97
|
-
[]
|
98
|
-
end
|
99
|
-
|
100
|
-
def parse_date(value)
|
101
|
-
DateTime.parse(value)
|
102
|
-
rescue => error
|
103
|
-
Saml::Kit.logger.error(error)
|
104
|
-
Time.at(0).to_datetime
|
105
|
-
end
|
106
|
-
|
107
54
|
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
|
108
55
|
end
|
109
56
|
end
|
@@ -17,15 +17,6 @@ module Saml
|
|
17
17
|
certificate.public_key = public_key
|
18
18
|
certificate.serial = 0x0
|
19
19
|
certificate.version = 2
|
20
|
-
factory = OpenSSL::X509::ExtensionFactory.new
|
21
|
-
factory.subject_certificate = factory.issuer_certificate = certificate
|
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
|
-
)
|
29
20
|
certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
|
30
21
|
[
|
31
22
|
certificate.to_pem,
|
data/lib/saml/kit/signature.rb
CHANGED
@@ -1,36 +1,23 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
class Signature
|
4
|
-
attr_reader :
|
5
|
-
attr_reader :
|
4
|
+
attr_reader :signatures
|
5
|
+
attr_reader :xml
|
6
6
|
|
7
|
-
def initialize(xml,
|
8
|
-
@
|
9
|
-
@sign = sign
|
7
|
+
def initialize(xml, signatures)
|
8
|
+
@signatures = signatures
|
10
9
|
@xml = xml
|
11
10
|
end
|
12
11
|
|
13
12
|
def template(reference_id)
|
14
|
-
|
15
|
-
signature = signatures.build(reference_id)
|
16
|
-
Template.new(signature).to_xml(xml: xml)
|
13
|
+
Template.new(signatures.build(reference_id)).to_xml(xml: xml)
|
17
14
|
end
|
18
15
|
|
19
|
-
def
|
16
|
+
def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
|
17
|
+
signatures = Saml::Kit::Signatures.new(configuration: configuration)
|
18
|
+
yield xml, new(xml, signatures)
|
20
19
|
signatures.complete(xml.target!)
|
21
20
|
end
|
22
|
-
|
23
|
-
def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
|
24
|
-
signature = new(xml, sign: sign, configuration: configuration)
|
25
|
-
yield xml, signature
|
26
|
-
signature.finalize
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def signatures
|
32
|
-
@signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
|
33
|
-
end
|
34
21
|
end
|
35
22
|
end
|
36
23
|
end
|
data/lib/saml/kit/signatures.rb
CHANGED
@@ -1,28 +1,22 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
class Signatures
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :configuration
|
5
5
|
|
6
|
-
def initialize(configuration
|
6
|
+
def initialize(configuration:)
|
7
7
|
@configuration = configuration
|
8
|
-
@sign = sign
|
9
8
|
end
|
10
9
|
|
11
10
|
def build(reference_id)
|
12
|
-
|
11
|
+
return nil unless configuration.sign?
|
12
|
+
Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
|
13
13
|
end
|
14
14
|
|
15
15
|
def complete(raw_xml)
|
16
|
-
return raw_xml unless sign
|
17
|
-
|
16
|
+
return raw_xml unless configuration.sign?
|
17
|
+
private_key = configuration.private_keys(use: :signing).sample
|
18
18
|
Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
|
19
19
|
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def private_key
|
24
|
-
configuration.signing_private_key
|
25
|
-
end
|
26
20
|
end
|
27
21
|
end
|
28
22
|
end
|
data/lib/saml/kit/templatable.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
module Templatable
|
4
|
+
attr_accessor :sign
|
5
|
+
|
4
6
|
def to_xml(xml: ::Builder::XmlMarkup.new)
|
5
7
|
signatures.complete(render(self, xml: xml))
|
6
8
|
end
|
7
9
|
|
8
10
|
def signature_for(reference_id:, xml:)
|
9
|
-
return unless sign
|
11
|
+
return unless sign?
|
10
12
|
render(signatures.build(reference_id), xml: xml)
|
11
13
|
end
|
12
14
|
|
15
|
+
def sign?
|
16
|
+
sign.nil? ? configuration.sign? : sign && configuration.sign?
|
17
|
+
end
|
18
|
+
|
13
19
|
def signatures
|
14
|
-
@signatures ||= Saml::Kit::Signatures.new(configuration: configuration
|
20
|
+
@signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
|
15
21
|
end
|
16
22
|
|
17
23
|
def encryption_for(xml:)
|
data/lib/saml/kit/template.rb
CHANGED
@@ -18,7 +18,8 @@ module Saml
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def template_path
|
21
|
-
File.
|
21
|
+
root_path = File.expand_path(File.dirname(__FILE__))
|
22
|
+
File.join(root_path, "builders/templates/", template_name)
|
22
23
|
end
|
23
24
|
|
24
25
|
def template
|
data/lib/saml/kit/trustable.rb
CHANGED
data/lib/saml/kit/version.rb
CHANGED
data/lib/saml/kit/xml.rb
CHANGED
@@ -3,8 +3,8 @@ module Saml
|
|
3
3
|
class XmlDecryption
|
4
4
|
attr_reader :private_key
|
5
5
|
|
6
|
-
def initialize(
|
7
|
-
@private_key =
|
6
|
+
def initialize(configuration: Saml::Kit.configuration)
|
7
|
+
@private_key = configuration.private_keys(use: :encryption).sample
|
8
8
|
end
|
9
9
|
|
10
10
|
def decrypt(data)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saml-kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|
@@ -188,6 +188,7 @@ files:
|
|
188
188
|
- exe/saml-kit-decode-http-post
|
189
189
|
- exe/saml-kit-decode-http-redirect
|
190
190
|
- lib/saml/kit.rb
|
191
|
+
- lib/saml/kit/assertion.rb
|
191
192
|
- lib/saml/kit/authentication_request.rb
|
192
193
|
- lib/saml/kit/bindings.rb
|
193
194
|
- lib/saml/kit/bindings/binding.rb
|
@@ -209,6 +210,7 @@ files:
|
|
209
210
|
- lib/saml/kit/builders/templates/identity_provider_metadata.builder
|
210
211
|
- lib/saml/kit/builders/templates/logout_request.builder
|
211
212
|
- lib/saml/kit/builders/templates/logout_response.builder
|
213
|
+
- lib/saml/kit/builders/templates/nil_class.builder
|
212
214
|
- lib/saml/kit/builders/templates/response.builder
|
213
215
|
- lib/saml/kit/builders/templates/service_provider_metadata.builder
|
214
216
|
- lib/saml/kit/builders/templates/xml_encryption.builder
|