devise_saml_authenticatable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +119 -0
- data/Rakefile +2 -0
- data/app/controllers/devise/saml_sessions_controller.rb +20 -0
- data/devise_saml_authenticatable.gemspec +20 -0
- data/lib/devise_saml_authenticatable.rb +43 -0
- data/lib/devise_saml_authenticatable/exception.rb +6 -0
- data/lib/devise_saml_authenticatable/logger.rb +11 -0
- data/lib/devise_saml_authenticatable/model.rb +69 -0
- data/lib/devise_saml_authenticatable/routes.rb +11 -0
- data/lib/devise_saml_authenticatable/saml_config.rb +8 -0
- data/lib/devise_saml_authenticatable/strategy.rb +31 -0
- data/lib/devise_saml_authenticatable/version.rb +3 -0
- data/rails/init.rb +2 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Josef Sauter
|
2
|
+
|
3
|
+
MIT License
|
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,119 @@
|
|
1
|
+
# DeviseSamlAuthenticatable
|
2
|
+
|
3
|
+
Devise Saml Authenticatable is a Single-Sign-On authentication strategy for devise that relies on SAML. It uses [ruby-saml](https://github.com/onelogin/ruby-saml) to handle all SAML related stuff.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'devise_saml_authenticatable'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install devise_saml_authenticatable
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
In app/models/<YOUR_MODEL>.rb set the :saml_authenticatable strategy. In the example the model is user.rb
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class User < ActiveRecord::Base
|
25
|
+
...
|
26
|
+
devise :saml_authenticatable, :trackable
|
27
|
+
...
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
In config/initializers/devise.rb
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Devise.setup do |config|
|
35
|
+
...
|
36
|
+
# ==> DeviseSamlAuthenticatable Configuration
|
37
|
+
|
38
|
+
# Create user if the user does not exist. (Default is false)
|
39
|
+
config.saml_create_user = true
|
40
|
+
|
41
|
+
# Set the default user key (default is email). The user will be looked up by this key. Make sure that the Authentication Response includes
|
42
|
+
# the attribute
|
43
|
+
config.saml_default_user_key = :email
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
In config directory, create a YAML file (idp.yml) with your SAML settings (see ruby-saml for more information). For example
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
# idp.yaml
|
51
|
+
development:
|
52
|
+
idp_metadata: ""
|
53
|
+
idp_metadata_ttl: ""
|
54
|
+
assertion_consumer_service_url: "http://localhost:3000/users/sign_in"
|
55
|
+
assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
56
|
+
name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
57
|
+
issuer: "http://localhost:3000"
|
58
|
+
authn_context: ""
|
59
|
+
idp_sso_target_url: "http://localhost/simplesaml/www/saml2/idp/SSOService.php"
|
60
|
+
idp_cert: |-
|
61
|
+
-----BEGIN CERTIFICATE-----
|
62
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
63
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
64
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
65
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
66
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
67
|
+
1111111111111_______IDP_CERTIFICATE________111111111111111111111
|
68
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
69
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
70
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
71
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
72
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
73
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
74
|
+
1111111111111111111111111111111111111111111111111111111111111111
|
75
|
+
111111111111111111
|
76
|
+
-----END CERTIFICATE-----
|
77
|
+
```
|
78
|
+
In config directory create a YAML file (attribute-map.yml) that maps SAML attributes with your model's fields
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
# attribute-map.yml
|
82
|
+
|
83
|
+
"urn:mace:dir:attribute-def:uid": "user_name"
|
84
|
+
"urn:mace:dir:attribute-def:email": "email"
|
85
|
+
"urn:mace:dir:attribute-def:name": "last_name"
|
86
|
+
"urn:mace:dir:attribute-def:givenName": "name"
|
87
|
+
```
|
88
|
+
|
89
|
+
The attribute mappings are very dependent on the way the IdP encodes the attributes. In this example the attributes are given in URN style. Other IdPs might provide them as OID's or other means.
|
90
|
+
|
91
|
+
You are now ready to test it against an IdP. When the user goes to /users/saml/sign_in he will be redirected to the login page of the IdP. Upon successful login the user is redirected to devise user_root_path.
|
92
|
+
|
93
|
+
## Identity Provider
|
94
|
+
|
95
|
+
If you don't have an identity provider an you would like to test the authentication against your app there are some options:
|
96
|
+
|
97
|
+
1. Use [ruby-saml-idp](https://github.com/lawrencepit/ruby-saml-idp).
|
98
|
+
|
99
|
+
You can add your own logic to your IdP, or you can also set it as a dummy IdP that always sends a valid authentication response to your app.
|
100
|
+
|
101
|
+
2. Use an online service that can act as an IdP. Onelogin, Salesforce and some others provide you with this functionality
|
102
|
+
3. Install your own IdP.
|
103
|
+
|
104
|
+
There are numerous IdPs that support SAML 2.0, there are propietary (like Microsoft ADFS 2.0 or Ping federate) and there are also open source solutions like Shibboleth and simplesamlphp.
|
105
|
+
|
106
|
+
[SimpleSAMLphp](http://simplesamlphp.org/) was my choice for development since it is a production-ready SAML solution, that is also really easy to install, configure and use.
|
107
|
+
|
108
|
+
## Limitations
|
109
|
+
|
110
|
+
1. At the moment there is no support for Single Logout (we're working on that)
|
111
|
+
2. The Authentication Requests (from your app to the IdP) are not signed and encrypted
|
112
|
+
|
113
|
+
## Contributing
|
114
|
+
|
115
|
+
1. Fork it
|
116
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
118
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "ruby-saml"
|
2
|
+
|
3
|
+
class Devise::SamlSessionsController < Devise::SessionsController
|
4
|
+
include DeviseSamlAuthenticatable::SamlConfig
|
5
|
+
unloadable
|
6
|
+
before_filter :get_saml_config
|
7
|
+
def new
|
8
|
+
resource = build_resource
|
9
|
+
request = Onelogin::Saml::Authrequest.new
|
10
|
+
action = request.create(@saml_config)
|
11
|
+
redirect_to action
|
12
|
+
end
|
13
|
+
|
14
|
+
def metadata
|
15
|
+
meta = Onelogin::Saml::Metadata.new
|
16
|
+
render :xml => meta.generate(@saml_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/devise_saml_authenticatable/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Josef Sauter"]
|
6
|
+
gem.email = ["Josef.Sauter@gmail.com"]
|
7
|
+
gem.description = %q{SAML Authentication for devise}
|
8
|
+
gem.summary = %q{SAML Authentication for devise }
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "devise_saml_authenticatable"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = DeviseSamlAuthenticatable::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency("devise","> 2.0.0")
|
19
|
+
gem.add_dependency("ruby-saml","> 0.7.1")
|
20
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "devise"
|
2
|
+
|
3
|
+
require "devise_saml_authenticatable/version"
|
4
|
+
require "devise_saml_authenticatable/exception"
|
5
|
+
require "devise_saml_authenticatable/logger"
|
6
|
+
require "devise_saml_authenticatable/routes"
|
7
|
+
require "devise_saml_authenticatable/saml_config"
|
8
|
+
begin
|
9
|
+
Rails::Engine
|
10
|
+
rescue
|
11
|
+
else
|
12
|
+
module DeviseSamlAuthenticatable
|
13
|
+
class Engine < Rails::Engine
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get saml information from config/saml.yml now
|
19
|
+
module Devise
|
20
|
+
# Allow logging
|
21
|
+
mattr_accessor :saml_logger
|
22
|
+
@@saml_logger = true
|
23
|
+
|
24
|
+
# Add valid users to database
|
25
|
+
mattr_accessor :saml_create_user
|
26
|
+
@@saml_create_user = false
|
27
|
+
|
28
|
+
mattr_accessor :saml_config
|
29
|
+
@@saml_config = "#{Rails.root}/config/saml.yml"
|
30
|
+
|
31
|
+
mattr_accessor :saml_default_user_key
|
32
|
+
@@saml_default_user_key
|
33
|
+
end
|
34
|
+
|
35
|
+
# Add saml_authenticatable strategy to defaults.
|
36
|
+
#
|
37
|
+
Devise.add_module(:saml_authenticatable,
|
38
|
+
:route => :saml_authenticatable,
|
39
|
+
:strategy => true,
|
40
|
+
:controller => :saml_sessions,
|
41
|
+
:model => 'devise_saml_authenticatable/model')
|
42
|
+
|
43
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'devise_saml_authenticatable/strategy'
|
2
|
+
|
3
|
+
module Devise
|
4
|
+
module Models
|
5
|
+
module SamlAuthenticatable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# Need to determine why these need to be included
|
9
|
+
included do
|
10
|
+
attr_reader :password, :current_password
|
11
|
+
attr_accessor :password_confirmation
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_with_password(params={})
|
15
|
+
params.delete(:current_password)
|
16
|
+
self.update_without_password(params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_without_password(params={})
|
20
|
+
params.delete(:password)
|
21
|
+
params.delete(:password_confirmation)
|
22
|
+
|
23
|
+
result = update_attributes(params)
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
include DeviseSamlAuthenticatable::SamlConfig
|
29
|
+
def authenticate_with_saml(attributes)
|
30
|
+
key = Devise.saml_default_user_key
|
31
|
+
inv_attr = attribute_map.invert
|
32
|
+
auth_value = attributes[inv_attr[key.to_s]]
|
33
|
+
auth_value.try(:downcase!) if Devise.case_insensitive_keys.include?(key)
|
34
|
+
resource = where(key => auth_value).first
|
35
|
+
if (resource.nil? && !Devise.saml_create_user)
|
36
|
+
logger.info("User(#{attributes[inv_attr[key.to_s]]}) not found. Not configured to create the user.")
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
if (resource.nil? && Devise.saml_create_user)
|
41
|
+
logger.info("Creating user(#{attributes[inv_attr[key.to_s]]}).")
|
42
|
+
resource = new
|
43
|
+
set_user_saml_attributes(resource,attributes)
|
44
|
+
resource.save!
|
45
|
+
end
|
46
|
+
|
47
|
+
resource
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_for_shibb_authentication(conditions)
|
51
|
+
find_for_authentication(conditions)
|
52
|
+
end
|
53
|
+
|
54
|
+
def attribute_map
|
55
|
+
@attribute_map ||= YAML.load(File.read("#{Rails.root}/config/attribute-map.yml"))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def set_user_saml_attributes(user,attributes)
|
61
|
+
attribute_map.each do |k,v|
|
62
|
+
Rails.logger.info "Setting: #{v}, #{attributes[k]}"
|
63
|
+
user.send "#{v}=", attributes[k]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
ActionDispatch::Routing::Mapper.class_eval do
|
2
|
+
protected
|
3
|
+
def devise_saml_authenticatable(mapping, controllers)
|
4
|
+
resource :session, :only => [], :controller => controllers[:saml_sessions], :path => "" do
|
5
|
+
get :new, :path => "saml/sign_in", :as => "new"
|
6
|
+
post :create, :path=>"saml/auth"
|
7
|
+
match :destroy, :path => mapping.path_names[:sign_out], :as => "destroy"
|
8
|
+
get :metadata, :path=>"saml/metadata"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'devise/strategies/authenticatable'
|
2
|
+
module Devise
|
3
|
+
module Strategies
|
4
|
+
class SamlAuthenticatable < Authenticatable
|
5
|
+
include DeviseSamlAuthenticatable::SamlConfig
|
6
|
+
def valid?
|
7
|
+
params[:SAMLResponse]
|
8
|
+
end
|
9
|
+
def authenticate!
|
10
|
+
@response = Onelogin::Saml::Response.new(params[:SAMLResponse])
|
11
|
+
@response.settings = get_saml_config
|
12
|
+
resource = mapping.to.authenticate_with_saml(@response.attributes)
|
13
|
+
if @response.is_valid?
|
14
|
+
success!(resource)
|
15
|
+
else
|
16
|
+
fail!(:invalid)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# This method should turn off storage whenever CSRF cannot be verified.
|
21
|
+
# Any known way on how to let the IdP send the CSRF token along with the SAMLResponse ?
|
22
|
+
# Please let me know!
|
23
|
+
def store?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Warden::Strategies.add(:saml_authenticatable, Devise::Strategies::SamlAuthenticatable)
|
data/rails/init.rb
ADDED
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devise_saml_authenticatable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josef Sauter
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-10 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: devise
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>'
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>'
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: ruby-saml
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>'
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.7.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>'
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.7.1
|
46
|
+
description: SAML Authentication for devise
|
47
|
+
email:
|
48
|
+
- Josef.Sauter@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- app/controllers/devise/saml_sessions_controller.rb
|
59
|
+
- devise_saml_authenticatable.gemspec
|
60
|
+
- lib/devise_saml_authenticatable.rb
|
61
|
+
- lib/devise_saml_authenticatable/exception.rb
|
62
|
+
- lib/devise_saml_authenticatable/logger.rb
|
63
|
+
- lib/devise_saml_authenticatable/model.rb
|
64
|
+
- lib/devise_saml_authenticatable/routes.rb
|
65
|
+
- lib/devise_saml_authenticatable/saml_config.rb
|
66
|
+
- lib/devise_saml_authenticatable/strategy.rb
|
67
|
+
- lib/devise_saml_authenticatable/version.rb
|
68
|
+
- rails/init.rb
|
69
|
+
homepage: ''
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.24
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: SAML Authentication for devise
|
93
|
+
test_files: []
|
94
|
+
has_rdoc:
|