saml-kit 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|