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