diff --git a/README.rdoc b/README.rdoc index 9c1f3e6..23920ad 100644 --- a/README.rdoc +++ b/README.rdoc @@ -87,6 +87,17 @@ admin_role:: +realm_access.roles+. Example: ROLES/REDMINE/ADMIN +groups_claim:: + The claim which provides the set of groups (optional). + + Example: +groups+ +group_names_pattern:: + A regex pattern to filter names of user groups that should be synchronized. + Default is +^$+ - match nothing. Note that if you're using local groups (not + synchronised with OIDC), they should not match this pattern, otherwise users + will be removed from these groups when they log in. + + Example: +^(Red|Blue)$+ == Mapping users diff --git a/app/controllers/oidc_controller.rb b/app/controllers/oidc_controller.rb index ee6b586..ff7355f 100644 --- a/app/controllers/oidc_controller.rb +++ b/app/controllers/oidc_controller.rb @@ -86,6 +86,7 @@ def create_user user.activate user.random_password user.last_login_on = Time.now + update_groups(user) user.save ? successful_login(user) : unsuccessful_login(user) end @@ -93,9 +94,34 @@ def update_user(user) user.update(@oidc_session.user_attributes) user.activate user.update_last_login_on! + update_groups(user) user.save ? successful_login(user) : unsuccessful_login(user) end + def update_groups(user) + return unless settings.update_groups? + + current = user.groups.select { |group| settings.group_names_regexp.match?(group.name) } + + target = @oidc_session.groups.map do |name| + begin + Group.named(name).first_or_create!(name: name) + rescue ActiveRecord::RecordInvalid => e + logger.error "Failed to create group #{name}: #{e}" + end + end + + unless (added = target - current).empty? + logger.info "Adding user #{user.login} to groups: #{added.map(&:name).join(', ')}" + user.groups += added + end + + unless (removed = current - target).empty? + logger.info "Removing user #{user.login} from groups: #{removed.map(&:name).join(', ')}" + user.groups -= removed + end + end + def successful_login(user) logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}" oidc_session = OidcSession.spawn(session) @@ -110,4 +136,7 @@ def unsuccessful_login(user) end end + def settings + @settings ||= RedmineOidc.settings + end end diff --git a/app/models/oidc_session.rb b/app/models/oidc_session.rb index 3c61cbc..a9320ac 100644 --- a/app/models/oidc_session.rb +++ b/app/models/oidc_session.rb @@ -103,6 +103,16 @@ def user_attributes } end + def groups + @groups ||= if settings.update_groups? + (decoded_id_token.raw_attributes[settings.groups_claim] || []) + .select { |name| settings.group_names_regexp.match?(name) } + .to_set + else + Set.new + end + end + def refresh_token_expiration_timestamp decoded_refresh_token['exp'] end diff --git a/app/views/settings/_redmine_oidc.html.erb b/app/views/settings/_redmine_oidc.html.erb index c77d19f..9eb7f93 100644 --- a/app/views/settings/_redmine_oidc.html.erb +++ b/app/views/settings/_redmine_oidc.html.erb @@ -38,6 +38,14 @@ <%= label_tag 'settings[admin_role]', l('oidc.settings.admin_role') %> <%= text_field_tag 'settings[admin_role]', oidc_settings.admin_role, size: 60 %>

+

+ <%= label_tag 'settings[groups_claim]', l('oidc.settings.groups_claim') %> + <%= text_field_tag 'settings[groups_claim]', oidc_settings.groups_claim, size: 60 %> +

+

+ <%= label_tag 'settings[group_names_pattern]', l('oidc.settings.group_names_pattern') %> + <%= text_field_tag 'settings[group_names_pattern]', oidc_settings.group_names_pattern, size: 60, placeholder: l('oidc.settings.group_names_pattern_placeholder') %> +

<%= label_tag 'settings[session_check_enabled]', l('oidc.settings.session_check_enabled') %> <%= check_box_tag 'settings[session_check_enabled]', 1, oidc_settings.session_check_enabled %> diff --git a/config/locales/de.yml b/config/locales/de.yml index 66adf98..904e4d2 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -34,6 +34,9 @@ de: roles_claim_placeholder: roles access_roles: Leerzeichen-separierte Liste der autorisierten Rollen admin_role: Administrationsrolle + groups_claim: Gruppe-Claim (optional) + group_names_pattern: Regex zum Filtern der Namen von Gruppen, die synchronisiert werden sollen + group_names_pattern_placeholder: ^$ session_check_enabled: Session Check aktivieren session_check_users_csv: Komma-separierte Liste der Logins mit Session Check (* = alle) error: diff --git a/config/locales/en.yml b/config/locales/en.yml index cedad2a..f03a82a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -34,6 +34,9 @@ en: roles_claim_placeholder: roles access_roles: Space-separated list of authorized roles admin_role: Administration role + groups_claim: Groups claim (optional) + group_names_pattern: Regex to filter names of groups that should be synchronized + group_names_pattern_placeholder: ^$ session_check_enabled: Enable session check session_check_users_csv: Comma-separated list of logins with session check (* = all) error: diff --git a/lib/redmine_oidc/settings.rb b/lib/redmine_oidc/settings.rb index 39f7444..8108900 100644 --- a/lib/redmine_oidc/settings.rb +++ b/lib/redmine_oidc/settings.rb @@ -31,6 +31,8 @@ class Settings roles_claim access_roles admin_role + groups_claim + group_names_pattern session_check_enabled session_check_users_csv ) @@ -70,6 +72,14 @@ def to_h serializable_hash end + def group_names_regexp + @group_names_regexp ||= Regexp.new(@group_names_pattern || '^$', Regexp::IGNORECASE) + end + + def update_groups? + !!@groups_claim + end + def session_check_users @session_check_users ||= @session_check_users_csv.split(',').map(&:strip) end