saml-kit 0.2.11 → 0.2.12
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 +5 -5
- data/README.md +218 -4
- data/lib/saml/kit/authentication_request.rb +27 -0
- data/lib/saml/kit/buildable.rb +3 -3
- data/lib/saml/kit/builders/assertion.rb +2 -2
- data/lib/saml/kit/builders/authentication_request.rb +2 -0
- data/lib/saml/kit/builders/logout_response.rb +1 -2
- data/lib/saml/kit/composite_metadata.rb +1 -1
- data/lib/saml/kit/configuration.rb +54 -4
- data/lib/saml/kit/default_registry.rb +16 -3
- data/lib/saml/kit/document.rb +25 -9
- data/lib/saml/kit/fingerprint.rb +6 -0
- data/lib/saml/kit/identity_provider_metadata.rb +27 -1
- data/lib/saml/kit/key_pair.rb +1 -1
- data/lib/saml/kit/logout_request.rb +18 -8
- data/lib/saml/kit/logout_response.rb +2 -0
- data/lib/saml/kit/requestable.rb +1 -0
- data/lib/saml/kit/respondable.rb +4 -0
- data/lib/saml/kit/signatures.rb +10 -2
- data/lib/saml/kit/translatable.rb +1 -0
- data/lib/saml/kit/trustable.rb +3 -0
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml.rb +2 -2
- data/lib/saml/kit/xsd_validatable.rb +1 -0
- data/saml-kit.gemspec +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6a933c25128ff1220caca50012fa928dc9512d9a
|
4
|
+
data.tar.gz: 9460788814e60d65169adb762ee084aa71743555
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9eb5d4474d9e4e4dbcb76fa37afc3e819863c00b239df34b0a504b0f008cb5fce0ac0e64211484a00eaf2a868073f9f8be8f61817440ea1ca914c35e241b02f
|
7
|
+
data.tar.gz: 23a961b54e7ad38e9c4fabd85550f4a0e8e5d64a87e2254e91cd70edee38dba14e0392e8ca9addb156c2ce2f4c6f3ba533f6b6323a6ea203d561c955892e43cb
|
data/README.md
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# Saml::Kit
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Saml::Kit is a library with the purpose of creating and consuming SAML
|
4
|
+
documents. It supports the HTTP Post and HTTP Redirect bindings. It can
|
5
|
+
create Service Provider Metadata, Identity Provider Metadata,
|
6
|
+
AuthnRequest, Response, LogoutRequest, LogoutResponse documents.
|
7
|
+
It also supports generating signed and encrypted assertions.
|
6
8
|
|
7
9
|
## Installation
|
8
10
|
|
@@ -22,7 +24,219 @@ Or install it yourself as:
|
|
22
24
|
|
23
25
|
## Usage
|
24
26
|
|
25
|
-
|
27
|
+
To specify a global configuration: (useful for a rails application)
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
Saml::Kit.configure do |configuration|
|
31
|
+
configuration.issuer = ENV['ISSUER']
|
32
|
+
configuration.generate_key_pair_for(use: :signing)
|
33
|
+
configuration.generate_key_pair_for(use: :signing)
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Metadata
|
38
|
+
|
39
|
+
To generate metadata for an Identity Provider.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
Saml::Kit::Metadata.build_xml do |builder|
|
43
|
+
builder.contact_email = 'hi@example.com'
|
44
|
+
builder.organization_name = "Acme, Inc"
|
45
|
+
builder.organization_url = 'https://www.example.com'
|
46
|
+
builder.build_identity_provider do |x|
|
47
|
+
x.add_single_sign_on_service('https://www.example.com/login', binding: :http_post)
|
48
|
+
x.add_single_sign_on_service('https://www.example.com/login', binding: :http_redirect)
|
49
|
+
x.add_single_logout_service('https://www.example.com/logout', binding: :http_post)
|
50
|
+
x.name_id_formats = [ Saml::Kit::Namespaces::EMAIL_ADDRESS ]
|
51
|
+
x.attributes << :id
|
52
|
+
x.attributes << :email
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Will produce something like:
|
58
|
+
|
59
|
+
```xml
|
60
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
61
|
+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_efe0c000-8d0d-4406-96b8-61f649e004f6" entityID="">
|
62
|
+
<IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
63
|
+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
|
64
|
+
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
65
|
+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/login"/>
|
66
|
+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://www.example.com/login"/>
|
67
|
+
<saml:Attribute Name="id"/>
|
68
|
+
<saml:Attribute Name="email"/>
|
69
|
+
</IDPSSODescriptor>
|
70
|
+
<Organization>
|
71
|
+
<OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
|
72
|
+
<OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
|
73
|
+
<OrganizationURL xml:lang="en">https://www.example.com</OrganizationURL>
|
74
|
+
</Organization>
|
75
|
+
<ContactPerson contactType="technical">
|
76
|
+
<Company>mailto:hi@example.com</Company>
|
77
|
+
</ContactPerson>
|
78
|
+
</EntityDescriptor>
|
79
|
+
```
|
80
|
+
|
81
|
+
To generate service provider metadata:
|
82
|
+
|
83
|
+
```xml
|
84
|
+
metadata = Saml::Kit::Metadata.build do |builder|
|
85
|
+
builder.contact_email = 'hi@example.com'
|
86
|
+
builder.organization_name = "Acme, Inc"
|
87
|
+
builder.organization_url = 'https://www.example.com'
|
88
|
+
builder.build_service_provider do |x|
|
89
|
+
x.add_assertion_consumer_service('https://www.example.com/consume', binding: :http_post)
|
90
|
+
x.add_single_logout_service('https://www.example.com/logout', binding: :http_post)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
puts metadata.to_xml(pretty: true)
|
94
|
+
```
|
95
|
+
|
96
|
+
Will produce something like:
|
97
|
+
|
98
|
+
```xml
|
99
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
100
|
+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_3ff5e4b3-4fce-4cc9-b278-6cb3a0a8cb10" entityID="">
|
101
|
+
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
102
|
+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
|
103
|
+
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
104
|
+
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/consume" index="0" isDefault="true"/>
|
105
|
+
</SPSSODescriptor>
|
106
|
+
<Organization>
|
107
|
+
<OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
|
108
|
+
<OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
|
109
|
+
<OrganizationURL xml:lang="en">https://www.example.com</OrganizationURL>
|
110
|
+
</Organization>
|
111
|
+
<ContactPerson contactType="technical">
|
112
|
+
<Company>mailto:hi@example.com</Company>
|
113
|
+
</ContactPerson>
|
114
|
+
</EntityDescriptor>
|
115
|
+
```
|
116
|
+
|
117
|
+
To produce Metadata with an IDPSSODescriptor and SPSSODescriptor.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
metadata = Saml::Kit::Metadata.build do |builder|
|
121
|
+
builder.contact_email = 'hi@example.com'
|
122
|
+
builder.organization_name = "Acme, Inc"
|
123
|
+
builder.organization_url = 'https://www.example.com'
|
124
|
+
builder.build_identity_provider do |x|
|
125
|
+
x.add_single_sign_on_service('https://www.example.com/login', binding: :http_post)
|
126
|
+
x.add_single_sign_on_service('https://www.example.com/login', binding: :http_redirect)
|
127
|
+
x.add_single_logout_service('https://www.example.com/logout', binding: :http_post)
|
128
|
+
x.name_id_formats = [ Saml::Kit::Namespaces::EMAIL_ADDRESS ]
|
129
|
+
x.attributes << :id
|
130
|
+
x.attributes << :email
|
131
|
+
end
|
132
|
+
builder.build_service_provider do |x|
|
133
|
+
x.add_assertion_consumer_service('https://www.example.com/consume', binding: :http_post)
|
134
|
+
x.add_single_logout_service('https://www.example.com/logout', binding: :http_post)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
puts metadata.to_xml(pretty: true)
|
138
|
+
```
|
139
|
+
|
140
|
+
```xml
|
141
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
142
|
+
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_a29a3a9d-ad16-4839-8f5d-a59daed6f3ce" entityID="">
|
143
|
+
<IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
144
|
+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
|
145
|
+
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
146
|
+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/login"/>
|
147
|
+
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://www.example.com/login"/>
|
148
|
+
<saml:Attribute Name="id"/>
|
149
|
+
<saml:Attribute Name="email"/>
|
150
|
+
</IDPSSODescriptor>
|
151
|
+
<SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
152
|
+
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
|
153
|
+
<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
|
154
|
+
<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/consume" index="0" isDefault="true"/>
|
155
|
+
</SPSSODescriptor>
|
156
|
+
<Organization>
|
157
|
+
<OrganizationName xml:lang="en">Acme, Inc</OrganizationName>
|
158
|
+
<OrganizationDisplayName xml:lang="en">Acme, Inc</OrganizationDisplayName>
|
159
|
+
<OrganizationURL xml:lang="en">https://www.example.com</OrganizationURL>
|
160
|
+
</Organization>
|
161
|
+
<ContactPerson contactType="technical">
|
162
|
+
<Company>mailto:hi@example.com</Company>
|
163
|
+
</ContactPerson>
|
164
|
+
</EntityDescriptor>
|
165
|
+
```
|
166
|
+
|
167
|
+
### AuthnRequest
|
168
|
+
|
169
|
+
To generate an Authentication Request choose the desired binding from
|
170
|
+
the metadata and use it to serialize a request.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
idp = Saml::Kit::IdentityProviderMetadata.new(raw_xml)
|
174
|
+
url, saml_params = idp.login_request_for(binding: :http_post)
|
175
|
+
puts [url, saml_params].inspect
|
176
|
+
# ["https://www.example.com/login", {"SAMLRequest"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbHA6QXV0aG5SZXF1ZXN0IHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfN2Y0YjkxZGMtNTMyNi00NjgzLTgyOWItYWViNzlkNjM0ZWYzIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNy0xMi0xOVQwNDo0ODoxMloiIERlc3RpbmF0aW9uPSJodHRwczovL3d3dy5leGFtcGxlLmNvbS9sb2dpbiI+PHNhbWw6SXNzdWVyLz48c2FtbHA6TmFtZUlEUG9saWN5IEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6cGVyc2lzdGVudCIvPjwvc2FtbHA6QXV0aG5SZXF1ZXN0Pg=="}]
|
177
|
+
```
|
178
|
+
|
179
|
+
### Response
|
180
|
+
|
181
|
+
To generate a Response you will need a request object and the desired binding
|
182
|
+
to serialize a response. You will also need to specify a user
|
183
|
+
object to create a response for.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
binding = idp.single_sign_on_service_for(binding: :http_post)
|
187
|
+
raw_params = Hash[uri.query.split("&").map { |x| x.split("=", 2) }].symbolize_keys
|
188
|
+
saml_request = binding.deserialize(raw_params)
|
189
|
+
|
190
|
+
url, saml_params = saml_request.response_for(user, binding: :http_post)
|
191
|
+
puts [url, saml_params].inspect
|
192
|
+
# ["https://www.example.com/consume", {"SAMLResponse"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48UmVzcG9uc2UgSUQ9Il9hZjFiNTg5Ni0wN2MzLTQ2Y2QtYTA5ZC0xOTRmZGNkNWZiZmYiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE3LTEyLTE5VDA1OjI5OjU0WiIgRGVzdGluYXRpb249Imh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2NvbnN1bWUiIENvbnNlbnQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpjb25zZW50OnVuc3BlY2lmaWVkIiBJblJlc3BvbnNlVG89Il9mYzg5MjllOC0zY2ZkLTQ5YmQtOTgzNi0xNTRhZGYzOTEzZjYiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiPjxJc3N1ZXIgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iLz48U3RhdHVzPjxTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L1N0YXR1cz48QXNzZXJ0aW9uIElEPSJfYjg4OWNmNzEtYTFmNS00ZWUxLWEzZTctMGM4ZTU5ZDY3ZTJkIiBJc3N1ZUluc3RhbnQ9IjIwMTctMTItMTlUMDU6Mjk6NTRaIiBWZXJzaW9uPSIyLjAiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj48SXNzdWVyLz48U3ViamVjdD48TmFtZUlEIEZvcm1hdD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOm5hbWVpZC1mb3JtYXQ6cGVyc2lzdGVudCI+Yjk2ODE1MDA
|
193
|
+
```
|
194
|
+
|
195
|
+
### LogoutRequest
|
196
|
+
|
197
|
+
To create a logout request you will need to choose the desired binding
|
198
|
+
from the metadata then generate a request for a specific user.
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
class User
|
202
|
+
attr_reader :id, :email
|
203
|
+
|
204
|
+
def initialize(id:, email:)
|
205
|
+
@id = id
|
206
|
+
@email = email
|
207
|
+
end
|
208
|
+
|
209
|
+
def name_id_for(name_id_format)
|
210
|
+
Saml::Kit::Namespaces::PERSISTENT == name_id_format ? id : email
|
211
|
+
end
|
212
|
+
|
213
|
+
def assertion_attributes_for(request)
|
214
|
+
request.trusted? ? { access_token: SecureRandom.uuid } : {}
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
user = User.new(id: SecureRandom.uuid, email: "hello@example.com")
|
219
|
+
sp = Saml::Kit::IdentityProviderMetadata.new(xml)
|
220
|
+
url, saml_params = sp.logout_request_for(user, binding: :http_post)
|
221
|
+
puts [url, saml_params].inspect
|
222
|
+
# ["https://www.example.com/logout", {"SAMLRequest"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48TG9nb3V0UmVxdWVzdCBJRD0iXzg3NjZiNTYyLTc2MzQtNDU4Zi04MzJmLTE4ODkwMjRlZDQ0MyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTctMTItMTlUMDQ6NTg6MThaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20vbG9nb3V0IiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIi8+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5kODc3YWEzZS01YTUyLTRhODAtYTA3ZC1lM2U5YzBjNTA1Nzk8L05hbWVJRD48L0xvZ291dFJlcXVlc3Q+"}]
|
223
|
+
```
|
224
|
+
|
225
|
+
### LogoutResponse
|
226
|
+
|
227
|
+
To generate a logout response, deserialize the logout request then
|
228
|
+
generate a response from the request.
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
idp = Saml::Kit::IdentityProviderMetadata.new(xml)
|
232
|
+
raw_params = Hash[uri.query.split("&").map { |x| x.split("=", 2) }].symbolize_keys
|
233
|
+
|
234
|
+
binding = idp.single_logout_service_for(binding: :http_post)
|
235
|
+
saml_request = binding.deserialize(raw_params)
|
236
|
+
url, saml_params = saml_request.response_for(binding: :http_post)
|
237
|
+
puts [url, saml_params].inspect
|
238
|
+
# ["https://www.example.com/logout", {"SAMLResponse"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48TG9nb3V0UmVzcG9uc2UgeG1sbnM9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCIgSUQ9Il9kZDA2YmY5MC04ODI2LTQ5ZTMtYmYxNS1jYzAxMWJkNzU3NGEiIFZlcnNpb249IjIuMCIgSXNzdWVJbnN0YW50PSIyMDE3LTEyLTE5VDA1OjQyOjQyWiIgRGVzdGluYXRpb249Imh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2xvZ291dCIgSW5SZXNwb25zZVRvPSJfYmVhZjJiN2ItMDlmNC00ZmFkLWJkYmYtOWQ0ZDc1N2I5ZDU0Ij48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIi8+PFN0YXR1cz48U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9TdGF0dXM+PC9Mb2dvdXRSZXNwb25zZT4="}]
|
239
|
+
```
|
26
240
|
|
27
241
|
## Development
|
28
242
|
|
@@ -1,20 +1,46 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This class can be used to parse a SAML AuthnRequest or generate one.
|
4
|
+
#
|
5
|
+
# To generate an AuthnRequest use the builder API.
|
6
|
+
#
|
7
|
+
# request = AuthenticationRequest.build do |builder|
|
8
|
+
# builder.name_id_format = [Saml::Kit::Namespaces::EMAIL_ADDRESS]
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
12
|
+
# <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_ca3a0e72-9530-41f1-9518-c53716de88b2" Version="2.0" IssueInstant="2017-12-19T16:27:44Z" Destination="http://hartmann.info" AssertionConsumerServiceURL="https://carroll.com/acs">
|
13
|
+
# <saml:Issuer>Day of the Dangerous Cousins</saml:Issuer>
|
14
|
+
# <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
|
15
|
+
# </samlp:AuthnRequest>
|
3
16
|
class AuthenticationRequest < Document
|
4
17
|
include Requestable
|
5
18
|
|
19
|
+
# Create an instance of an AuthnRequest document.
|
20
|
+
#
|
21
|
+
# @param xml [String] the raw xml.
|
22
|
+
# @param configuration [Saml::Kit::Configuration] defaults to the global configuration.
|
6
23
|
def initialize(xml, configuration: Saml::Kit.configuration)
|
7
24
|
super(xml, name: "AuthnRequest", configuration: configuration)
|
8
25
|
end
|
9
26
|
|
27
|
+
# Extract the AssertionConsumerServiceURL from the AuthnRequest
|
28
|
+
# <samlp:AuthnRequest AssertionConsumerServiceURL="https://carroll.com/acs"></samlp:AuthnRequest>
|
10
29
|
def assertion_consumer_service_url
|
11
30
|
to_h[name]['AssertionConsumerServiceURL']
|
12
31
|
end
|
13
32
|
|
33
|
+
# Extract the NameIDPolicy from the AuthnRequest
|
34
|
+
# <samlp:AuthnRequest>
|
35
|
+
# <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
|
36
|
+
# </samlp:AuthnRequest>
|
14
37
|
def name_id_format
|
15
38
|
to_h[name]['NameIDPolicy']['Format']
|
16
39
|
end
|
17
40
|
|
41
|
+
# Generate a Response for a specific user.
|
42
|
+
# @param user [Object] this is a custom user object that can be used for generating a nameid and assertion attributes.
|
43
|
+
# @param binding [Symbol] the SAML binding to use `:http_post` or `:http_redirect`.
|
18
44
|
def response_for(user, binding:, relay_state: nil)
|
19
45
|
response_binding = provider.assertion_consumer_service_for(binding: binding)
|
20
46
|
builder = Saml::Kit::Response.builder(user, self) do |x|
|
@@ -24,6 +50,7 @@ module Saml
|
|
24
50
|
response_binding.serialize(builder, relay_state: relay_state)
|
25
51
|
end
|
26
52
|
|
53
|
+
# @deprecated Use {#Saml::Kit::Builders::AuthenticationRequest} instead of this.
|
27
54
|
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::AuthenticationRequest::Builder', 'Saml::Kit::Builders::AuthenticationRequest')
|
28
55
|
end
|
29
56
|
end
|
data/lib/saml/kit/buildable.rb
CHANGED
@@ -4,15 +4,15 @@ module Saml
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
class_methods do
|
7
|
-
def build(*args, &block)
|
7
|
+
def build(*args, &block) # :yields builder
|
8
8
|
builder(*args, &block).build
|
9
9
|
end
|
10
10
|
|
11
|
-
def build_xml(*args, &block)
|
11
|
+
def build_xml(*args, &block) # :yields builder
|
12
12
|
builder(*args, &block).to_xml
|
13
13
|
end
|
14
14
|
|
15
|
-
def builder(*args)
|
15
|
+
def builder(*args) # :yields builder
|
16
16
|
builder_class.new(*args).tap do |builder|
|
17
17
|
yield builder if block_given?
|
18
18
|
end
|
@@ -45,7 +45,7 @@ module Saml
|
|
45
45
|
def conditions_options
|
46
46
|
{
|
47
47
|
NotBefore: now.utc.iso8601,
|
48
|
-
NotOnOrAfter: configuration.session_timeout.
|
48
|
+
NotOnOrAfter: configuration.session_timeout.since(now).utc.iso8601,
|
49
49
|
}
|
50
50
|
end
|
51
51
|
|
@@ -53,7 +53,7 @@ module Saml
|
|
53
53
|
{
|
54
54
|
AuthnInstant: now.iso8601,
|
55
55
|
SessionIndex: reference_id,
|
56
|
-
SessionNotOnOrAfter:
|
56
|
+
SessionNotOnOrAfter: configuration.session_timeout.since(now).utc.iso8601,
|
57
57
|
}
|
58
58
|
end
|
59
59
|
end
|
@@ -16,11 +16,13 @@ module Saml
|
|
16
16
|
@version = "2.0"
|
17
17
|
end
|
18
18
|
|
19
|
+
# @deprecated Use {#assertion_consumer_service_url} instead of this method.
|
19
20
|
def acs_url
|
20
21
|
Saml::Kit.deprecate("acs_url is deprecated. Use assertion_consumer_service_url instead")
|
21
22
|
self.assertion_consumer_service_url
|
22
23
|
end
|
23
24
|
|
25
|
+
# @deprecated Use {#assertion_consumer_service_url=} instead of this method.
|
24
26
|
def acs_url=(value)
|
25
27
|
Saml::Kit.deprecate("acs_url= is deprecated. Use assertion_consumer_service_url= instead")
|
26
28
|
self.assertion_consumer_service_url = value
|
@@ -7,14 +7,13 @@ module Saml
|
|
7
7
|
attr_reader :request
|
8
8
|
attr_reader :configuration
|
9
9
|
|
10
|
-
def initialize(
|
10
|
+
def initialize(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
16
|
@status_code = Namespaces::SUCCESS
|
17
|
-
@user = user
|
18
17
|
@version = "2.0"
|
19
18
|
end
|
20
19
|
|
@@ -1,12 +1,39 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This class represents the main configuration that is use for generating SAML documents.
|
4
|
+
#
|
5
|
+
# Saml::Kit::Configuration.new do |config|
|
6
|
+
# config.issuer = "com:saml:kit"
|
7
|
+
# config.signature_method = :SHA256
|
8
|
+
# config.digest_method = :SHA256
|
9
|
+
# config.registry = Saml::Kit::DefaultRegistry.new
|
10
|
+
# config.session_timeout = 30.minutes
|
11
|
+
# config.logger = Rails.logger
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# To specify global configuration it is best to do this in an initialize
|
15
|
+
# that runs at the start of the program.
|
16
|
+
#
|
17
|
+
# Saml::Kit.configure do |configuration|
|
18
|
+
# configuration.issuer = "https://www.example.com/saml/metadata"
|
19
|
+
# configuration.generate_key_pair_for(use: :signing)
|
20
|
+
# configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], password: ENV['PRIVATE_KEY_PASSWORD'], use: :encryption)
|
21
|
+
# end
|
3
22
|
class Configuration
|
23
|
+
# The issuer or entity_id to use.
|
4
24
|
attr_accessor :issuer
|
5
|
-
|
6
|
-
attr_accessor :
|
25
|
+
# The signature method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
|
26
|
+
attr_accessor :signature_method
|
27
|
+
# The digest method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::DIGEST_METHODS})
|
28
|
+
attr_accessor :digest_method
|
29
|
+
# The metadata registry to use for searching for metadata associated with an issuer.
|
30
|
+
attr_accessor :registry
|
31
|
+
# The session timeout to use when generating an Assertion.
|
32
|
+
attr_accessor :session_timeout
|
33
|
+
# The logger to write log messages to.
|
7
34
|
attr_accessor :logger
|
8
35
|
|
9
|
-
def initialize
|
36
|
+
def initialize # :yields configuration
|
10
37
|
@signature_method = :SHA256
|
11
38
|
@digest_method = :SHA256
|
12
39
|
@registry = DefaultRegistry.new
|
@@ -16,42 +43,65 @@ module Saml
|
|
16
43
|
yield self if block_given?
|
17
44
|
end
|
18
45
|
|
19
|
-
|
46
|
+
# Add a key pair that can be used for either signing or encryption.
|
47
|
+
#
|
48
|
+
# @param certificate [String] the x509 certificate with public key.
|
49
|
+
# @param private_key [String] the plain text private key.
|
50
|
+
# @param password [String] the password to decrypt the private key.
|
51
|
+
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
|
52
|
+
def add_key_pair(certificate, private_key, password: '', use: :signing)
|
20
53
|
@key_pairs.push(KeyPair.new(certificate, private_key, password, use.to_sym))
|
21
54
|
end
|
22
55
|
|
56
|
+
# Generates a unique key pair that can be used for signing or encryption.
|
57
|
+
#
|
58
|
+
# @param use [Symbol] the type of key pair, `:signing` or `:encryption`
|
59
|
+
# @param password [String] the private key password to use.
|
23
60
|
def generate_key_pair_for(use:, password: SecureRandom.uuid)
|
24
61
|
certificate, private_key = SelfSignedCertificate.new(password).create
|
25
62
|
add_key_pair(certificate, private_key, password: password, use: use)
|
26
63
|
end
|
27
64
|
|
65
|
+
# Return each key pair for a specific use.
|
66
|
+
#
|
67
|
+
# @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
|
28
68
|
def key_pairs(use: nil)
|
29
69
|
use.present? ? @key_pairs.find_all { |x| x.for?(use) } : @key_pairs
|
30
70
|
end
|
31
71
|
|
72
|
+
# Return each certificate for a specific use.
|
73
|
+
#
|
74
|
+
# @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
|
32
75
|
def certificates(use: nil)
|
33
76
|
key_pairs(use: use).flat_map(&:certificate)
|
34
77
|
end
|
35
78
|
|
79
|
+
# Return each private for a specific use.
|
80
|
+
#
|
81
|
+
# @param use [Symbol] the type of key pair to return `nil`, `:signing` or `:encryption`
|
36
82
|
def private_keys(use: :signing)
|
37
83
|
key_pairs(use: use).flat_map(&:private_key)
|
38
84
|
end
|
39
85
|
|
86
|
+
# @deprecated Use {#certificates} instead of this method.
|
40
87
|
def encryption_certificate
|
41
88
|
Saml::Kit.deprecate("encryption_certificate is deprecated. Use certificates(use: :encryption) instead")
|
42
89
|
certificates(use: :encryption).last
|
43
90
|
end
|
44
91
|
|
92
|
+
# @deprecated Use {#private_keys} instead of this method.
|
45
93
|
def signing_private_key
|
46
94
|
Saml::Kit.deprecate("signing_private_key is deprecated. Use private_keys(use: :signing) instead")
|
47
95
|
private_keys(use: :signing).last
|
48
96
|
end
|
49
97
|
|
98
|
+
# @deprecated Use {#private_keys} instead of this method.
|
50
99
|
def encryption_private_key
|
51
100
|
Saml::Kit.deprecate("encryption_private_key is deprecated. Use private_keys(use: :encryption) instead")
|
52
101
|
private_keys(use: :encryption).last
|
53
102
|
end
|
54
103
|
|
104
|
+
# Returns true if there is at least one signing certificate registered.
|
55
105
|
def sign?
|
56
106
|
certificates(use: :signing).any?
|
57
107
|
end
|
@@ -1,27 +1,38 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# The default metadata registry is used to fetch the metadata associated with an issuer or entity id.
|
4
|
+
# The metadata associated with an issuer is used to verify trust for any SAML documents that are received.
|
3
5
|
class DefaultRegistry
|
4
6
|
def initialize(items = {})
|
5
7
|
@items = items
|
6
8
|
end
|
7
9
|
|
10
|
+
# Register a metadata document
|
11
|
+
#
|
12
|
+
# @param metadata [Saml::Kit::Metadata] the metadata to register.
|
8
13
|
def register(metadata)
|
9
14
|
Saml::Kit.logger.debug(metadata.to_xml(pretty: true))
|
10
15
|
@items[metadata.entity_id] = metadata
|
11
16
|
end
|
12
17
|
|
18
|
+
# Register metadata via a remote URL.
|
19
|
+
# This will attempt to connect to the remove URL to download the metadata and register it in the registry.
|
20
|
+
#
|
21
|
+
# @param url [String] the url to download the metadata from.
|
22
|
+
# @param verify_ssl [Boolean] enable/disable SSL peer verification.
|
13
23
|
def register_url(url, verify_ssl: true)
|
14
24
|
content = HttpApi.new(url, verify_ssl: verify_ssl).get
|
15
25
|
register(Saml::Kit::Metadata.from(content))
|
16
26
|
end
|
17
27
|
|
28
|
+
# Returns the metadata document associated with an issuer or entityID.
|
29
|
+
#
|
30
|
+
# @param entity_id [String] the unique entityID/Issuer associated with metadata.
|
18
31
|
def metadata_for(entity_id)
|
19
32
|
@items[entity_id]
|
20
33
|
end
|
21
34
|
|
22
|
-
class HttpApi
|
23
|
-
attr_reader :uri, :verify_ssl
|
24
|
-
|
35
|
+
class HttpApi # :nodoc:
|
25
36
|
def initialize(url, verify_ssl: true)
|
26
37
|
@uri = URI.parse(url)
|
27
38
|
@verify_ssl = verify_ssl
|
@@ -37,6 +48,8 @@ module Saml
|
|
37
48
|
|
38
49
|
private
|
39
50
|
|
51
|
+
attr_reader :uri, :verify_ssl
|
52
|
+
|
40
53
|
def http
|
41
54
|
http = Net::HTTP.new(uri.host, uri.port)
|
42
55
|
http.read_timeout = 30
|
data/lib/saml/kit/document.rb
CHANGED
@@ -13,8 +13,6 @@ module Saml
|
|
13
13
|
validate :must_be_expected_type
|
14
14
|
validate :must_be_valid_version
|
15
15
|
|
16
|
-
attr_reader :content, :name, :configuration
|
17
|
-
|
18
16
|
def initialize(xml, name:, configuration: Saml::Kit.configuration)
|
19
17
|
@configuration = configuration
|
20
18
|
@content = xml
|
@@ -22,39 +20,45 @@ module Saml
|
|
22
20
|
@xml_hash = Hash.from_xml(xml) || {}
|
23
21
|
end
|
24
22
|
|
23
|
+
# Returns the ID for the SAML document.
|
25
24
|
def id
|
26
25
|
to_h.fetch(name, {}).fetch('ID', nil)
|
27
26
|
end
|
28
27
|
|
28
|
+
# Returns the Issuer for the SAML document.
|
29
29
|
def issuer
|
30
30
|
to_h.fetch(name, {}).fetch('Issuer', nil)
|
31
31
|
end
|
32
32
|
|
33
|
+
# Returns the Version of the SAML document.
|
33
34
|
def version
|
34
35
|
to_h.fetch(name, {}).fetch('Version', {})
|
35
36
|
end
|
36
37
|
|
38
|
+
# Returns the Destination of the SAML document.
|
37
39
|
def destination
|
38
40
|
to_h.fetch(name, {}).fetch('Destination', nil)
|
39
41
|
end
|
40
42
|
|
43
|
+
# Returns the Destination of the SAML document.
|
41
44
|
def issue_instant
|
42
|
-
to_h[name]['IssueInstant']
|
43
|
-
end
|
44
|
-
|
45
|
-
def expected_type?
|
46
|
-
return false if to_xml.blank?
|
47
|
-
to_h[name].present?
|
45
|
+
Time.parse(to_h[name]['IssueInstant'])
|
48
46
|
end
|
49
47
|
|
48
|
+
# Returns the SAML document returned as a Hash.
|
50
49
|
def to_h
|
51
50
|
@xml_hash
|
52
51
|
end
|
53
52
|
|
53
|
+
# Returns the SAML document as an XML string.
|
54
|
+
#
|
55
|
+
# @param pretty [Boolean] formats the xml or returns the raw xml.
|
54
56
|
def to_xml(pretty: false)
|
55
57
|
pretty ? Nokogiri::XML(content).to_xml(indent: 2) : content
|
56
58
|
end
|
57
59
|
|
60
|
+
# Returns the SAML document as an XHTML string.
|
61
|
+
# This is useful for rendering in a web page.
|
58
62
|
def to_xhtml
|
59
63
|
Nokogiri::XML(content, &:noblanks).to_xhtml
|
60
64
|
end
|
@@ -64,6 +68,10 @@ module Saml
|
|
64
68
|
end
|
65
69
|
|
66
70
|
class << self
|
71
|
+
# Returns the raw xml as a Saml::Kit SAML document.
|
72
|
+
#
|
73
|
+
# @param xml [String] the raw xml string.
|
74
|
+
# @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
|
67
75
|
def to_saml_document(xml, configuration: Saml::Kit.configuration)
|
68
76
|
hash = Hash.from_xml(xml)
|
69
77
|
if hash['Response'].present?
|
@@ -80,7 +88,8 @@ module Saml
|
|
80
88
|
InvalidDocument.new(xml)
|
81
89
|
end
|
82
90
|
|
83
|
-
|
91
|
+
# @!visibility private
|
92
|
+
def builder_class # :nodoc:
|
84
93
|
case name
|
85
94
|
when Saml::Kit::Response.to_s
|
86
95
|
Saml::Kit::Builders::Response
|
@@ -98,6 +107,8 @@ module Saml
|
|
98
107
|
|
99
108
|
private
|
100
109
|
|
110
|
+
attr_reader :content, :name, :configuration
|
111
|
+
|
101
112
|
def must_match_xsd
|
102
113
|
matches_xsd?(PROTOCOL_XSD)
|
103
114
|
end
|
@@ -108,6 +119,11 @@ module Saml
|
|
108
119
|
errors[:base] << error_message(:invalid) unless expected_type?
|
109
120
|
end
|
110
121
|
|
122
|
+
def expected_type?
|
123
|
+
return false if to_xml.blank?
|
124
|
+
to_h[name].present?
|
125
|
+
end
|
126
|
+
|
111
127
|
def must_be_valid_version
|
112
128
|
return unless expected_type?
|
113
129
|
return if "2.0" == version
|
data/lib/saml/kit/fingerprint.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This generates a fingerprint for an X509 Certificate.
|
3
4
|
class Fingerprint
|
5
|
+
# The OpenSSL::X509::Certificate
|
4
6
|
attr_reader :x509
|
5
7
|
|
6
8
|
def initialize(raw_certificate)
|
7
9
|
@x509 = Certificate.to_x509(raw_certificate)
|
8
10
|
end
|
9
11
|
|
12
|
+
# Generates a formatted fingerprint using the specified hash algorithm.
|
13
|
+
#
|
14
|
+
# @param algorithm [OpenSSL::Digest] the openssl algorithm to use `OpenSSL::Digest::SHA256`, `OpenSSL::Digest::SHA1`.
|
15
|
+
# @return [String] in the format of `"BF:ED:C5:F1:6C:AB:F5:B2:15:1F:BF:BD:7D:68:1A:F9:A5:4E:4C:19:30:BC:6D:25:B1:8E:98:D4:23:FD:B4:09"`
|
10
16
|
def algorithm(algorithm)
|
11
17
|
pretty_fingerprint(algorithm.new.hexdigest(x509.to_der))
|
12
18
|
end
|
@@ -1,10 +1,23 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This class is used to parse the IDPSSODescriptor from a SAML metadata document.
|
4
|
+
#
|
5
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
6
|
+
# <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c" entityID="">
|
7
|
+
# <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
8
|
+
# <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/logout"/>
|
9
|
+
# <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
|
10
|
+
# <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://www.example.com/login"/>
|
11
|
+
# <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://www.example.com/login"/>
|
12
|
+
# <saml:Attribute Name="id"/>
|
13
|
+
# </IDPSSODescriptor>
|
14
|
+
# </EntityDescriptor>
|
3
15
|
class IdentityProviderMetadata < Metadata
|
4
16
|
def initialize(xml)
|
5
17
|
super("IDPSSODescriptor", xml)
|
6
18
|
end
|
7
19
|
|
20
|
+
# Returns the IDPSSODescriptor/@WantAuthnRequestsSigned attribute.
|
8
21
|
def want_authn_requests_signed
|
9
22
|
xpath = "/md:EntityDescriptor/md:#{name}"
|
10
23
|
attribute = document.find_by(xpath).attribute("WantAuthnRequestsSigned")
|
@@ -12,14 +25,19 @@ module Saml
|
|
12
25
|
attribute.text.downcase == "true"
|
13
26
|
end
|
14
27
|
|
28
|
+
# Returns each of the SingleSignOnService elements.
|
15
29
|
def single_sign_on_services
|
16
30
|
services('SingleSignOnService')
|
17
31
|
end
|
18
32
|
|
33
|
+
# Returns a SingleSignOnService elements with the specified binding.
|
34
|
+
#
|
35
|
+
# @param binding [Symbol] `:http_post` or `:http_redirect`.
|
19
36
|
def single_sign_on_service_for(binding:)
|
20
37
|
service_for(binding: binding, type: 'SingleSignOnService')
|
21
38
|
end
|
22
39
|
|
40
|
+
# Returns each of the Attributes in the metadata.
|
23
41
|
def attributes
|
24
42
|
document.find_all("/md:EntityDescriptor/md:#{name}/saml:Attribute").map do |item|
|
25
43
|
{
|
@@ -29,7 +47,13 @@ module Saml
|
|
29
47
|
end
|
30
48
|
end
|
31
49
|
|
32
|
-
|
50
|
+
# Creates a AuthnRequest document for the specified binding.
|
51
|
+
#
|
52
|
+
# @param binding [Symbol] `:http_post` or `:http_redirect`.
|
53
|
+
# @param relay_state [Object] The RelayState to include the returned SAML params.
|
54
|
+
# @param configuration [Saml::Kit::Configuration] the configuration to use for generating the request.
|
55
|
+
# @return [Array] The url and saml params encoded using the rules for the specified binding.
|
56
|
+
def login_request_for(binding:, relay_state: nil, configuration: Saml::Kit.configuration) # :yields builder
|
33
57
|
builder = Saml::Kit::AuthenticationRequest.builder(configuration: configuration) do |x|
|
34
58
|
x.embed_signature = want_authn_requests_signed
|
35
59
|
yield x if block_given?
|
@@ -38,10 +62,12 @@ module Saml
|
|
38
62
|
request_binding.serialize(builder, relay_state: relay_state)
|
39
63
|
end
|
40
64
|
|
65
|
+
# @!visibility private
|
41
66
|
def self.builder_class
|
42
67
|
Saml::Kit::Builders::IdentityProviderMetadata
|
43
68
|
end
|
44
69
|
|
70
|
+
# @deprecated Use {#Saml::Kit::Builders::IdentityProviderMetadata} instead of this.
|
45
71
|
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::IdentityProviderMetadata::Builder', 'Saml::Kit::Builders::IdentityProviderMetadata')
|
46
72
|
end
|
47
73
|
end
|
data/lib/saml/kit/key_pair.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This class parses a LogoutRequest SAML document.
|
3
4
|
class LogoutRequest < Document
|
4
5
|
include Requestable
|
5
6
|
validates_presence_of :single_logout_service, if: :expected_type?
|
@@ -8,25 +9,34 @@ module Saml
|
|
8
9
|
super(xml, name: "LogoutRequest", configuration: configuration)
|
9
10
|
end
|
10
11
|
|
12
|
+
# Returns the NameID value.
|
11
13
|
def name_id
|
12
14
|
to_h[name]['NameID']
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
builder = Saml::Kit::LogoutResponse.builder(user, self) do |x|
|
17
|
+
# Generates a Serialized LogoutResponse using the encoding rules for the specified binding.
|
18
|
+
#
|
19
|
+
# @param binding [Symbol] The binding to use `:http_redirect` or `:http_post`.
|
20
|
+
# @param relay_state [Object] The RelayState to include in the RelayState param.
|
21
|
+
# @return [Array] Returns an array with a url and Hash of parameters to return to the requestor.
|
22
|
+
def response_for(binding:, relay_state: nil)
|
23
|
+
builder = Saml::Kit::LogoutResponse.builder(self) do |x|
|
23
24
|
yield x if block_given?
|
24
25
|
end
|
25
26
|
response_binding = provider.single_logout_service_for(binding: binding)
|
26
27
|
response_binding.serialize(builder, relay_state: relay_state)
|
27
28
|
end
|
28
29
|
|
30
|
+
# @deprecated Use {#Saml::Kit::Builders::LogoutRequest} instead of this.
|
29
31
|
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutRequest::Builder', 'Saml::Kit::Builders::LogoutRequest')
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def single_logout_service
|
36
|
+
return if provider.nil?
|
37
|
+
urls = provider.single_logout_services
|
38
|
+
urls.first
|
39
|
+
end
|
30
40
|
end
|
31
41
|
end
|
32
42
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# This class is used to parse a LogoutResponse SAML document.
|
3
4
|
class LogoutResponse < Document
|
4
5
|
include Respondable
|
5
6
|
|
@@ -8,6 +9,7 @@ module Saml
|
|
8
9
|
super(xml, name: "LogoutResponse", configuration: configuration)
|
9
10
|
end
|
10
11
|
|
12
|
+
# @deprecated Use {#Saml::Kit::Builders::LogoutResponse} instead of this.
|
11
13
|
Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::LogoutResponse::Builder', 'Saml::Kit::Builders::LogoutResponse')
|
12
14
|
end
|
13
15
|
end
|
data/lib/saml/kit/requestable.rb
CHANGED
data/lib/saml/kit/respondable.rb
CHANGED
@@ -9,18 +9,22 @@ module Saml
|
|
9
9
|
validate :must_match_request_id
|
10
10
|
end
|
11
11
|
|
12
|
+
# @!visibility private
|
12
13
|
def query_string_parameter
|
13
14
|
'SAMLResponse'
|
14
15
|
end
|
15
16
|
|
17
|
+
# Returns the /Status/StatusCode@Value
|
16
18
|
def status_code
|
17
19
|
to_h.fetch(name, {}).fetch('Status', {}).fetch('StatusCode', {}).fetch('Value', nil)
|
18
20
|
end
|
19
21
|
|
22
|
+
# Returns the /InResponseTo attribute.
|
20
23
|
def in_response_to
|
21
24
|
to_h.fetch(name, {}).fetch('InResponseTo', nil)
|
22
25
|
end
|
23
26
|
|
27
|
+
# Returns true if the Status code is #{Saml::Kit::Namespaces::SUCCESS}
|
24
28
|
def success?
|
25
29
|
Namespaces::SUCCESS == status_code
|
26
30
|
end
|
data/lib/saml/kit/signatures.rb
CHANGED
@@ -1,37 +1,45 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
-
class Signatures
|
3
|
+
class Signatures # :nodoc:
|
4
|
+
# @!visibility private
|
4
5
|
attr_reader :configuration
|
5
6
|
|
7
|
+
# @!visibility private
|
6
8
|
def initialize(configuration:)
|
7
9
|
@configuration = configuration
|
8
10
|
end
|
9
11
|
|
12
|
+
# @!visibility private
|
10
13
|
def build(reference_id)
|
11
14
|
return nil unless configuration.sign?
|
12
15
|
Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
|
13
16
|
end
|
14
17
|
|
18
|
+
# @!visibility private
|
15
19
|
def complete(raw_xml)
|
16
20
|
return raw_xml unless configuration.sign?
|
17
21
|
private_key = configuration.private_keys(use: :signing).last
|
18
22
|
Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
|
19
23
|
end
|
20
24
|
|
25
|
+
# @!visibility private
|
21
26
|
def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
|
22
27
|
signatures = Saml::Kit::Signatures.new(configuration: configuration)
|
23
28
|
yield xml, XmlSignatureTemplate.new(xml, signatures)
|
24
29
|
signatures.complete(xml.target!)
|
25
30
|
end
|
26
31
|
|
27
|
-
class XmlSignatureTemplate
|
32
|
+
class XmlSignatureTemplate # :nodoc:
|
33
|
+
# @!visibility private
|
28
34
|
attr_reader :signatures, :xml
|
29
35
|
|
36
|
+
# @!visibility private
|
30
37
|
def initialize(xml, signatures)
|
31
38
|
@signatures = signatures
|
32
39
|
@xml = xml
|
33
40
|
end
|
34
41
|
|
42
|
+
# @!visibility private
|
35
43
|
def template(reference_id)
|
36
44
|
Template.new(signatures.build(reference_id)).to_xml(xml: xml)
|
37
45
|
end
|
data/lib/saml/kit/trustable.rb
CHANGED
@@ -13,6 +13,7 @@ module Saml
|
|
13
13
|
signature_manually_verified || signature.present?
|
14
14
|
end
|
15
15
|
|
16
|
+
# @!visibility private
|
16
17
|
def signature
|
17
18
|
xml_hash = to_h.fetch(name, {}).fetch('Signature', nil)
|
18
19
|
xml_hash ? Signature.new(xml_hash) : nil
|
@@ -24,10 +25,12 @@ module Saml
|
|
24
25
|
signature.trusted?(provider)
|
25
26
|
end
|
26
27
|
|
28
|
+
# @!visibility private
|
27
29
|
def provider
|
28
30
|
configuration.registry.metadata_for(issuer)
|
29
31
|
end
|
30
32
|
|
33
|
+
# @!visibility private
|
31
34
|
def signature_verified!
|
32
35
|
@signature_manually_verified = true
|
33
36
|
end
|
data/lib/saml/kit/version.rb
CHANGED
data/lib/saml/kit/xml.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
-
class Xml
|
3
|
+
class Xml # :nodoc:
|
4
4
|
include ActiveModel::Validations
|
5
5
|
NAMESPACES = {
|
6
6
|
"NameFormat": Namespaces::ATTR_SPLAT,
|
7
7
|
"ds": Namespaces::XMLDSIG,
|
8
8
|
"md": Namespaces::METADATA,
|
9
9
|
"saml": Namespaces::ASSERTION,
|
10
|
-
"
|
10
|
+
"samlp": Namespaces::PROTOCOL,
|
11
11
|
}.freeze
|
12
12
|
|
13
13
|
attr_reader :raw_xml, :document
|
data/saml-kit.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
19
|
f.match(%r{^(test|spec|features)/})
|
20
20
|
end
|
21
|
+
spec.metadata["yard.run"] = "yri"
|
21
22
|
spec.bindir = "exe"
|
22
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
24
|
spec.require_paths = ["lib"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
@@ -278,7 +278,8 @@ files:
|
|
278
278
|
homepage: http://www.mokhan.ca
|
279
279
|
licenses:
|
280
280
|
- MIT
|
281
|
-
metadata:
|
281
|
+
metadata:
|
282
|
+
yard.run: yri
|
282
283
|
post_install_message:
|
283
284
|
rdoc_options: []
|
284
285
|
require_paths:
|
@@ -295,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
295
296
|
version: '0'
|
296
297
|
requirements: []
|
297
298
|
rubyforge_project:
|
298
|
-
rubygems_version: 2.
|
299
|
+
rubygems_version: 2.6.14
|
299
300
|
signing_key:
|
300
301
|
specification_version: 4
|
301
302
|
summary: A simple toolkit for working with SAML.
|