Squashed 'plugins/redmine_openid_connect/' content from commit 470de18

git-subtree-dir: plugins/redmine_openid_connect
git-subtree-split: 470de181cb7810db0c6c1d865454f29b8d8dfdc1
This commit is contained in:
2023-05-30 15:44:02 +02:00
commit 14d85815cd
25 changed files with 934 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/.bundle
/Gemfile.local
.idea

21
CHANGELOG.md Normal file
View File

@@ -0,0 +1,21 @@
# Changelog
## ??? 0.9.5
* Pull server-side errors from locale files
* Log-messages/some less prominent errors hard-coded in English again
* Do not render `rpiframe`, if OpenID config does not contain a `check_session_iframe`
* Some more documentation about setting up
## 0.9.4
* Support Redmine 4
* Upgrade deprecated calls
## 0.9.3
* fix problem with symbols vs. strings usage
## 0.9.2
* fix settings page
* move to github
* Avoid error if members_of is empty during check
* Add disable ssl validation
* Add protocol to hostname

35
FusionAuth.md Normal file
View File

@@ -0,0 +1,35 @@
## Connect to fusionAuth
## In FusionAuth
### Add App
* In Aplications >> Add a new app, choose a name and default options, you just need to pay attention to:
```
Authorized redirect URLs add: http(or s)://your-host-url/oic/local_login http(or s)://your-host-url/oic/local_logout
Logout URL: http(or s)://your-host-url/oic/local_logout
```
### Add role
In Application List, select the app and click manage roles, add two roles, User and Admin.
## In Redmine /settings/plugin/redmine_openid_connect
Settings>> Plugins >>Open id
* Just configure id and secret from Application Details in FusionAuth
* OpenID Connect server url: http(or s)://your-fusion-url/
* Scope: openid
* Authorized group: User
* Admins group: Admin
## Add the role to the user
* Just add the role to the users in FusionAuth and thats all,
* User>>Manage>>Registrations add the app and the desidered role.
* if you choose both roles, Redmine considerer the user as Admin

2
Gemfile Normal file
View File

@@ -0,0 +1,2 @@
source 'https://rubygems.org'
gem 'httparty', '~> 0.14.0'

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
# Redmine OpenID Connect Plugin #
Based on the work from [intelimina](https://bitbucket.org/intelimina/redmine_openid_connect) and [devopskube](https://github.com/devopskube).
## Introduction ##
This is a plugin based on the implementation of redmine_cas.
It redirects to an SSO server bypassing the original Redmine login authentication using the SSO server authentication in its place.
## Important ##
User registration is implicit and cannot be disabled at the moment.
So your OpenID provider should probably provide unique endpoints for your needs.
Check out [FusionAuth](https://fusionauth.io/) for an excellent solution.
## Server Settings ##
Just include `username` in the scope being sent and replied to the client app.
## Usage ##
### Configure Redmine ###
1. Go to your Redmine plugins directory.
2. Clone/copy this plugin.
3. Run `bundle install`
4. Run `bundle exec rake redmine:plugins:migrate RAILS_ENV=production`
5. Restart your server
6. Login as administrator and head over to the plugins page.
7. Open the configuration page for redmine openid connect plugin.
8. Fill in the details.
### Configure Your OpenID Provider ###
1. Go to your SSO server and add these urls as authorized redirect urls:
* `https://<your-redmine-domain>/oic/local_login`
* `https://<your-redmine-domain>/oic/local_logout`
2. Check the JWT Token generation. You need the following contents:
* **`member_of`**: `String[]` of role/group names that your config maps to user properties like *is administrator* or *is authorized to log in*
* **`user_name`**: `String` with the user's desired username (required for user creation), aliases: `nickname`, `preferred_username`
* **`given_name`**: `String` with the user's first name (required for user creation)
* **`family_name`**: `String` with the user's surname (required for user creation)
* **`name`**: `String` with the user's full name (used as a fallback for first name and surname)
* Should some of these fields be missing, try finding *Lambda* functions or *Generators* that allow you to customize the JWT Tokens issued
## In Case Your OpenID Provider Is Offline ##
If you enable the OpenId Connect plugin and your OpenId Connect Server is not reachable, but you still would like to login, you can use an additional parameter, to be able to login directly into redmine:
```https://<your-redmine-domain>/login?local_login=true```
Enjoy!

264
app/models/oic_session.rb Normal file
View File

@@ -0,0 +1,264 @@
class OicSession < ActiveRecord::Base
unloadable
before_create :randomize_state!
before_create :randomize_nonce!
def self.client_config
Setting.plugin_redmine_openid_connect
end
def client_config
self.class.client_config
end
def self.host_name
Setting.protocol + "://" + Setting.host_name
end
def host_name
self.class.host_name
end
def self.enabled?
client_config['enabled']
end
def self.disabled?
!self.enabled?
end
def self.login_selector?
client_config['login_selector']
end
def self.create_user_if_not_exists?
client_config['create_user_if_not_exists']
end
def self.disallowed_auth_sources_login
client_config['disallowed_auth_sources_login'].to_a
end
def self.openid_configuration_url
client_config['openid_connect_server_url'] + '/.well-known/openid-configuration'
end
def self.get_dynamic_config
hash = Digest::SHA1.hexdigest client_config.to_json
expiry = client_config['dynamic_config_expiry'] || 86400
Rails.cache.fetch("oic_session_dynamic_#{hash}", expires_in: expiry) do
HTTParty::Basement.default_options.update(verify: false) if client_config['disable_ssl_validation']
ActiveSupport::HashWithIndifferentAccess.new HTTParty.get(openid_configuration_url)
end
end
def self.dynamic_config
@dynamic_config ||= get_dynamic_config
end
def dynamic_config
self.class.dynamic_config
end
def self.get_token(query)
uri = dynamic_config['token_endpoint']
HTTParty::Basement.default_options.update(verify: false) if client_config['disable_ssl_validation']
response = HTTParty.post(
uri,
body: query,
basic_auth: {username: client_config['client_id'], password: client_config['client_secret'] }
)
end
def get_access_token!
response = self.class.get_token(access_token_query)
if response["error"].blank?
self.access_token = response["access_token"] if response["access_token"].present?
self.refresh_token = response["refresh_token"] if response["refresh_token"].present?
self.id_token = response["id_token"] if response["id_token"].present?
self.expires_at = (DateTime.now + response["expires_in"].seconds) if response["expires_in"].present?
self.save!
end
return response
end
def refresh_access_token!
response = self.class.get_token(refresh_token_query)
if response["error"].blank?
self.access_token = response["access_token"] if response["access_token"].present?
self.refresh_token = response["refresh_token"] if response["refresh_token"].present?
self.id_token = response["id_token"] if response["id_token"].present?
self.expires_at = (DateTime.now + response["expires_in"].seconds) if response["expires_in"].present?
self.save!
end
return response
end
def self.parse_token(token)
jwt = token.split('.')
return JSON::parse(Base64::decode64(jwt[1]))
end
def claims
if @claims.blank? || id_token_changed?
@claims = self.class.parse_token(id_token)
end
return @claims
end
def get_user_info!
uri = dynamic_config['userinfo_endpoint']
HTTParty::Basement.default_options.update(verify: false) if client_config['disable_ssl_validation']
response = HTTParty.get(
uri,
headers: { "Authorization" => "Bearer #{access_token}" }
)
if response.headers["content-type"] == 'application/jwt'
# signed / encrypted response, extract before using
return self.class.parse_token(response)
else
# unsigned response, just return the bare json
return JSON::parse(response.body)
decoded_token = response.body
end
end
def check_keycloak_role(role)
# keycloak way...
kc_is_in_role = false
if user["realm_access"].present?
kc_is_in_role = user["realm_access"]["roles"].include?(role)
end
if user["resource_access"].present? && user["resource_access"][client_config['client_id']].present?
kc_is_in_role = user["resource_access"][client_config['client_id']]["roles"].include?(role)
end
return true if kc_is_in_role
end
def authorized?
if client_config['group'].blank?
return true
end
return true if check_keycloak_role client_config['group']
return false if !user["member_of"] && !user["roles"]
return true if self.admin?
if client_config['group'].present?
return true if user["member_of"].present? && user["member_of"].include?(client_config['group'])
return true if user["roles"].present? && user["roles"].include?(client_config['group']) || user["roles"].include?(client_config['admin_group'])
end
return false
end
def admin?
if client_config['admin_group'].present?
if user["member_of"].present?
return true if user["member_of"].include?(client_config['admin_group'])
end
if user["roles"].present?
return true if user["roles"].include?(client_config['admin_group'])
end
# keycloak way...
return true if check_keycloak_role client_config['admin_group']
end
return false
end
def user
if access_token? # keycloak way...
@user = JSON::parse(Base64::decode64(access_token.split('.')[1]))
else
@user = JSON::parse(Base64::decode64(id_token.split('.')[1]))
end
return @user
end
def authorization_url
config = dynamic_config
config["authorization_endpoint"] + "?" + authorization_query.to_param
end
def end_session_url
config = dynamic_config
return if config["end_session_endpoint"].nil?
config["end_session_endpoint"] + "?" + end_session_query.to_param
end
def randomize_state!
self.state = SecureRandom.uuid unless self.state.present?
end
def randomize_nonce!
self.nonce = SecureRandom.uuid unless self.nonce.present?
end
def authorization_query
query = {
"response_type" => "code",
"state" => self.state,
"nonce" => self.nonce,
"scope" => scopes,
"redirect_uri" => "#{host_name}/oic/local_login",
"client_id" => client_config['client_id'],
}
end
def access_token_query
query = {
'grant_type' => 'authorization_code',
'code' => code,
'scope' => scopes,
'id_token' => id_token,
'redirect_uri' => "#{host_name}/oic/local_login",
}
end
def refresh_token_query
query = {
'grant_type' => 'refresh_token',
'refresh_token' => refresh_token,
'scope' => scopes,
}
end
def end_session_query
query = {
'session_state' => session_state,
'post_logout_redirect_uri' => "#{host_name}/oic/local_logout",
}
if id_token.present?
query['id_token_hint'] = id_token
end
return query
end
def expired?
self.expires_at.nil? ? false : (self.expires_at < DateTime.now)
end
def incomplete?
self.access_token.blank?
end
def complete?
self.access_token.present?
end
def scopes
if client_config["scopes"].blank?
return "openid profile email user_name"
else
client_config["scopes"].split(',').each(&:strip).join(' ')
end
end
end

View File

@@ -0,0 +1,17 @@
<%= javascript_tag do %>
var wl = window.location
, url
, port;
if(wl.hash.length > 0) {
if(wl.port == 80 || wl.port == 443) {
port = null;
} else {
port = ':' + wl.port;
}
url = wl.protocol + '//' + wl.hostname + port + wl.pathname + '?' + wl.hash.substring(1);
wl.replace(url);
document.write('<p class="oic-click-for-redirect">Click <a href="' + url + '">here</a>, if redirection is not working.</p>');
}
<% end %>

View File

@@ -0,0 +1,3 @@
<p class="oic-logged-out">
<%= l(:oic_logout_success, home_url).html_safe %>
</p>

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>PMGov - RP iframe</title>
<script type="text/javascript">
var stat = "unchanged";
var client_id = "<%= @oic_session.client_config['client_id'] %>";
var check_interval = 5*1000;
var source_origin = window.location.origin;
var target_origin = new URL("<%= @oic_session.client_config['openid_connect_server_url'] %>").origin;
var session_state = "<%= @oic_session.session_state %>";
var mes = client_id + " " + session_state;
var reauthorize_url = "<%= oic_local_logout_url %>";
var timer = setInterval(checkSession(), check_interval);
function checkSession() {
var opiframe = window.parent.document.getElementById("opiframe").contentWindow;
opiframe.postMessage( mes, target_origin);
}
window.addEventListener("message", receiveMessage, false);
function receiveMessage(e) {
if (e.origin !== target_origin) { return alert('Wrong target origin: ' + target_origin); }
stat = e.data;
if (stat == "changed") {
window.parent.location.href = reauthorize_url;
}
}
</script>
</head>
<body></body>
</html>

View File

@@ -0,0 +1,7 @@
<div id="login-form-openid">
<%= form_tag(oic_login_url, :method => :get) do %>
<%= back_url_hidden_field_tag %>
<input type="submit" name="login-openid" value="<%=l(:button_login_sso)%>" tabindex="5" id="login-submit-openid" />
<% end %>
</div>

View File

@@ -0,0 +1,22 @@
<% if oic_session.dynamic_config.key?('check_session_iframe') %>
<iframe id="rpiframe" src="<%= oic_rpiframe_path %>" style="display:none" onload="checkSessionPoll('rpiframe')"></iframe>
<iframe id="opiframe" src="<%= oic_session.dynamic_config['check_session_iframe'] %>" style="display:none" onload="checkSessionPoll('opiframe')"></iframe>
<script type="text/javascript">
checkSessionPoll = (function () {
var rpiframe_loaded = false;
var opiframe_loaded = false;
return function(frame) {
if (frame == 'rpiframe') rpiframe_loaded = true;
if (frame == 'opiframe') opiframe_loaded = true;
if (rpiframe_loaded && opiframe_loaded) {
var rpiframe = document.getElementById('rpiframe').contentWindow;
poll = setInterval(rpiframe.checkSession, rpiframe.check_interval);
}
};
})();
</script>
<% else %>
<script type="text/javascript">
console.info('OpenID Connect did not specify a check_session_iframe URL.');
</script>
<% end %>

View File

@@ -0,0 +1,61 @@
<h3><%= t('config.header') %></h3>
<p>
<label><%= t('config.enabled') %></label>
<%= check_box_tag 'settings[enabled]', false, @settings['enabled'] %>
</p>
<p>
<label><%= t('config.client_id') %></label>
<%= text_field_tag 'settings[client_id]', @settings['client_id'] %>
</p>
<p>
<label><%= t('config.openid_connect_server_url') %></label>
<%= text_field_tag 'settings[openid_connect_server_url]', @settings['openid_connect_server_url'], :size => '60' %>
</p>
<p>
<label><%= t('config.client_secret') %></label>
<%= password_field_tag 'settings[client_secret]', @settings['client_secret'], :size => '60' %>
</p>
<p>
<label><%= t('config.scopes') %></label>
<%= text_field_tag 'settings[scopes]', @settings['scopes'], :size => '60' %>
</p>
<p>
<label><%= t('config.group') %></label>
<%= text_field_tag 'settings[group]', @settings['group'] %>
</p>
<p>
<label><%= t('config.admin_group') %></label>
<%= text_field_tag 'settings[admin_group]', @settings['admin_group'] %>
</p>
<p>
<label><%= t('config.dynamic_config_expiry') %></label>
<%= text_field_tag 'settings[dynamic_config_expiry]', @settings['dynamic_config_expiry'] %>
</p>
<p>
<label><%= t('config.disable_ssl_validation') %></label>
<%= check_box_tag 'settings[disable_ssl_validation]', true, @settings['disable_ssl_validation'] %>
</p>
<p>
<label><%= t('config.login_selector') %></label>
<%= check_box_tag 'settings[login_selector]', false, @settings['login_selector'] %>
</p>
<p>
<label><%= t('config.create_user_if_not_exists') %></label>
<%= check_box_tag 'settings[create_user_if_not_exists]', true, @settings['create_user_if_not_exists'] %>
</p>
<p>
<label><%= t('config.disallowed_auth_sources_login') %></label>
<%= select_tag 'settings[disallowed_auth_sources_login]', options_for_select(AuthSource.all.map { |a| [a.name, a.id] }, OicSession.disallowed_auth_sources_login), :multiple => true, :include_blank => true, :size => 5 %>
</p>

View File

@@ -0,0 +1,14 @@
#login-form-openid {
margin: 1em auto 2em auto;
padding: 20px;
width: 340px;
border: 1px solid #FDBF3B;
background-color: #FFEBC1;
border-radius: 4px;
box-sizing: border-box;
}
#login-form-openid input[type=submit] {
display: block;
width: 100%;
}

19
config/locales/de.yml Normal file
View File

@@ -0,0 +1,19 @@
de:
config:
enabled: Aktiv
login_selector: Login-Auswahl
header: OpenID Connect Konfiguration
client_id: Client-ID
openid_connect_server_url: OpenID Connect Server-Url
scopes: OpenID Connect Scopes (kommasepariert)
client_secret: Client-Secret
group: Rolle "darf einloggen" (leer lassen, falls jeder authentifizierte User einloggen darf)
admin_group: Rolle "Administratoren" (User mit dieser Rolle werden als Administrator behandelt)
dynamic_config_expiry: "Intervall für Aktualisierung der OpenID-Einstellungen (Default: 1 day)"
create_user_if_not_exists: "Benutzer erstellen, falls nicht vorhanden"
disallowed_auth_sources_login: "Benutzer aus den folgenden Authentifizierungsquellen müssen sich mit SSO anmelden"
oic_logout_success: 'Sie wurden ausgeloggt. <a href="%{value}">Klicken Sie hier, um sich erneut einzuloggen</a>.'
oic_cannot_create_user: "Der Benutzer %{value} konnte nicht angelegt werden: "
oic_try_another_account: "<a href='%{value}'>Mit einem anderen Account einloggen.</a>"
oic_cannot_login_user: "Benutzer %{value} konnte sich nicht anmelden: Bitte melden Sie sich mit der SSO-Option an"
button_login_sso: Melden Sie sich mit SSO an

20
config/locales/en.yml Normal file
View File

@@ -0,0 +1,20 @@
# English strings go here for Rails i18n
en:
config:
enabled: Enabled
login_selector: Login Selector
header: OpenID Connect Configuration
client_id: Client ID
openid_connect_server_url: OpenID Connect server url
scopes: OpenID Connect scopes (comma-separated)
client_secret: Client Secret
group: Authorized group (blank if all users are authorized)
admin_group: Admins group (members of this group are treated as admin)
dynamic_config_expiry: How often to retrieve openid configuration (default 1 day)
create_user_if_not_exists: Create user if not exists
disallowed_auth_sources_login: Users from the following auth sources will be required to login with SSO
oic_logout_success: 'You have been logged out. <a href="%{value}">Click here to log in again</a>.'
oic_cannot_create_user: "Could not create the user %{value}: "
oic_try_another_account: "<a href='%{value}'>Try logging in with another account</a>"
oic_cannot_login_user: "User %{value} could not login: Please login using the SSO option"
button_login_sso: Login with SSO

20
config/locales/pt.yml Normal file
View File

@@ -0,0 +1,20 @@
# Portuguese strings go here for Rails i18n
pt:
config:
enabled: Ativado
login_selector: Seletor de login
header: "Configuração OpenID Connect"
client_id: Client ID
openid_connect_server_url: Url do servidor OpenID Connect
scopes: OpenID Connect scopes (separados por virgula)
client_secret: Client Secret
group: "Grupo autorizado (vazio se todos os utilizadores são autorizados)"
admin_group: "Grupo de Administradores (membros deste grupo são tratados como administradores)"
dynamic_config_expiry: "Com que frequência obter configuração do openid (padrão 1 dia)"
create_user_if_not_exists: "Criar utilizador caso não exista"
disallowed_auth_sources_login: "Utilizadores das fontes selecionadas deverão fazer login SSO"
oic_logout_success: 'Saiu com sucesso. <a href="%{value}">Clique aqui para voltar a entrar</a>.'
oic_cannot_create_user: "Não foi possível criar o utilizador %{value}: "
oic_try_another_account: "<a href='%{value}'>Tente entrar com uma conta diferente</a>"
oic_cannot_login_user: "Não foi possível autenticar o utilizador %{value}: Por favor use o login SSO"
button_login_sso: Entrar com SSO

8
config/routes.rb Normal file
View File

@@ -0,0 +1,8 @@
# Plugin's routes
# See: http://guides.rubyonrails.org/routing.html
get 'oic/login', to: 'account#oic_login'
get 'oic/logout', to: 'account#oic_logout'
get 'oic/local_login', to: 'account#oic_local_login'
get 'oic/local_logout', to: 'account#oic_local_logout'
get 'oic/rpiframe', to: 'account#rpiframe'

2
contributors.txt Normal file
View File

@@ -0,0 +1,2 @@
Alfonso Juan Dillera
Markus M. May

View File

@@ -0,0 +1,25 @@
class CreateOicSessions < ActiveRecord::Migration[4.2]
def self.up
create_table :oic_sessions do |t|
t.references :user, foreign_key: { on_delete: :cascade }
t.text :code
t.string :state
t.string :nonce
t.string :session_state
t.text :id_token
t.text :access_token
t.text :refresh_token
t.datetime :expires_at
t.timestamps
end
add_index :oic_sessions, :user_id
add_index :oic_sessions, :access_token, length: 64
add_index :oic_sessions, :refresh_token, length: 64
add_index :oic_sessions, :id_token, length: 64
end
def self.down
drop_table :oic_sessions
end
end

20
init.rb Normal file
View File

@@ -0,0 +1,20 @@
require 'redmine'
require_relative 'lib/redmine_openid_connect/application_controller_patch'
require_relative 'lib/redmine_openid_connect/account_controller_patch'
require_relative 'lib/redmine_openid_connect/hooks'
Redmine::Plugin.register :redmine_openid_connect do
name 'Redmine Openid Connect plugin'
author 'Alfonso Juan Dillera / Markus M. May'
description 'OpenID Connect implementation for Redmine'
version '0.9.4'
url 'https://github.com/devopskube/redmine_openid_connect'
author_url 'http://github.com/adillera'
settings :default => { 'empty' => true }, partial: 'settings/redmine_openid_connect_settings'
end
ApplicationController.prepend(RedmineOpenidConnect::ApplicationControllerPatch)
AccountController.prepend(RedmineOpenidConnect::AccountControllerPatch)

View File

@@ -0,0 +1,197 @@
module RedmineOpenidConnect
module AccountControllerPatch
def login
if OicSession.disabled? || OicSession.login_selector? || params[:local_login].present? || request.post?
return super
end
redirect_to oic_login_url
end
def logout
if OicSession.disabled? || params[:local_login].present?
return super
end
oic_session = OicSession.find(session[:oic_session_id])
oic_session.destroy
logout_user
reset_session
redirect_to oic_session.end_session_url if oic_session.end_session_url
rescue ActiveRecord::RecordNotFound => e
redirect_to oic_local_logout_url
end
# performs redirect to SSO server
def oic_login
if session[:oic_session_id].blank?
oic_session = OicSession.create
session[:oic_session_id] = oic_session.id
else
begin
oic_session = OicSession.find session[:oic_session_id]
rescue ActiveRecord::RecordNotFound => e
oic_session = OicSession.create
session[:oic_session_id] = oic_session.id
end
if oic_session.complete? && oic_session.expired?
response = oic_session.refresh_access_token!
if response[:error].present?
oic_session.destroy
oic_session = OicSession.create
session[:oic_session_id] = oic_session.id
end
end
end
redirect_to oic_session.authorization_url
end
def oic_local_logout
logout_user
reset_session
end
def oic_local_login
if params[:code]
oic_session = OicSession.find(session[:oic_session_id])
unless oic_session.present?
return invalid_credentials
end
# verify request state or reauthorize
unless oic_session.state == params[:state]
flash[:error] = "Requête OpenID Connect invalide."
return redirect_to oic_local_logout
end
oic_session.update!(authorize_params)
# verify id token nonce or reauthorize
if oic_session.id_token.present?
unless oic_session.claims['nonce'] == oic_session.nonce
flash[:error] = "ID Token invalide."
return redirect_to oic_local_logout
end
end
# get access token and user info
oic_session.get_access_token!
user_info = oic_session.get_user_info!
# verify application authorization
unless oic_session.authorized?
return invalid_credentials
end
# Check if there's already an existing user
user = User.find_by_mail(user_info["email"])
if user.nil?
if !OicSession.create_user_if_not_exists?
flash.now[:warning] ||= l(:oic_cannot_create_user, value: user_info["email"])
logger.warn "Could not create user #{user_info["email"]}, the system is not allowed to create new users through openid"
flash.now[:warning] += "The system is not allowed to create new users through openid"
return invalid_credentials
end
user = User.new
user.login = user_info["user_name"] || user_info["nickname"] || user_info["preferred_username"] || user_info["email"]
firstname = user_info["given_name"]
lastname = user_info["family_name"]
if (firstname.nil? || lastname.nil?) && user_info["name"]
parts = user_info["name"].split
if parts.length >= 2
firstname = parts[0]
lastname = parts[-1]
end
end
attributes = {
firstname: firstname || "",
lastname: lastname || "",
mail: user_info["email"],
mail_notification: 'only_my_events',
last_login_on: Time.now
}
user.assign_attributes attributes
if user.save
user.update_attribute(:admin, oic_session.admin?)
oic_session.user_id = user.id
oic_session.save!
# after user creation just show "My Page" don't redirect to remember
successful_authentication(user)
else
flash.now[:warning] ||= l(:oic_cannot_create_user, value:user.login)
user.errors.full_messages.each do |error|
logger.warn "Could not create user #{user.login}, error was #{error}"
flash.now[:warning] += "#{error}. "
end
return invalid_credentials
end
else
user.update_attribute(:admin, oic_session.admin?)
oic_session.user_id = user.id
oic_session.save!
# redirect back to initial URL
if session[:remember_url]
params[:back_url] = session[:remember_url]
session[:remember_url] = nil
end
successful_authentication(user)
end # if user.nil?
end
end
def password_authentication
user = User.find_by_login(params[:username])
if OicSession.enabled? and !user.nil? and !user.auth_source.nil? and OicSession.disallowed_auth_sources_login.map(&:to_i).include? user.auth_source.id
flash.now[:warning] ||= l(:oic_cannot_login_user, params[:username])
logger.warn "User #{params[:username]} cannot login because it was disallowed by the openid plugin configuration"
else
return super
end
end
def invalid_credentials
return super unless OicSession.enabled?
logger.warn "Failed login attempt for '#{params[:username]}' from #{request.remote_ip} at #{Time.now.utc}"
flash.now[:error] = (l(:notice_account_invalid_credentials) + " " + l(:oic_try_another_account, signout_path)).html_safe
end
def rpiframe
@oic_session = OicSession.find(session[:oic_session_id])
render layout: false
end
def authorize_params
# compatible with both rails 3 and 4
if params.respond_to?(:permit)
params.permit(
:code,
:id_token,
:session_state,
)
else
params.select do |k,v|
[
'code',
'id_token',
'session_state',
].include?(k)
end
end
end
end # AccountControllerPatch
end

View File

@@ -0,0 +1,31 @@
module RedmineOpenidConnect
module ApplicationControllerPatch
def require_login
return super unless (OicSession.enabled? && !OicSession.login_selector?)
if !User.current.logged?
if request.get?
url = request.original_url
else
url = url_for(:controller => params[:controller], :action => params[:action], :id => params[:id], :project_id => params[:project_id])
end
session[:remember_url] = url
redirect_to oic_login_url
return false
end
true
end
# set the current user _without_ resetting the session first
def logged_user=(user)
return super(user) unless OicSession.enabled?
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
end # ApplicationControllerPatch
end

View File

@@ -0,0 +1,42 @@
require_relative '../../app/models/oic_session'
module RedmineOpenidConnect
class Hooks < Redmine::Hook::ViewListener
def request
ActionDispatch::Request.new(ENV)
end
def session
request.session
end
def view_account_login_bottom(context={})
return unless (OicSession.enabled? && OicSession.login_selector?)
if context[:request].session[:oic_session_id].blank?
oic_session = OicSession.create
else
oic_session = OicSession.find context[:request].session[:oic_session_id]
end
context[:oic_session] = oic_session
context[:controller].send(:render_to_string, {
partial: 'hooks/redmine_openid_connect/view_account_login_bottom',
locals: context
})
rescue ActiveRecord::RecordNotFound => e
end
def view_layouts_base_body_bottom(context={})
return unless OicSession.enabled?
oic_session = OicSession.find context[:request].session[:oic_session_id]
context[:oic_session] = oic_session
context[:controller].send(:render_to_string, {
partial: 'hooks/redmine_openid_connect/view_layouts_base_body_bottom',
locals: context
})
rescue ActiveRecord::RecordNotFound => e
end
def view_layouts_base_html_head(context={})
stylesheet_link_tag(:redmine_openid_connect, :plugin => 'redmine_openid_connect')
end
end
end

2
test/test_helper.rb Normal file
View File

@@ -0,0 +1,2 @@
# Load the Redmine helper
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')

View File

@@ -0,0 +1,9 @@
require File.expand_path('../../test_helper', __FILE__)
class OicSessionTest < ActiveSupport::TestCase
# Replace this with your real tests.
def test_truth
assert true
end
end