shibbolite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +108 -0
- data/Rakefile +21 -0
- data/app/concerns/shibbolite/filters.rb +44 -0
- data/app/concerns/shibbolite/helpers.rb +48 -0
- data/app/concerns/shibbolite/user.rb +21 -0
- data/app/controllers/shibbolite/shibboleth_controller.rb +53 -0
- data/app/views/layouts/shibbolite/shibboleth.html.erb +32 -0
- data/app/views/shibbolite/shibboleth/access_denied.html.erb +7 -0
- data/app/views/shibbolite/shibboleth/logout_message.html.erb +7 -0
- data/config/routes.rb +6 -0
- data/lib/generators/shibbolite/install_generator.rb +17 -0
- data/lib/generators/shibbolite/migration_generator.rb +14 -0
- data/lib/generators/templates/shibbolite_config.rb +51 -0
- data/lib/shibbolite/engine.rb +13 -0
- data/lib/shibbolite/version.rb +3 -0
- data/lib/shibbolite.rb +58 -0
- data/spec/controllers/filters_test_controller_spec.rb +146 -0
- data/spec/controllers/helpers_test_controller_spec.rb +146 -0
- data/spec/controllers/shibbolite/shibboleth_controller_spec.rb +114 -0
- data/spec/controllers/static_controller_spec.rb +5 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/assets/stylesheets/scaffold.css +56 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/filters_test_controller.rb +37 -0
- data/spec/dummy/app/controllers/helpers_test_controller.rb +44 -0
- data/spec/dummy/app/controllers/static_controller.rb +21 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/filters_test/dummy.html.erb +0 -0
- data/spec/dummy/app/views/helpers_test/dummy.html.erb +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/static/admin_resource.html.erb +3 -0
- data/spec/dummy/app/views/static/home.html.erb +35 -0
- data/spec/dummy/app/views/static/user_resource.html.erb +5 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config/application.rb +17 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/shibbolite_config.rb +51 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/routes.rb +6 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20140404162119_create_users.rb +9 -0
- data/spec/dummy/db/migrate/20140414172304_add_shibboleth_attributes_to_users.rb +12 -0
- data/spec/dummy/db/schema.rb +30 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +19082 -0
- data/spec/dummy/log/test.log +46778 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/1be2f6345400afa38cfd6c919f2cf297 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/27c12eb9977c123bfb2ef83640964c02 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/35634bbec2c7419d3efa1a72c23db7e0 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/368329c663775348c7db5500ff959f80 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/371bf96e99717688ed7313a0c53f4212 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/510da110ae528e2d22533be39ff696c5 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/6fc757c2c8329244ca95d6909865bbc2 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/83de9c3d672e9a3420dd8a36aaaab517 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/371bf96e99717688ed7313a0c53f4212 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/6fc757c2c8329244ca95d6909865bbc2 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/factories/shibboleth_attributes.rb +28 -0
- data/spec/factories/users.rb +13 -0
- data/spec/models/user_spec.rb +46 -0
- data/spec/spec_helper.rb +32 -0
- metadata +320 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 David Bassett
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
= Shibbolite
|
2
|
+
|
3
|
+
Are you running Rails apps in a Shibboleth environment? Do you want to use all those sweet Shibboleth provided attributes for super simple access control? Shibbolite is the gem for you.
|
4
|
+
|
5
|
+
== Caveats
|
6
|
+
So far Shibbolite has been tested in one environment (mine). That is:
|
7
|
+
|
8
|
+
* Apache 2.2
|
9
|
+
* Shibboleth NativeSP 2.5 (older versions will not work due to URL redirect options not existing before this)
|
10
|
+
* Phusion Passenger
|
11
|
+
* Rails 4.0.* (older won't work)
|
12
|
+
|
13
|
+
Shibbolite should work fine in any environment that provides the NativeSP attributes as environment variables. As of this writing that includes all versions of Apache by default, and that's pretty much it. Some quick googling tells me that Nginx support can be enabled with FastCGI, and possibly Mongrel as well, but it looks like this will provide the attributes as request headers, which will not work out of the box.
|
14
|
+
|
15
|
+
== Install
|
16
|
+
=== Before you start...
|
17
|
+
Shibbolite expects that you already
|
18
|
+
1. Have root set to something
|
19
|
+
2. Created a User model at some point
|
20
|
+
|
21
|
+
=== OK let's go
|
22
|
+
In your Gemfile
|
23
|
+
gem 'shibbolite'
|
24
|
+
Then run bundle to install it. Now run the handy generator
|
25
|
+
rails generate shibbolite:install
|
26
|
+
...which will copy shibbolite_config.rb to your app's initializer folder. Take a look over the various options. You'll have to add your SP's attributes to the configuration, as well as specify the name of your user class if it isn't "User". You can also configure the list of valid group types you can assign to your users.
|
27
|
+
|
28
|
+
If your SP handler locations are configured differently from the common defaults you'll have to specify those as well. See your SP's /Status handler more information, as well as the {Shibboleth documentation}[https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPSessions].
|
29
|
+
|
30
|
+
Now generate the migration
|
31
|
+
rails generate shibbolite:migration
|
32
|
+
This will create a migration for your User model that adds all of the Shibbolith attributes. Take a look at it then run
|
33
|
+
rake db:migrate
|
34
|
+
|
35
|
+
Go to your User model and add
|
36
|
+
include Shibbolite::User
|
37
|
+
|
38
|
+
Go to your ApplicationController and add
|
39
|
+
include Shibbolite::Filters
|
40
|
+
|
41
|
+
Restart your application
|
42
|
+
|
43
|
+
== Usage
|
44
|
+
Shibbolite::User adds some validations as well as .find_user, which returns the user that has the supplied :primary_user_id
|
45
|
+
|
46
|
+
Shibbolite::Filters gives you a gaggle of before filters to enable access control and a handful of helpers for views. Below are some examples, see the source for a complete list.
|
47
|
+
|
48
|
+
=== Filters
|
49
|
+
before_action :require_login
|
50
|
+
|
51
|
+
In your ApplicationController will require that users have a valid Shibboleth session before they can launch any action
|
52
|
+
|
53
|
+
before_action { |c| c.require_group :admin }
|
54
|
+
|
55
|
+
will require that the user belong to the admin group
|
56
|
+
|
57
|
+
before_action { |c| c.require_group :admin, :user }
|
58
|
+
|
59
|
+
will allow anyone who has the admin or the user group
|
60
|
+
|
61
|
+
before_action { |c| c.require_group_or_id :admin, some_user.id }
|
62
|
+
|
63
|
+
is useful for things like profile pages that are accessible to both an owner and anyone in the admin group
|
64
|
+
|
65
|
+
before_action :use_attributes_if_available, only: :public
|
66
|
+
|
67
|
+
will attempt to load a Shibboleth session if one is available but do nothing otherwise. Use this if you have a public page but still want to make use of attributes for users that have logged in.
|
68
|
+
|
69
|
+
=== Helpers
|
70
|
+
These helpers will be available to views of any controllers that include Shibbolite::Filters. See the source for a complete list.
|
71
|
+
|
72
|
+
current_user
|
73
|
+
|
74
|
+
will return the User object of the Shibboleth authenticated user or nil if no one is authenticated, or the user isn't registered in the database. NOTE: current_user can still return nil after the user authenticates if one of the above filters isn't used before the controller action.
|
75
|
+
|
76
|
+
Since current_user relies on a database lookup, you'll have to register (ie persist) your users before they can interact with most of the features of Shibbolite. How you want to handle user registration is left up to you.
|
77
|
+
|
78
|
+
logged_in?
|
79
|
+
|
80
|
+
checks to see that a username is set in the session
|
81
|
+
|
82
|
+
user_in_group? (:admin)
|
83
|
+
|
84
|
+
will return true if the current user has the admin group
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
=== Actions
|
89
|
+
A couple of actions are available from Shibbolite::ShibbolethController
|
90
|
+
|
91
|
+
shibbolite.login_path #=> '/shibbolite/login'
|
92
|
+
|
93
|
+
will attempt to load a Shibboleth session, or redirect to the Shibbolite.session_initiator for authentication if there is none. After authentication the user is brought back to the root_path.
|
94
|
+
|
95
|
+
shibbolite.logout_path #=> 'shibbolite/logout
|
96
|
+
|
97
|
+
likewise redirects the client to the Shibbolite.logout_initiator
|
98
|
+
|
99
|
+
== Contribute
|
100
|
+
1. Fork the repository
|
101
|
+
2. Make sure the tests pass
|
102
|
+
3. Write a test for your change and make it pass
|
103
|
+
4. Push the changes to your Fork
|
104
|
+
5. Submit a pull request
|
105
|
+
|
106
|
+
== License
|
107
|
+
|
108
|
+
Shibbolite is released under the MIT License
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
8
|
+
load 'rails/tasks/engine.rake'
|
9
|
+
|
10
|
+
Bundler::GemHelper.install_tasks
|
11
|
+
|
12
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
13
|
+
|
14
|
+
require 'rspec/core'
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
|
17
|
+
desc "Run all specs in spec directory (excluding plugin specs)"
|
18
|
+
RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
|
19
|
+
|
20
|
+
task :default => :spec
|
21
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# controller filters for access control
|
2
|
+
module Shibbolite
|
3
|
+
module Filters
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
include Shibbolite::Helpers
|
7
|
+
|
8
|
+
def require_login
|
9
|
+
redirect_to login_or_access_denied unless logged_in?
|
10
|
+
end
|
11
|
+
|
12
|
+
def require_registered
|
13
|
+
redirect_to login_or_access_denied unless registered_user?
|
14
|
+
end
|
15
|
+
|
16
|
+
def require_group(*groups)
|
17
|
+
in_group = false
|
18
|
+
groups.flatten.each { |group| in_group ||= user_in_group?(group) }
|
19
|
+
redirect_to login_or_access_denied unless in_group
|
20
|
+
end
|
21
|
+
|
22
|
+
def require_id(id)
|
23
|
+
redirect_to login_or_access_denied unless user_has_id?(id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def require_group_or_id(*groups, id)
|
27
|
+
unless user_has_id?(id)
|
28
|
+
require_group(groups)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def use_attributes_if_available
|
33
|
+
if request.env[Shibbolite.pid.to_s] and not logged_in?
|
34
|
+
redirect_to login_or_access_denied
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# a handy redirect target
|
39
|
+
def login_or_access_denied
|
40
|
+
session[:requested_url] = request.fullpath
|
41
|
+
logged_in? ? shibbolite.access_denied_url : shibbolite.login_url
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
module Helpers
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :logged_in?, :current_user, :anonymous_user?, :guest_user?,
|
7
|
+
:registered_user?, :user_in_group?, :user_has_id?
|
8
|
+
end
|
9
|
+
|
10
|
+
# true when the primary_user_id has been saved in session
|
11
|
+
def logged_in?
|
12
|
+
session[Shibbolite.pid]
|
13
|
+
end
|
14
|
+
|
15
|
+
# sets current user from the session user id
|
16
|
+
def current_user
|
17
|
+
logged_in? ? @current_user ||= Shibbolite.user_class.find_user(session[Shibbolite.pid]) : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# true when user hasn't signed in to SSO
|
21
|
+
def anonymous_user?
|
22
|
+
not logged_in?
|
23
|
+
end
|
24
|
+
|
25
|
+
# true when the user signed in to the SSO
|
26
|
+
# but they haven't been registered with the app
|
27
|
+
def guest_user?
|
28
|
+
current_user.nil? and logged_in?
|
29
|
+
end
|
30
|
+
|
31
|
+
# true when the user exists in the database
|
32
|
+
def registered_user?
|
33
|
+
current_user
|
34
|
+
end
|
35
|
+
|
36
|
+
def user_in_group?(group, user = nil)
|
37
|
+
user ||= current_user
|
38
|
+
return false unless user
|
39
|
+
user.group == group.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_has_id?(id, user = nil)
|
43
|
+
user ||= current_user
|
44
|
+
return false unless user
|
45
|
+
user.id == id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
module User
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
unless Shibbolite.skip_validations
|
7
|
+
validates Shibbolite.primary_user_id, :group, presence: true
|
8
|
+
validates Shibbolite.primary_user_id, uniqueness: true
|
9
|
+
validates :group, inclusion: { in: Shibbolite.groups.map(&:to_s),
|
10
|
+
message: "%{value} is not included in Shibbolite.groups" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# gets the user who matches the shibboleth primary key
|
16
|
+
def find_user(pid)
|
17
|
+
find_by(Shibbolite.primary_user_id => pid)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
class ShibbolethController < ActionController::Base
|
3
|
+
|
4
|
+
include Shibbolite::Helpers
|
5
|
+
|
6
|
+
def access_denied
|
7
|
+
@requested_url = session.delete(:requested_url)
|
8
|
+
end
|
9
|
+
|
10
|
+
def login
|
11
|
+
session[:requested_url] ||= main_app.root_path
|
12
|
+
load_session
|
13
|
+
redirect_to logged_in? ? session.delete(:requested_url) : sp_login_url
|
14
|
+
end
|
15
|
+
|
16
|
+
def logout
|
17
|
+
session.delete(Shibbolite.pid)
|
18
|
+
redirect_to sp_logout_url
|
19
|
+
end
|
20
|
+
|
21
|
+
# required to prevent displaying default shibboleth logout message
|
22
|
+
def logout_message ; end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# loads the session data created by shibboleth
|
27
|
+
# ensures that the user's id is set in session
|
28
|
+
# and updates the user's shibboleth attributes
|
29
|
+
def load_session
|
30
|
+
unless logged_in?
|
31
|
+
session[Shibbolite.pid] = request.env[Shibbolite.pid.to_s]
|
32
|
+
current_user.update(get_attributes) if registered_user?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# reads shibboleth attributes from environment
|
37
|
+
def get_attributes
|
38
|
+
request.env.with_indifferent_access.slice(*Shibbolite.attributes)
|
39
|
+
end
|
40
|
+
|
41
|
+
def sp_login_url
|
42
|
+
request.protocol + request.host +
|
43
|
+
Shibbolite.handler_url + Shibbolite.session_initiator +
|
44
|
+
'?' + URI.encode_www_form(target: login_url)
|
45
|
+
end
|
46
|
+
|
47
|
+
def sp_logout_url
|
48
|
+
request.protocol + request.host +
|
49
|
+
Shibbolite.handler_url + Shibbolite.logout_initiator +
|
50
|
+
'?' + URI.encode_www_form(return: logout_message_url)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Shibbolite</title>
|
5
|
+
<style>
|
6
|
+
body {
|
7
|
+
background-color: white;
|
8
|
+
color: #474747;
|
9
|
+
text-align: left;
|
10
|
+
font-family: arial, sans-serif;
|
11
|
+
}
|
12
|
+
|
13
|
+
div.message {
|
14
|
+
width: 20em;
|
15
|
+
margin: 2em 0 0 2em;
|
16
|
+
border: 20px solid #D8000F;
|
17
|
+
border-radius: 70px;
|
18
|
+
padding: 7px 3em 1em 3em;
|
19
|
+
}
|
20
|
+
|
21
|
+
h1 {
|
22
|
+
font-size: 25px;
|
23
|
+
color: #D8000F;
|
24
|
+
line-height: 2em;
|
25
|
+
}
|
26
|
+
|
27
|
+
</style>
|
28
|
+
</head>
|
29
|
+
|
30
|
+
<body>
|
31
|
+
<%= yield %>
|
32
|
+
</body>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates", __FILE__)
|
5
|
+
|
6
|
+
desc 'Creates the shibbolite_config.rb initializer and mounts the Shibbolite engine'
|
7
|
+
|
8
|
+
def create_initializer
|
9
|
+
copy_file 'shibbolite_config.rb', 'config/initializers/shibbolite_config.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
def mount_engine
|
13
|
+
route "mount Shibbolite::Engine => '/shibbolite'"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
module Generators
|
3
|
+
class MigrationGenerator < Rails::Generators::Base
|
4
|
+
source_root File.expand_path("../../templates", __FILE__)
|
5
|
+
|
6
|
+
desc "Creates the migration to add shibboleth attributes to the main app's User class"
|
7
|
+
|
8
|
+
def creat_migration
|
9
|
+
generate "migration AddShibbolethAttributesTo#{Shibbolite.user_table_name} group:string" <<
|
10
|
+
Shibbolite.attributes.collect { |attr| " #{attr}:string" }.join
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# These configuration options must be set before Shibbolite
|
2
|
+
# can be loaded. Mandatory settings are :attributes, :user_class,
|
3
|
+
# and :primary_user_id. Depending on your environment you may
|
4
|
+
# also have to change :handler_url, :session_initiator, or :logout_initiator
|
5
|
+
# if they are different from the default values. Check with your SP's
|
6
|
+
# shibboleth.xml configuration for the correct settings.
|
7
|
+
Shibbolite.config do |c|
|
8
|
+
|
9
|
+
# any shibboleth attributes available in your environment that you want
|
10
|
+
# passed to your application.
|
11
|
+
c.attributes = [:eppn, :some_other_attribute]
|
12
|
+
|
13
|
+
# SP attribute used as a unique username
|
14
|
+
# typically this should be the same attribute that
|
15
|
+
# your SP uses to set the REMOTE_USER environment variable
|
16
|
+
# Use the getter alias Shibbolite.pid of you want to be concise
|
17
|
+
c.primary_user_id = :eppn
|
18
|
+
|
19
|
+
# The defaults for these options will work for most installations
|
20
|
+
# all options are listed with their default values, only uncomment
|
21
|
+
# if you need to change them
|
22
|
+
|
23
|
+
# friendly display name for views
|
24
|
+
# concise alias Shibbolite.pid_display is available too
|
25
|
+
#c.primary_user_id_display = 'Username'
|
26
|
+
|
27
|
+
# name of your application's User model
|
28
|
+
#c.user_class = 'User'
|
29
|
+
|
30
|
+
# used with the generated migration.
|
31
|
+
# Only override if your table doesn't follow
|
32
|
+
# normal pluralization or name conventions
|
33
|
+
#c.user_table_name = c.user_class.pluralize
|
34
|
+
|
35
|
+
# NativeSP base location
|
36
|
+
# used to construct urls to interact with SP
|
37
|
+
#c.handler_url = '/Shibboleth.sso'
|
38
|
+
|
39
|
+
# NativeSP handler location for starting sessions
|
40
|
+
#c.session_initiator = '/Login'
|
41
|
+
|
42
|
+
# NativeSP handler location for logging out
|
43
|
+
#c.logout_initiator = '/Logout'
|
44
|
+
|
45
|
+
# the types of groups to assign users
|
46
|
+
#c.groups = [:user, :admin]
|
47
|
+
|
48
|
+
# setting to true will skip including validations
|
49
|
+
# from the Shibbolite::User class
|
50
|
+
#c.skip_validations = false
|
51
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Shibbolite
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Shibbolite
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, :fixture => false
|
7
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
8
|
+
g.assets false
|
9
|
+
g.helper false
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/shibbolite.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "shibbolite/engine"
|
2
|
+
|
3
|
+
module Shibbolite
|
4
|
+
|
5
|
+
# shibboleth attributes available in the environment
|
6
|
+
mattr_accessor :attributes
|
7
|
+
|
8
|
+
# shibboleth primary key
|
9
|
+
mattr_accessor :primary_user_id
|
10
|
+
|
11
|
+
mattr_accessor :primary_user_id_display
|
12
|
+
@@primary_user_id_display = 'Username'
|
13
|
+
|
14
|
+
# groups available to assign to users
|
15
|
+
mattr_accessor :groups
|
16
|
+
@@groups = [:user, :admin]
|
17
|
+
|
18
|
+
# the parent application's User model
|
19
|
+
mattr_accessor :user_class
|
20
|
+
@@user_class = 'User'
|
21
|
+
|
22
|
+
def self.user_class
|
23
|
+
@@user_class.constantize
|
24
|
+
end
|
25
|
+
|
26
|
+
mattr_accessor :user_table_name
|
27
|
+
@@user_table_name = @@user_class.pluralize
|
28
|
+
|
29
|
+
mattr_accessor :skip_validations
|
30
|
+
@@skip_validations = false
|
31
|
+
|
32
|
+
# NativeSP base location
|
33
|
+
mattr_accessor :handler_url
|
34
|
+
@@handler_url = '/Shibboleth.sso'
|
35
|
+
|
36
|
+
# NativeSP handler location for starting sessions
|
37
|
+
mattr_accessor :session_initiator
|
38
|
+
@@session_initiator = '/Login'
|
39
|
+
|
40
|
+
# NativeSP handler location for logging out
|
41
|
+
mattr_accessor :logout_initiator
|
42
|
+
@@logout_initiator = '/Logout'
|
43
|
+
|
44
|
+
# shortened/alternate accessors
|
45
|
+
|
46
|
+
def self.pid
|
47
|
+
primary_user_id
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.pid_display
|
51
|
+
primary_user_id_display
|
52
|
+
end
|
53
|
+
|
54
|
+
# friendly config
|
55
|
+
def self.config
|
56
|
+
yield self
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# a dummy controller used to test the Shibbolite::Filters concern
|
4
|
+
|
5
|
+
describe FiltersTestController do
|
6
|
+
|
7
|
+
# presume the session has been loaded
|
8
|
+
before { allow(subject).to receive(:load_session) }
|
9
|
+
|
10
|
+
describe '#require_login' do
|
11
|
+
|
12
|
+
context 'when logged in' do
|
13
|
+
it 'allows the action to continue' do
|
14
|
+
allow(subject).to receive(:logged_in?).and_return(true)
|
15
|
+
get :_require_login
|
16
|
+
expect(response).to render_template(:dummy)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'when not logged in' do
|
22
|
+
it 'redirects' do
|
23
|
+
allow(subject).to receive(:logged_in?).and_return(false)
|
24
|
+
get :_require_login
|
25
|
+
expect(response).to redirect_to('/shibbolite/login')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#require_registered' do
|
31
|
+
|
32
|
+
context 'when user is registered' do
|
33
|
+
it 'allows the action to continue' do
|
34
|
+
allow(subject).to receive(:registered_user?).and_return(true)
|
35
|
+
get :_require_registered
|
36
|
+
expect(response).to render_template(:dummy)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when the user is not registered' do
|
41
|
+
it 'redirects' do
|
42
|
+
allow(subject).to receive(:registered_user?).and_return(false)
|
43
|
+
get :_require_registered
|
44
|
+
expect(response).to redirect_to('/shibbolite/login')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe '#use_attributes_if_available' do
|
50
|
+
|
51
|
+
context 'when the user authenticated but login isn\'t required' do
|
52
|
+
it 'redirects to login to load the session' do
|
53
|
+
allow(subject).to receive(:logged_in?).and_return(false)
|
54
|
+
request.env[Shibbolite.pid.to_s] = 'not nil'
|
55
|
+
get :_use_attributes_if_available
|
56
|
+
expect(response).to redirect_to('/shibbolite/login')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when the user did not authenticate' do
|
61
|
+
it 'allows the action' do
|
62
|
+
get :_use_attributes_if_available
|
63
|
+
expect(response).to render_template(:dummy)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'filters that require a user object' do
|
69
|
+
|
70
|
+
let!(:user_id) { 17 }
|
71
|
+
let!(:admin_id) { 1 }
|
72
|
+
let(:guest) { double('guest', id: nil, group: nil )}
|
73
|
+
let(:user) { double('user' , id: user_id, group: 'user')}
|
74
|
+
let(:admin) { double('admin', id: admin_id, group: 'admin' )}
|
75
|
+
|
76
|
+
describe '#require_group' do
|
77
|
+
|
78
|
+
context 'when the user has a group listed' do
|
79
|
+
it 'allows the action to continue' do
|
80
|
+
allow(subject).to receive(:current_user).and_return(user)
|
81
|
+
get :_require_group, groups: ['user', 'admin']
|
82
|
+
expect(response).to render_template(:dummy)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'when the user is not a member' do
|
87
|
+
it 'redirects' do
|
88
|
+
allow(subject).to receive(:current_user).and_return(guest)
|
89
|
+
get :_require_group, groups: ['user', 'admin']
|
90
|
+
expect(response).to redirect_to('/shibbolite/login')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#require_id' do
|
96
|
+
|
97
|
+
context 'when the user has the id listed' do
|
98
|
+
it 'allows the action to continue' do
|
99
|
+
allow(subject).to receive(:current_user).and_return(admin)
|
100
|
+
get :_require_id, id: admin_id
|
101
|
+
expect(response).to render_template(:dummy)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'when the user does not have the id' do
|
106
|
+
it 'redirects' do
|
107
|
+
allow(subject).to receive(:current_user).and_return(guest)
|
108
|
+
get :_require_id, id: user_id
|
109
|
+
expect(response).to redirect_to('/shibbolite/login')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#require_group_or_id' do
|
115
|
+
|
116
|
+
context 'happy paths' do
|
117
|
+
|
118
|
+
it 'allows action with matching id' do
|
119
|
+
allow(subject).to receive(:current_user).and_return(user)
|
120
|
+
get :_require_group_or_id, groups: 'admin', id: user_id
|
121
|
+
expect(response).to render_template(:dummy)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'allows action with matching group' do
|
125
|
+
allow(subject).to receive(:current_user).and_return(user)
|
126
|
+
get :_require_group_or_id, groups: 'user', id: admin_id
|
127
|
+
expect(response).to render_template(:dummy)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'allows action with both id and group matching' do
|
131
|
+
allow(subject).to receive(:current_user).and_return(admin)
|
132
|
+
get :_require_group_or_id, groups: 'admin', id: admin_id
|
133
|
+
expect(response).to render_template(:dummy)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'with no matching criteria' do
|
138
|
+
it 'redirects' do
|
139
|
+
allow(subject).to receive(:current_user).and_return(guest)
|
140
|
+
get :_require_group_or_id, groups: 'user', id: user_id
|
141
|
+
expect(response).to redirect_to('/shibbolite/login')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|