saml_idp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +197 -0
- data/app/controllers/saml_idp/idp_controller.rb +42 -0
- data/app/views/saml_idp/idp/new.html.erb +21 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +13 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +144 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +45 -0
- data/lib/saml_idp/controller.rb +71 -0
- data/lib/saml_idp/default.rb +28 -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/metadata_builder.rb +158 -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 +79 -0
- data/lib/saml_idp/response_builder.rb +60 -0
- data/lib/saml_idp/saml_response.rb +63 -0
- data/lib/saml_idp/service_provider.rb +68 -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 +51 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +168 -0
- data/saml_idp.gemspec +38 -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 +27 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +31 -0
- data/spec/lib/saml_idp/configurator_spec.rb +30 -0
- data/spec/lib/saml_idp/controller_spec.rb +51 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +9 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +39 -0
- data/spec/lib/saml_idp/request_spec.rb +14 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +32 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +27 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +21 -0
- data/spec/lib/saml_idp/signable_spec.rb +74 -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 +19 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +136 -0
- metadata +407 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YmY2NmYwOWVjNjFlNDcxMDg3MGEyNDRmN2ZlNTM3MDk2MWNiOWVjYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ODQ2MDYxY2VjNWRmNjc1MzUwY2FjZmFmMGQyNTY5NDMxNWU1YTU2NA==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDFkYjUwMjRkZmIxMThmOWFkZmQxYjFmYzc0YjFlOGY0MGYxM2E1NzVlMzAy
|
10
|
+
NzY2MzE1N2NhYjY1MWE0N2MxM2I1YTI0MjViZjAzMzk3ZjZiNGYzOTA2MmJj
|
11
|
+
NTFmMmI3ZmE4YzQwMzg0OGJkNmQwMzUyZTFmZDk4MmNiM2MyNmQ=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NTEyMzQxNWEzYzA5ZGJhNDk3MTk2ZDZlMmZlYmQzYmU3OGU0NWI2ZjVlMDcy
|
14
|
+
NzMxOTYxYjJiMjEzMTk5YmQyOTg3NDRmNWUxOGZjOGE0MDFjNjM5OGMzNmYy
|
15
|
+
NjUzNTZkMWI1M2U4NGE0ZjIzNWFiMzM1MWJiYjAxYTZiOGIyZjc=
|
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,197 @@
|
|
1
|
+
# Ruby SAML Identity Provider (IdP)
|
2
|
+
Forked from https://github.com/lawrencepit/ruby-saml-idp
|
3
|
+
|
4
|
+
[![Build Status](https://travis-ci.org/sportngin/saml_idp.png)](https://travis-ci.org/sportngin/saml_idp)
|
5
|
+
|
6
|
+
The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows
|
7
|
+
your application to act as an IdP (Identity Provider) using the
|
8
|
+
[SAML v2.0](http://en.wikipedia.org/wiki/Security_Assertion_Markup_Language)
|
9
|
+
protocol. It provides a means for managing authentication requests and confirmation responses for SPs (Service Providers).
|
10
|
+
|
11
|
+
This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
|
12
|
+
SAML IDP implementation.
|
13
|
+
|
14
|
+
# Installation and Usage
|
15
|
+
|
16
|
+
Add this to your Gemfile:
|
17
|
+
|
18
|
+
gem 'saml_idp'
|
19
|
+
|
20
|
+
## Not using rails?
|
21
|
+
Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
|
22
|
+
|
23
|
+
Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
|
24
|
+
`saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
|
25
|
+
a user is entirely up to you.
|
26
|
+
|
27
|
+
Once a user has successfully authenticated on your system send the Service Provider a SAMLReponse by
|
28
|
+
posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
|
29
|
+
`encode_response(user_email)`.
|
30
|
+
|
31
|
+
## Using rails?
|
32
|
+
Add to your `routes.rb` file, for example:
|
33
|
+
|
34
|
+
``` ruby
|
35
|
+
get '/saml/auth' => 'saml_idp#new'
|
36
|
+
get '/saml/metadata' => 'saml_idp#show'
|
37
|
+
post '/saml/auth' => 'saml_idp#create'
|
38
|
+
```
|
39
|
+
|
40
|
+
Create a controller that looks like this, customize to your own situation:
|
41
|
+
|
42
|
+
``` ruby
|
43
|
+
class SamlIdpController < SamlIdp::IdpController
|
44
|
+
def idp_authenticate(email, password) # not using params intentionally
|
45
|
+
user = User.by_email(email).first
|
46
|
+
user && user.valid_password?(password) ? user : nil
|
47
|
+
end
|
48
|
+
private :idp_authenticate
|
49
|
+
|
50
|
+
def idp_make_saml_response(found_user) # not using params intentionally
|
51
|
+
encode_response found_user
|
52
|
+
end
|
53
|
+
private :idp_make_saml_response
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
## Configuration
|
58
|
+
|
59
|
+
Be sure to load a file like this during your app initialization:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
SamlIdp.configure do |config|
|
63
|
+
base = "http://example.com"
|
64
|
+
|
65
|
+
config.x509_certificate = <<-CERT
|
66
|
+
-----BEGIN CERTIFICATE-----
|
67
|
+
CERTIFICATE DATA
|
68
|
+
-----END CERTIFICATE-----
|
69
|
+
CERT
|
70
|
+
|
71
|
+
config.secret_key = <<-CERT
|
72
|
+
-----BEGIN RSA PRIVATE KEY-----
|
73
|
+
KEY DATA
|
74
|
+
-----END RSA PRIVATE KEY-----
|
75
|
+
CERT
|
76
|
+
|
77
|
+
# config.algorithm = :sha256
|
78
|
+
# config.organization_name = "Your Organization"
|
79
|
+
# config.organization_url = "http://example.com"
|
80
|
+
# config.base_saml_location = "#{base}/saml"
|
81
|
+
# config.reference_id_generator # Default: -> { UUID.generate }
|
82
|
+
# config.attribute_service_location = "#{base}/saml/attributes"
|
83
|
+
# config.single_service_post_location = "#{base}/saml/auth"
|
84
|
+
|
85
|
+
# Principal is passed in when you `encode_response`
|
86
|
+
#
|
87
|
+
# config.name_id_formats # =>
|
88
|
+
# { # All 2.0
|
89
|
+
# email_address: -> (principal) { principal.email_address },
|
90
|
+
# transient: -> (principal) { principal.id },
|
91
|
+
# persistent: -> (p) { p.id },
|
92
|
+
# }
|
93
|
+
# OR
|
94
|
+
#
|
95
|
+
# {
|
96
|
+
# "1.1" => {
|
97
|
+
# email_address: -> (principal) { principal.email_address },
|
98
|
+
# },
|
99
|
+
# "2.0" => {
|
100
|
+
# transient: -> (principal) { principal.email_address },
|
101
|
+
# persistent: -> (p) { p.id },
|
102
|
+
# },
|
103
|
+
# }
|
104
|
+
|
105
|
+
# config.attributes # =>
|
106
|
+
# {
|
107
|
+
# <friendly_name> => { # required (ex "eduPersonAffiliation")
|
108
|
+
# "name" => <attrname> # required (ex "urn:oid:1.3.6.1.4.1.5923.1.1.1.1")
|
109
|
+
# "name_format" => "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", # not required
|
110
|
+
# "getter" => ->(principal) { # not required
|
111
|
+
# principal.get_eduPersonAffiliation # If no "getter" defined, will try
|
112
|
+
# } # `principal.eduPersonAffiliation`, or no values will
|
113
|
+
# } # be output
|
114
|
+
#
|
115
|
+
## EXAMPLE ##
|
116
|
+
# config.attributes = {
|
117
|
+
# GivenName: {
|
118
|
+
# getter: :first_name,
|
119
|
+
# },
|
120
|
+
# SurName: {
|
121
|
+
# getter: :last_name,
|
122
|
+
# },
|
123
|
+
# }
|
124
|
+
## EXAMPLE ##
|
125
|
+
|
126
|
+
# config.technical_contact.company = "Example"
|
127
|
+
# config.technical_contact.given_name = "Jonny"
|
128
|
+
# config.technical_contact.sur_name = "Support"
|
129
|
+
# config.technical_contact.telephone = "55555555555"
|
130
|
+
# config.technical_contact.email_address = "example@example.com"
|
131
|
+
|
132
|
+
service_providers = {
|
133
|
+
"some-issuer-url.com/saml" => {
|
134
|
+
fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
|
135
|
+
metadata_url: "http://some-issuer-url.com/saml/metadata"
|
136
|
+
},
|
137
|
+
}
|
138
|
+
|
139
|
+
# `identifier` is the entity_id or issuer of the Service Provider,
|
140
|
+
# settings is an IncomingMetadata object which has a to_h method that needs to be persisted
|
141
|
+
config.service_provider.metadata_persister = ->(identifier, settings) {
|
142
|
+
fname = identifier.to_s.gsub(/\/|:/,"_")
|
143
|
+
`mkdir -p #{Rails.root.join("cache/saml/metadata")}`
|
144
|
+
File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
|
145
|
+
Marshal.dump settings.to_h, f
|
146
|
+
end
|
147
|
+
}
|
148
|
+
|
149
|
+
# `identifier` is the entity_id or issuer of the Service Provider,
|
150
|
+
# `service_provider` is a ServiceProvider object. Based on the `identifier` or the
|
151
|
+
# `service_provider` you should return the settings.to_h from above
|
152
|
+
config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
|
153
|
+
fname = identifier.to_s.gsub(/\/|:/,"_")
|
154
|
+
`mkdir -p #{Rails.root.join("cache/saml/metadata")}`
|
155
|
+
File.open Rails.root.join("cache/saml/metadata/#{fname}"), "rb" do |f|
|
156
|
+
Marshal.load f
|
157
|
+
end
|
158
|
+
}
|
159
|
+
|
160
|
+
# Find ServiceProvider metadata_url and fingerprint based on our settings
|
161
|
+
config.service_provider.finder = ->(issuer_or_entity_id) do
|
162
|
+
service_providers[issuer_or_entity_id]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
# Keys and Secrets
|
168
|
+
To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
|
169
|
+
You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
|
170
|
+
Obviously you shouldn't use these if you intend to use this in production environments. In that case,
|
171
|
+
within the controller set the properties `x509_certificate` and `secret_key` using a `prepend_before_filter`
|
172
|
+
callback within the current request context or set them globally via the `SamlIdp.config.x509_certificate`
|
173
|
+
and `SamlIdp.config.secret_key` properties.
|
174
|
+
|
175
|
+
The fingerprint to use, if you use the default X.509 certificate of this gem, is:
|
176
|
+
|
177
|
+
```
|
178
|
+
9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
|
179
|
+
```
|
180
|
+
|
181
|
+
|
182
|
+
# Service Providers
|
183
|
+
To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
|
184
|
+
excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
|
185
|
+
|
186
|
+
|
187
|
+
# Author
|
188
|
+
Jon Phenow, jon.phenow@sportngin.com
|
189
|
+
|
190
|
+
Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
|
191
|
+
|
192
|
+
# Copyright
|
193
|
+
Copyright (c) 2012 Sport Ngin.
|
194
|
+
Portions Copyright (c) 2010 OneLogin, LLC
|
195
|
+
Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
|
196
|
+
|
197
|
+
See LICENSE for details.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module SamlIdp
|
3
|
+
class IdpController < ActionController::Base
|
4
|
+
include SamlIdp::Controller
|
5
|
+
|
6
|
+
unloadable
|
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 idp_authenticate(email, password)
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
protected :idp_authenticate
|
36
|
+
|
37
|
+
def idp_make_saml_response(person)
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
protected :idp_make_saml_response
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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
|
+
|
8
|
+
<p>
|
9
|
+
<%= label_tag :email %>
|
10
|
+
<%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
11
|
+
</p>
|
12
|
+
|
13
|
+
<p>
|
14
|
+
<%= label_tag :password %>
|
15
|
+
<%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
|
16
|
+
</p>
|
17
|
+
|
18
|
+
<p>
|
19
|
+
<%= submit_tag "Sign in", :class => "button big blueish" %>
|
20
|
+
</p>
|
21
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
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
|
+
<%= submit_tag "Submit" %>
|
11
|
+
<% end %>
|
12
|
+
</body>
|
13
|
+
</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
|
+
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
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SamlIdp
|
2
|
+
module Algorithmable
|
3
|
+
def algorithm
|
4
|
+
algorithm_check = raw_algorithm || SamlIdp.config.algorithm
|
5
|
+
return algorithm_check if algorithm_check.respond_to?(:digest)
|
6
|
+
begin
|
7
|
+
OpenSSL::Digest.const_get(algorithm_check.to_s.upcase)
|
8
|
+
rescue NameError
|
9
|
+
OpenSSL::Digest::SHA1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
private :algorithm
|
13
|
+
|
14
|
+
def algorithm_name
|
15
|
+
algorithm.to_s.split('::').last.downcase
|
16
|
+
end
|
17
|
+
private :algorithm_name
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'saml_idp/algorithmable'
|
3
|
+
require 'saml_idp/signable'
|
4
|
+
module SamlIdp
|
5
|
+
class AssertionBuilder
|
6
|
+
include Algorithmable
|
7
|
+
include Signable
|
8
|
+
attr_accessor :reference_id
|
9
|
+
attr_accessor :issuer_uri
|
10
|
+
attr_accessor :principal
|
11
|
+
attr_accessor :audience_uri
|
12
|
+
attr_accessor :saml_request_id
|
13
|
+
attr_accessor :saml_acs_url
|
14
|
+
attr_accessor :raw_algorithm
|
15
|
+
|
16
|
+
delegate :config, to: :SamlIdp
|
17
|
+
|
18
|
+
def initialize(reference_id, issuer_uri, principal, audience_uri, saml_request_id, saml_acs_url, raw_algorithm)
|
19
|
+
self.reference_id = reference_id
|
20
|
+
self.issuer_uri = issuer_uri
|
21
|
+
self.principal = principal
|
22
|
+
self.audience_uri = audience_uri
|
23
|
+
self.saml_request_id = saml_request_id
|
24
|
+
self.saml_acs_url = saml_acs_url
|
25
|
+
self.raw_algorithm = raw_algorithm
|
26
|
+
end
|
27
|
+
|
28
|
+
def fresh
|
29
|
+
builder = Builder::XmlMarkup.new
|
30
|
+
builder.Assertion xmlns: Saml::XML::Namespaces::ASSERTION,
|
31
|
+
ID: reference_string,
|
32
|
+
IssueInstant: now_iso,
|
33
|
+
Version: "2.0" do |assertion|
|
34
|
+
assertion.Issuer issuer_uri
|
35
|
+
sign assertion
|
36
|
+
assertion.Subject do |subject|
|
37
|
+
subject.NameID name_id, Format: name_id_format[:name]
|
38
|
+
subject.SubjectConfirmation Method: Saml::XML::Namespaces::Methods::BEARER do |confirmation|
|
39
|
+
confirmation.SubjectConfirmationData "", InResponseTo: saml_request_id,
|
40
|
+
NotOnOrAfter: not_on_or_after_subject,
|
41
|
+
Recipient: saml_acs_url
|
42
|
+
end
|
43
|
+
end
|
44
|
+
assertion.Conditions NotBefore: not_before, NotOnOrAfter: not_on_or_after_condition do |conditions|
|
45
|
+
conditions.AudienceRestriction do |restriction|
|
46
|
+
restriction.Audience audience_uri
|
47
|
+
end
|
48
|
+
end
|
49
|
+
assertion.AttributeStatement do |attr_statement|
|
50
|
+
config.attributes.each do |friendly_name, attrs|
|
51
|
+
attrs = (attrs || {}).with_indifferent_access
|
52
|
+
attr_statement.Attribute Name: attrs[:name] || friendly_name,
|
53
|
+
NameFormat: attrs[:name_format] || Saml::XML::Namespaces::Formats::Attr::URI,
|
54
|
+
FriendlyName: friendly_name.to_s do |attr|
|
55
|
+
values = get_values_for friendly_name, attrs[:getter]
|
56
|
+
values.each do |val|
|
57
|
+
attr.AttributeValue val.to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
assertion.AuthnStatement AuthnInstant: now_iso, SessionIndex: reference_string do |statement|
|
63
|
+
statement.AuthnContext do |context|
|
64
|
+
context.AuthnContextClassRef Saml::XML::Namespaces::AuthnContext::ClassRef::PASSWORD
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias_method :raw, :fresh
|
70
|
+
private :fresh
|
71
|
+
|
72
|
+
def get_values_for(friendly_name, getter)
|
73
|
+
result = nil
|
74
|
+
if getter.present?
|
75
|
+
if getter.respond_to?(:call)
|
76
|
+
result = getter.call(principal)
|
77
|
+
else
|
78
|
+
message = getter.to_s.underscore
|
79
|
+
result = principal.public_send(message) if principal.respond_to?(message)
|
80
|
+
end
|
81
|
+
elsif getter.nil?
|
82
|
+
message = friendly_name.to_s.underscore
|
83
|
+
result = principal.public_send(message) if principal.respond_to?(message)
|
84
|
+
end
|
85
|
+
Array(result)
|
86
|
+
end
|
87
|
+
private :get_values_for
|
88
|
+
|
89
|
+
def name_id
|
90
|
+
name_id_getter.call principal
|
91
|
+
end
|
92
|
+
private :name_id
|
93
|
+
|
94
|
+
def name_id_getter
|
95
|
+
getter = name_id_format[:getter]
|
96
|
+
if getter.respond_to? :call
|
97
|
+
getter
|
98
|
+
else
|
99
|
+
->(principal) { principal.public_send getter.to_s }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
private :name_id_getter
|
103
|
+
|
104
|
+
def name_id_format
|
105
|
+
@name_id_format ||= NameIdFormatter.new(config.name_id.formats).chosen
|
106
|
+
end
|
107
|
+
private :name_id_format
|
108
|
+
|
109
|
+
def reference_string
|
110
|
+
"_#{reference_id}"
|
111
|
+
end
|
112
|
+
private :reference_string
|
113
|
+
|
114
|
+
def now
|
115
|
+
@now ||= Time.now.utc
|
116
|
+
end
|
117
|
+
private :now
|
118
|
+
|
119
|
+
def now_iso
|
120
|
+
iso { now }
|
121
|
+
end
|
122
|
+
private :now_iso
|
123
|
+
|
124
|
+
def not_before
|
125
|
+
iso { now - 5 }
|
126
|
+
end
|
127
|
+
private :not_before
|
128
|
+
|
129
|
+
def not_on_or_after_condition
|
130
|
+
iso { now + 60 * 60 }
|
131
|
+
end
|
132
|
+
private :not_on_or_after_condition
|
133
|
+
|
134
|
+
def not_on_or_after_subject
|
135
|
+
iso { now + 3 * 60 }
|
136
|
+
end
|
137
|
+
private :not_on_or_after_subject
|
138
|
+
|
139
|
+
def iso
|
140
|
+
yield.iso8601
|
141
|
+
end
|
142
|
+
private :iso
|
143
|
+
end
|
144
|
+
end
|