From 86900837efda299bd69c254bffae3945c3ae3241 Mon Sep 17 00:00:00 2001 From: Johan Vervloet Date: Thu, 26 Jun 2014 15:06:00 +0200 Subject: [PATCH 1/4] refs #9 - CAS-login for redmine with public content. This is my second attempt. If authentication is required, you will be redirected to the CAS login page whenever you are not authenticated. (Except when you visit the default login page.) If authentication is not required, the login page will show a link, that allows you to login using CAS. --- README.md | 8 ++- .../redmine_cas/_cas_login_link.html.erb | 5 ++ config/routes.rb | 3 + init.rb | 2 + lib/redmine_cas/account_controller_patch.rb | 55 +++++++++++++++++++ .../application_controller_patch.rb | 46 +--------------- lib/redmine_cas_hook_listener.rb | 5 ++ 7 files changed, 78 insertions(+), 46 deletions(-) create mode 100644 app/views/redmine_cas/_cas_login_link.html.erb create mode 100644 config/routes.rb create mode 100644 lib/redmine_cas_hook_listener.rb diff --git a/README.md b/README.md index 85d66b5..74b9e12 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,12 @@ We use [CASino](http://casino.rbcas.com) as CAS server, but it might work with o ### Usage -This plugin was made for redmine installations without public areas ("Authentication required"). -The default login page will still work when you access it directly (http://example.com/path-to-redmine/login). +If your installation has no public areas ("Authentication required") and you are not logged in, you will be +redirected to the CAS-login page. The default login page will still work when you access it directly +(http://example.com/path-to-redmine/login). + +If your installation is not "Authentication required", the login page will show a link that lets you login +with CAS. ### Single Sign Out, Single Logout diff --git a/app/views/redmine_cas/_cas_login_link.html.erb b/app/views/redmine_cas/_cas_login_link.html.erb new file mode 100644 index 0000000..37ef4b2 --- /dev/null +++ b/app/views/redmine_cas/_cas_login_link.html.erb @@ -0,0 +1,5 @@ +<% if Setting.plugin_redmine_cas[:enabled] %> +

+ <%= link_to("Login with CAS", :controller => "account", :action => "cas") %> +

