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]) %>
+
+ <% else %>
+
+ <% 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]) %>
<% else %>
@@ -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 %>
+
+ <% end %>
+
+
+
+ <%= l(:oauth_custom_name_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_auth_endpoint]', @settings[:custom_auth_endpoint], size: 80 %>
+ <%= l(:oauth_custom_auth_endpoint_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_token_endpoint]', @settings[:custom_token_endpoint], size: 80 %>
+ <%= l(:oauth_custom_token_endpoint_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_profile_endpoint]', @settings[:custom_profile_endpoint], size: 80 %>
+ <%= l(:oauth_custom_profile_endpoint_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_scope]', @settings[:custom_scope], size: 40 %>
+ <%= l(:oauth_custom_scope_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_uid_field]', @settings[:custom_uid_field], size: 40 %>
+ <%= l(:oauth_custom_uid_field_info) %>
+
+
+
+ <%= text_field_tag 'settings[custom_email_field]', @settings[:custom_email_field], size: 40 %>
+ <%= l(:oauth_custom_email_field_info) %>
+
+
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