public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
Allow additional domains in OAuth2 redirect URLs
692355a0
data/web/api/openapi.yaml | 6 ++
data/web/inc/functions.inc.php | 27 +++++++-
data/web/js/site/admin.js | 30 +++++++++
.../admin/tab-config-identity-provider.twig | 72 ++++++++++++++++++++--
4 files changed, 130 insertions(+), 5 deletions(-)
Diff
diff --git a/data/web/api/openapi.yaml b/data/web/api/openapi.yaml
index afab5fb0..f207ee6a 100644
--- a/data/web/api/openapi.yaml
+++ b/data/web/api/openapi.yaml
@@ -5847,6 +5847,7 @@ paths:
client_id: "mailcow_client"
client_secret: "*"
redirect_url: "https://mail.mailcow.tld"
+ redirect_url_extra: ["https://extramail.mailcow.tld"]
version: "26.1.3"
default_template: "Default"
mappers:
@@ -5900,6 +5901,9 @@ paths:
redirect_url:
description: The redirect URL that OIDC Provider will use after authentication. Required if `authsource` is keycloak or generic-oidc.
type: string
+ redirect_url_extra:
+ description: Additional redirect URLs that OIDC Provider can use after authentication if valid.
+ type: array
version:
description: Specifies the Keycloak version. Required if `authsource` is keycloak.
type: string
@@ -5990,6 +5994,7 @@ paths:
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
+ redirect_url_extra: ["https://extramail.mailcow.tld"]
version: "26.1.3"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
@@ -6034,6 +6039,7 @@ paths:
client_id: "mailcow_client"
client_secret: "Xy7GdPqvJ9m3R8sT2LkVZ5W1oNbCaYQf"
redirect_url: "https://mail.mailcow.tld"
+ redirect_url_extra: ["https://extramail.mailcow.tld"]
client_scopes: "openid profile email mailcow_template"
default_template: "Default"
mappers: ["small_mbox", "medium_mbox"]
diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php
index 49e26b97..5f9c772d 100644
--- a/data/web/inc/functions.inc.php
+++ b/data/web/inc/functions.inc.php
@@ -2286,6 +2286,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($rows as $row){
switch ($row["key"]) {
+ case "redirect_url_extra":
case "mappers":
case "templates":
$settings[$row["key"]] = json_decode($row["value"]);
@@ -2418,6 +2419,18 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
}
$pdo->commit();
+ // add redirect_url_extra
+ if (isset($_data['redirect_url_extra'])){
+ $_data['redirect_url_extra'] = (!is_array($_data['redirect_url_extra'])) ? array($_data['redirect_url_extra']) : $_data['redirect_url_extra'];
+
+ $redirect_url_extra = array_filter($_data['redirect_url_extra']);
+ $redirect_url_extra = json_encode($redirect_url_extra);
+
+ $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('redirect_url_extra', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
+ $stmt->bindParam(':value', $redirect_url_extra);
+ $stmt->execute();
+ }
+
// add default template
if (isset($_data['default_template'])) {
$_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template'];
@@ -2851,7 +2864,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
case "get-redirect":
if ($iam_settings['authsource'] != 'keycloak' && $iam_settings['authsource'] != 'generic-oidc')
return false;
- $authUrl = $iam_provider->getAuthorizationUrl();
+ $options = [];
+ if (isset($iam_settings['redirect_url_extra'])) {
+ // check if the current domain is used in an extra redirect URL
+ $targetDomain = strtolower($_SERVER['HTTP_HOST']);
+ foreach ($iam_settings['redirect_url_extra'] as $testUrl) {
+ $testUrlParsed = parse_url($testUrl);
+ if (isset($testUrlParsed['host']) && strtolower($testUrlParsed['host']) == $targetDomain) {
+ $options['redirect_uri'] = $testUrl;
+ break;
+ }
+ }
+ }
+ $authUrl = $iam_provider->getAuthorizationUrl($options);
$_SESSION['oauth2state'] = $iam_provider->getState();
return $authUrl;
break;
diff --git a/data/web/js/site/admin.js b/data/web/js/site/admin.js
index b6edf6f8..cf1efd82 100644
--- a/data/web/js/site/admin.js
+++ b/data/web/js/site/admin.js
@@ -789,6 +789,18 @@ jQuery(function($){
$('.iam_ldap_rolemap_del').click(async function(e){
deleteAttributeMappingRow(this, e);
});
+ $('.iam_redirect_add_keycloak').click(async function(e){
+ addRedirectUrlRow('#iam_keycloak_redirect_list', '.iam_keycloak_redirect_del', e);
+ });
+ $('.iam_redirect_add_generic').click(async function(e){
+ addRedirectUrlRow('#iam_generic_redirect_list', '.iam_generic_redirect_del', e);
+ });
+ $('.iam_keycloak_redirect_del').click(async function(e){
+ deleteRedirectUrlRow(this, e);
+ });
+ $('.iam_generic_redirect_del').click(async function(e){
+ deleteRedirectUrlRow(this, e);
+ });
// selecting identity provider
$('#iam_provider').on('change', function(){
// toggle password fields
@@ -833,4 +845,22 @@ jQuery(function($){
if ($(elem).parent().parent().parent().parent().children().length > 1)
$(elem).parent().parent().parent().remove();
}
+ function addRedirectUrlRow(list_id, del_class, e) {
+ e.preventDefault();
+
+ var parent = $(list_id)
+ $(parent).children().last().clone().appendTo(parent);
+ var newChild = $(parent).children().last();
+ $(newChild).find('input').val('');
+
+ $(del_class).off('click');
+ $(del_class).click(async function(e){
+ deleteRedirectUrlRow(this, e);
+ });
+ }
+ function deleteRedirectUrlRow(elem, e) {
+ e.preventDefault();
+ if ($(elem).parent().parent().parent().parent().children().length > 2)
+ $(elem).parent().parent().parent().remove();
+ }
});
diff --git a/data/web/templates/admin/tab-config-identity-provider.twig b/data/web/templates/admin/tab-config-identity-provider.twig
index dce26f8c..a9300225 100644
--- a/data/web/templates/admin/tab-config-identity-provider.twig
+++ b/data/web/templates/admin/tab-config-identity-provider.twig
@@ -64,10 +64,42 @@
</div>
<div class="row mb-2">
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
- <label class="control-label" for="iam_keycloak_redirecturl">{{ lang.admin.iam_redirect_url }}:</label>
+ <label class="control-label">{{ lang.admin.iam_redirect_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
- <input type="text" class="form-control" id="iam_keycloak_redirecturl" name="redirect_url" value="{{ iam_settings.redirect_url }}" required>
+ <div class="row px-2 align-items-center">
+ <span class="col-10 p-0 pe-2">
+ <input type="text" class="form-control" name="redirect_url" value="{{ iam_settings.redirect_url }}" required>
+ </span>
+ <div class="col-2 p-0 d-flex">
+ <button class="btn btn-sm d-block d-sm-inline btn-secondary ms-auto iam_redirect_add_keycloak"><i class="bi bi-plus-lg"></i></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2" id="iam_keycloak_redirect_list">
+ <input type="hidden" name="redirect_url_extra" value="">
+ {% for key, url in iam_settings.redirect_url_extra %}
+ <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+ <div class="row px-2">
+ <div class="col-10 p-0 pe-2">
+ <input type="text" class="form-control me-2" name="redirect_url_extra" value="{{ iam_settings.redirect_url_extra[key] }}">
+ </div>
+ <div class="col-2 p-0 d-flex">
+ <button class="iam_keycloak_redirect_del btn btn-sm d-block d-sm-inline btn-secondary ms-auto"><i class="bi bi-x-lg"></i></button>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+ <div class="row px-2">
+ <div class="col-10 p-0 pe-2">
+ <input type="text" class="form-control me-2" name="redirect_url_extra" value="">
+ </div>
+ <div class="col-2 p-0 d-flex">
+ <button class="iam_keycloak_redirect_del btn btn-sm d-block d-sm-inline btn-secondary ms-auto"><i class="bi bi-x-lg"></i></button>
+ </div>
+ </div>
</div>
</div>
<div class="row mb-4">
@@ -274,10 +306,42 @@
</div>
<div class="row mb-2">
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
- <label class="control-label" for="iam_redirect_url">{{ lang.admin.iam_redirect_url }}:</label>
+ <label class="control-label">{{ lang.admin.iam_redirect_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
- <input type="text" class="form-control" id="iam_redirect_url" name="redirect_url" value="{{ iam_settings.redirect_url }}" required>
+ <div class="row px-2 align-items-center">
+ <span class="col-10 p-0 pe-2">
+ <input type="text" class="form-control" name="redirect_url" value="{{ iam_settings.redirect_url }}" required>
+ </span>
+ <div class="col-2 p-0 d-flex">
+ <button class="btn btn-sm d-block d-sm-inline btn-secondary ms-auto iam_redirect_add_generic"><i class="bi bi-plus-lg"></i></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row mb-2" id="iam_generic_redirect_list">
+ <input type="hidden" name="redirect_url_extra" value="">
+ {% for key, url in iam_settings.redirect_url_extra %}
+ <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+ <div class="row px-2">
+ <div class="col-10 p-0 pe-2">
+ <input type="text" class="form-control me-2" name="redirect_url_extra" value="{{ iam_settings.redirect_url_extra[key] }}">
+ </div>
+ <div class="col-2 p-0 d-flex">
+ <button class="iam_generic_redirect_del btn btn-sm d-block d-sm-inline btn-secondary ms-auto"><i class="bi bi-x-lg"></i></button>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+ <div class="row px-2">
+ <div class="col-10 p-0 pe-2">
+ <input type="text" class="form-control me-2" name="redirect_url_extra" value="">
+ </div>
+ <div class="col-2 p-0 d-flex">
+ <button class="iam_generic_redirect_del btn btn-sm d-block d-sm-inline btn-secondary ms-auto"><i class="bi bi-x-lg"></i></button>
+ </div>
+ </div>
</div>
</div>
<div class="row mb-4">