icn_saml_idp 0.4.1
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 +7 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +238 -0
- data/app/controllers/saml_idp/idp_controller.rb +53 -0
- data/app/views/saml_idp/idp/new.html.erb +22 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +14 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +172 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +48 -0
- data/lib/saml_idp/controller.rb +128 -0
- data/lib/saml_idp/default.rb +49 -0
- data/lib/saml_idp/encryptor.rb +86 -0
- data/lib/saml_idp/engine.rb +5 -0
- data/lib/saml_idp/hashable.rb +26 -0
- data/lib/saml_idp/incoming_metadata.rb +144 -0
- data/lib/saml_idp/logout_builder.rb +42 -0
- data/lib/saml_idp/logout_request_builder.rb +34 -0
- data/lib/saml_idp/logout_response_builder.rb +35 -0
- data/lib/saml_idp/metadata_builder.rb +160 -0
- data/lib/saml_idp/name_id_formatter.rb +68 -0
- data/lib/saml_idp/persisted_metadata.rb +10 -0
- data/lib/saml_idp/request.rb +180 -0
- data/lib/saml_idp/response_builder.rb +62 -0
- data/lib/saml_idp/saml_response.rb +79 -0
- data/lib/saml_idp/service_provider.rb +76 -0
- data/lib/saml_idp/signable.rb +131 -0
- data/lib/saml_idp/signature_builder.rb +42 -0
- data/lib/saml_idp/signed_info_builder.rb +88 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +181 -0
- data/saml_idp.gemspec +65 -0
- data/spec/acceptance/acceptance_helper.rb +9 -0
- data/spec/acceptance/idp_controller_spec.rb +16 -0
- data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
- data/spec/lib/saml_idp/assertion_builder_spec.rb +106 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +53 -0
- data/spec/lib/saml_idp/configurator_spec.rb +43 -0
- data/spec/lib/saml_idp/controller_spec.rb +94 -0
- data/spec/lib/saml_idp/encryptor_spec.rb +27 -0
- data/spec/lib/saml_idp/logout_request_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/logout_response_builder_spec.rb +41 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +42 -0
- data/spec/lib/saml_idp/request_spec.rb +106 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +42 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +68 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +27 -0
- data/spec/lib/saml_idp/signable_spec.rb +77 -0
- data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
- data/spec/rails_app/.gitignore +15 -0
- data/spec/rails_app/README.rdoc +261 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +60 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +37 -0
- data/spec/rails_app/config/environments/production.rb +67 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +6 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/doc/README_FOR_APP +2 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/rails_app/log/.gitkeep +0 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/test/fixtures/.gitkeep +0 -0
- data/spec/rails_app/test/functional/.gitkeep +0 -0
- data/spec/rails_app/test/integration/.gitkeep +0 -0
- data/spec/rails_app/test/performance/browsing_test.rb +12 -0
- data/spec/rails_app/test/test_helper.rb +13 -0
- data/spec/rails_app/test/unit/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/certificates/certificate1 +12 -0
- data/spec/support/certificates/r1_certificate2_base64 +1 -0
- data/spec/support/responses/adfs_response_sha1.xml +46 -0
- data/spec/support/responses/adfs_response_sha256.xml +46 -0
- data/spec/support/responses/adfs_response_sha384.xml +46 -0
- data/spec/support/responses/adfs_response_sha512.xml +46 -0
- data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
- data/spec/support/responses/no_signature_ns.xml +48 -0
- data/spec/support/responses/open_saml_response.xml +56 -0
- data/spec/support/responses/r1_response6.xml.base64 +1 -0
- data/spec/support/responses/response1.xml.base64 +1 -0
- data/spec/support/responses/response2.xml.base64 +79 -0
- data/spec/support/responses/response3.xml.base64 +66 -0
- data/spec/support/responses/response4.xml.base64 +93 -0
- data/spec/support/responses/response5.xml.base64 +102 -0
- data/spec/support/responses/response_with_ampersands.xml +139 -0
- data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
- data/spec/support/responses/simple_saml_php.xml +71 -0
- data/spec/support/responses/starfield_response.xml.base64 +1 -0
- data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
- data/spec/support/saml_request_macros.rb +38 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +137 -0
- metadata +465 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: ea7921102fe43bfbba0086ee40cfd65f7783e1ad
|
|
4
|
+
data.tar.gz: 542bc9cc6358ccb222f47a164506354afa70e0ab
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0602eb253d25f4dac6106ff2dc1f2a8ebd2eb4cab128d4ec588ce1e528427374d7a8f44d44d888266548f3c035ecc44eb735cb6e16e83be3d3cccfdc9a2d30df
|
|
7
|
+
data.tar.gz: 10aa55f5cdc46f505910a8fcb1b86c4a843ec046be75d33ca29ea3baacb4cea1e3b1bb8518120fc22079a19f7365bfc86d47675bcd75927c673386c56905169a
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Sport Ngin (http://sportngin.com)
|
|
2
|
+
Portions Copyright (c) 2010 OneLogin, LLC
|
|
3
|
+
Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Ruby SAML Identity Provider (IdP)
|
|
2
|
+
Forked from https://github.com/lawrencepit/ruby-saml-idp
|
|
3
|
+
|
|
4
|
+
[](https://travis-ci.org/sportngin/saml_idp)
|
|
5
|
+
[](http://badge.fury.io/rb/saml_idp)
|
|
6
|
+
|
|
7
|
+
The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows
|
|
8
|
+
your application to act as an IdP (Identity Provider) using the
|
|
9
|
+
[SAML v2.0](http://en.wikipedia.org/wiki/Security_Assertion_Markup_Language)
|
|
10
|
+
protocol. It provides a means for managing authentication requests and confirmation responses for SPs (Service Providers).
|
|
11
|
+
|
|
12
|
+
This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
|
|
13
|
+
SAML IDP implementation.
|
|
14
|
+
|
|
15
|
+
# Installation and Usage
|
|
16
|
+
|
|
17
|
+
Add this to your Gemfile:
|
|
18
|
+
|
|
19
|
+
gem 'saml_idp'
|
|
20
|
+
|
|
21
|
+
## Not using rails?
|
|
22
|
+
Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
|
|
23
|
+
|
|
24
|
+
Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
|
25
|
+
`saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
|
|
26
|
+
a user is entirely up to you.
|
|
27
|
+
|
|
28
|
+
Once a user has successfully authenticated on your system send the Service Provider a SAMLReponse by
|
|
29
|
+
posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
|
|
30
|
+
`encode_response(user_email)`.
|
|
31
|
+
|
|
32
|
+
## Using rails?
|
|
33
|
+
Add to your `routes.rb` file, for example:
|
|
34
|
+
|
|
35
|
+
``` ruby
|
|
36
|
+
get '/saml/auth' => 'saml_idp#new'
|
|
37
|
+
get '/saml/metadata' => 'saml_idp#show'
|
|
38
|
+
post '/saml/auth' => 'saml_idp#create'
|
|
39
|
+
match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Create a controller that looks like this, customize to your own situation:
|
|
43
|
+
|
|
44
|
+
``` ruby
|
|
45
|
+
class SamlIdpController
|
|
46
|
+
include SamlIdp::IdpController
|
|
47
|
+
|
|
48
|
+
def idp_authenticate(email, password) # not using params intentionally
|
|
49
|
+
user = User.by_email(email).first
|
|
50
|
+
user && user.valid_password?(password) ? user : nil
|
|
51
|
+
end
|
|
52
|
+
private :idp_authenticate
|
|
53
|
+
|
|
54
|
+
def idp_make_saml_response(found_user) # not using params intentionally
|
|
55
|
+
# NOTE encryption is optional
|
|
56
|
+
encode_response found_user, encryption: {
|
|
57
|
+
cert: saml_request.service_provider.cert,
|
|
58
|
+
block_encryption: 'aes256-cbc',
|
|
59
|
+
key_transport: 'rsa-oaep-mgf1p'
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
private :idp_make_saml_response
|
|
63
|
+
|
|
64
|
+
def idp_logout
|
|
65
|
+
user = User.by_email(saml_request.name_id)
|
|
66
|
+
user.logout
|
|
67
|
+
end
|
|
68
|
+
private :idp_logout
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
Be sure to load a file like this during your app initialization:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
SamlIdp.configure do |config|
|
|
78
|
+
base = "http://example.com"
|
|
79
|
+
|
|
80
|
+
config.x509_certificate = <<-CERT
|
|
81
|
+
-----BEGIN CERTIFICATE-----
|
|
82
|
+
CERTIFICATE DATA
|
|
83
|
+
-----END CERTIFICATE-----
|
|
84
|
+
CERT
|
|
85
|
+
|
|
86
|
+
config.secret_key = <<-CERT
|
|
87
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
88
|
+
KEY DATA
|
|
89
|
+
-----END RSA PRIVATE KEY-----
|
|
90
|
+
CERT
|
|
91
|
+
|
|
92
|
+
# config.password = "secret_key_password"
|
|
93
|
+
# config.algorithm = :sha256
|
|
94
|
+
# config.organization_name = "Your Organization"
|
|
95
|
+
# config.organization_url = "http://example.com"
|
|
96
|
+
# config.base_saml_location = "#{base}/saml"
|
|
97
|
+
# config.reference_id_generator # Default: -> { UUID.generate }
|
|
98
|
+
# config.attribute_service_location = "#{base}/saml/attributes"
|
|
99
|
+
# config.single_service_post_location = "#{base}/saml/auth"
|
|
100
|
+
|
|
101
|
+
# Principal (e.g. User) is passed in when you `encode_response`
|
|
102
|
+
#
|
|
103
|
+
# config.name_id.formats # =>
|
|
104
|
+
# { # All 2.0
|
|
105
|
+
# email_address: -> (principal) { principal.email_address },
|
|
106
|
+
# transient: -> (principal) { principal.id },
|
|
107
|
+
# persistent: -> (p) { p.id },
|
|
108
|
+
# }
|
|
109
|
+
# OR
|
|
110
|
+
#
|
|
111
|
+
# {
|
|
112
|
+
# "1.1" => {
|
|
113
|
+
# email_address: -> (principal) { principal.email_address },
|
|
114
|
+
# },
|
|
115
|
+
# "2.0" => {
|
|
116
|
+
# transient: -> (principal) { principal.email_address },
|
|
117
|
+
# persistent: -> (p) { p.id },
|
|
118
|
+
# },
|
|
119
|
+
# }
|
|
120
|
+
|
|
121
|
+
# If Principal responds to a method called `asserted_attributes`
|
|
122
|
+
# the return value of that method will be used in lieu of the
|
|
123
|
+
# attributes defined here in the global space. This allows for
|
|
124
|
+
# per-user attribute definitions.
|
|
125
|
+
#
|
|
126
|
+
## EXAMPLE **
|
|
127
|
+
# class User
|
|
128
|
+
# def asserted_attributes
|
|
129
|
+
# {
|
|
130
|
+
# phone: { getter: :phone },
|
|
131
|
+
# email: {
|
|
132
|
+
# getter: :email,
|
|
133
|
+
# name_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS,
|
|
134
|
+
# name_id_format: Saml::XML::Namespaces::Formats::NameId::EMAIL_ADDRESS
|
|
135
|
+
# }
|
|
136
|
+
# }
|
|
137
|
+
# end
|
|
138
|
+
# end
|
|
139
|
+
#
|
|
140
|
+
# If you have a method called `asserted_attributes` in your Principal class,
|
|
141
|
+
# there is no need to define it here in the config.
|
|
142
|
+
|
|
143
|
+
# config.attributes # =>
|
|
144
|
+
# {
|
|
145
|
+
# <friendly_name> => { # required (ex "eduPersonAffiliation")
|
|
146
|
+
# "name" => <attrname> # required (ex "urn:oid:1.3.6.1.4.1.5923.1.1.1.1")
|
|
147
|
+
# "name_format" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", # not required
|
|
148
|
+
# "getter" => ->(principal) { # not required
|
|
149
|
+
# principal.get_eduPersonAffiliation # If no "getter" defined, will try
|
|
150
|
+
# } # `principal.eduPersonAffiliation`, or no values will
|
|
151
|
+
# } # be output
|
|
152
|
+
#
|
|
153
|
+
## EXAMPLE ##
|
|
154
|
+
# config.attributes = {
|
|
155
|
+
# GivenName: {
|
|
156
|
+
# getter: :first_name,
|
|
157
|
+
# },
|
|
158
|
+
# SurName: {
|
|
159
|
+
# getter: :last_name,
|
|
160
|
+
# },
|
|
161
|
+
# }
|
|
162
|
+
## EXAMPLE ##
|
|
163
|
+
|
|
164
|
+
# config.technical_contact.company = "Example"
|
|
165
|
+
# config.technical_contact.given_name = "Jonny"
|
|
166
|
+
# config.technical_contact.sur_name = "Support"
|
|
167
|
+
# config.technical_contact.telephone = "55555555555"
|
|
168
|
+
# config.technical_contact.email_address = "example@example.com"
|
|
169
|
+
|
|
170
|
+
service_providers = {
|
|
171
|
+
"some-issuer-url.com/saml" => {
|
|
172
|
+
fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
|
|
173
|
+
metadata_url: "http://some-issuer-url.com/saml/metadata"
|
|
174
|
+
},
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# `identifier` is the entity_id or issuer of the Service Provider,
|
|
178
|
+
# settings is an IncomingMetadata object which has a to_h method that needs to be persisted
|
|
179
|
+
config.service_provider.metadata_persister = ->(identifier, settings) {
|
|
180
|
+
fname = identifier.to_s.gsub(/\/|:/,"_")
|
|
181
|
+
`mkdir -p #{Rails.root.join("cache/saml/metadata")}`
|
|
182
|
+
File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
|
|
183
|
+
Marshal.dump settings.to_h, f
|
|
184
|
+
end
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# `identifier` is the entity_id or issuer of the Service Provider,
|
|
188
|
+
# `service_provider` is a ServiceProvider object. Based on the `identifier` or the
|
|
189
|
+
# `service_provider` you should return the settings.to_h from above
|
|
190
|
+
config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
|
|
191
|
+
fname = identifier.to_s.gsub(/\/|:/,"_")
|
|
192
|
+
`mkdir -p #{Rails.root.join("cache/saml/metadata")}`
|
|
193
|
+
full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
|
|
194
|
+
if File.file?(full_filename)
|
|
195
|
+
File.open full_filename, "rb" do |f|
|
|
196
|
+
Marshal.load f
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Find ServiceProvider metadata_url and fingerprint based on our settings
|
|
202
|
+
config.service_provider.finder = ->(issuer_or_entity_id) do
|
|
203
|
+
service_providers[issuer_or_entity_id]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
# Keys and Secrets
|
|
209
|
+
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
|
210
|
+
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
|
211
|
+
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
|
212
|
+
within the controller set the properties `x509_certificate` and `secret_key` using a `prepend_before_filter`
|
|
213
|
+
callback within the current request context or set them globally via the `SamlIdp.config.x509_certificate`
|
|
214
|
+
and `SamlIdp.config.secret_key` properties.
|
|
215
|
+
|
|
216
|
+
The fingerprint to use, if you use the default X.509 certificate of this gem, is:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# Service Providers
|
|
224
|
+
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
|
|
225
|
+
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# Author
|
|
229
|
+
Jon Phenow, me@jphenow.com
|
|
230
|
+
|
|
231
|
+
Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
|
|
232
|
+
|
|
233
|
+
# Copyright
|
|
234
|
+
Copyright (c) 2012 Sport Ngin.
|
|
235
|
+
Portions Copyright (c) 2010 OneLogin, LLC
|
|
236
|
+
Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
|
|
237
|
+
|
|
238
|
+
See LICENSE for details.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module SamlIdp
|
|
3
|
+
class IdpController < ActionController::Base
|
|
4
|
+
include SamlIdp::Controller
|
|
5
|
+
|
|
6
|
+
unloadable unless Rails::VERSION::MAJOR >= 4
|
|
7
|
+
protect_from_forgery
|
|
8
|
+
before_filter :validate_saml_request, only: [:new, :create]
|
|
9
|
+
|
|
10
|
+
def new
|
|
11
|
+
render template: "saml_idp/idp/new"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def show
|
|
15
|
+
render xml: SamlIdp.metadata.signed
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create
|
|
19
|
+
unless params[:email].blank? && params[:password].blank?
|
|
20
|
+
person = idp_authenticate(params[:email], params[:password])
|
|
21
|
+
if person.nil?
|
|
22
|
+
@saml_idp_fail_msg = "Incorrect email or password."
|
|
23
|
+
else
|
|
24
|
+
@saml_response = idp_make_saml_response(person)
|
|
25
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
render :template => "saml_idp/idp/new"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def logout
|
|
33
|
+
idp_logout
|
|
34
|
+
@saml_response = idp_make_saml_response(nil)
|
|
35
|
+
render :template => "saml_idp/idp/saml_post", :layout => false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def idp_logout
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
end
|
|
41
|
+
private :idp_logout
|
|
42
|
+
|
|
43
|
+
def idp_authenticate(email, password)
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
end
|
|
46
|
+
protected :idp_authenticate
|
|
47
|
+
|
|
48
|
+
def idp_make_saml_response(person)
|
|
49
|
+
raise NotImplementedError
|
|
50
|
+
end
|
|
51
|
+
protected :idp_make_saml_response
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<% if @saml_idp_fail_msg %>
|
|
2
|
+
<div id="saml_idp_fail_msg" class="flash error"><%= @saml_idp_fail_msg %></div>
|
|
3
|
+
<% end %>
|
|
4
|
+
|
|
5
|
+
<%= form_tag do %>
|
|
6
|
+
<%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
|
|
7
|
+
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
|
8
|
+
|
|
9
|
+
<p>
|
|
10
|
+
<%= label_tag :email %>
|
|
11
|
+
<%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p>
|
|
15
|
+
<%= label_tag :password %>
|
|
16
|
+
<%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p>
|
|
20
|
+
<%= submit_tag "Sign in", :class => "button big blueish" %>
|
|
21
|
+
</p>
|
|
22
|
+
<% end %>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
|
6
|
+
</head>
|
|
7
|
+
<body onload="document.forms[0].submit();" style="visibility:hidden;">
|
|
8
|
+
<%= form_tag(saml_acs_url) do %>
|
|
9
|
+
<%= hidden_field_tag("SAMLResponse", @saml_response) %>
|
|
10
|
+
<%= hidden_field_tag("RelayState", params[:RelayState]) %>
|
|
11
|
+
<%= submit_tag "Submit" %>
|
|
12
|
+
<% end %>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
data/lib/saml_idp.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module SamlIdp
|
|
3
|
+
require 'active_support/all'
|
|
4
|
+
require 'saml_idp/saml_response'
|
|
5
|
+
require 'saml_idp/xml_security'
|
|
6
|
+
require 'saml_idp/configurator'
|
|
7
|
+
require 'saml_idp/controller'
|
|
8
|
+
require 'saml_idp/default'
|
|
9
|
+
require 'saml_idp/metadata_builder'
|
|
10
|
+
require 'saml_idp/version'
|
|
11
|
+
require 'saml_idp/engine' if defined?(::Rails) && Rails::VERSION::MAJOR > 2
|
|
12
|
+
|
|
13
|
+
def self.config
|
|
14
|
+
@config ||= SamlIdp::Configurator.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.configure
|
|
18
|
+
yield config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.metadata
|
|
22
|
+
@metadata ||= MetadataBuilder.new(config)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# TODO Needs extraction out
|
|
27
|
+
module Saml
|
|
28
|
+
module XML
|
|
29
|
+
module Namespaces
|
|
30
|
+
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata"
|
|
31
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
|
32
|
+
SIGNATURE = "http://www.w3.org/2000/09/xmldsig#"
|
|
33
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
|
34
|
+
|
|
35
|
+
module Statuses
|
|
36
|
+
SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module Consents
|
|
40
|
+
UNSPECIFIED = "urn:oasis:names:tc:SAML:2.0:consent:unspecified"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module AuthnContext
|
|
44
|
+
module ClassRef
|
|
45
|
+
PASSWORD = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
|
|
46
|
+
PASSWORD_PROTECTED = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
module Methods
|
|
51
|
+
BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module Formats
|
|
55
|
+
module Attr
|
|
56
|
+
URI = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
module NameId
|
|
60
|
+
EMAIL_ADDRESS = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
61
|
+
TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
|
62
|
+
PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class Document < Nokogiri::XML::Document
|
|
68
|
+
def signed?
|
|
69
|
+
!!xpath("//ds:Signature", ds: signature_namespace).first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def valid_signature?(fingerprint)
|
|
73
|
+
signed? &&
|
|
74
|
+
signed_document.validate(fingerprint, :soft)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def signed_document
|
|
78
|
+
SamlIdp::XMLSecurity::SignedDocument.new(to_xml)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def signature_namespace
|
|
82
|
+
Namespaces::SIGNATURE
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_xml
|
|
86
|
+
super(
|
|
87
|
+
save_with: Nokogiri::XML::Node::SaveOptions::AS_XML | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
|
|
88
|
+
).strip
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|