saml-kit 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/exe/saml-kit-create-self-signed-certificate +21 -0
- data/lib/saml/kit.rb +9 -1
- data/lib/saml/kit/authentication_request.rb +9 -47
- data/lib/saml/kit/bindings/http_post.rb +0 -1
- data/lib/saml/kit/buildable.rb +21 -0
- data/lib/saml/kit/builders.rb +13 -0
- data/lib/saml/kit/builders/authentication_request.rb +48 -0
- data/lib/saml/kit/builders/identity_provider_metadata.rb +103 -0
- data/lib/saml/kit/builders/logout_request.rb +55 -0
- data/lib/saml/kit/builders/logout_response.rb +50 -0
- data/lib/saml/kit/builders/response.rb +182 -0
- data/lib/saml/kit/builders/service_provider_metadata.rb +89 -0
- data/lib/saml/kit/document.rb +16 -0
- data/lib/saml/kit/id.rb +9 -0
- data/lib/saml/kit/identity_provider_metadata.rb +11 -96
- data/lib/saml/kit/logout_request.rb +7 -53
- data/lib/saml/kit/logout_response.rb +1 -50
- data/lib/saml/kit/metadata.rb +9 -0
- data/lib/saml/kit/response.rb +11 -181
- data/lib/saml/kit/self_signed_certificate.rb +12 -5
- data/lib/saml/kit/service_provider_metadata.rb +5 -82
- data/lib/saml/kit/signature.rb +1 -1
- data/lib/saml/kit/trustable.rb +1 -5
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/{cryptography.rb → xml_decryption.rb} +1 -1
- data/saml-kit.gemspec +2 -2
- metadata +32 -21
@@ -0,0 +1,182 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
module Builders
|
4
|
+
class Response
|
5
|
+
attr_reader :user, :request
|
6
|
+
attr_accessor :id, :reference_id, :now
|
7
|
+
attr_accessor :version, :status_code
|
8
|
+
attr_accessor :issuer, :sign, :destination, :encrypt
|
9
|
+
|
10
|
+
def initialize(user, request)
|
11
|
+
@user = user
|
12
|
+
@request = request
|
13
|
+
@id = Id.generate
|
14
|
+
@reference_id = Id.generate
|
15
|
+
@now = Time.now.utc
|
16
|
+
@version = "2.0"
|
17
|
+
@status_code = Namespaces::SUCCESS
|
18
|
+
@issuer = configuration.issuer
|
19
|
+
@destination = destination_for(request)
|
20
|
+
@sign = want_assertions_signed
|
21
|
+
@encrypt = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def want_assertions_signed
|
25
|
+
request.provider.want_assertions_signed
|
26
|
+
rescue => error
|
27
|
+
Saml::Kit.logger.error(error)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_xml
|
32
|
+
Signature.sign(sign: sign) do |xml, signature|
|
33
|
+
xml.Response response_options do
|
34
|
+
xml.Issuer(issuer, xmlns: Namespaces::ASSERTION)
|
35
|
+
signature.template(id)
|
36
|
+
xml.Status do
|
37
|
+
xml.StatusCode Value: status_code
|
38
|
+
end
|
39
|
+
assertion(xml, signature)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build
|
45
|
+
Saml::Kit::Response.new(to_xml, request_id: request.id)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def assertion(xml, signature)
|
51
|
+
with_encryption(xml) do |xml|
|
52
|
+
xml.Assertion(assertion_options) do
|
53
|
+
xml.Issuer issuer
|
54
|
+
signature.template(reference_id) unless encrypt
|
55
|
+
xml.Subject do
|
56
|
+
xml.NameID user.name_id_for(request.name_id_format), Format: request.name_id_format
|
57
|
+
xml.SubjectConfirmation Method: Namespaces::BEARER do
|
58
|
+
xml.SubjectConfirmationData "", subject_confirmation_data_options
|
59
|
+
end
|
60
|
+
end
|
61
|
+
xml.Conditions conditions_options do
|
62
|
+
xml.AudienceRestriction do
|
63
|
+
xml.Audience request.issuer
|
64
|
+
end
|
65
|
+
end
|
66
|
+
xml.AuthnStatement authn_statement_options do
|
67
|
+
xml.AuthnContext do
|
68
|
+
xml.AuthnContextClassRef Namespaces::PASSWORD
|
69
|
+
end
|
70
|
+
end
|
71
|
+
assertion_attributes = user.assertion_attributes_for(request)
|
72
|
+
if assertion_attributes.any?
|
73
|
+
xml.AttributeStatement do
|
74
|
+
assertion_attributes.each do |key, value|
|
75
|
+
xml.Attribute Name: key, NameFormat: Namespaces::URI, FriendlyName: key do
|
76
|
+
xml.AttributeValue value.to_s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def with_encryption(xml)
|
86
|
+
if encrypt
|
87
|
+
temp = ::Builder::XmlMarkup.new
|
88
|
+
yield temp
|
89
|
+
raw_xml_to_encrypt = temp.target!
|
90
|
+
|
91
|
+
encryption_certificate = request.provider.encryption_certificates.first
|
92
|
+
public_key = encryption_certificate.public_key
|
93
|
+
|
94
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
95
|
+
cipher.encrypt
|
96
|
+
key = cipher.random_key
|
97
|
+
iv = cipher.random_iv
|
98
|
+
encrypted = cipher.update(raw_xml_to_encrypt) + cipher.final
|
99
|
+
|
100
|
+
Saml::Kit.logger.debug ['+iv', iv].inspect
|
101
|
+
Saml::Kit.logger.debug ['+key', key].inspect
|
102
|
+
|
103
|
+
xml.EncryptedAssertion xmlns: Namespaces::ASSERTION do
|
104
|
+
xml.EncryptedData xmlns: Namespaces::XMLENC do
|
105
|
+
xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#aes256-cbc"
|
106
|
+
xml.KeyInfo xmlns: Namespaces::XMLDSIG do
|
107
|
+
xml.EncryptedKey xmlns: Namespaces::XMLENC do
|
108
|
+
xml.EncryptionMethod Algorithm: "http://www.w3.org/2001/04/xmlenc#rsa-1_5"
|
109
|
+
xml.CipherData do
|
110
|
+
xml.CipherValue Base64.encode64(public_key.public_encrypt(key))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
xml.CipherData do
|
115
|
+
xml.CipherValue Base64.encode64(iv + encrypted)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
else
|
120
|
+
yield xml
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def destination_for(request)
|
125
|
+
if request.signed? && request.trusted?
|
126
|
+
request.assertion_consumer_service_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
|
127
|
+
else
|
128
|
+
request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def configuration
|
133
|
+
Saml::Kit.configuration
|
134
|
+
end
|
135
|
+
|
136
|
+
def response_options
|
137
|
+
{
|
138
|
+
ID: id,
|
139
|
+
Version: version,
|
140
|
+
IssueInstant: now.iso8601,
|
141
|
+
Destination: destination,
|
142
|
+
Consent: Namespaces::UNSPECIFIED,
|
143
|
+
InResponseTo: request.id,
|
144
|
+
xmlns: Namespaces::PROTOCOL,
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def assertion_options
|
149
|
+
{
|
150
|
+
ID: reference_id,
|
151
|
+
IssueInstant: now.iso8601,
|
152
|
+
Version: "2.0",
|
153
|
+
xmlns: Namespaces::ASSERTION,
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def subject_confirmation_data_options
|
158
|
+
{
|
159
|
+
InResponseTo: request.id,
|
160
|
+
NotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
161
|
+
Recipient: request.assertion_consumer_service_url,
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def conditions_options
|
166
|
+
{
|
167
|
+
NotBefore: now.utc.iso8601,
|
168
|
+
NotOnOrAfter: Saml::Kit.configuration.session_timeout.from_now.utc.iso8601,
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def authn_statement_options
|
173
|
+
{
|
174
|
+
AuthnInstant: now.iso8601,
|
175
|
+
SessionIndex: assertion_options[:ID],
|
176
|
+
SessionNotOnOrAfter: 3.hours.since(now).utc.iso8601,
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Saml
|
2
|
+
module Kit
|
3
|
+
module Builders
|
4
|
+
class ServiceProviderMetadata
|
5
|
+
attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
|
6
|
+
attr_accessor :want_assertions_signed
|
7
|
+
|
8
|
+
def initialize(configuration = Saml::Kit.configuration)
|
9
|
+
@id = Id.generate
|
10
|
+
@configuration = configuration
|
11
|
+
@entity_id = configuration.issuer
|
12
|
+
@acs_urls = []
|
13
|
+
@logout_urls = []
|
14
|
+
@name_id_formats = [Namespaces::PERSISTENT]
|
15
|
+
@sign = true
|
16
|
+
@want_assertions_signed = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_assertion_consumer_service(url, binding: :http_post)
|
20
|
+
@acs_urls.push(location: url, binding: Bindings.binding_for(binding))
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_single_logout_service(url, binding: :http_post)
|
24
|
+
@logout_urls.push(location: url, binding: Bindings.binding_for(binding))
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_xml
|
28
|
+
Signature.sign(sign: sign) do |xml, signature|
|
29
|
+
xml.instruct!
|
30
|
+
xml.EntityDescriptor entity_descriptor_options do
|
31
|
+
signature.template(id)
|
32
|
+
xml.SPSSODescriptor descriptor_options do
|
33
|
+
if @configuration.signing_certificate_pem.present?
|
34
|
+
xml.KeyDescriptor use: "signing" do
|
35
|
+
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
36
|
+
xml.X509Data do
|
37
|
+
xml.X509Certificate @configuration.stripped_signing_certificate
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
if @configuration.encryption_certificate_pem.present?
|
43
|
+
xml.KeyDescriptor use: "encryption" do
|
44
|
+
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
45
|
+
xml.X509Data do
|
46
|
+
xml.X509Certificate @configuration.stripped_encryption_certificate
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
logout_urls.each do |item|
|
52
|
+
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
53
|
+
end
|
54
|
+
name_id_formats.each do |format|
|
55
|
+
xml.NameIDFormat format
|
56
|
+
end
|
57
|
+
acs_urls.each_with_index do |item, index|
|
58
|
+
xml.AssertionConsumerService Binding: item[:binding], Location: item[:location], index: index, isDefault: index == 0 ? true : false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def build
|
66
|
+
Saml::Kit::ServiceProviderMetadata.new(to_xml)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def entity_descriptor_options
|
72
|
+
{
|
73
|
+
'xmlns': Namespaces::METADATA,
|
74
|
+
ID: id,
|
75
|
+
entityID: entity_id,
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def descriptor_options
|
80
|
+
{
|
81
|
+
AuthnRequestsSigned: sign,
|
82
|
+
WantAssertionsSigned: want_assertions_signed,
|
83
|
+
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/saml/kit/document.rb
CHANGED
@@ -5,6 +5,7 @@ module Saml
|
|
5
5
|
include XsdValidatable
|
6
6
|
include ActiveModel::Validations
|
7
7
|
include Trustable
|
8
|
+
include Buildable
|
8
9
|
validates_presence_of :content
|
9
10
|
validates_presence_of :id
|
10
11
|
validate :must_match_xsd
|
@@ -76,6 +77,21 @@ module Saml
|
|
76
77
|
Saml::Kit.logger.error(error)
|
77
78
|
InvalidDocument.new(xml)
|
78
79
|
end
|
80
|
+
|
81
|
+
def builder_class
|
82
|
+
case name
|
83
|
+
when Saml::Kit::Response.to_s
|
84
|
+
Saml::Kit::Builders::Response
|
85
|
+
when Saml::Kit::LogoutResponse.to_s
|
86
|
+
Saml::Kit::Builders::LogoutResponse
|
87
|
+
when Saml::Kit::AuthenticationRequest.to_s
|
88
|
+
Saml::Kit::Builders::AuthenticationRequest
|
89
|
+
when Saml::Kit::LogoutRequest.to_s
|
90
|
+
Saml::Kit::Builders::LogoutRequest
|
91
|
+
else
|
92
|
+
raise ArgumentError.new("Unknown SAML Document #{name}")
|
93
|
+
end
|
94
|
+
end
|
79
95
|
end
|
80
96
|
|
81
97
|
private
|
data/lib/saml/kit/id.rb
ADDED
@@ -29,105 +29,20 @@ module Saml
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
attr_accessor :want_authn_requests_signed, :sign
|
37
|
-
attr_reader :logout_urls, :single_sign_on_urls
|
38
|
-
|
39
|
-
def initialize(configuration = Saml::Kit.configuration)
|
40
|
-
@id = SecureRandom.uuid
|
41
|
-
@entity_id = configuration.issuer
|
42
|
-
@attributes = []
|
43
|
-
@name_id_formats = [Namespaces::PERSISTENT]
|
44
|
-
@single_sign_on_urls = []
|
45
|
-
@logout_urls = []
|
46
|
-
@configuration = configuration
|
47
|
-
@sign = true
|
48
|
-
@want_authn_requests_signed = true
|
49
|
-
end
|
50
|
-
|
51
|
-
def add_single_sign_on_service(url, binding: :http_post)
|
52
|
-
@single_sign_on_urls.push(location: url, binding: Bindings.binding_for(binding))
|
53
|
-
end
|
54
|
-
|
55
|
-
def add_single_logout_service(url, binding: :http_post)
|
56
|
-
@logout_urls.push(location: url, binding: Bindings.binding_for(binding))
|
57
|
-
end
|
58
|
-
|
59
|
-
def to_xml
|
60
|
-
Signature.sign(sign: sign) do |xml, signature|
|
61
|
-
xml.instruct!
|
62
|
-
xml.EntityDescriptor entity_descriptor_options do
|
63
|
-
signature.template(id)
|
64
|
-
xml.IDPSSODescriptor idp_sso_descriptor_options do
|
65
|
-
if @configuration.signing_certificate_pem.present?
|
66
|
-
xml.KeyDescriptor use: "signing" do
|
67
|
-
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
68
|
-
xml.X509Data do
|
69
|
-
xml.X509Certificate @configuration.stripped_signing_certificate
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
if @configuration.encryption_certificate_pem.present?
|
75
|
-
xml.KeyDescriptor use: "encryption" do
|
76
|
-
xml.KeyInfo "xmlns": Namespaces::XMLDSIG do
|
77
|
-
xml.X509Data do
|
78
|
-
xml.X509Certificate @configuration.stripped_encryption_certificate
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
logout_urls.each do |item|
|
84
|
-
xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
|
85
|
-
end
|
86
|
-
name_id_formats.each do |format|
|
87
|
-
xml.NameIDFormat format
|
88
|
-
end
|
89
|
-
single_sign_on_urls.each do |item|
|
90
|
-
xml.SingleSignOnService Binding: item[:binding], Location: item[:location]
|
91
|
-
end
|
92
|
-
attributes.each do |attribute|
|
93
|
-
xml.tag! 'saml:Attribute', Name: attribute
|
94
|
-
end
|
95
|
-
end
|
96
|
-
xml.Organization do
|
97
|
-
xml.OrganizationName organization_name, 'xml:lang': "en"
|
98
|
-
xml.OrganizationDisplayName organization_name, 'xml:lang': "en"
|
99
|
-
xml.OrganizationURL organization_url, 'xml:lang': "en"
|
100
|
-
end
|
101
|
-
xml.ContactPerson contactType: "technical" do
|
102
|
-
xml.Company "mailto:#{contact_email}"
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def build
|
109
|
-
IdentityProviderMetadata.new(to_xml)
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def entity_descriptor_options
|
115
|
-
{
|
116
|
-
'xmlns': Namespaces::METADATA,
|
117
|
-
'xmlns:ds': Namespaces::XMLDSIG,
|
118
|
-
'xmlns:saml': Namespaces::ASSERTION,
|
119
|
-
ID: "_#{id}",
|
120
|
-
entityID: entity_id,
|
121
|
-
}
|
32
|
+
def login_request_for(binding:, relay_state: nil)
|
33
|
+
builder = Saml::Kit::AuthenticationRequest.builder do |x|
|
34
|
+
x.sign = want_authn_requests_signed
|
35
|
+
yield x if block_given?
|
122
36
|
end
|
37
|
+
request_binding = single_sign_on_service_for(binding: binding)
|
38
|
+
request_binding.serialize(builder, relay_state: relay_state)
|
39
|
+
end
|
123
40
|
|
124
|
-
|
125
|
-
|
126
|
-
WantAuthnRequestsSigned: want_authn_requests_signed,
|
127
|
-
protocolSupportEnumeration: Namespaces::PROTOCOL,
|
128
|
-
}
|
129
|
-
end
|
41
|
+
def self.builder_class
|
42
|
+
Saml::Kit::Builders::IdentityProviderMetadata
|
130
43
|
end
|
44
|
+
|
45
|
+
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::IdentityProviderMetadata::Builder', 'Saml::Kit::Builders::IdentityProviderMetadata')
|
131
46
|
end
|
132
47
|
end
|
133
48
|
end
|
@@ -18,61 +18,15 @@ module Saml
|
|
18
18
|
urls.first
|
19
19
|
end
|
20
20
|
|
21
|
-
def response_for(user)
|
22
|
-
LogoutResponse
|
23
|
-
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
class Builder
|
28
|
-
attr_accessor :id, :destination, :issuer, :name_id_format, :now
|
29
|
-
attr_accessor :sign, :version
|
30
|
-
attr_reader :user
|
31
|
-
|
32
|
-
def initialize(user, configuration: Saml::Kit.configuration, sign: true)
|
33
|
-
@user = user
|
34
|
-
@id = SecureRandom.uuid
|
35
|
-
@issuer = configuration.issuer
|
36
|
-
@name_id_format = Saml::Kit::Namespaces::PERSISTENT
|
37
|
-
@now = Time.now.utc
|
38
|
-
@version = "2.0"
|
39
|
-
@sign = sign
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_xml
|
43
|
-
Signature.sign(sign: sign) do |xml, signature|
|
44
|
-
xml.instruct!
|
45
|
-
xml.LogoutRequest logout_request_options do
|
46
|
-
xml.Issuer({ xmlns: Namespaces::ASSERTION }, issuer)
|
47
|
-
signature.template(id)
|
48
|
-
xml.NameID name_id_options, user.name_id_for(name_id_format)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def build
|
54
|
-
Saml::Kit::LogoutRequest.new(to_xml)
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
def logout_request_options
|
60
|
-
{
|
61
|
-
ID: "_#{id}",
|
62
|
-
Version: version,
|
63
|
-
IssueInstant: now.utc.iso8601,
|
64
|
-
Destination: destination,
|
65
|
-
xmlns: Namespaces::PROTOCOL,
|
66
|
-
}
|
67
|
-
end
|
68
|
-
|
69
|
-
def name_id_options
|
70
|
-
{
|
71
|
-
Format: name_id_format,
|
72
|
-
xmlns: Namespaces::ASSERTION,
|
73
|
-
}
|
21
|
+
def response_for(user, binding:, relay_state: nil)
|
22
|
+
builder = Saml::Kit::LogoutResponse.builder(user, self) do |x|
|
23
|
+
yield x if block_given?
|
74
24
|
end
|
25
|
+
response_binding = provider.single_logout_service_for(binding: binding)
|
26
|
+
response_binding.serialize(builder, relay_state: relay_state)
|
75
27
|
end
|
28
|
+
|
29
|
+
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutRequest::Builder', 'Saml::Kit::Builders::LogoutRequest')
|
76
30
|
end
|
77
31
|
end
|
78
32
|
end
|