From e1fe5b53a04dc5d49c78a195661633d04436b5ac Mon Sep 17 00:00:00 2001 From: Vladimir D Date: Fri, 23 Feb 2024 17:21:57 +0400 Subject: [PATCH 1/2] Custom OIDC provider --- app/controllers/redmine_oauth_controller.rb | 27 ++++++++++ .../hooks/_view_account_login_bottom.html.erb | 2 +- app/views/settings/_oauth_settings.html.erb | 53 +++++++++++++++++-- assets/javascripts/redmine_oauth.js | 42 +++++++++++++-- config/locales/cs.yml | 14 +++++ config/locales/de.yml | 14 +++++ config/locales/en.yml | 14 +++++ config/locales/fr.yml | 14 +++++ 8 files changed, 171 insertions(+), 9 deletions(-) diff --git a/app/controllers/redmine_oauth_controller.rb b/app/controllers/redmine_oauth_controller.rb index 79cd7d4..e09a4ca 100644 --- a/app/controllers/redmine_oauth_controller.rb +++ b/app/controllers/redmine_oauth_controller.rb @@ -61,6 +61,12 @@ def oauth state: oauth_csrf_token, scope: 'openid profile email' ) + when 'Custom' + redirect_to oauth_client.auth_code.authorize_url( + redirect_uri: oauth_callback_url, + state: oauth_csrf_token, + scope: Setting.plugin_redmine_oauth[:custom_scope] + ) else flash['error'] = l(:oauth_invalid_provider) redirect_to signin_path @@ -106,6 +112,19 @@ def oauth_callback user_info = JSON.parse(userinfo_response.body) user_info['login'] = user_info['preferred_username'] email = user_info['email'] + when 'Custom' + token = oauth_client.auth_code.get_token(params['code'], redirect_uri: oauth_callback_url) + if Setting.plugin_redmine_oauth[:custom_profile_endpoint].strip.empty? + user_info = JWT.decode(token.token, nil, false).first + else + userinfo_response = token.get( + Setting.plugin_redmine_oauth[:custom_profile_endpoint], + headers: { 'Accept' => 'application/json' } + ) + user_info = JSON.parse(userinfo_response.body) + end + user_info['login'] = user_info[Setting.plugin_redmine_oauth[:custom_uid_field]] + email = user_info[Setting.plugin_redmine_oauth[:custom_email_field]] else raise StandardError, l(:oauth_invalid_provider) end @@ -218,6 +237,14 @@ def oauth_client authorize_url: "/oauth2/#{Setting.plugin_redmine_oauth[:tenant_id]}/v1/authorize", token_url: "/oauth2/#{Setting.plugin_redmine_oauth[:tenant_id]}/v1/token" ) + when 'Custom' + OAuth2::Client.new( + Setting.plugin_redmine_oauth[:client_id], + Setting.plugin_redmine_oauth[:client_secret], + site: site, + authorize_url: Setting.plugin_redmine_oauth[:custom_auth_endpoint], + token_url: Setting.plugin_redmine_oauth[:custom_token_endpoint] + ) else raise StandardError, l(:oauth_invalid_provider) end diff --git a/app/views/hooks/_view_account_login_bottom.html.erb b/app/views/hooks/_view_account_login_bottom.html.erb index a3993bc..fbda800 100644 --- a/app/views/hooks/_view_account_login_bottom.html.erb +++ b/app/views/hooks/_view_account_login_bottom.html.erb @@ -27,7 +27,7 @@ <%= button_tag(name: 'login-oauth', tabindex: 6, id: 'login-oauth-submit', title: l(:oauth_login_with), style: "background: #{Setting.plugin_redmine_oauth[:button_color]}") do %> - <%= l(:oauth_login_via, oauth: Setting.plugin_redmine_oauth[:oauth_name]).html_safe %> + <%= l(:oauth_login_via, oauth: Setting.plugin_redmine_oauth[:custom_name].strip.empty? ? Setting.plugin_redmine_oauth[:oauth_name] : Setting.plugin_redmine_oauth[:custom_name]).html_safe %> <% end %> <% end %> <% end %> diff --git a/app/views/settings/_oauth_settings.html.erb b/app/views/settings/_oauth_settings.html.erb index f1ff49f..08611e9 100644 --- a/app/views/settings/_oauth_settings.html.erb +++ b/app/views/settings/_oauth_settings.html.erb @@ -30,6 +30,7 @@ %w(Google Google), %w(Keycloak Keycloak), %w(Okta Okta), + %w(Custom), [" ".html_safe, 'none'] ], @settings[:oauth_name]), onchange: 'oauth_settings_visibility()' %> <%= l(:oauth_provider_info) %> @@ -56,12 +57,16 @@ <%= button_tag(name: 'login-oauth', id: 'login-oauth-button', title: l(:oauth_login_with), style: style, disabled: true) do %> - <%= l(:oauth_login_via, oauth: @settings[:oauth_name]).html_safe %> + <%= l(:oauth_login_via, oauth: !@settings[:custom_name].nil? && !@settings[:custom_name].empty? ? @settings[:custom_name] : @settings[:oauth_name]).html_safe %> <% end %> <%= l(:oauth_button_info) %>

