.
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Redmine CAS plugin
2 |
3 | Plugin to CASify your Redmine installation.
4 |
5 | ## Compatibility
6 |
7 | Tested with Redmine 2.2.x, 2.3.x, 2.4.x and 2.5.x but it should work fine with Redmine 2.x and possibly 1.x.
8 | We use [CASino](http://casino.rbcas.com) as CAS server, but it might work with others as well.
9 |
10 | ## Installation
11 |
12 | 1. Download or clone this repository and place it in the Redmine `plugins` directory as `redmine_cas`
13 | 2. Restart your webserver
14 | 3. Open Redmine and check if the plugin is visible under Administration > Plugins
15 | 4. Follow the "Configure" link and set the parameters
16 | 5. Party
17 |
18 | ## Notes
19 |
20 | ### Usage
21 |
22 | If your installation has no public areas ("Authentication required") and you are not logged in, you will be
23 | redirected to the CAS-login page. The default login page will still work when you access it directly
24 | (http://example.com/path-to-redmine/login).
25 |
26 | If your installation is not "Authentication required", the login page will show a link that lets you login
27 | with CAS.
28 |
29 | ### Single Sign Out, Single Logout
30 |
31 | The sessions have to be stored in the database to make Single Sign Out work.
32 | You can achieve this with a tiny plugin: [redmine_activerecord_session_store](https://github.com/pencil/redmine_activerecord_session_store)
33 |
34 | ### Auto-create users
35 |
36 | By enabling this setting, successfully authenticated users will be automatically added into Redmine if they do not already exist. You *must* define the attribute mapping for at least firstname, lastname and mail attributes for this to work.
37 |
38 | ## About
39 |
40 | This redmine plugin is currently maintained and funded by [nine](https://nine.ch).
41 |
42 | [](https://www.nine.ch)
43 |
44 |
--------------------------------------------------------------------------------
/app/views/redmine_cas/_cas_login_link.html.erb:
--------------------------------------------------------------------------------
1 | <% if Setting.plugin_redmine_cas[:enabled] %>
2 |
3 | <%= link_to("Login with CAS", :controller => "account", :action => "cas", :ref => '/my/page') %>
4 |
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/app/views/redmine_cas/_settings.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= label_tag "settings[enabled]", l(:redmine_cas_settings_enabled_label) %>
3 | <%= check_box_tag "settings[enabled]", 1, @settings[:enabled] %>
4 |
5 |
6 | <%= label_tag "settings[cas_url]", l(:redmine_cas_settings_cas_url_label) %>
7 | <%= text_field_tag "settings[cas_url]", @settings[:cas_url], :size => 50 %>
8 | <%= l(:redmine_cas_settings_cas_url_helptext).html_safe %>
9 |
10 |
11 | <%= label_tag "settings[attributes_mapping]", l(:redmine_cas_settings_attributes_mapping_label) %>
12 | <%= text_field_tag "settings[attributes_mapping]", @settings[:attributes_mapping], :size => 50 %>
13 | <%= l(:redmine_cas_settings_attributes_mapping_helptext, :attribute_names => User.attribute_names.join(', ')).html_safe %>
14 |
15 |
16 | <%= label_tag "settings[autocreate_users]", l(:redmine_cas_settings_autocreate_users_label) %>
17 | <%= check_box_tag "settings[autocreate_users]", 1, @settings[:autocreate_users] %>
18 | <%= l(:redmine_cas_settings_autocreate_users_helptext).html_safe %>
19 |
20 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | redmine_cas_settings_enabled_label: 'Enabled'
3 | redmine_cas_settings_cas_url_label: 'CAS server URL'
4 | redmine_cas_settings_cas_url_helptext: 'Base URL to your CAS server.'
5 | redmine_cas_settings_attributes_mapping_label: 'Attributes mapping'
6 | redmine_cas_settings_attributes_mapping_helptext: 'This is how the plugin maps extended attributes from the CAS server to the Redmine model.
attribute_name_in_redmine=attribute_name_in_cas_response
Separate entries with &
(query-string).
Example: firstname=first_name&lastname=last_name&mail=email
Valid attribute names: %{attribute_names}
'
7 | redmine_cas_settings_autocreate_users_label: 'Auto-create users'
8 | redmine_cas_settings_autocreate_users_helptext: 'Automatically create a redmine user if it is successfully authenticated.
Will only work if you specify firstname, lastname and mail in the attributes mapping setting above.'
9 | redmine_cas_user_not_found: '"%{user}" was authenticated but needs to be created in Redmine first.'
10 | redmine_cas_user_not_created: '"%{user}" was authenticated but could not be created automatically in Redmine. It must be added manually.'
11 |
--------------------------------------------------------------------------------
/config/locales/fr.yml:
--------------------------------------------------------------------------------
1 | fr:
2 | redmine_cas_settings_enabled_label: 'Activé'
3 | redmine_cas_settings_cas_url_label: 'URL du serveur CAS'
4 | redmine_cas_settings_cas_url_helptext: 'URL de base de votre serveur CAS.'
5 | redmine_cas_settings_attributes_mapping_label: 'Association d''attributs'
6 | redmine_cas_settings_attributes_mapping_helptext: 'Voici la manière dont le plugin associe les attributs étendus provenant du serveur CAS vers le modèle de Redmine.
nom_d_attribut_dans_redmine=nom_d_attribut_dans_la_reponse_cas
Séparer les entrées avec &
(query-string).
Exemple: firstname=first_name&lastname=last_name&mail=email
Noms d''attribut valides: %{attribute_names}
'
7 | redmine_cas_settings_autocreate_users_label: 'Créer les utilisateurs automatiquement'
8 | redmine_cas_settings_autocreate_users_helptext: 'Créer automatiquement un utilisateur redmine s''il est authentifié avec succès.
Ceci ne fonctionnera que si vous spécifiez firstname, lastname et mail dans les associations d''attributs ci-dessus.'
9 | redmine_cas_user_not_found: 'L''utilisateur "%{user}" a été authentifié mais doit d''abord être créé dans Redmine.'
10 | redmine_cas_user_not_created: 'L''utilisateur "%{user}" a été authentifié, mais n''a pas pu être créé automatiquement dans Redmine. Il doit être ajouté manuellement.'
11 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | RedmineApp::Application.routes.draw do
2 | get 'cas', :to => 'account#cas'
3 | post 'cas', :to => 'account#cas'
4 | end
5 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require 'redmine'
2 | require 'redmine_cas'
3 | require 'redmine_cas/application_controller_patch'
4 | require 'redmine_cas/account_controller_patch'
5 |
6 | require_dependency 'redmine_cas_hook_listener'
7 |
8 | Redmine::Plugin.register :redmine_cas do
9 | name 'Redmine CAS'
10 | author 'Nils Caspar (Nine Internet Solutions AG)'
11 | description 'Plugin to CASify your Redmine installation.'
12 | version '1.2.1'
13 | url 'https://github.com/ninech/redmine_cas'
14 | author_url 'http://www.nine.ch/'
15 |
16 | settings :default => {
17 | 'enabled' => false,
18 | 'cas_url' => 'https://',
19 | 'attributes_mapping' => 'firstname=first_name&lastname=last_name&mail=email',
20 | 'autocreate_users' => false
21 | }, :partial => 'redmine_cas/settings'
22 |
23 | Rails.configuration.to_prepare do
24 | ApplicationController.send(:include, RedmineCAS::ApplicationControllerPatch)
25 | AccountController.send(:include, RedmineCAS::AccountControllerPatch)
26 | end
27 | ActionDispatch::Callbacks.before do
28 | RedmineCAS.setup!
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/redmine_cas.rb:
--------------------------------------------------------------------------------
1 | require 'casclient'
2 | require 'casclient/frameworks/rails/filter'
3 |
4 | module RedmineCAS
5 | extend self
6 |
7 | def setting(name)
8 | Setting.plugin_redmine_cas[name]
9 | end
10 |
11 | def enabled?
12 | setting(:enabled)
13 | end
14 |
15 | def autocreate_users?
16 | setting(:autocreate_users)
17 | end
18 |
19 | def setup!
20 | return unless enabled?
21 | CASClient::Frameworks::Rails::Filter.configure(
22 | :cas_base_url => setting(:cas_url),
23 | :logger => Rails.logger,
24 | :enable_single_sign_out => single_sign_out_enabled?
25 | )
26 | end
27 |
28 | def single_sign_out_enabled?
29 | ActiveRecord::Base.connection.table_exists?(:sessions)
30 | end
31 |
32 | def user_extra_attributes_from_session(session)
33 | attrs = {}
34 | map = Rack::Utils.parse_nested_query setting(:attributes_mapping)
35 | extra_attributes = session[:cas_extra_attributes] || {}
36 | map.each_pair do |key_redmine, key_cas|
37 | value = extra_attributes[key_cas]
38 | if User.attribute_method?(key_redmine) && value
39 | attrs[key_redmine] = (value.is_a? Array) ? value.first : value
40 | end
41 | end
42 | attrs
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/redmine_cas/account_controller_patch.rb:
--------------------------------------------------------------------------------
1 | require 'redmine_cas'
2 |
3 | module RedmineCAS
4 | module AccountControllerPatch
5 | def self.included(base)
6 | base.send(:include, InstanceMethods)
7 | base.class_eval do
8 | alias_method_chain :logout, :cas
9 | end
10 | end
11 |
12 | module InstanceMethods
13 | def logout_with_cas
14 | return logout_without_cas unless RedmineCAS.enabled?
15 | logout_user
16 | CASClient::Frameworks::Rails::Filter.logout(self, home_url)
17 | end
18 |
19 | def cas
20 | return redirect_to_action('login') unless RedmineCAS.enabled?
21 |
22 | if User.current.logged?
23 | # User already logged in.
24 | redirect_to_ref_or_default
25 | return
26 | end
27 |
28 | if CASClient::Frameworks::Rails::Filter.filter(self)
29 | user = User.find_by_login(session[:cas_user])
30 |
31 | # Auto-create user if possible
32 | if user.nil? && RedmineCAS.autocreate_users?
33 | user = User.new
34 | user.login = session[:cas_user]
35 | user.assign_attributes(RedmineCAS.user_extra_attributes_from_session(session))
36 | return cas_user_not_created(user) if !user.save
37 | user.reload
38 | end
39 |
40 | return cas_user_not_found if user.nil?
41 | return cas_account_pending unless user.active?
42 |
43 | user.update_attribute(:last_login_on, Time.now)
44 | user.update_attributes(RedmineCAS.user_extra_attributes_from_session(session))
45 | if RedmineCAS.single_sign_out_enabled?
46 | # logged_user= would start a new session and break single sign-out
47 | User.current = user
48 | start_user_session(user)
49 | else
50 | self.logged_user = user
51 | end
52 |
53 | redirect_to_ref_or_default
54 | end
55 | end
56 |
57 | def redirect_to_ref_or_default
58 | default_url = url_for(params.merge(:ticket => nil))
59 | if params.has_key?(:ref)
60 | # do some basic validation on ref, to prevent a malicious link to redirect
61 | # to another site.
62 | new_url = params[:ref]
63 | if /http(s)?:\/\/|@/ =~ new_url
64 | # evil referrer!
65 | redirect_to default_url
66 | else
67 | redirect_to request.base_url + params[:ref]
68 | end
69 | else
70 | redirect_to default_url
71 | end
72 | end
73 |
74 | def cas_account_pending
75 | render_403 :message => l(:notice_account_pending)
76 | end
77 |
78 | def cas_user_not_found
79 | render_403 :message => l(:redmine_cas_user_not_found, :user => session[:cas_user])
80 | end
81 |
82 | def cas_user_not_created(user)
83 | logger.error "Could not auto-create user: #{user.errors.full_messages.to_sentence}"
84 | render_403 :message => l(:redmine_cas_user_not_created, :user => session[:cas_user])
85 | end
86 |
87 | end
88 | end
89 | end
90 |
--------------------------------------------------------------------------------
/lib/redmine_cas/application_controller_patch.rb:
--------------------------------------------------------------------------------
1 | require 'redmine_cas'
2 |
3 | module RedmineCAS
4 | module ApplicationControllerPatch
5 | def self.included(base)
6 | base.send(:include, InstanceMethods)
7 | base.class_eval do
8 | alias_method_chain :verify_authenticity_token, :cas
9 | alias_method_chain :require_login, :cas
10 | end
11 | end
12 |
13 | module InstanceMethods
14 | def require_login_with_cas
15 | return require_login_without_cas unless RedmineCAS.enabled?
16 | if !User.current.logged?
17 | referrer = request.fullpath;
18 | respond_to do |format|
19 | # pass referer to cas action, to work around this problem:
20 | # https://github.com/ninech/redmine_cas/pull/13#issuecomment-53697288
21 | format.html { redirect_to :controller => 'account', :action => 'cas', :ref => referrer }
22 | format.atom { redirect_to :controller => 'account', :action => 'cas', :ref => referrer }
23 | format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
24 | format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
25 | format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' }
26 | end
27 | return false
28 | end
29 | true
30 | end
31 |
32 | def verify_authenticity_token_with_cas
33 | if cas_logout_request?
34 | logger.info 'CAS logout request detected: Skipping validation of authenticity token'
35 | else
36 | verify_authenticity_token_without_cas
37 | end
38 | end
39 |
40 | def cas_logout_request?
41 | request.post? && params.has_key?('logoutRequest')
42 | end
43 |
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/redmine_cas_hook_listener.rb:
--------------------------------------------------------------------------------
1 | module RedmineCAS
2 | class RedmineCASHookListener < Redmine::Hook::ViewListener
3 | render_on :view_account_login_top, :partial => 'redmine_cas/cas_login_link'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------