+<% end %> diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..2d16db9 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,3 @@ +RedmineApp::Application.routes.draw do + get 'cas', :to => 'account#cas' +end diff --git a/init.rb b/init.rb index 60ac357..90d7e97 100644 --- a/init.rb +++ b/init.rb @@ -3,6 +3,8 @@ require 'redmine_cas' require 'redmine_cas/application_controller_patch' require 'redmine_cas/account_controller_patch' +require_dependency 'redmine_cas_hook_listener' + Redmine::Plugin.register :redmine_cas do name 'Redmine CAS' author 'Nils Caspar (Nine Internet Solutions AG)' diff --git a/lib/redmine_cas/account_controller_patch.rb b/lib/redmine_cas/account_controller_patch.rb index e6b32f0..2c04560 100644 --- a/lib/redmine_cas/account_controller_patch.rb +++ b/lib/redmine_cas/account_controller_patch.rb @@ -15,6 +15,61 @@ module RedmineCAS logout_user CASClient::Frameworks::Rails::Filter.logout(self, home_url) end + + def cas + return redirect_to_action('login') unless RedmineCAS.enabled? + + if User.current.logged? + # User already logged in. + redirect_back_or_default my_page_path + return + end + + # The rest of this file just contains what was in + # application_controller_patch.rb before. + + if CASClient::Frameworks::Rails::Filter.filter(self) + user = User.find_by_login(session[:cas_user]) + + # Auto-create user if possible + if user.nil? && RedmineCAS.autocreate_users? + user = User.new + user.login = session[:cas_user] + user.assign_attributes(RedmineCAS.user_extra_attributes_from_session(session)) + return cas_user_not_created(user) if !user.save + user.reload + end + + return cas_user_not_found if user.nil? + return cas_account_pending unless user.active? + user.update_attribute(:last_login_on, Time.now) + user.update_attributes(RedmineCAS.user_extra_attributes_from_session(session)) + if RedmineCAS.single_sign_out_enabled? + # logged_user= would start a new session and break single sign-out + User.current = user + start_user_session(user) + else + self.logged_user = user + end + redirect_to url_for(params.merge(:ticket => nil)) + else + # CASClient called redirect_to + end + end + + def cas_account_pending + render_403 :message => l(:notice_account_pending) + end + + def cas_user_not_found + render_403 :message => l(:redmine_cas_user_not_found, :user => session[:cas_user]) + end + + def cas_user_not_created(user) + logger.error "Could not auto-create user: #{user.errors.full_messages.to_sentence}" + render_403 :message => l(:redmine_cas_user_not_created, :user => session[:cas_user]) + end + end end end diff --git a/lib/redmine_cas/application_controller_patch.rb b/lib/redmine_cas/application_controller_patch.rb index f3b5e6f..484db28 100644 --- a/lib/redmine_cas/application_controller_patch.rb +++ b/lib/redmine_cas/application_controller_patch.rb @@ -15,8 +15,8 @@ module RedmineCAS return require_login_without_cas unless RedmineCAS.enabled? if !User.current.logged? respond_to do |format| - format.html { login_with_cas } - format.atom { login_with_cas } + format.html { redirect_to :controller => 'account', :action => 'cas' } + format.atom { redirect_to :controller => 'account', :action => 'cas' } format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } @@ -26,36 +26,6 @@ module RedmineCAS true end - def login_with_cas - if CASClient::Frameworks::Rails::Filter.filter(self) - user = User.find_by_login(session[:cas_user]) - - # Auto-create user if possible - if user.nil? && RedmineCAS.autocreate_users? - user = User.new - user.login = session[:cas_user] - user.assign_attributes(RedmineCAS.user_extra_attributes_from_session(session)) - return cas_user_not_created(user) if !user.save - user.reload - end - - return cas_user_not_found if user.nil? - return cas_account_pending unless user.active? - user.update_attribute(:last_login_on, Time.now) - user.update_attributes(RedmineCAS.user_extra_attributes_from_session(session)) - if RedmineCAS.single_sign_out_enabled? - # logged_user= would start a new session and break single sign-out - User.current = user - start_user_session(user) - else - self.logged_user = user - end - redirect_to url_for(params.merge(:ticket => nil)) - else - # CASClient called redirect_to - end - end - def verify_authenticity_token_with_cas if cas_logout_request? logger.info 'CAS logout request detected: Skipping validation of authenticity token' @@ -68,18 +38,6 @@ module RedmineCAS request.post? && params.has_key?('logoutRequest') end - def cas_account_pending - render_403 :message => l(:notice_account_pending) - end - - def cas_user_not_found - render_403 :message => l(:redmine_cas_user_not_found, :user => session[:cas_user]) - end - - def cas_user_not_created(user) - logger.error "Could not auto-create user: #{user.errors.full_messages.to_sentence}" - render_403 :message => l(:redmine_cas_user_not_created, :user => session[:cas_user]) - end end end end diff --git a/lib/redmine_cas_hook_listener.rb b/lib/redmine_cas_hook_listener.rb new file mode 100644 index 0000000..2142704 --- /dev/null +++ b/lib/redmine_cas_hook_listener.rb @@ -0,0 +1,5 @@ +module RedmineCAS + class RedmineCASHookListener < Redmine::Hook::ViewListener + render_on :view_account_login_top, :partial => 'redmine_cas/cas_login_link' + end +end From bd22c5b3ffc8e8adf8590c03af8fa99d11fdde7a Mon Sep 17 00:00:00 2001 From: Johan Vervloet Date: Thu, 4 Sep 2014 11:33:29 +0200 Subject: [PATCH 2/4] Removed a comment. As requested by @luxflux. --- lib/redmine_cas/account_controller_patch.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/redmine_cas/account_controller_patch.rb b/lib/redmine_cas/account_controller_patch.rb index 2c04560..ed9a24d 100644 --- a/lib/redmine_cas/account_controller_patch.rb +++ b/lib/redmine_cas/account_controller_patch.rb @@ -25,9 +25,6 @@ module RedmineCAS return end - # The rest of this file just contains what was in - # application_controller_patch.rb before. - if CASClient::Frameworks::Rails::Filter.filter(self) user = User.find_by_login(session[:cas_user]) From 38d025cc2b9add11d5334d477c44710ea8b65362 Mon Sep 17 00:00:00 2001 From: Johan Vervloet Date: Tue, 16 Sep 2014 17:17:58 +0200 Subject: [PATCH 3/4] refs #9 - fix for usability problem. Fix for this problem: https://github.com/ninech/redmine_cas/pull/13#issuecomment-53697288 --- lib/redmine_cas/account_controller_patch.rb | 19 ++++++++++++++++++- .../application_controller_patch.rb | 7 +++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/redmine_cas/account_controller_patch.rb b/lib/redmine_cas/account_controller_patch.rb index ed9a24d..5fbc6cf 100644 --- a/lib/redmine_cas/account_controller_patch.rb +++ b/lib/redmine_cas/account_controller_patch.rb @@ -48,7 +48,24 @@ module RedmineCAS else self.logged_user = user end - redirect_to url_for(params.merge(:ticket => nil)) + + # If a parameter :ref exists, redirect to :ref. This is a workaround for the + # usability problem I introduced trying to fix issue #9. + # https://github.com/ninech/redmine_cas/pull/13#issuecomment-53697288 + + if params.has_key?(:ref) + # do some basic validation on ref, to prevent a malicious link to redirect + # to another site. + new_url = params[:ref] + if /http(s)?:\/\/|@/ =~ new_url + # evil referrer! + redirect_to url_for(params.merge(:ticket => nil)) + else + redirect_to request.base_url + params[:ref] + end + else + redirect_to url_for(params.merge(:ticket => nil)) + end else # CASClient called redirect_to end diff --git a/lib/redmine_cas/application_controller_patch.rb b/lib/redmine_cas/application_controller_patch.rb index 484db28..b63eaca 100644 --- a/lib/redmine_cas/application_controller_patch.rb +++ b/lib/redmine_cas/application_controller_patch.rb @@ -14,9 +14,12 @@ module RedmineCAS def require_login_with_cas return require_login_without_cas unless RedmineCAS.enabled? if !User.current.logged? + referrer = request.fullpath; respond_to do |format| - format.html { redirect_to :controller => 'account', :action => 'cas' } - format.atom { redirect_to :controller => 'account', :action => 'cas' } + # pass referer to cas action, to work around this problem: + # https://github.com/ninech/redmine_cas/pull/13#issuecomment-53697288 + format.html { redirect_to :controller => 'account', :action => 'cas', :ref => referrer } + format.atom { redirect_to :controller => 'account', :action => 'cas', :ref => referrer } format.xml { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } format.json { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } From 9a7d0fb04a2da94c92bb50418fab4bea93a57298 Mon Sep 17 00:00:00 2001 From: Raffael Schmid Date: Tue, 23 Sep 2014 13:41:56 +0200 Subject: [PATCH 4/4] also use ref parameter when user is logged in already --- lib/redmine_cas/account_controller_patch.rb | 32 ++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/redmine_cas/account_controller_patch.rb b/lib/redmine_cas/account_controller_patch.rb index 5fbc6cf..6f13ea7 100644 --- a/lib/redmine_cas/account_controller_patch.rb +++ b/lib/redmine_cas/account_controller_patch.rb @@ -21,7 +21,7 @@ module RedmineCAS if User.current.logged? # User already logged in. - redirect_back_or_default my_page_path + redirect_to_ref_or_default return end @@ -39,6 +39,7 @@ module RedmineCAS return cas_user_not_found if user.nil? return cas_account_pending unless user.active? + user.update_attribute(:last_login_on, Time.now) user.update_attributes(RedmineCAS.user_extra_attributes_from_session(session)) if RedmineCAS.single_sign_out_enabled? @@ -49,25 +50,24 @@ module RedmineCAS self.logged_user = user end - # If a parameter :ref exists, redirect to :ref. This is a workaround for the - # usability problem I introduced trying to fix issue #9. - # https://github.com/ninech/redmine_cas/pull/13#issuecomment-53697288 + redirect_to_ref_or_default + end + end - if params.has_key?(:ref) - # do some basic validation on ref, to prevent a malicious link to redirect - # to another site. - new_url = params[:ref] - if /http(s)?:\/\/|@/ =~ new_url - # evil referrer! - redirect_to url_for(params.merge(:ticket => nil)) - else - redirect_to request.base_url + params[:ref] - end + def redirect_to_ref_or_default + default_url = url_for(params.merge(:ticket => nil)) + if params.has_key?(:ref) + # do some basic validation on ref, to prevent a malicious link to redirect + # to another site. + new_url = params[:ref] + if /http(s)?:\/\/|@/ =~ new_url + # evil referrer! + redirect_to default_url else - redirect_to url_for(params.merge(:ticket => nil)) + redirect_to request.base_url + params[:ref] end else - # CASClient called redirect_to + redirect_to default_url end end