-

+ <% if %w(Custom).include?(@settings[:oauth_name]) %> +

+ <% end %> <%= text_field_tag 'settings[site]', @settings[:site], size: 40 %> <%= l(:oauth_site_info) %> @@ -76,7 +81,7 @@ <%= text_field_tag 'settings[client_secret]', @settings[:client_secret], size: 40 %> <%= l(:oauth_client_secret_info) %>

- <% if %w(GitLab Google).include?(@settings[:oauth_name]) %> + <% if %w(GitLab Google Custom).include?(@settings[:oauth_name]) %>

@@ -85,4 +90,46 @@ <%= text_field_tag 'settings[tenant_id]', @settings[:tenant_id], size: 40 %> <%= l(:oauth_tenant_id_info) %>

+ <% if %w(Custom).include?(@settings[:oauth_name]) %> +
+ <% else %> +
diff --git a/assets/javascripts/redmine_oauth.js b/assets/javascripts/redmine_oauth.js index 16ee2a6..2e4da0d 100644 --- a/assets/javascripts/redmine_oauth.js +++ b/assets/javascripts/redmine_oauth.js @@ -40,18 +40,27 @@ function oauth_set_icon() icon.addClass(icon_class); } +function oauth_set_btn_title() +{ + let oauth_name = $("input#settings_custom_name").val().trim() ? $("input#settings_custom_name").val().trim() : $("#settings_oauth_name option:selected").val(); + let button = $("button#login-oauth-button"); + let html = button.html(); + html = html.replace(/.*<\/b>/, "" + oauth_name + ""); + button.html(html); +} + function oauth_settings_visibility() { let div_oauth_options = $("div#oauth_options"); - let tenant_id = $("input#settings_tenant_id"); - let button = $("button#login-oauth-button"); + let tenant_id = $("input#settings_tenant_id"); let oauth_name = $("#settings_oauth_name option:selected").val(); - let html = button.html(); + $("input#settings_custom_name").val(oauth_name); + oauth_set_btn_title(); + $("input#settings_site").val(""); $("input#settings_client_id").val(""); $("input#settings_client_secret").val(""); - html = html.replace(/.*<\/b>/, "" + oauth_name + ""); - button.html(html); + switch(oauth_name) { case 'none': div_oauth_options.hide(); @@ -59,27 +68,50 @@ function oauth_settings_visibility() break; case 'Azure AD': div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').show(); div_oauth_options.find('#oauth_options_tenant').show(); + div_oauth_options.find('#oauth_options_custom').hide(); tenant_id.val(""); break; case 'GitLab': div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').show(); div_oauth_options.find('#oauth_options_tenant').hide(); + div_oauth_options.find('#oauth_options_custom').hide(); break; case 'Google': div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').show(); div_oauth_options.find('#oauth_options_tenant').hide(); + div_oauth_options.find('#oauth_options_custom').hide(); break; case 'Keycloak': div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').show(); div_oauth_options.find('#oauth_options_tenant').show(); + div_oauth_options.find('#oauth_options_custom').hide(); tenant_id.val(""); break; case 'Okta': div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').show(); div_oauth_options.find('#oauth_options_tenant').show(); + div_oauth_options.find('#oauth_options_custom').hide(); tenant_id.val("default"); break; + case 'Custom': + div_oauth_options.show(); + div_oauth_options.find('#oauth_options_site').hide(); + div_oauth_options.find('#oauth_options_tenant').hide(); + tenant_id.val(""); + div_oauth_options.find('#oauth_options_custom').show(); + $("input#settings_custom_auth_endpoint").val(""); + $("input#settings_custom_token_endpoint").val(""); + $("input#settings_custom_profile_endpoint").val(""); + $("input#settings_custom_scope").val("openid profile email"); + $("input#settings_custom_uid_field").val("preferred_username"); + $("input#settings_custom_email_field").val("email"); + break; default: break; } diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 32ca571..29daaf4 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -36,3 +36,17 @@ cs: oauth_tenant_id_info: ID adresáře (tenanta) oauth_button_info: Barva a ikonka (třída Awesome fontu) přihlašovacího tlačítka OAuth (prázdné pro žádné tlačítko) oauth_login_button: Přihlašovací tlačítko + oauth_custom_name: Provider name + oauth_custom_name_info: Title to be shown on the OAuth login button + oauth_custom_auth_endpoint: Auth endpoint + oauth_custom_auth_endpoint_info: Application Auth endpoint + oauth_custom_token_endpoint: Token endpoint + oauth_custom_token_endpoint_info: Application Token endpoint + oauth_custom_profile_endpoint: Profile endpoint + oauth_custom_profile_endpoint_info: Application Profile endpoint + oauth_custom_scope: OAuth scope + oauth_custom_scope_info: OAuth scope (default - 'openid profile email') + oauth_custom_uid_field: UID field + oauth_custom_uid_field_info: UID field (default - sub) + oauth_custom_email_field: Email field + oauth_custom_email_field_info: Email field (default - email) diff --git a/config/locales/de.yml b/config/locales/de.yml index 8a1e867..a480f55 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -36,3 +36,17 @@ de: oauth_tenant_id_info: Verzeichnis-ID (Mandant) oauth_button_info: Farbe und Symbol (Awesome-Schriftklasse) des OAuth-Anmeldebuttons (Leer für keinen Button) oauth_login_button: Anmeldebutton + oauth_custom_name: Provider name + oauth_custom_name_info: Title to be shown on the OAuth login button + oauth_custom_auth_endpoint: Auth endpoint + oauth_custom_auth_endpoint_info: Application Auth endpoint + oauth_custom_token_endpoint: Token endpoint + oauth_custom_token_endpoint_info: Application Token endpoint + oauth_custom_profile_endpoint: Profile endpoint + oauth_custom_profile_endpoint_info: Application Profile endpoint + oauth_custom_scope: OAuth scope + oauth_custom_scope_info: OAuth scope (default - 'openid profile email') + oauth_custom_uid_field: UID field + oauth_custom_uid_field_info: UID field (default - sub) + oauth_custom_email_field: Email field + oauth_custom_email_field_info: Email field (default - email) diff --git a/config/locales/en.yml b/config/locales/en.yml index 8b6fa07..3b11e12 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -36,3 +36,17 @@ en: oauth_tenant_id_info: Directory (tenant) ID oauth_button_info: Colour and icon (Awesome font class) of the OAuth login button (Empty for no button) oauth_login_button: Login button + oauth_custom_name: Provider name + oauth_custom_name_info: Title to be shown on the OAuth login button + oauth_custom_auth_endpoint: Auth endpoint + oauth_custom_auth_endpoint_info: Application Auth endpoint + oauth_custom_token_endpoint: Token endpoint + oauth_custom_token_endpoint_info: Application Token endpoint + oauth_custom_profile_endpoint: Profile endpoint + oauth_custom_profile_endpoint_info: Application Profile endpoint + oauth_custom_scope: OAuth scope + oauth_custom_scope_info: OAuth scope (default - 'openid profile email') + oauth_custom_uid_field: UID field + oauth_custom_uid_field_info: UID field (default - sub) + oauth_custom_email_field: Email field + oauth_custom_email_field_info: Email field (default - email) diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1133941..9fff6dd 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -36,3 +36,17 @@ fr: oauth_tenant_id_info: ID de répertoire (tenant) oauth_button_info: Couleur et icône (classe Font Awesome) du bouton de connexion via OAuth (Laissez vite pour masquer le bouton) oauth_login_button: Bouton de connexion + oauth_custom_name: Provider name + oauth_custom_name_info: Title to be shown on the OAuth login button + oauth_custom_auth_endpoint: Auth endpoint + oauth_custom_auth_endpoint_info: Application Auth endpoint + oauth_custom_token_endpoint: Token endpoint + oauth_custom_token_endpoint_info: Application Token endpoint + oauth_custom_profile_endpoint: Profile endpoint + oauth_custom_profile_endpoint_info: Application Profile endpoint + oauth_custom_scope: OAuth scope + oauth_custom_scope_info: OAuth scope (default - 'openid profile email') + oauth_custom_uid_field: UID field + oauth_custom_uid_field_info: UID field (default - sub) + oauth_custom_email_field: Email field + oauth_custom_email_field_info: Email field (default - email) From 0b744bb5414188704ce57af348894a977d81261e Mon Sep 17 00:00:00 2001 From: Vladimir D Date: Fri, 23 Feb 2024 17:40:43 +0400 Subject: [PATCH 2/2] code tidying up --- app/controllers/redmine_oauth_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/redmine_oauth_controller.rb b/app/controllers/redmine_oauth_controller.rb index e09a4ca..e62488a 100644 --- a/app/controllers/redmine_oauth_controller.rb +++ b/app/controllers/redmine_oauth_controller.rb @@ -61,7 +61,7 @@ def oauth state: oauth_csrf_token, scope: 'openid profile email' ) - when 'Custom' + when 'Custom' redirect_to oauth_client.auth_code.authorize_url( redirect_uri: oauth_callback_url, state: oauth_csrf_token, @@ -124,7 +124,7 @@ def oauth_callback user_info = JSON.parse(userinfo_response.body) end user_info['login'] = user_info[Setting.plugin_redmine_oauth[:custom_uid_field]] - email = user_info[Setting.plugin_redmine_oauth[:custom_email_field]] + email = user_info[Setting.plugin_redmine_oauth[:custom_email_field]] else raise StandardError, l(:oauth_invalid_provider) end @@ -244,7 +244,7 @@ def oauth_client site: site, authorize_url: Setting.plugin_redmine_oauth[:custom_auth_endpoint], token_url: Setting.plugin_redmine_oauth[:custom_token_endpoint] - ) + ) else raise StandardError, l(:oauth_invalid_provider) end