mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2024-12-23 02:04:46 +02:00
[Web] iam - add switch for direct login flow
This commit is contained in:
parent
5bbb12b53e
commit
f0689e08d9
@ -52,7 +52,12 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
|||||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if (!$row){
|
if (!$row){
|
||||||
// mbox does not exist, call keycloak login and create mbox if possible
|
// mbox does not exist, call keycloak login and create mbox if possible
|
||||||
$result = keycloak_mbox_login_rest($user, $pass, $is_dovecot, true);
|
$identity_provider_settings = identity_provider('get');
|
||||||
|
if ($identity_provider_settings['login_flow'] == 'ropc'){
|
||||||
|
$result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot, true);
|
||||||
|
} else {
|
||||||
|
$result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot, true);
|
||||||
|
}
|
||||||
if ($result){
|
if ($result){
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
@ -65,7 +70,12 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = keycloak_mbox_login_rest($user, $pass, $is_dovecot);
|
$identity_provider_settings = identity_provider('get');
|
||||||
|
if ($identity_provider_settings['login_flow'] == 'ropc'){
|
||||||
|
$result = keycloak_mbox_login_ropc($user, $pass, $identity_provider_settings, $is_dovecot);
|
||||||
|
} else {
|
||||||
|
$result = keycloak_mbox_login_rest($user, $pass, $identity_provider_settings, $is_dovecot);
|
||||||
|
}
|
||||||
if ($result){
|
if ($result){
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
@ -318,15 +328,14 @@ function mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal
|
|||||||
|
|
||||||
// ROPC Flow (deprecated oAuth2.1)
|
// ROPC Flow (deprecated oAuth2.1)
|
||||||
// uses direct user credentials for UI, IMAP and SMTP Auth
|
// uses direct user credentials for UI, IMAP and SMTP Auth
|
||||||
function keycloak_mbox_login_ropc($user, $pass, $is_internal = false, $create = false){
|
function keycloak_mbox_login_ropc($user, $pass, $iam_settings, $is_internal = false, $create = false){
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$identity_provider_settings = identity_provider('get');
|
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token";
|
||||||
$url = "{$identity_provider_settings['server_url']}/realms/{$identity_provider_settings['realm']}/protocol/openid-connect/token";
|
|
||||||
$req = http_build_query(array(
|
$req = http_build_query(array(
|
||||||
'grant_type' => 'password',
|
'grant_type' => 'password',
|
||||||
'client_id' => $identity_provider_settings['client_id'],
|
'client_id' => $iam_settings['client_id'],
|
||||||
'client_secret' => $identity_provider_settings['client_secret'],
|
'client_secret' => $iam_settings['client_secret'],
|
||||||
'username' => $user,
|
'username' => $user,
|
||||||
'password' => $pass,
|
'password' => $pass,
|
||||||
));
|
));
|
||||||
@ -347,36 +356,38 @@ function keycloak_mbox_login_ropc($user, $pass, $is_internal = false, $create =
|
|||||||
// check if $user is email address, only accept email address as username
|
// check if $user is email address, only accept email address as username
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($create && !empty($identity_provider_settings['mappers'])){
|
if ($create && !empty($iam_settings['mappers'])){
|
||||||
// try to create mbox on successfull login
|
// try to create mbox on successfull login
|
||||||
$user_roles = $user_data['realm_access']['roles'];
|
|
||||||
$mbox_template = null;
|
$mbox_template = null;
|
||||||
// check if matching rolemapping exist
|
// check if matching attribute mapping exists
|
||||||
foreach ($user_roles as $index => $role){
|
foreach ($iam_settings['mappers'] as $index => $mapper){
|
||||||
if (in_array($role, $identity_provider_settings['roles'])) {
|
if (in_array($mapper, $iam_settings['mappers'])) {
|
||||||
$mbox_template = $identity_provider_settings['templates'][$index];
|
$mbox_template = $iam_settings['templates'][$index];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($mbox_template){
|
if (!$mbox_template){
|
||||||
$stmt = $pdo->prepare("SELECT * FROM `templates`
|
// no matching template found
|
||||||
WHERE `template` = :template AND type = 'mailbox'");
|
return false;
|
||||||
$stmt->execute(array(
|
}
|
||||||
":template" => $mbox_template
|
|
||||||
));
|
$stmt = $pdo->prepare("SELECT * FROM `templates`
|
||||||
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
WHERE `template` = :template AND type = 'mailbox'");
|
||||||
|
$stmt->execute(array(
|
||||||
|
":template" => $mbox_template
|
||||||
|
));
|
||||||
|
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!empty($mbox_template_data)){
|
if (!empty($mbox_template_data)){
|
||||||
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
|
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
|
||||||
$mbox_template_data['domain'] = explode('@', $user)[1];
|
$mbox_template_data['domain'] = explode('@', $user)[1];
|
||||||
$mbox_template_data['local_part'] = explode('@', $user)[0];
|
$mbox_template_data['local_part'] = explode('@', $user)[0];
|
||||||
$mbox_template_data['authsource'] = 'keycloak';
|
$mbox_template_data['authsource'] = 'keycloak';
|
||||||
$_SESSION['iam_create_login'] = true;
|
$_SESSION['iam_create_login'] = true;
|
||||||
$create_res = mailbox('add', 'mailbox', $mbox_template_data);
|
$create_res = mailbox('add', 'mailbox', $mbox_template_data);
|
||||||
$_SESSION['iam_create_login'] = false;
|
$_SESSION['iam_create_login'] = false;
|
||||||
if (!$create_res){
|
if (!$create_res){
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -394,17 +405,15 @@ function keycloak_mbox_login_ropc($user, $pass, $is_internal = false, $create =
|
|||||||
// Keycloak REST Api Flow - auth user by mailcow_password attribute
|
// Keycloak REST Api Flow - auth user by mailcow_password attribute
|
||||||
// This password will be used for direct UI, IMAP and SMTP Auth
|
// This password will be used for direct UI, IMAP and SMTP Auth
|
||||||
// To use direct user credentials, only Authorization Code Flow is valid
|
// To use direct user credentials, only Authorization Code Flow is valid
|
||||||
function keycloak_mbox_login_rest($user, $pass, $is_internal = false, $create = false){
|
function keycloak_mbox_login_rest($user, $pass, $iam_settings, $is_internal = false, $create = false){
|
||||||
global $pdo;
|
global $pdo;
|
||||||
|
|
||||||
$identity_provider_settings = identity_provider('get');
|
|
||||||
|
|
||||||
// get access_token for service account of mailcow client
|
// get access_token for service account of mailcow client
|
||||||
$url = "{$identity_provider_settings['server_url']}/realms/{$identity_provider_settings['realm']}/protocol/openid-connect/token";
|
$url = "{$iam_settings['server_url']}/realms/{$iam_settings['realm']}/protocol/openid-connect/token";
|
||||||
$req = http_build_query(array(
|
$req = http_build_query(array(
|
||||||
'grant_type' => 'client_credentials',
|
'grant_type' => 'client_credentials',
|
||||||
'client_id' => $identity_provider_settings['client_id'],
|
'client_id' => $iam_settings['client_id'],
|
||||||
'client_secret' => $identity_provider_settings['client_secret']
|
'client_secret' => $iam_settings['client_secret']
|
||||||
));
|
));
|
||||||
$curl = curl_init();
|
$curl = curl_init();
|
||||||
curl_setopt($curl, CURLOPT_URL, $url);
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||||||
@ -420,7 +429,7 @@ function keycloak_mbox_login_rest($user, $pass, $is_internal = false, $create =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the mailcow_password attribute from keycloak user
|
// get the mailcow_password attribute from keycloak user
|
||||||
$url = "{$identity_provider_settings['server_url']}/admin/realms/{$identity_provider_settings['realm']}/users";
|
$url = "{$iam_settings['server_url']}/admin/realms/{$iam_settings['realm']}/users";
|
||||||
$queryParams = array('email' => $user, 'exact' => true);
|
$queryParams = array('email' => $user, 'exact' => true);
|
||||||
$queryString = http_build_query($queryParams);
|
$queryString = http_build_query($queryParams);
|
||||||
$curl = curl_init();
|
$curl = curl_init();
|
||||||
@ -448,7 +457,7 @@ function keycloak_mbox_login_rest($user, $pass, $is_internal = false, $create =
|
|||||||
// get mapped template, if not set return false
|
// get mapped template, if not set return false
|
||||||
// also return false if no mappers were defined
|
// also return false if no mappers were defined
|
||||||
$user_template = $user_data['attributes']['mailcow_template'][0];
|
$user_template = $user_data['attributes']['mailcow_template'][0];
|
||||||
if ($create && (empty($identity_provider_settings['mappers']) || $user_template)){
|
if ($create && (empty($iam_settings['mappers']) || $user_template)){
|
||||||
return false;
|
return false;
|
||||||
} else if (!$create) {
|
} else if (!$create) {
|
||||||
// login success - dont create mailbox
|
// login success - dont create mailbox
|
||||||
@ -462,10 +471,10 @@ function keycloak_mbox_login_rest($user, $pass, $is_internal = false, $create =
|
|||||||
|
|
||||||
// try to create mbox on successfull login
|
// try to create mbox on successfull login
|
||||||
$mbox_template = null;
|
$mbox_template = null;
|
||||||
// check if matching rolemapping exist
|
// check if matching attribute mapping exists
|
||||||
foreach ($identity_provider_settings['mappers'] as $index => $mapper){
|
foreach ($iam_settings['mappers'] as $index => $mapper){
|
||||||
if (in_array($mapper, $identity_provider_settings['mappers'])) {
|
if (in_array($mapper, $iam_settings['mappers'])) {
|
||||||
$mbox_template = $identity_provider_settings['templates'][$index];
|
$mbox_template = $iam_settings['templates'][$index];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2104,8 +2104,10 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
|
|||||||
$data_log['client_secret'] = '*';
|
$data_log['client_secret'] = '*';
|
||||||
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
|
$stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES (:key, :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
|
||||||
|
|
||||||
|
$_data['login_flow'] = (isset($_data['login_flow']) && $_data['login_flow'] == 'ropc') ? 'ropc' : 'rest';
|
||||||
|
|
||||||
// add connection settings
|
// add connection settings
|
||||||
$required_settings = array('server_url', 'authsource', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version');
|
$required_settings = array('server_url', 'authsource', 'realm', 'client_id', 'client_secret', 'redirect_url', 'version', 'login_flow');
|
||||||
foreach($required_settings as $setting){
|
foreach($required_settings as $setting){
|
||||||
if (!$_data[$setting]){
|
if (!$_data[$setting]){
|
||||||
$_SESSION['return'][] = array(
|
$_SESSION['return'][] = array(
|
||||||
|
@ -407,7 +407,6 @@ $(document).ready(function() {
|
|||||||
$('#rl_frame').selectpicker('val', template.rl_frame);
|
$('#rl_frame').selectpicker('val', template.rl_frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(template.active)
|
|
||||||
if (template.active){
|
if (template.active){
|
||||||
$('#mbox_active').selectpicker('val', template.active.toString());
|
$('#mbox_active').selectpicker('val', template.active.toString());
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,11 +212,17 @@
|
|||||||
"host": "Host",
|
"host": "Host",
|
||||||
"html": "HTML",
|
"html": "HTML",
|
||||||
"iam": "Identity Provider",
|
"iam": "Identity Provider",
|
||||||
|
"iam_auth_flow": "Authentication Flow",
|
||||||
|
"iam_auth_flow_info": "In addition to the Authorization Code Flow (Standard Flow in Keycloak), which is used for Single-Sign On login, mailcow also supports Authentication Flows with direct Credentials",
|
||||||
|
"iam_auth_flow_rest_info": "1. Mailpassword Flow (Default)<br>The Mailpassword Flow attempts to validate the user's credentials by using the Keycloak Admin REST API. mailcow retrieves the hashed password from the <code>mailcow_password</code> attribute, which is mapped in Keycloak. If this attribute is not found, the user needs to log in to the mailcow UI via Single-Sign On and create an App Password to use a mail client.<br>To enable this flow, the mailcow client in Keycloak must have <code>Service accounts roles</code> checked under <code>Authentication Flow</code>.",
|
||||||
|
"iam_auth_flow_ropc_info": "2. Resource Owner Password Flow<br>We do not recommend using this flow, as it is probably deprecated in the new OAuth 2.1 protocol. The Resource Owner Password Flow allows direct validation of the user's credentials. Therefore, the user has to trust mailcow to handle their external credentials securely. No Mailpassword or App Password is required to use a mail client.<br>To enable this flow, the mailcow client in Keycloak must have <code>Direct access grants</code> checked under <code>Authentication Flow</code>.",
|
||||||
"iam_client_id": "Client Id",
|
"iam_client_id": "Client Id",
|
||||||
"iam_client_secret": "Client Secret",
|
"iam_client_secret": "Client Secret",
|
||||||
"iam_description": "Here, you can configure the integration with an external Keycloak service. The Keycloak user's mailboxes will be automatically created upon their first login, provided that a attribute mapping has been set.",
|
"iam_description": "Here, you can configure the integration with an external Keycloak service. The Keycloak user's mailboxes will be automatically created upon their first login, provided that a attribute mapping has been set.",
|
||||||
"iam_realm": "Realm",
|
"iam_realm": "Realm",
|
||||||
"iam_redirect_url": "Redirect Url",
|
"iam_redirect_url": "Redirect Url",
|
||||||
|
"iam_ropc_flow": "Resource Owner Password Flow",
|
||||||
|
"iam_rest_flow": "Mailpassword Flow",
|
||||||
"iam_mapping": "Attribute Mapping",
|
"iam_mapping": "Attribute Mapping",
|
||||||
"iam_server_url": "Server Url",
|
"iam_server_url": "Server Url",
|
||||||
"iam_sso": "SSO",
|
"iam_sso": "SSO",
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
<input type="text" class="form-control" id="iam_version" name="version" value="{{ identity_provider_settings.version }}" required>
|
<input type="text" class="form-control" id="iam_version" name="version" value="{{ identity_provider_settings.version }}" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-4">
|
||||||
<label class="control-label col-sm-3 text-sm-end" for="iam_version">{{ lang.admin.iam_mapping }}:</label>
|
<label class="control-label col-sm-3 text-sm-end" for="iam_version">{{ lang.admin.iam_mapping }}:</label>
|
||||||
<div class="col-4 d-flex mb-2">
|
<div class="col-4 d-flex mb-2">
|
||||||
<span class="w-100 me-2">Attribute</span>
|
<span class="w-100 me-2">Attribute</span>
|
||||||
@ -83,13 +83,32 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-2">
|
||||||
|
<label class="control-label col-sm-3 text-sm-end">{{ lang.admin.iam_auth_flow }}</label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="btn-group">
|
||||||
|
<input type="radio" class="btn-check" name="login_flow" id="iam_login_flow_rest" autocomplete="off" value="rest" {% if identity_provider_settings.login_flow != 'ropc' %}checked{% endif %}>
|
||||||
|
<label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="iam_login_flow_rest">{{ lang.admin.iam_rest_flow }}</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="login_flow" id="iam_login_flow_ropc" autocomplete="off" value="ropc" {% if identity_provider_settings.login_flow == 'ropc' %}checked{% endif %}>
|
||||||
|
<label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-secondary" for="iam_login_flow_ropc">{{ lang.admin.iam_ropc_flow }}</label>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted">
|
||||||
|
<small>
|
||||||
|
{{ lang.admin.iam_auth_flow_info|raw }}<br>
|
||||||
|
{{ lang.admin.iam_auth_flow_rest_info|raw }}<br>
|
||||||
|
{{ lang.admin.iam_auth_flow_ropc_info|raw }}
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mt-4 mb-2">
|
<div class="row mt-4 mb-2">
|
||||||
<div class="offset-sm-3 col-sm-9">
|
<div class="offset-sm-3 col-sm-9 d-flex">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button id="iam_test_connection" class="btn btn-sm d-block d-sm-inline btn-secondary"><i class="bi bi-play"></i> {{ lang.admin.iam_test_connection }}</button>
|
<button id="iam_test_connection" class="btn btn-sm d-block d-sm-inline btn-secondary"><i class="bi bi-play"></i> {{ lang.admin.iam_test_connection }}</button>
|
||||||
<button class="btn btn-sm d-block d-sm-inline btn-success" data-item="iam_sso" data-action="edit_selected" data-id="iam_sso" data-api-url='edit/identity-provider' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
<button class="btn btn-sm d-block d-sm-inline btn-success" data-item="iam_sso" data-action="edit_selected" data-id="iam_sso" data-api-url='edit/identity-provider' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-sm d-block d-sm-inline btn-danger ms-2" data-item="identity-provider" data-action="delete_selected" data-id="iam_sso" data-api-url='delete/identity-provider'><i class="bi bi-trash"></i> {{ lang.mailbox.remove }}</button>
|
<button class="btn btn-sm d-block d-sm-inline btn-danger ms-auto" data-item="identity-provider" data-action="delete_selected" data-id="iam_sso" data-api-url='delete/identity-provider'><i class="bi bi-trash"></i> {{ lang.mailbox.remove }}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
Loading…
Reference in New Issue
Block a user