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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devise_saml_authenticatable.gemspec
4
+ gemspec
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,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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,6 @@
1
+ module DeviseSamlAuthenticatable
2
+
3
+ class SamlException < Exception
4
+ end
5
+
6
+ end
@@ -0,0 +1,11 @@
1
+ module DeviseSamlAuthenticatable
2
+
3
+ class Logger
4
+ def self.send(message, logger = Rails.logger)
5
+ if ::Devise.saml_logger
6
+ logger.add 0, " \e[36msaml:\e[0m #{message}"
7
+ end
8
+ end
9
+ end
10
+
11
+ end
@@ -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,8 @@
1
+ require 'ruby-saml'
2
+ module DeviseSamlAuthenticatable
3
+ module SamlConfig
4
+ def get_saml_config
5
+ @saml_config = Onelogin::Saml::Settings.new(YAML.load(File.read("#{Rails.root}/config/idp.yml"))[Rails.env])
6
+ end
7
+ end
8
+ 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)
@@ -0,0 +1,3 @@
1
+ module DeviseSamlAuthenticatable
2
+ VERSION = "0.0.1"
3
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ #Include hook code here
2
+ require 'devise_saml_authenticatable'
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: