Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
source 'https://rubygems.org'
gem 'httparty', '~> 0.14.0'
gem 'httparty', '~> 0.21.0'
11 changes: 10 additions & 1 deletion app/models/oic_session.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class OicSession < ActiveRecord::Base
unloadable if self.respond_to?(:unloadable)

before_create :randomize_state!
before_create :randomize_nonce!

Expand Down Expand Up @@ -42,9 +44,13 @@ def self.openid_configuration_url
client_config['openid_connect_server_url'] + '/.well-known/openid-configuration'
end

def self.also_logout_from_oic_server?
client_config['also_logout_from_oic_server']
end

def self.get_dynamic_config
hash = Digest::SHA1.hexdigest client_config.to_json
expiry = client_config['dynamic_config_expiry'] || 86400
expiry = (client_config['dynamic_config_expiry'] || 86400).to_i
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)
Expand Down Expand Up @@ -134,6 +140,9 @@ def check_keycloak_role(role)
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
if user["groups"].present?
kc_is_in_role = user["groups"].include?(role)
end
return true if kc_is_in_role
end

Expand Down
10 changes: 10 additions & 0 deletions app/views/settings/_redmine_openid_connect_settings.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,13 @@
<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>

<p>
<label><%= t('config.override_user_attributes') %></label>
<%= check_box_tag 'settings[override_user_attributes]', true, @settings['override_user_attributes'] %>
</p>

<p>
<label><%= t('config.also_logout_from_oic_server') %></label>
<%= check_box_tag 'settings[also_logout_from_oic_server]', false, @settings['also_logout_from_oic_server'] %>
</p>
4 changes: 3 additions & 1 deletion config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ de:
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>.'
override_user_attributes: "Benutzerattribute bei jedem Login überschreiben"
also_logout_from_oic_server: "Auch vom OpenID Connect Server abmelden"
oic_logout_success: 'Sie wurden ausgeloggt. <a href="%{value}/login">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"
Expand Down
4 changes: 3 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ en:
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>.'
override_user_attributes: Override user attributes at each login
also_logout_from_oic_server: Also logout from OpenID Connect server
oic_logout_success: 'You have been logged out. <a href="%{value}/login">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"
Expand Down
4 changes: 3 additions & 1 deletion config/locales/pt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ pt:
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>.'
override_user_attributes: "Sobrescrever atributos do utilizador em cada login"
also_logout_from_oic_server: "Também sair do servidor OpenID Connect"
oic_logout_success: 'Saiu com sucesso. <a href="%{value}/login">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"
Expand Down
13 changes: 4 additions & 9 deletions init.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
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'
require "redmine"
require "#{File.dirname(__FILE__)}/lib/redmine_openid_connect/application_controller_patch"
require "#{File.dirname(__FILE__)}/lib/redmine_openid_connect/account_controller_patch"
require "#{File.dirname(__FILE__)}/lib/redmine_openid_connect/hooks"

Redmine::Plugin.register :redmine_openid_connect do
name 'Redmine Openid Connect plugin'
Expand All @@ -13,8 +13,3 @@

settings :default => { 'empty' => true }, partial: 'settings/redmine_openid_connect_settings'
end


ApplicationController.prepend(RedmineOpenidConnect::ApplicationControllerPatch)
AccountController.prepend(RedmineOpenidConnect::AccountControllerPatch)

88 changes: 83 additions & 5 deletions lib/redmine_openid_connect/account_controller_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ def logout
oic_session.destroy
logout_user
reset_session
redirect_to oic_session.end_session_url if oic_session.end_session_url

if OicSession.also_logout_from_oic_server?
redirect_to oic_session.end_session_url if oic_session.end_session_url
else
redirect_to oic_local_logout_url
end
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?
Expand Down Expand Up @@ -64,7 +69,7 @@ def oic_local_login

# verify request state or reauthorize
unless oic_session.state == params[:state]
flash[:error] = "Requête OpenID Connect invalide."
flash[:error] = "Invalid OpenID Connect request."
return redirect_to oic_local_logout
end

Expand All @@ -73,7 +78,7 @@ def oic_local_login
# 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."
flash[:error] = "ID Token invalid."
return redirect_to oic_local_logout
end
end
Expand All @@ -87,8 +92,32 @@ def oic_local_login
return invalid_credentials
end

username = user_info["user_name"] || user_info["nickname"] || user_info["preferred_username"] || user_info["username"]

# Check if there's already an existing user
user = User.find_by_mail(user_info["email"])
user = User.find_by_login(username)

if user.nil?
if !OicSession.create_user_if_not_exists?
flash.now[:warning] ||= l(:oic_cannot_create_user, 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
end

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

if user.nil?
if !OicSession.create_user_if_not_exists?
Expand Down Expand Up @@ -127,6 +156,9 @@ def oic_local_login

if user.save
user.update_attribute(:admin, oic_session.admin?)

update_groups(user,user_info['groups'])

oic_session.user_id = user.id
oic_session.save!
# after user creation just show "My Page" don't redirect to remember
Expand All @@ -141,6 +173,15 @@ def oic_local_login
end
else
user.update_attribute(:admin, oic_session.admin?)

if Setting.plugin_redmine_openid_connect['override_user_attributes']
user.update_attribute(:firstname, firstname)
user.update_attribute(:lastname, lastname)
user.update_attribute(:mail, user_info["email"])
end

update_groups(user,user_info['groups'])

oic_session.user_id = user.id
oic_session.save!
# redirect back to initial URL
Expand Down Expand Up @@ -193,5 +234,42 @@ def authorize_params
end
end
end

def update_groups(user, groups)
# Normalize group names
group_names = groups.map(&:to_s).map(&:downcase)

# Load existing groups in one query
existing_groups = Group.where('LOWER(lastname) IN (?)', group_names).index_by { |g| g.lastname.downcase }

group_names.each do |gname|
rm_g = existing_groups[gname]

if rm_g
# Only add the user if not already in the group
unless rm_g.users.exists?(user.id)
rm_g.users << user
logger.info("[OIDC] Added user #{user.login} to group #{rm_g.name}")
else
logger.info("[OIDC] User #{user.login} already in group #{rm_g.name}")
end
else
# Group doesn’t exist → create and add user
g = Group.new(lastname: gname, name: gname)
g.users << user
if g.save
logger.info("[OIDC] Created group #{gname} and added user #{user.login}")
existing_groups[gname] = g
else
logger.error("[OIDC] Failed to create group #{gname}: #{g.errors.full_messages.join(', ')}")
end
end
rescue => e
logger.error("[OIDC] Error updating group #{gname} for user #{user.login}: #{e.message}")
end
end

end # AccountControllerPatch
end

AccountController.prepend RedmineOpenidConnect::AccountControllerPatch
2 changes: 2 additions & 0 deletions lib/redmine_openid_connect/application_controller_patch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ def logged_user=(user)
end
end # ApplicationControllerPatch
end

ApplicationController.prepend RedmineOpenidConnect::ApplicationControllerPatch