├── .github └── workflows │ ├── 5_1_8.yml │ ├── 6_0_5.yml │ └── master.yml ├── Gemfile ├── LICENSE ├── README.md ├── app ├── overrides │ └── account │ │ └── login.rb └── views │ ├── redmine_omniauth_cas │ └── _view_account_login_top.html.erb │ └── settings │ └── _omniauth_cas_settings.html.erb ├── assets └── stylesheets │ └── login.css ├── config ├── locales │ ├── en.yml │ ├── fr.yml │ ├── pt-BR.yml │ └── zh.yml └── routes.rb ├── init.rb ├── initializers └── redmine_omniauth_cas.rb ├── lib ├── omni_auth │ ├── dynamic_full_host.rb │ └── patches.rb ├── redmine_omniauth_cas.rb └── redmine_omniauth_cas │ ├── account_controller_patch.rb │ ├── account_helper_patch.rb │ ├── application_controller_patch.rb │ └── hooks.rb └── spec ├── controllers └── account_controller_patch_spec.rb ├── helpers └── account_helper_patch_spec.rb ├── integration └── account_patch_spec.rb └── models └── redmine_omniauth_cas_spec.rb /.github/workflows/5_1_8.yml: -------------------------------------------------------------------------------- 1 | name: Tests 5.1.8 2 | 3 | env: 4 | PLUGIN_NAME: redmine_omniauth_cas 5 | REDMINE_VERSION: 5.1.8 6 | RAILS_ENV: test 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | name: ${{ github.workflow }} ${{ matrix.db }} ruby-${{ matrix.ruby }} 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | ruby: ['3.2'] 20 | db: ['postgres'] 21 | fail-fast: false 22 | 23 | services: 24 | postgres: 25 | image: postgres:13 26 | env: 27 | POSTGRES_DB: redmine 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | ports: 31 | - 5432:5432 32 | options: >- 33 | --health-cmd pg_isready 34 | --health-interval 10s 35 | --health-timeout 5s 36 | --health-retries 5 37 | 38 | steps: 39 | - name: Checkout Redmine 40 | uses: actions/checkout@v4 41 | with: 42 | repository: redmine/redmine 43 | ref: ${{ env.REDMINE_VERSION }} 44 | path: redmine 45 | 46 | - name: Update package archives 47 | run: sudo apt-get update --yes --quiet 48 | 49 | - name: Install package dependencies 50 | run: > 51 | sudo apt-get update && sudo apt-get install --yes --quiet 52 | build-essential 53 | cmake 54 | libicu-dev 55 | libpq-dev 56 | ghostscript 57 | gsfonts 58 | 59 | - name: Set up chromedriver 60 | uses: nanasess/setup-chromedriver@master 61 | - run: | 62 | export DISPLAY=:99 63 | chromedriver --url-base=/wd/hub & 64 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional 65 | 66 | - name: Allow imagemagick to read PDF files 67 | run: | 68 | echo '' > policy.xml 69 | echo '' >> policy.xml 70 | echo '' >> policy.xml 71 | sudo rm /etc/ImageMagick-6/policy.xml 72 | sudo mv policy.xml /etc/ImageMagick-6/policy.xml 73 | 74 | - name: Setup Ruby 75 | uses: ruby/setup-ruby@v1 76 | with: 77 | ruby-version: ${{ matrix.ruby }} 78 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 79 | 80 | - name: Setup Bundler 81 | run: gem install bundler -v '~> 1.0' 82 | 83 | - name: Checkout dependencies - Base RSpec plugin 84 | uses: actions/checkout@v4 85 | with: 86 | repository: jbbarth/redmine_base_rspec 87 | path: redmine/plugins/redmine_base_rspec 88 | 89 | - name: Prepare Redmine source 90 | working-directory: redmine 91 | run: | 92 | sed -i '/rubocop/d' Gemfile 93 | rm -f .rubocop* 94 | cp plugins/redmine_base_rspec/spec/support/database-${{ matrix.db }}.yml config/database.yml 95 | 96 | - name: Install Ruby dependencies 97 | working-directory: redmine 98 | run: | 99 | bundle install --jobs=4 --retry=3 --without development 100 | 101 | - name: Generate session store secret 102 | env: 103 | RAILS_ENV: test 104 | working-directory: redmine 105 | run: | 106 | bundle exec rake generate_secret_token 107 | 108 | - name: Run Redmine DB and migration tasks 109 | env: 110 | RAILS_ENV: test 111 | working-directory: redmine 112 | run: | 113 | bundle exec rake db:create db:migrate 114 | bundle exec rails test:scm:setup:subversion 115 | 116 | - name: Checkout dependencies - Base Deface plugin 117 | uses: actions/checkout@v4 118 | with: 119 | repository: jbbarth/redmine_base_deface 120 | path: redmine/plugins/redmine_base_deface 121 | 122 | - name: Checkout dependencies - Base StimulusJS plugin 123 | uses: actions/checkout@v4 124 | with: 125 | repository: nanego/redmine_base_stimulusjs 126 | path: redmine/plugins/redmine_base_stimulusjs 127 | 128 | - name: Checkout plugin 129 | uses: actions/checkout@v4 130 | with: 131 | path: redmine/plugins/${{ env.PLUGIN_NAME }} 132 | 133 | - name: Install plugins dependencies and run plugins migrations 134 | env: 135 | RAILS_ENV: test 136 | working-directory: redmine 137 | run: | 138 | cp plugins/redmine_omniauth_cas/initializers/redmine_omniauth_cas.rb config/initializers/redmine_omniauth_cas.rb 139 | bundle install --jobs=4 --retry=3 --without development 140 | bundle exec rake redmine:plugins:migrate 141 | # cp -i plugins/*/spec/fixtures/*yml test/fixtures/ 142 | bundle exec rails db:fixtures:load 143 | 144 | - name: Run core tests 145 | env: 146 | RAILS_ENV: test 147 | working-directory: redmine 148 | run: bundle exec rails test 149 | 150 | - name: Run plugin tests 151 | env: 152 | RAILS_ENV: test 153 | working-directory: redmine 154 | run: bundle exec rails redmine:plugins:test NAME=${{ env.PLUGIN_NAME }} RUBYOPT="-W0" 155 | 156 | - name: Run uninstall test 157 | env: 158 | RAILS_ENV: test 159 | working-directory: redmine 160 | run: bundle exec rake redmine:plugins:migrate NAME=${{ env.PLUGIN_NAME }} VERSION=0 161 | -------------------------------------------------------------------------------- /.github/workflows/6_0_5.yml: -------------------------------------------------------------------------------- 1 | name: Tests 6.0.5 2 | 3 | env: 4 | PLUGIN_NAME: redmine_omniauth_cas 5 | REDMINE_VERSION: 6.0.5 6 | RAILS_ENV: test 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | name: ${{ github.workflow }} ${{ matrix.db }} ruby-${{ matrix.ruby }} 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | ruby: ['3.3'] 20 | db: ['postgres'] 21 | fail-fast: false 22 | 23 | services: 24 | postgres: 25 | image: postgres:13 26 | env: 27 | POSTGRES_DB: redmine 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | ports: 31 | - 5432:5432 32 | options: >- 33 | --health-cmd pg_isready 34 | --health-interval 10s 35 | --health-timeout 5s 36 | --health-retries 5 37 | 38 | steps: 39 | - name: Checkout Redmine 40 | uses: actions/checkout@v4 41 | with: 42 | repository: redmine/redmine 43 | ref: ${{ env.REDMINE_VERSION }} 44 | path: redmine 45 | 46 | - name: Update package archives 47 | run: sudo apt-get update --yes --quiet 48 | 49 | - name: Install package dependencies 50 | run: > 51 | sudo apt-get update && sudo apt-get install --yes --quiet 52 | build-essential 53 | cmake 54 | libicu-dev 55 | libpq-dev 56 | ghostscript 57 | gsfonts 58 | 59 | - name: Set up chromedriver 60 | uses: nanasess/setup-chromedriver@master 61 | - run: | 62 | export DISPLAY=:99 63 | chromedriver --url-base=/wd/hub & 64 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional 65 | 66 | - name: Allow imagemagick to read PDF files 67 | run: | 68 | echo '' > policy.xml 69 | echo '' >> policy.xml 70 | echo '' >> policy.xml 71 | sudo rm /etc/ImageMagick-6/policy.xml 72 | sudo mv policy.xml /etc/ImageMagick-6/policy.xml 73 | 74 | - name: Setup Ruby 75 | uses: ruby/setup-ruby@v1 76 | with: 77 | ruby-version: ${{ matrix.ruby }} 78 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 79 | 80 | - name: Setup Bundler 81 | run: gem install bundler -v '~> 1.0' 82 | 83 | - name: Checkout dependencies - Base RSpec plugin 84 | uses: actions/checkout@v4 85 | with: 86 | repository: jbbarth/redmine_base_rspec 87 | path: redmine/plugins/redmine_base_rspec 88 | 89 | - name: Prepare Redmine source 90 | working-directory: redmine 91 | run: | 92 | sed -i '/rubocop/d' Gemfile 93 | rm -f .rubocop* 94 | cp plugins/redmine_base_rspec/spec/support/database-${{ matrix.db }}.yml config/database.yml 95 | 96 | - name: Install Ruby dependencies 97 | working-directory: redmine 98 | run: | 99 | bundle install --jobs=4 --retry=3 --without development 100 | 101 | - name: Generate session store secret 102 | env: 103 | RAILS_ENV: test 104 | working-directory: redmine 105 | run: | 106 | bundle exec rake generate_secret_token 107 | 108 | - name: Run Redmine DB and migration tasks 109 | env: 110 | RAILS_ENV: test 111 | working-directory: redmine 112 | run: | 113 | bundle exec rake db:create db:migrate 114 | bundle exec rails test:scm:setup:subversion 115 | 116 | - name: Checkout dependencies - Base Deface plugin 117 | uses: actions/checkout@v4 118 | with: 119 | repository: jbbarth/redmine_base_deface 120 | path: redmine/plugins/redmine_base_deface 121 | 122 | - name: Checkout dependencies - Base StimulusJS plugin 123 | uses: actions/checkout@v4 124 | with: 125 | repository: nanego/redmine_base_stimulusjs 126 | path: redmine/plugins/redmine_base_stimulusjs 127 | 128 | - name: Checkout plugin 129 | uses: actions/checkout@v4 130 | with: 131 | path: redmine/plugins/${{ env.PLUGIN_NAME }} 132 | 133 | - name: Install plugins dependencies and run plugins migrations 134 | env: 135 | RAILS_ENV: test 136 | working-directory: redmine 137 | run: | 138 | cp plugins/redmine_omniauth_cas/initializers/redmine_omniauth_cas.rb config/initializers/redmine_omniauth_cas.rb 139 | bundle install --jobs=4 --retry=3 --without development 140 | bundle exec rake redmine:plugins:migrate 141 | # cp -i plugins/*/spec/fixtures/*yml test/fixtures/ 142 | bundle exec rails db:fixtures:load 143 | 144 | - name: Run core tests 145 | env: 146 | RAILS_ENV: test 147 | working-directory: redmine 148 | run: bundle exec rails test 149 | 150 | - name: Run plugin tests 151 | env: 152 | RAILS_ENV: test 153 | working-directory: redmine 154 | run: bundle exec rails redmine:plugins:test NAME=${{ env.PLUGIN_NAME }} RUBYOPT="-W0" 155 | 156 | - name: Run uninstall test 157 | env: 158 | RAILS_ENV: test 159 | working-directory: redmine 160 | run: bundle exec rake redmine:plugins:migrate NAME=${{ env.PLUGIN_NAME }} VERSION=0 161 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Tests master 2 | 3 | env: 4 | PLUGIN_NAME: redmine_omniauth_cas 5 | REDMINE_VERSION: master 6 | RAILS_ENV: test 7 | 8 | on: 9 | push: 10 | pull_request: 11 | 12 | jobs: 13 | test: 14 | name: ${{ github.workflow }} ${{ matrix.db }} ruby-${{ matrix.ruby }} 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | ruby: ['3.3'] 20 | db: ['postgres'] 21 | fail-fast: false 22 | 23 | services: 24 | postgres: 25 | image: postgres:13 26 | env: 27 | POSTGRES_DB: redmine 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | ports: 31 | - 5432:5432 32 | options: >- 33 | --health-cmd pg_isready 34 | --health-interval 10s 35 | --health-timeout 5s 36 | --health-retries 5 37 | 38 | steps: 39 | - name: Checkout Redmine 40 | uses: actions/checkout@v4 41 | with: 42 | repository: redmine/redmine 43 | ref: ${{ env.REDMINE_VERSION }} 44 | path: redmine 45 | 46 | - name: Update package archives 47 | run: sudo apt-get update --yes --quiet 48 | 49 | - name: Install package dependencies 50 | run: > 51 | sudo apt-get update && sudo apt-get install --yes --quiet 52 | build-essential 53 | cmake 54 | libicu-dev 55 | libpq-dev 56 | ghostscript 57 | gsfonts 58 | 59 | - name: Set up chromedriver 60 | uses: nanasess/setup-chromedriver@master 61 | - run: | 62 | export DISPLAY=:99 63 | chromedriver --url-base=/wd/hub & 64 | sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional 65 | 66 | - name: Allow imagemagick to read PDF files 67 | run: | 68 | echo '' > policy.xml 69 | echo '' >> policy.xml 70 | echo '' >> policy.xml 71 | sudo rm /etc/ImageMagick-6/policy.xml 72 | sudo mv policy.xml /etc/ImageMagick-6/policy.xml 73 | 74 | - name: Setup Ruby 75 | uses: ruby/setup-ruby@v1 76 | with: 77 | ruby-version: ${{ matrix.ruby }} 78 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 79 | 80 | - name: Setup Bundler 81 | run: gem install bundler -v '~> 1.0' 82 | 83 | - name: Checkout dependencies - Base RSpec plugin 84 | uses: actions/checkout@v4 85 | with: 86 | repository: jbbarth/redmine_base_rspec 87 | path: redmine/plugins/redmine_base_rspec 88 | 89 | - name: Prepare Redmine source 90 | working-directory: redmine 91 | run: | 92 | sed -i '/rubocop/d' Gemfile 93 | rm -f .rubocop* 94 | cp plugins/redmine_base_rspec/spec/support/database-${{ matrix.db }}.yml config/database.yml 95 | 96 | - name: Install Ruby dependencies 97 | working-directory: redmine 98 | run: | 99 | bundle install --jobs=4 --retry=3 --without development 100 | 101 | - name: Generate session store secret 102 | env: 103 | RAILS_ENV: test 104 | working-directory: redmine 105 | run: | 106 | bundle exec rake generate_secret_token 107 | 108 | - name: Run Redmine DB and migration tasks 109 | env: 110 | RAILS_ENV: test 111 | working-directory: redmine 112 | run: | 113 | bundle exec rake db:create db:migrate 114 | bundle exec rails test:scm:setup:subversion 115 | 116 | - name: Checkout dependencies - Base Deface plugin 117 | uses: actions/checkout@v4 118 | with: 119 | repository: jbbarth/redmine_base_deface 120 | path: redmine/plugins/redmine_base_deface 121 | 122 | - name: Checkout dependencies - Base StimulusJS plugin 123 | uses: actions/checkout@v4 124 | with: 125 | repository: nanego/redmine_base_stimulusjs 126 | path: redmine/plugins/redmine_base_stimulusjs 127 | 128 | - name: Checkout plugin 129 | uses: actions/checkout@v4 130 | with: 131 | path: redmine/plugins/${{ env.PLUGIN_NAME }} 132 | 133 | - name: Install plugins dependencies and run plugins migrations 134 | env: 135 | RAILS_ENV: test 136 | working-directory: redmine 137 | run: | 138 | cp plugins/redmine_omniauth_cas/initializers/redmine_omniauth_cas.rb config/initializers/redmine_omniauth_cas.rb 139 | bundle install --jobs=4 --retry=3 --without development 140 | bundle exec rake redmine:plugins:migrate 141 | # cp -i plugins/*/spec/fixtures/*yml test/fixtures/ 142 | bundle exec rails db:fixtures:load 143 | 144 | - name: Run core tests 145 | env: 146 | RAILS_ENV: test 147 | working-directory: redmine 148 | run: bundle exec rails test 149 | 150 | - name: Run plugin tests 151 | env: 152 | RAILS_ENV: test 153 | working-directory: redmine 154 | run: bundle exec rails redmine:plugins:test NAME=${{ env.PLUGIN_NAME }} RUBYOPT="-W0" 155 | 156 | - name: Run uninstall test 157 | env: 158 | RAILS_ENV: test 159 | working-directory: redmine 160 | run: bundle exec rake redmine:plugins:migrate NAME=${{ env.PLUGIN_NAME }} VERSION=0 161 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | gem 'omniauth', '~> 2.1' 2 | gem 'omniauth-cas', '~> 3.0' 3 | gem 'omniauth-rails_csrf_protection' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jean-Baptiste Barth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redmine OmniAuth CAS plugin 2 | 3 | This plugins adds CAS authentication support for [Redmine](http://www.redmine.org) thanks to the [OmniAuth authentication framework](https://github.com/intridea/omniauth). OmniAuth is a Rack middleware that let you authenticate against many sources (see [the list of supported sources](https://github.com/intridea/omniauth/blob/master/README.md)). This plugin aims at being an example of integration for the CAS protocol, but it shouldn't be that difficult to build a plugin that allows authentication against other sources. 4 | 5 | *NB*: the plugin doesn't support on-the-fly registration for now. 6 | 7 | ## Install 8 | 9 | You can first take a look at general instructions for plugins [here](http://www.redmine.org/wiki/redmine/Plugins) 10 | 11 | Note that the plugin is now *only compatible with Redmine 2.0.0 or higher*. Compatibility with Redmine 1.x has been removed in August, 2012. 12 | 13 | Then : 14 | * clone this repository in your plugins/ directory ; if you have a doubt you put it at the good level, you can go to your redmine root directoryand check you have a @plugins/redmine_omniauth_cas/init.rb@ file 15 | * install the dependencies with bundler : `bundle install` 16 | * copy assets by running this command from your redmine root directory (note: the plugin has no migration for now) : `RAILS_ENV=production rake redmine:plugins` 17 | * copy `initializers/redmine_omniauth_cas.rb` to `${REDMINE}/config/initializers/redmine_omniauth_cas.rb` 18 | * restart your Redmine instance (depends on how you host it) 19 | 20 | Finally you can configure your CAS server URL directly in your redmine instance, in "Administration" > "Plugins" > "Configure" on the OmniAuth CAS plugin line. 21 | 22 | ## Coming soon features 23 | 24 | Here are some ideas that could be implemented in future releases. I'm really open to suggestions on this plugin, so don't hesitate to fill an issue directly on GitHub : 25 | * implement ticket validation when first opening your browser (for now you’ll be considered as logged out if your session has expired on Redmine but your ticket is still valid on the CAS server) 26 | * add a plugin option to hide 'normal' login/password form 27 | * authorize on-the-fly registration 28 | * authorize non-conventional CAS URLs (for now you can just specify the base CAS url, and login, logout, validate, etc. URLs are deduced) 29 | 30 | ## Internals 31 | 32 | ### Why not use the AuthSource Redmine system ? 33 | 34 | From a functionality point of view, Redmine's AuthSource system is useful for 2 things : 35 | * you want to be able to define multiple occurrences of the same authentication source => not possible afaik with OmniAuth CAS strategy 36 | * you want to restrict users to a certain auth source => not so interesting if the login is filled in an external form 37 | 38 | Actually, OpenID authentication in core is not an AuthSource neither. 39 | 40 | ### Why is there a default on http://localhost:9292/ everywhere ? 41 | 42 | There are some limitations with the current implementation of OmniAuth CAS strategy. To be clear, it doesn't support dynamic parameters very well, and forces to have a default :cas_server or :cas_login_url defined in the initialization process. I hope I'll have the time to propose a fix or develop my own CAS strategy soon. 43 | 44 | ## Contribute 45 | 46 | If you like this plugin, it's a good idea to contribute : 47 | * by giving feed back on what is cool, what should be improved 48 | * by reporting bugs : you can open issues directly on github for the moment 49 | * by forking it and sending pull request if you have a patch or a feature you want to implement 50 | 51 | ## Changelog 52 | 53 | ### master/current 54 | 55 | * Fix: avoid potential errors when "service_validate" option is not set in plugin configuration, leading to 500 error after CAS redirect 56 | * Fix: correctly update User#last_login_on when authenticating through CAS 57 | * Fix: disable SSL certificate verification since it's totally broken 58 | * Fix: repare different url for validation 59 | * Feature: upgrade to OmniAuth 1.x and Redmine 2.x 60 | * Feature: clean log out from CAS for users logged in through CAS 61 | * Feature: supported Redmine 5.x 62 | 63 | ### v0.1.2 64 | 65 | * Feature: allow having a different host for ticket validation (think: internet redirect for user's login, but ticket validation done through internal URL) 66 | 67 | ### v0.1.1 68 | 69 | * Fix: avoid potential 500 error with some CAS servers when using the (bad) default :cas_server option 70 | * Fix: do not show CAS button on login page if CAS URL is not set 71 | * Fix: bad default :cas_server URL, lead to malformed '/login' URL not supported by rubycas-server 72 | 73 | ### v0.1.0 (first release) 74 | 75 | * Feature: upgrade to Redmine >= 1.2.0, since the latest versions of OmniAuth do not support Rack 1.0.1 76 | * Feature: provide a link to logout from CAS completely if username doesn't exist in redmine 77 | * Feature: make the CAS server URL configurable 78 | * Feature: provide a way to override standard text on login page 79 | * Feature: configure OmniAuth 'full_host' in case the application runs behing a reverse-proxy 80 | * Feature: basic CAS login 81 | 82 | ## Test status 83 | 84 | |Plugin branch| Redmine Version | Test Status | 85 | |-------------|-----------------|-------------------| 86 | |master | 6.0.5 | [![6.0.5][1]][5] | 87 | |master | 5.1.8 | [![5.1.8][2]][5] | 88 | |master | master | [![master][4]][5] | 89 | 90 | [1]: https://github.com/jbbarth/redmine_omniauth_cas/actions/workflows/6_0_5.yml/badge.svg 91 | [2]: https://github.com/jbbarth/redmine_omniauth_cas/actions/workflows/5_1_8.yml/badge.svg 92 | [4]: https://github.com/jbbarth/redmine_omniauth_cas/actions/workflows/master.yml/badge.svg 93 | [5]: https://github.com/jbbarth/redmine_omniauth_cas/actions 94 | 95 | ## License 96 | 97 | This project is released under the MIT license, see LICENSE file. 98 | -------------------------------------------------------------------------------- /app/overrides/account/login.rb: -------------------------------------------------------------------------------- 1 | Deface::Override.new :virtual_path => 'account/login', 2 | :name => 'hide-login-form', 3 | :surround => '#login-form', 4 | :text => <<-HTML 5 | <% if RedmineOmniauthCas.enabled? && RedmineOmniauthCas.cas_server.present? %> 6 |
7 | 8 | <%= link_to_function "ou s'authentifier par login / mot de passe", "$('#login-form-container').show(); $(this).hide();" %> 9 | 10 |
11 | 14 | <% else %> 15 | <%= render_original %> 16 | <% end %> 17 | HTML 18 | -------------------------------------------------------------------------------- /app/views/redmine_omniauth_cas/_view_account_login_top.html.erb: -------------------------------------------------------------------------------- 1 | <% if RedmineOmniauthCas.enabled? && RedmineOmniauthCas.cas_server.present? %> 2 | 3 | <% content_for :header_tags do %> 4 | <%= stylesheet_link_tag "login", :plugin => "redmine_omniauth_cas" %> 5 | <% end %> 6 | 7 |
8 | <% 9 | @back_url = back_url 10 | #unescapes back_url on-the-fly because it might be double-escaped in some environments 11 | #(it happens for me at work with 2 reverse-proxies in front of the app...) 12 | @back_url = CGI.unescape(@back_url) if @back_url && @back_url.match(/^https?%3A/) 13 | %> 14 | <%= button_to label_for_cas_login, :controller => "account", :action => "login_with_cas_redirect", :provider => "cas", :origin => @back_url %> 15 |
16 | 17 | <% end %> 18 | -------------------------------------------------------------------------------- /app/views/settings/_omniauth_cas_settings.html.erb: -------------------------------------------------------------------------------- 1 | <% flash.now[:error] = "#{l("label_cas_server")} #{l("activerecord.errors.messages.blank")}" if @settings['cas_server'].blank? %> 2 | 3 |

