1
0
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:
FreddleSpl0it 2023-04-12 11:21:29 +02:00 committed by DerLinkman
parent 5bbb12b53e
commit f0689e08d9
No known key found for this signature in database
GPG Key ID: F109FD97469550A2
5 changed files with 83 additions and 48 deletions

View File

@ -52,7 +52,12 @@ function check_login($user, $pass, $app_passwd_data = false) {
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row){
// 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){
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){
return $result;
}
@ -318,15 +328,14 @@ function mailcow_mbox_apppass_login($user, $pass, $app_passwd_data, $is_internal
// ROPC Flow (deprecated oAuth2.1)
// 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;
$identity_provider_settings = identity_provider('get');
$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(
'grant_type' => 'password',
'client_id' => $identity_provider_settings['client_id'],
'client_secret' => $identity_provider_settings['client_secret'],
'client_id' => $iam_settings['client_id'],
'client_secret' => $iam_settings['client_secret'],
'username' => $user,
'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
return false;
}
if ($create && !empty($identity_provider_settings['mappers'])){
if ($create && !empty($iam_settings['mappers'])){
// try to create mbox on successfull login
$user_roles = $user_data['realm_access']['roles'];
$mbox_template = null;
// check if matching rolemapping exist
foreach ($user_roles as $index => $role){
if (in_array($role, $identity_provider_settings['roles'])) {
$mbox_template = $identity_provider_settings['templates'][$index];
// check if matching attribute mapping exists
foreach ($iam_settings['mappers'] as $index => $mapper){
if (in_array($mapper, $iam_settings['mappers'])) {
$mbox_template = $iam_settings['templates'][$index];
break;
}
}
if ($mbox_template){
$stmt = $pdo->prepare("SELECT * FROM `templates`
WHERE `template` = :template AND type = 'mailbox'");
$stmt->execute(array(
":template" => $mbox_template
));
$mbox_template_data = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$mbox_template){
// no matching template found
return false;
}
if (!empty($mbox_template_data)){
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['domain'] = explode('@', $user)[1];
$mbox_template_data['local_part'] = explode('@', $user)[0];
$mbox_template_data['authsource'] = 'keycloak';
$_SESSION['iam_create_login'] = true;
$create_res = mailbox('add', 'mailbox', $mbox_template_data);
$_SESSION['iam_create_login'] = false;
if (!$create_res){
return false;
}
$stmt = $pdo->prepare("SELECT * FROM `templates`
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)){
$mbox_template_data = json_decode($mbox_template_data["attributes"], true);
$mbox_template_data['domain'] = explode('@', $user)[1];
$mbox_template_data['local_part'] = explode('@', $user)[0];
$mbox_template_data['authsource'] = 'keycloak';
$_SESSION['iam_create_login'] = true;
$create_res = mailbox('add', 'mailbox', $mbox_template_data);
$_SESSION['iam_create_login'] = false;
if (!$create_res){
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
// This password will be used for direct UI, IMAP and SMTP Auth
// 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;
$identity_provider_settings = identity_provider('get');
// 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(
'grant_type' => 'client_credentials',
'client_id' => $identity_provider_settings['client_id'],
'client_secret' => $identity_provider_settings['client_secret']
'client_id' => $iam_settings['client_id'],
'client_secret' => $iam_settings['client_secret']
));
$curl = curl_init();
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
$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);
$queryString = http_build_query($queryParams);
$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
// also return false if no mappers were defined
$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;
} else if (!$create) {
// 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
$mbox_template = null;
// check if matching rolemapping exist
foreach ($identity_provider_settings['mappers'] as $index => $mapper){
if (in_array($mapper, $identity_provider_settings['mappers'])) {
$mbox_template = $identity_provider_settings['templates'][$index];
// check if matching attribute mapping exists
foreach ($iam_settings['mappers'] as $index => $mapper){
if (in_array($mapper, $iam_settings['mappers'])) {
$mbox_template = $iam_settings['templates'][$index];
break;
}
}

View File

@ -2104,8 +2104,10 @@ function identity_provider($_action, $_data = null, $hide_secret = false) {
$data_log['client_secret'] = '*';
$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
$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){
if (!$_data[$setting]){
$_SESSION['return'][] = array(

View File

@ -407,7 +407,6 @@ $(document).ready(function() {
$('#rl_frame').selectpicker('val', template.rl_frame);
}
console.log(template.active)
if (template.active){
$('#mbox_active').selectpicker('val', template.active.toString());
} else {

View File

@ -212,11 +212,17 @@
"host": "Host",
"html": "HTML",
"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_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_realm": "Realm",
"iam_redirect_url": "Redirect Url",
"iam_ropc_flow": "Resource Owner Password Flow",
"iam_rest_flow": "Mailpassword Flow",
"iam_mapping": "Attribute Mapping",
"iam_server_url": "Server Url",
"iam_sso": "SSO",

View File

@ -49,7 +49,7 @@
<input type="text" class="form-control" id="iam_version" name="version" value="{{ identity_provider_settings.version }}" required>
</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>
<div class="col-4 d-flex mb-2">
<span class="w-100 me-2">Attribute</span>
@ -83,13 +83,32 @@
</div>
{% endif %}
</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="offset-sm-3 col-sm-9">
<div class="offset-sm-3 col-sm-9 d-flex">
<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 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>
<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>
</form>