4 | 5 | <%= check_box_tag 'settings[enabled]', true, @settings['enabled'] %> 6 |

7 |

8 | 9 | <%= text_field_tag 'settings[cas_server]', @settings['cas_server'], :size => 50 %> 10 |
11 | <%= l(:label_example) %>: https://cas.example.com 12 |

13 |

14 | 15 | <%= text_field_tag 'settings[cas_service_validate_url]', @settings['cas_service_validate_url'], :size => 50 %> 16 |
17 | <%= l(:label_example) %>: https://cas.example.net/serviceValidate 18 |

19 |

20 | 21 | <%= text_field_tag 'settings[label_login_with_cas]', @settings['label_login_with_cas'], :size => 50 %> 22 |

23 |

24 | 25 | <%= check_box_tag 'settings[replace_redmine_login]', true, @settings['replace_redmine_login'] %> 26 |

27 | -------------------------------------------------------------------------------- /assets/stylesheets/login.css: -------------------------------------------------------------------------------- 1 | #cas-login { 2 | margin: 3em auto 0; 3 | text-align: center; 4 | } 5 | 6 | #cas-login form { 7 | display: inline-block; 8 | max-width: 800px; 9 | } 10 | 11 | #cas-login input[type="submit"] { 12 | padding-left: 1em; 13 | padding-right: 1em; 14 | -moz-border-radius: 20px; 15 | -webkit-border-radius: 20px; 16 | border-radius: 20px; 17 | border: none; 18 | color: #fff; 19 | background-color: #229e43; 20 | height: 32px; 21 | font-size: 16px; 22 | font-weight: bold; 23 | } 24 | 25 | #cas-login input[type="submit"]:hover { 26 | background-color: #20ad46; 27 | } 28 | 29 | #cas-login input[type="submit"]:active { 30 | position: relative; 31 | top: 1px; 32 | } 33 | 34 | #login-form table { 35 | margin-top: 2em; 36 | } 37 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | label_login_with_cas: Login with CAS 3 | label_login_page_text: Login page text 4 | label_cas_enabled: Enable CAS authentication 5 | label_cas_server: CAS server URL 6 | label_cas_service_validate_url: CAS validation URL (if different) 7 | label_replace_redmine_login: Replace Redmine login page 8 | text_full_logout_proposal: You may want to %{value} before trying an other username. 9 | text_logout_from_cas: logout from CAS 10 | error_cas_no_ticket: No CAS ticket was found during callback. Please try to authenticate again 11 | error_cas_invalid_ticket: An invalid CAS ticket was specified, it may have expired. Please try authenticating in again. 12 | error_cas_unknown: An unknown error occurred during CAS callback processing. 13 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | label_login_with_cas: S'authentifier avec CAS 3 | label_login_page_text: Texte de la page de login 4 | label_cas_enabled: Activer l'authentification CAS 5 | label_cas_server: Adresse du serveur CAS 6 | label_cas_service_validate_url: Adresse de validation CAS (si différente) 7 | label_replace_redmine_login: Remplacer la page de login de Redmine 8 | text_full_logout_proposal: Peut-être voulez-vous vous %{value} avant d'essayer un autre nom d'utilisateur ? 9 | text_logout_from_cas: déconnecter de CAS 10 | error_cas_no_ticket: Aucun ticket CAS trouvé. Vous devriez re-tenter une authentification 11 | error_cas_invalid_ticket: Un ticket CAS invalide a été spécifié, il a peut-être expiré. Veuillez réessayer de vous authentifier. 12 | error_cas_unknown: Une erreur inconnue s'est produite lors du retour d'authentification CAS. 13 | -------------------------------------------------------------------------------- /config/locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | label_login_with_cas: Login com CAS 3 | label_login_page_text: Texto na página de login 4 | label_cas_enabled: Habiltar autenticação com CAS 5 | label_cas_server: URL do servidor CAS 6 | label_cas_service_validate_url: URL de validação do CAS (se diferente) 7 | label_replace_redmine_login: Substituir página de login do Redmine 8 | text_full_logout_proposal: Você precisa acessar %{value} antes de tentar logar com outro usuário. 9 | text_logout_from_cas: logout do CAS 10 | error_cas_no_ticket: Ticket do CAS não recebido no callback. Tente autenticar novamente. 11 | error_cas_invalid_ticket: Ticket inválido retornado, ele pode estar expirado. Tente logar novamente. 12 | error_cas_unknown: Erro desconhecido ao processar o callback do CAS. 13 | -------------------------------------------------------------------------------- /config/locales/zh.yml: -------------------------------------------------------------------------------- 1 | zh: 2 | label_login_with_cas: CAS 登录 3 | label_login_page_text: 登录页文本 4 | label_cas_enabled: 开启 CAS 登录 5 | label_cas_server: CAS 服务器地址 6 | label_cas_service_validate_url: CAS 验证地址 (如果不同) 7 | label_replace_redmine_login: 替换 Redmine 登录页 8 | text_full_logout_proposal: 在尝试其他用户名之前,您可能需要 %{value}。 9 | text_logout_from_cas: 从 CAS 登出 10 | error_cas_no_ticket: 回调期间未找到 CAS ticket。请尝试再次进行身份验证 11 | error_cas_invalid_ticket: 指定了无效的 CAS ticket,它可能已过期。请再次尝试身份验证。 12 | error_cas_unknown: CAS 回调处理过程中发生未知错误。 13 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | RedmineApp::Application.routes.draw do 2 | match 'auth/failure', :controller => 'account', :action => 'login_with_cas_failure', via: [:get, :post] 3 | match 'auth/:provider/callback', :controller => 'account', :action => 'login_with_cas_callback', via: [:get, :post] 4 | match 'auth/:provider', :controller => 'account', :action => 'login_with_cas_redirect', via: [:get, :post] 5 | end 6 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | initializers_dir = File.join(Rails.root, "config", "initializers") 4 | if Dir.glob(File.join(initializers_dir, "redmine_omniauth_cas.rb")).blank? 5 | $stderr.puts "Omniauth CAS Plugin: Missing initialization file config/initializers/redmine_omniauth_cas.rb. " \ 6 | "Please copy the provided file to the config/initializers/ directory.\n" \ 7 | "You can copy/paste this command:\n" \ 8 | "cp #{File.join(Rails.root, "plugins", "redmine_omniauth_cas")}/initializers/redmine_omniauth_cas.rb #{File.join(initializers_dir, "redmine_omniauth_cas.rb")}" 9 | exit 1 10 | end 11 | 12 | require 'redmine' 13 | require_relative 'lib/redmine_omniauth_cas' 14 | require_relative 'lib/redmine_omniauth_cas/hooks' 15 | require_relative 'lib/omni_auth/patches' 16 | require_relative 'lib/omni_auth/dynamic_full_host' 17 | 18 | # Plugin generic informations 19 | Redmine::Plugin.register :redmine_omniauth_cas do 20 | name 'Redmine Omniauth plugin' 21 | description 'This plugin adds Omniauth support to Redmine' 22 | author 'Jean-Baptiste BARTH' 23 | author_url 'mailto:jeanbaptiste.barth@gmail.com' 24 | url 'https://github.com/jbbarth/redmine_omniauth_cas' 25 | version '6.0.4' 26 | requires_redmine :version_or_higher => '4.0.0' 27 | requires_redmine_plugin :redmine_base_rspec, :version_or_higher => '0.0.3' if Rails.env.test? 28 | settings :default => { 'enabled' => 'true', 'label_login_with_cas' => '', 'cas_server' => '' }, 29 | :partial => 'settings/omniauth_cas_settings' 30 | end 31 | -------------------------------------------------------------------------------- /initializers/redmine_omniauth_cas.rb: -------------------------------------------------------------------------------- 1 | 2 | # OmniAuth CAS 3 | setup_app = Proc.new do |env| 4 | addr = RedmineOmniauthCas.cas_server 5 | cas_server = URI.parse(addr) 6 | if cas_server 7 | env['omniauth.strategy'].options.merge! :host => cas_server.host, 8 | :port => cas_server.port, 9 | :path => (cas_server.path != "/" ? cas_server.path : nil), 10 | :ssl => cas_server.scheme == "https" 11 | end 12 | validate = RedmineOmniauthCas.cas_service_validate_url 13 | if validate 14 | env['omniauth.strategy'].options.merge! :service_validate_url => validate 15 | end 16 | # Dirty, not happy with it, but as long as I can't reproduce the bug 17 | # users are blocked because of failing OpenSSL checks, while the cert 18 | # is actually good, so... 19 | # TODO: try to understand why cert verification fails 20 | # Maybe https://github.com/intridea/omniauth/issues/404 can help 21 | env['omniauth.strategy'].options.merge! :disable_ssl_verification => true 22 | end 23 | 24 | begin 25 | # tell Rails we use this middleware, with some default value just in case 26 | Rails.application.config.middleware.use OmniAuth::Builder do 27 | provider :cas, :host => "localhost", 28 | :port => "9292", 29 | :ssl => false, 30 | :setup => setup_app 31 | end 32 | rescue FrozenError 33 | # This can happen if there is a crash after Rails has 34 | # started booting but before we've added our middleware. 35 | # The middlewares array will only be frozen if an earlier error occurs 36 | Rails.logger.warn("Unable to add OmniAuth::Builder middleware as the middleware stack is frozen") 37 | puts "/!\\ Unable to add OmniAuth::Builder middleware as the middleware stack is frozen" 38 | end 39 | -------------------------------------------------------------------------------- /lib/omni_auth/dynamic_full_host.rb: -------------------------------------------------------------------------------- 1 | # configures public url for our application 2 | module OmniAuth::DynamicFullHost 3 | def self.full_host_url(url = nil) 4 | # unescapes url on-the-fly because it might be double-escaped in some environments 5 | #(it happens for me at work with 2 reverse-proxies in front of the app...) 6 | url = CGI.unescape(url) if url 7 | 8 | # if no url found, fall back to config/app_config.yml addresses 9 | if url.blank? 10 | url = Setting["host_name"] 11 | # else, parse it and remove both request_uri and query_string 12 | else 13 | uri = URI.parse(URI::Parser.new.escape(url)) # Encode to ensure we only have ASCII charaters in url 14 | url = "#{uri.scheme}://#{uri.host}" 15 | url << ":#{uri.port}" unless uri.default_port == uri.port 16 | end 17 | url 18 | end 19 | end 20 | 21 | OmniAuth.config.full_host = Proc.new do |env| 22 | OmniAuth::DynamicFullHost.full_host_url(env["rack.session"]["omniauth.origin"] || env["omniauth.origin"]) 23 | end 24 | -------------------------------------------------------------------------------- /lib/omni_auth/patches.rb: -------------------------------------------------------------------------------- 1 | require 'omniauth/cas' 2 | 3 | module OmniAuth::Patches 4 | # patch to disable return_url to avoid polluting the service URL 5 | def return_url 6 | {} 7 | end 8 | end 9 | 10 | module OmniAuth 11 | module Strategies 12 | class CAS 13 | prepend OmniAuth::Patches 14 | 15 | # patch to accept path (subdir) in cas_host 16 | option :path, nil 17 | 18 | # patch to accept a different host for service_validate_url 19 | def service_validate_url_with_different_host(service_url, ticket) 20 | service_url = Addressable::URI.parse(service_url) 21 | service_url.query_values = service_url.query_values.tap { |qs| qs.delete('ticket') } 22 | 23 | validate_url = Addressable::URI.parse(@options.service_validate_url) 24 | 25 | if service_url.host.nil? || validate_url.host.nil? 26 | cas_url + append_params(@options.service_validate_url, { :service => service_url.to_s, :ticket => ticket }) 27 | else 28 | append_params(@options.service_validate_url, { :service => service_url.to_s, :ticket => ticket }) 29 | end 30 | end 31 | 32 | # alias_method_chain is deprecated in Rails 5: replaced with two alias_method 33 | # as a quick workaround. Using the 'prepend' method can generate an 34 | # 'stack level too deep' error in conjunction with other (non ported) plugins. 35 | # alias_method_chain :service_validate_url, :different_host 36 | alias_method :service_validate_url_without_different_host, :service_validate_url 37 | alias_method :service_validate_url, :service_validate_url_with_different_host 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/redmine_omniauth_cas.rb: -------------------------------------------------------------------------------- 1 | module RedmineOmniauthCas 2 | class << self 3 | def settings_hash 4 | Setting["plugin_redmine_omniauth_cas"] 5 | end 6 | 7 | def enabled? 8 | settings_hash["enabled"] 9 | end 10 | 11 | def cas_server 12 | settings_hash["cas_server"] 13 | end 14 | 15 | def cas_service_validate_url 16 | settings_hash["cas_service_validate_url"].presence || nil 17 | end 18 | 19 | def label_login_with_cas 20 | settings_hash["label_login_with_cas"] 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/redmine_omniauth_cas/account_controller_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'account_controller' 2 | 3 | module RedmineOmniauthCas 4 | module AccountControllerPatch 5 | def self.included(base) 6 | base.send(:include, InstanceMethods) 7 | base.class_eval do 8 | # alias_method_chain is deprecated in Rails 5: replaced with two alias_method 9 | # as a quick workaround. Using the 'prepend' method can generate an 10 | # 'stack level too deep' error in conjunction with other (non ported) plugins. 11 | # alias_method_chain :logout, :cas 12 | # alias_method_chain :login, :cas 13 | alias_method :login_without_cas, :login 14 | alias_method :login, :login_with_cas 15 | alias_method :logout_without_cas, :logout 16 | alias_method :logout, :logout_with_cas 17 | end 18 | end 19 | 20 | module InstanceMethods 21 | 22 | def login_with_cas 23 | #TODO: test 'replace_redmine_login' feature 24 | if cas_settings["enabled"] && cas_settings["replace_redmine_login"] 25 | redirect_to :controller => "account", :action => "login_with_cas_redirect", :provider => "cas", :origin => back_url 26 | else 27 | login_without_cas 28 | end 29 | end 30 | 31 | def login_with_cas_redirect 32 | render plain: "Not Found", status: 404 33 | end 34 | 35 | def login_with_cas_callback 36 | auth = request.env["omniauth.auth"] 37 | 38 | #add a hook where you could define auto-creation of users for instance 39 | call_hook(:controller_account_before_cas_login, { :params => params, :auth => auth, :cookies => cookies }) 40 | 41 | #user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) 42 | user = User.find_by_login(auth["uid"]) || User.find_by_mail(auth["uid"]) 43 | 44 | # taken from original AccountController 45 | # maybe it should be splitted in core 46 | if user.blank? 47 | logger.warn "Failed login for '#{auth[:uid]}' from #{request.remote_ip} at #{Time.now.utc}" 48 | error = l(:notice_account_invalid_credentials).sub(/\.$/, '') 49 | if cas_settings["cas_server"].present? 50 | link = self.class.helpers.link_to(l(:text_logout_from_cas), cas_logout_url, :target => "_blank") 51 | error << ". #{l(:text_full_logout_proposal, :value => link)}" 52 | end 53 | if cas_settings["replace_redmine_login"] 54 | render_error({:message => error.html_safe, :status => 403}) 55 | return false 56 | else 57 | flash[:error] = error 58 | redirect_to signin_url 59 | end 60 | else 61 | user.update_attribute(:last_login_on, Time.now) 62 | params[:back_url] = request.env["omniauth.origin"] unless request.env["omniauth.origin"].blank? 63 | successful_authentication(user) 64 | #cannot be set earlier, because sucessful_authentication() triggers reset_session() 65 | session[:logged_in_with_cas] = true 66 | end 67 | end 68 | 69 | def login_with_cas_failure 70 | error = params[:message] || 'unknown' 71 | error = 'error_cas_' + error 72 | if cas_settings["replace_redmine_login"] 73 | render_error({:message => error.to_sym, :status => 500}) 74 | return false 75 | else 76 | flash[:error] = l(error.to_sym) 77 | redirect_to signin_url 78 | end 79 | end 80 | 81 | def logout_with_cas 82 | if cas_settings["enabled"] && session[:logged_in_with_cas] 83 | logout_user 84 | redirect_to cas_logout_url(home_url) 85 | else 86 | logout_without_cas 87 | end 88 | end 89 | 90 | private 91 | def cas_settings 92 | RedmineOmniauthCas.settings_hash 93 | end 94 | 95 | def cas_logout_url(service = nil) 96 | logout_uri = URI.parse(cas_settings["cas_server"] + "/").merge("./logout") 97 | if !service.blank? 98 | logout_uri.query = "gateway=1&service=#{service}" 99 | end 100 | logout_uri.to_s 101 | end 102 | 103 | end 104 | end 105 | end 106 | 107 | unless AccountController.included_modules.include? RedmineOmniauthCas::AccountControllerPatch 108 | AccountController.send(:include, RedmineOmniauthCas::AccountControllerPatch) 109 | end 110 | 111 | class AccountController 112 | skip_before_action :verify_authenticity_token, only: [:login_with_cas_callback] 113 | end 114 | -------------------------------------------------------------------------------- /lib/redmine_omniauth_cas/account_helper_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'account_helper' 2 | 3 | module RedmineOmniauthCas::AccountHelperPatch 4 | def label_for_cas_login 5 | RedmineOmniauthCas.label_login_with_cas.presence || l(:label_login_with_cas) 6 | end 7 | end 8 | 9 | AccountHelper.prepend RedmineOmniauthCas::AccountHelperPatch 10 | ActionView::Base.prepend AccountHelper 11 | -------------------------------------------------------------------------------- /lib/redmine_omniauth_cas/application_controller_patch.rb: -------------------------------------------------------------------------------- 1 | require_dependency 'application_controller' 2 | 3 | module RedmineOmniauthCas 4 | module ApplicationControllerPatch 5 | extend ActiveSupport::Concern 6 | 7 | def require_login 8 | if !User.current.logged? 9 | # Extract only the basic url parameters on non-GET requests 10 | if request.get? 11 | url = request.original_url 12 | 13 | ## START PATCH 14 | url.gsub!('http:', Setting.protocol + ":") 15 | ## END PATCH 16 | 17 | else 18 | url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id]) 19 | end 20 | respond_to do |format| 21 | format.html { 22 | if request.xhr? 23 | head :unauthorized 24 | else 25 | redirect_to signin_path(:back_url => url) 26 | end 27 | } 28 | format.any(:atom, :pdf, :csv) { 29 | redirect_to signin_path(:back_url => url) 30 | } 31 | format.api { 32 | if (Setting.rest_api_enabled? && accept_api_auth?) || Redmine::VERSION.to_s < '4.1' 33 | head(:unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"') 34 | else 35 | head(:forbidden) 36 | end 37 | } 38 | format.js { head :unauthorized, 'WWW-Authenticate' => 'Basic realm="Redmine API"' } 39 | format.any { head :unauthorized } 40 | end 41 | return false 42 | end 43 | true 44 | end 45 | 46 | # Returns a validated URL string if back_url is a valid url for redirection, 47 | # otherwise false 48 | def validate_back_url(back_url) 49 | return false if back_url.blank? 50 | 51 | if CGI.unescape(back_url).include?('..') 52 | return false 53 | end 54 | 55 | begin 56 | uri = Addressable::URI.parse(back_url) 57 | ## PATCHED : ignore scheme HTTPS/HTTP and port so redirection works behind reverse proxies 58 | [:host].each do |component| 59 | if uri.send(component).present? && uri.send(component) != request.send(component) 60 | return false 61 | end 62 | end 63 | # Remove unnecessary components to convert the URL into a relative URL 64 | uri.omit!(:scheme, :authority) 65 | rescue Addressable::URI::InvalidURIError 66 | return false 67 | end 68 | 69 | path = uri.to_s 70 | # Ensure that the remaining URL starts with a slash, followed by a 71 | # non-slash character or the end 72 | unless %r{\A/([^/]|\z)}.match?(path) 73 | return false 74 | end 75 | 76 | if %r{/(login|account/register|account/lost_password)}.match?(path) 77 | return false 78 | end 79 | 80 | if relative_url_root.present? && !path.starts_with?(relative_url_root) 81 | return false 82 | end 83 | 84 | return path 85 | end 86 | 87 | end 88 | end 89 | 90 | ApplicationController.prepend RedmineOmniauthCas::ApplicationControllerPatch 91 | -------------------------------------------------------------------------------- /lib/redmine_omniauth_cas/hooks.rb: -------------------------------------------------------------------------------- 1 | module RedmineOmniauthCas 2 | class Hooks < Redmine::Hook::ViewListener 3 | render_on :view_account_login_top, :partial => 'redmine_omniauth_cas/view_account_login_top' 4 | end 5 | 6 | class ModelHook < Redmine::Hook::Listener 7 | def after_plugins_loaded(_context = {}) 8 | require_relative 'account_controller_patch' 9 | require_relative 'account_helper_patch' 10 | require_relative 'application_controller_patch' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/controllers/account_controller_patch_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe AccountController, type: :controller do 4 | render_views 5 | fixtures :users, :roles 6 | 7 | context "GET /login CAS button" do 8 | it "should show up only if there's a plugin setting for CAS URL" do 9 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "" 10 | get :login 11 | assert_select '#cas-login', 0 12 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "blah" 13 | get :login 14 | assert_select '#cas-login' 15 | end 16 | 17 | it "should correct double-escaped URL" do 18 | #I don't really know where this bug comes from but it seems URLs are escaped twice 19 | #in my setup which causes the back_url to be invalid. Let's try to be smart about 20 | #this directly in the plugin 21 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "blah" 22 | get :login, params: {:back_url => "https%3A%2F%2Fblah%2F"} 23 | assert_select '#cas-login > form[action=?]', '/auth/cas?origin=https%3A%2F%2Fblah%2F' 24 | end 25 | end 26 | 27 | context "GET login_with_cas_callback" do 28 | it "should redirect to /my/page after successful login" do 29 | request.env["omniauth.auth"] = {"uid" => "admin"} 30 | get :login_with_cas_callback, params: {:provider => "cas"} 31 | expect(response).to redirect_to('/my/page') 32 | end 33 | 34 | it "should redirect to /login after failed login" do 35 | request.env["omniauth.auth"] = {"uid" => "non-existent"} 36 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "http://cas.server/" 37 | get :login_with_cas_callback, params: {:provider => "cas"} 38 | expect(response).to redirect_to('/login') 39 | end 40 | 41 | it "should set a boolean in session to keep track of login" do 42 | request.env["omniauth.auth"] = {"uid" => "admin"} 43 | get :login_with_cas_callback, params: {:provider => "cas"} 44 | expect(response).to redirect_to('/my/page') 45 | assert session[:logged_in_with_cas] 46 | end 47 | 48 | it "should redirect to Home if not logged in with CAS" do 49 | get :logout 50 | expect(response).to redirect_to(home_url) 51 | end 52 | 53 | it "should redirect to CAS logout if previously logged in with CAS" do 54 | session[:logged_in_with_cas] = true 55 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "http://cas.server" 56 | get :logout 57 | expect(response).to redirect_to('http://cas.server/logout?gateway=1&service=http://test.host/') 58 | end 59 | 60 | it "should respect path in CAS server when generating logout url" do 61 | session[:logged_in_with_cas] = true 62 | Setting["plugin_redmine_omniauth_cas"]["cas_server"] = "http://cas.server/cas" 63 | get :logout 64 | expect(response).to redirect_to('http://cas.server/cas/logout?gateway=1&service=http://test.host/') 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/helpers/account_helper_patch_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe AccountHelper do 4 | include Redmine::I18n 5 | 6 | context "#label_for_cas_login" do 7 | it "should use label_login_with_cas plugin setting if not blank" do 8 | label = "Log in with SSO" 9 | Setting["plugin_redmine_omniauth_cas"]["label_login_with_cas"] = label 10 | expect(label_for_cas_login).to eq label 11 | end 12 | 13 | it "should default to localized :label_login_with_cas if no setting present" do 14 | Setting["plugin_redmine_omniauth_cas"]["label_login_with_cas"] = nil 15 | expect(label_for_cas_login).to eq l(:label_login_with_cas) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/integration/account_patch_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "AccountPatch", :type => :request do 4 | fixtures :users, :roles, :email_addresses 5 | 6 | context "GET /auth/:provider" do 7 | it "should route to a blank action (intercepted by omniauth middleware)" do 8 | assert_routing( 9 | { :method => :get, :path => "/auth/blah" }, 10 | { :controller => 'account', :action => 'login_with_cas_redirect', :provider => 'blah' } 11 | ) 12 | end 13 | # TODO: some real test? 14 | end 15 | context "GET /auth/:provider/callback" do 16 | it "should route things correctly" do 17 | assert_routing( 18 | { :method => :get, :path => "/auth/blah/callback" }, 19 | { :controller => 'account', :action => 'login_with_cas_callback', :provider => 'blah' } 20 | ) 21 | end 22 | 23 | context "OmniAuth CAS strategy" do 24 | before do 25 | Setting.default_language = 'en' 26 | OmniAuth.config.test_mode = true 27 | end 28 | 29 | it "should authorize login if user exists with this login" do 30 | OmniAuth.config.mock_auth[:cas] = OmniAuth::AuthHash.new({ 'uid' => 'admin' }) 31 | Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:cas] 32 | get '/auth/cas/callback' 33 | expect(response).to redirect_to('/my/page') 34 | get '/my/page' 35 | expect(response.body).to match /Logged in as.*admin/im 36 | end 37 | 38 | it "should authorize login if user exists with this email" do 39 | OmniAuth.config.mock_auth[:cas] = OmniAuth::AuthHash.new({ 'uid' => 'admin@somenet.foo' }) 40 | Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:cas] 41 | get '/auth/cas/callback' 42 | expect(response).to redirect_to('/my/page') 43 | get '/my/page' 44 | expect(response.body).to match /Logged in as.*admin/im 45 | end 46 | 47 | it "should update last_login_on field" do 48 | user = User.find(1) 49 | user.update_attribute(:last_login_on, Time.now - 6.hours) 50 | OmniAuth.config.mock_auth[:cas] = OmniAuth::AuthHash.new({ 'uid' => 'admin' }) 51 | Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:cas] 52 | get '/auth/cas/callback' 53 | expect(response).to redirect_to('/my/page') 54 | user.reload 55 | assert Time.now - user.last_login_on < 30.seconds 56 | end 57 | 58 | it "should refuse login if user doesn't exist" do 59 | OmniAuth.config.mock_auth[:cas] = OmniAuth::AuthHash.new({ 'uid' => 'johndoe' }) 60 | Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:cas] 61 | get '/auth/cas/callback' 62 | expect(response).to redirect_to('/login') 63 | follow_redirect! 64 | expect(User.current).to eq User.anonymous 65 | assert_select 'div.flash.error', :text => /Invalid user or password/ 66 | end 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/models/redmine_omniauth_cas_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "RedmineOmniAuthCAS" do 4 | context "#cas_service_validate_url" do 5 | it "should return setting if not blank" do 6 | url = "cas.example.com/validate" 7 | Setting["plugin_redmine_omniauth_cas"]["cas_service_validate_url"] = url 8 | expect(RedmineOmniauthCas.cas_service_validate_url).to eq url 9 | end 10 | 11 | it "should return nil if setting is blank" do 12 | Setting["plugin_redmine_omniauth_cas"]["cas_service_validate_url"] = "" 13 | expect(RedmineOmniauthCas.cas_service_validate_url).to be_nil 14 | end 15 | end 16 | 17 | context "dynamic full host" do 18 | it "should return host name from setting if no url" do 19 | Setting["host_name"] = "http://redmine.example.com" 20 | expect(OmniAuth::DynamicFullHost.full_host_url).to eq "http://redmine.example.com" 21 | end 22 | 23 | it "should return host name from url if url is present" do 24 | url = "https://redmine.example.com:3000/some/path" 25 | expect(OmniAuth::DynamicFullHost.full_host_url(url)).to eq "https://redmine.example.com:3000" 26 | end 27 | end 28 | end 29 | --------------------------------------------------------------------------------