2
0
Эх сурвалжийг харах

Add default template for IdP attribute mapping

FreddleSpl0it 5 сар өмнө
parent
commit
887b7114a8

+ 18 - 22
data/conf/phpfpm/crons/keycloak-sync.php

@@ -154,17 +154,6 @@ while (true) {
       logMsg("warning", "No email address in keycloak found for user " . $user['name']);
       continue;
     }
-    if (!isset($user['attributes'])){
-      logMsg("warning", "No attributes in keycloak found for user " . $user['email']);
-      continue;
-    }
-    if (!isset($user['attributes']['mailcow_template']) ||
-        !is_array($user['attributes']['mailcow_template']) ||
-        count($user['attributes']['mailcow_template']) == 0) {
-      logMsg("warning", "No mailcow_template in keycloak found for user " . $user['email']);
-      continue;
-    }
-    $mailcow_template = $user['attributes']['mailcow_template'];
 
     // try get mailbox user
     $stmt = $pdo->prepare("SELECT
@@ -178,20 +167,22 @@ while (true) {
     $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
     // check if matching attribute mapping exists
-    $mbox_template = null;
-    foreach ($iam_settings['mappers'] as $index => $mapper){
-      if (in_array($mapper, $user['attributes']['mailcow_template'])) {
-        $mbox_template = $mapper;
-        break;
-      }
-    }
-    if (!$mbox_template){
-      logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
-      continue;
-    }
+    $user_template = $user['attributes']['mailcow_template'][0];
+    $mapper_key = array_search($user_template, $iam_settings['mappers']);
 
     $_SESSION['access_all_exception'] = '1';
     if (!$row && intval($iam_settings['import_users']) == 1){
+      if ($mapper_key === false){
+        if (!empty($iam_settings['default_template'])) {
+          $mbox_template = $iam_settings['default_template'];
+          logMsg("warning", "Using default template for user " . $user['email']);
+        } else {
+          logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
+          continue;
+        }
+      } else {
+        $mbox_template = $iam_settings['templates'][$mapper_key];
+      }
       // mailbox user does not exist, create...
       logMsg("info", "Creating user " . $user['email']);
       $create_res = mailbox('add', 'mailbox_from_template', array(
@@ -206,6 +197,11 @@ while (true) {
         continue;
       }
     } else if ($row && intval($iam_settings['periodic_sync']) == 1) {
+      if ($mapper_key === false){
+        logMsg("warning", "No matching attribute mapping found for user " . $user['email']);
+        continue;
+      }
+      $mbox_template = $iam_settings['templates'][$mapper_key];
       // mailbox user does exist, sync attribtues...
       logMsg("info", "Syncing attributes for user " . $user['email']);
       mailbox('edit', 'mailbox_from_template', array(

+ 17 - 11
data/conf/phpfpm/crons/ldap-sync.php

@@ -137,17 +137,8 @@ foreach ($response as $user) {
   $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
   // check if matching attribute mapping exists
-  $mbox_template = null;
-  foreach ($iam_settings['mappers'] as $index => $mapper){
-    if ($mapper ==  $mailcow_template) {
-      $mbox_template = $iam_settings['templates'][$index];
-      break;
-    }
-  }
-  if (!$mbox_template){
-    logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
-    continue;
-  }
+  $user_template = $user_res[$iam_settings['attribute_field']][0];
+  $mapper_key = array_search($user_template, $iam_settings['mappers']);
 
   if (empty($user[$iam_settings['username_field']][0])){
     logMsg("warning", "Skipping user " . $user['displayname'][0] . " due to empty LDAP ". $iam_settings['username_field'] . " property.");
@@ -156,6 +147,16 @@ foreach ($response as $user) {
 
   $_SESSION['access_all_exception'] = '1';
   if (!$row && intval($iam_settings['import_users']) == 1){
+    if ($mapper_key === false){
+      if (!empty($iam_settings['default_template'])) {
+        $mbox_template = $iam_settings['default_template'];
+      } else {
+        logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
+        continue;
+      }
+    } else {
+      $mbox_template = $iam_settings['templates'][$mapper_key];
+    }
     // mailbox user does not exist, create...
     logMsg("info", "Creating user " .  $user[$iam_settings['username_field']][0]);
     $create_res = mailbox('add', 'mailbox_from_template', array(
@@ -170,6 +171,11 @@ foreach ($response as $user) {
       continue;
     }
   } else if ($row && intval($iam_settings['periodic_sync']) == 1) {
+    if ($mapper_key === false){
+      logMsg("warning", "No matching attribute mapping found for user " . $user[$iam_settings['username_field']][0]);
+      continue;
+    }
+    $mbox_template = $iam_settings['templates'][$mapper_key];
     // mailbox user does exist, sync attribtues...
     logMsg("info", "Syncing attributes for user " . $user[$iam_settings['username_field']][0]);
     mailbox('edit', 'mailbox_from_template', array(

+ 26 - 14
data/web/inc/functions.auth.inc.php

@@ -529,12 +529,18 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
 
   // check if matching attribute exist
   if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
-    $_SESSION['return'][] =  array(
-      'type' => 'danger',
-      'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
-      'msg' => 'generic_server_error'
-    );
-    return false;
+    if (!empty($iam_settings['default_template'])) {
+      $mbox_template = $iam_settings['default_template'];
+    } else {
+      $_SESSION['return'][] =  array(
+        'type' => 'danger',
+        'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
+        'msg' => 'generic_server_error'
+      );
+      return false;
+    }
+  } else {
+    $mbox_template = $iam_settings['templates'][$mapper_key];
   }
 
   // create mailbox
@@ -544,7 +550,7 @@ function keycloak_mbox_login_rest($user, $pass, $extra = null){
     'local_part' => explode('@', $user)[0],
     'name' => $user_res['name'],
     'authsource' => 'keycloak',
-    'template' => $iam_settings['templates'][$mapper_key]
+    'template' => $mbox_template
   ));
   $_SESSION['access_all_exception'] = '0';
   if (!$create_res){
@@ -636,12 +642,18 @@ function ldap_mbox_login($user, $pass, $extra = null){
 
   // check if matching attribute exist
   if (empty($iam_settings['mappers']) || !$user_template || $mapper_key === false) {
-    $_SESSION['return'][] =  array(
-      'type' => 'danger',
-      'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
-      'msg' => 'generic_server_error'
-    );
-    return false;
+    if (!empty($iam_settings['default_tempalte'])) {
+      $mbox_template = $iam_settings['default_tempalte'];
+    } else {
+      $_SESSION['return'][] =  array(
+        'type' => 'danger',
+        'log' => array(__FUNCTION__, $user, '*', 'No matching attribute mapping was found'),
+        'msg' => 'generic_server_error'
+      );
+      return false;
+    }
+  } else {
+    $mbox_template = $iam_settings['templates'][$mapper_key];
   }
 
   // create mailbox
@@ -651,7 +663,7 @@ function ldap_mbox_login($user, $pass, $extra = null){
     'local_part' => explode('@', $user)[0],
     'name' => $user_res['displayname'][0],
     'authsource' => 'ldap',
-    'template' => $iam_settings['templates'][$mapper_key]
+    'template' => $mbox_template
   ));
   $_SESSION['access_all_exception'] = '0';
   if (!$create_res){

+ 23 - 9
data/web/inc/functions.inc.php

@@ -2387,8 +2387,16 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
       }
       $pdo->commit();
 
+      // add default template
+      if (isset($_data['default_template'])) {
+        $_data['default_template'] = (empty($_data['default_template'])) ? "" : $_data['default_template'];
+        $stmt = $pdo->prepare("INSERT INTO identity_provider (`key`, `value`) VALUES ('default_template', :value) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`);");
+        $stmt->bindParam(':value', $_data['default_template']);
+        $stmt->execute();
+      }
+
       // add mappers
-      if ($_data['mappers'] && $_data['templates']){
+      if (isset($_data['mappers']) && isset($_data['templates'])){
         $_data['mappers'] = (!is_array($_data['mappers'])) ? array($_data['mappers']) : $_data['mappers'];
         $_data['templates'] = (!is_array($_data['templates'])) ? array($_data['templates']) : $_data['templates'];
 
@@ -2714,13 +2722,19 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
       }
 
       if (empty($iam_settings['mappers']) || empty($user_template) || $mapper_key === false){
-        clear_session();
-        $_SESSION['return'][] =  array(
-          'type' => 'danger',
-          'log' => array(__FUNCTION__, $info['email'], 'No matching attribute mapping was found'),
-          'msg' => 'login_failed'
-        );
-        return false;
+        if (!empty($iam_settings['default_template'])) {
+          $mbox_template = $iam_settings['default_template'];
+        } else {
+          clear_session();
+          $_SESSION['return'][] =  array(
+            'type' => 'danger',
+            'log' => array(__FUNCTION__, $info['email'], 'No matching attribute mapping was found'),
+            'msg' => 'login_failed'
+          );
+          return false;
+        }
+      } else {
+        $mbox_template = $iam_settings['templates'][$mapper_key];
       }
 
       // create mailbox
@@ -2730,7 +2744,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
         'local_part' => explode('@', $info['email'])[0],
         'name' => $info['name'],
         'authsource' => $iam_settings['authsource'],
-        'template' => $iam_settings['templates'][$mapper_key]
+        'template' => $mbox_template
       ));
       $_SESSION['access_all_exception'] = '0';
       if (!$create_res){

+ 38 - 68
data/web/js/site/admin.js

@@ -711,7 +711,7 @@ jQuery(function($){
   // App links
   // setup eventlistener
   setAppHideEvent();
-  function setAppHideEvent(){ 
+  function setAppHideEvent(){
     $('.app_hide').off('change');
     $('.app_hide').on('change', function (e) {
       var value = $(this).is(':checked') ? '1' : '0';
@@ -756,13 +756,13 @@ jQuery(function($){
   $('.iam_test_connection').click(async function(e){
     e.preventDefault();
     var data = { attr: $('form[data-id="' + $(this).data('id') + '"]').serializeObject() };
-    var res = await fetch("/api/v1/edit/identity-provider-test", { 
+    var res = await fetch("/api/v1/edit/identity-provider-test", {
       headers: {
         "Content-Type": "application/json",
       },
-      method:'POST', 
-      cache:'no-cache', 
-      body: JSON.stringify(data) 
+      method:'POST',
+      cache:'no-cache',
+      body: JSON.stringify(data)
     });
     res = await res.json();
     if (res.type === 'success'){
@@ -772,79 +772,22 @@ jQuery(function($){
   });
 
   $('.iam_rolemap_add_keycloak').click(async function(e){
-    e.preventDefault();
-
-    var parent = $('#iam_keycloak_mapping_list')
-    $(parent).children().last().clone().appendTo(parent);
-    var newChild = $(parent).children().last();
-    $(newChild).find('input').val('');
-    $(newChild).find('.dropdown-toggle').remove();
-    $(newChild).find('.dropdown-menu').remove();
-    $(newChild).find('.bs-title-option').remove();
-    $(newChild).find('select').selectpicker('destroy');
-    $(newChild).find('select').selectpicker();
-
-    $('.iam_keycloak_rolemap_del').off('click');
-    $('.iam_keycloak_rolemap_del').click(async function(e){
-      e.preventDefault();
-      if ($(this).parent().parent().parent().parent().children().length > 1)
-        $(this).parent().parent().parent().remove();
-    });
+    addAttributeMappingRow('#iam_keycloak_mapping_list', '.iam_keycloak_rolemap_del', e);
   });
   $('.iam_rolemap_add_generic').click(async function(e){
-    e.preventDefault();
-
-    var parent = $('#iam_generic_mapping_list')
-    $(parent).children().last().clone().appendTo(parent);
-    var newChild = $(parent).children().last();
-    $(newChild).find('input').val('');
-    $(newChild).find('.dropdown-toggle').remove();
-    $(newChild).find('.dropdown-menu').remove();
-    $(newChild).find('.bs-title-option').remove();
-    $(newChild).find('select').selectpicker('destroy');
-    $(newChild).find('select').selectpicker();
-
-    $('.iam_generic_rolemap_del').off('click');
-    $('.iam_generic_rolemap_del').click(async function(e){
-      e.preventDefault();
-      if ($(this).parent().parent().parent().parent().children().length > 1)
-        $(this).parent().parent().parent().remove();
-    });
+    addAttributeMappingRow('#iam_generic_mapping_list', '.iam_generic_rolemap_del', e);
   });
   $('.iam_rolemap_add_ldap').click(async function(e){
-    e.preventDefault();
-
-    var parent = $('#iam_ldap_mapping_list')
-    $(parent).children().last().clone().appendTo(parent);
-    var newChild = $(parent).children().last();
-    $(newChild).find('input').val('');
-    $(newChild).find('.dropdown-toggle').remove();
-    $(newChild).find('.dropdown-menu').remove();
-    $(newChild).find('.bs-title-option').remove();
-    $(newChild).find('select').selectpicker('destroy');
-    $(newChild).find('select').selectpicker();
-
-    $('.iam_ldap_rolemap_del').off('click');
-    $('.iam_ldap_rolemap_del').click(async function(e){
-      e.preventDefault();
-      if ($(this).parent().parent().parent().parent().children().length > 1)
-        $(this).parent().parent().parent().remove();
-    });
+    addAttributeMappingRow('#iam_ldap_mapping_list', '.iam_ldap_rolemap_del', e);
   });
   $('.iam_keycloak_rolemap_del').click(async function(e){
-    e.preventDefault();
-    if ($(this).parent().parent().parent().parent().children().length > 1)
-      $(this).parent().parent().parent().remove();
+    deleteAttributeMappingRow(this, e);
   });
   $('.iam_generic_rolemap_del').click(async function(e){
-    e.preventDefault();
-    if ($(this).parent().parent().parent().parent().children().length > 1)
-      $(this).parent().parent().parent().remove();
+    deleteAttributeMappingRow(this, e);
   });
   $('.iam_ldap_rolemap_del').click(async function(e){
-    e.preventDefault();
-    if ($(this).parent().parent().parent().parent().children().length > 1)
-      $(this).parent().parent().parent().remove();
+    deleteAttributeMappingRow(this, e);
   });
   // selecting identity provider
   $('#iam_provider').on('change', function(){
@@ -863,4 +806,31 @@ jQuery(function($){
       $('#keycloak_settings').addClass('d-none');
     }
   });
+  function addAttributeMappingRow(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('');
+    $(newChild).find('input').val('').prop('required', true);
+    $(newChild).find('.dropdown-toggle').remove();
+    $(newChild).find('.dropdown-menu').remove();
+    $(newChild).find('.bs-title-option').remove();
+    $(newChild).find('select').selectpicker('destroy');
+    $(newChild).find('select').selectpicker();
+    $(newChild).find('select').selectpicker().prop('required', true);
+
+    $(del_class).off('click');
+    $(del_class).click(async function(e){
+      deleteAttributeMappingRow(this, e);
+    });
+  }
+  function deleteAttributeMappingRow(elem, e) {
+    e.preventDefault();
+    if(!$(elem).parent().parent().parent().find('select').prop('required'))
+      return true;
+    if ($(elem).parent().parent().parent().parent().children().length > 1)
+      $(elem).parent().parent().parent().remove();
+  }
 });

+ 2 - 0
data/web/lang/lang.de-de.json

@@ -215,6 +215,8 @@
         "iam_client_id": "Client ID",
         "iam_client_secret": "Client Secret",
         "iam_client_scopes": "Client Scopes",
+        "iam_default_template": "Standardvorlage",
+        "iam_default_template_description": "Falls für einen Benutzer kein Template hinterlegt ist, wird die Standardvorlage zum Erstellen der Mailbox verwendet, jedoch nicht zum Aktualisieren der Mailbox.",
         "iam_description": "Konfiguriere einen externen Identity Provider für die Authentifizierung<br>Die Mailboxen der Benutzer werden bei ihrer ersten Anmeldung automatisch erstellt, vorausgesetzt, dass ein Attribut Mapping festgelegt wurde.",
         "iam_extra_permission": "Damit die folgenden Einstellungen funktionieren, benötigt der mailcow Client in Keycloak ein <code>Service-Konto</code> und die Berechtigung <code>view-users</code>.",
         "iam_host": "Host",

+ 2 - 0
data/web/lang/lang.en-gb.json

@@ -222,6 +222,8 @@
         "iam_client_id": "Client ID",
         "iam_client_secret": "Client Secret",
         "iam_client_scopes": "Client Scopes",
+        "iam_default_template": "Default Template",
+        "iam_default_template_description": "If no template is assigned to a user, the default template will be used for creating the mailbox, but not for updating the mailbox.",
         "iam_description": "Configure an external Provider for Authentication<br>User's mailboxes will be automatically created upon their first login, provided that an attribute mapping has been set.",
         "iam_extra_permission": "For the following settings to work, the mailcow client in Keycloak needs a <code>Service account</code> and the permission to <code>view-users</code>.",
         "iam_host": "Host",

+ 75 - 15
data/web/templates/admin/tab-config-identity-provider.twig

@@ -93,6 +93,27 @@
             </div>
           </div>
           <div class="row mb-2" id="iam_keycloak_mapping_list">
+            <input type="hidden" name="mappers" value="">
+            <input type="hidden" name="templates" value="">
+            <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+              <div class="row px-2">
+                <div class="col-5 p-0 pe-2">
+                  <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_default_template_description }}"></i>
+                  <span>{{ lang.admin.iam_default_template}}</span>
+                </div>
+                <div class="col-5 p-0 pe-2 align-content-end">
+                  <select data-live-search="true" name="default_template" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" {% if not iam_settings.default_template %}selected{% endif %}>-- {{ lang.mailbox.template }} --</option>
+                  {% for mbox_template in mbox_templates %}
+                    <option {% if mbox_template.template == iam_settings.default_template %}selected{% endif %}>
+                      {{ mbox_template.template }}
+                    </option>
+                  {% endfor %}
+                  </select>
+                </div>
+                <div class="col-2 p-0 d-flex"></div>
+              </div>
+            </div>
             {% for key, role in iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
@@ -100,7 +121,7 @@
                   <input type="text" class="form-control me-2" name="mappers" value="{{ iam_settings.mappers[key] }}" required>
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --" required>
                   {% for mbox_template in mbox_templates %}
                     <option{% if mbox_template.template == iam_settings.templates[key] %} selected{% endif %}>
                       {{ mbox_template.template }}
@@ -114,14 +135,14 @@
               </div>
             </div>
             {% endfor %}
-            {% if not iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
                 <div class="col-5 p-0 pe-2">
-                  <input type="text" class="form-control me-2" name="mappers" value="" required>
+                  <input type="text" class="form-control me-2" name="mappers" value="">
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" selected>-- {{ lang.mailbox.template }} --</option>
                   {% for mbox_template in mbox_templates %}
                     <option>
                       {{ mbox_template.template }}
@@ -134,7 +155,6 @@
                 </div>
               </div>
             </div>
-            {% endif %}
           </div>
           <div class="row mb-2 mt-4">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end"></div>
@@ -283,6 +303,27 @@
             </div>
           </div>
           <div class="row mb-2" id="iam_generic_mapping_list">
+            <input type="hidden" name="mappers" value="">
+            <input type="hidden" name="templates" value="">
+            <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+              <div class="row px-2">
+                <div class="col-5 p-0 pe-2">
+                  <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_default_template_description }}"></i>
+                  <span>{{ lang.admin.iam_default_template}}</span>
+                </div>
+                <div class="col-5 p-0 pe-2 align-content-end">
+                  <select data-live-search="true" name="default_template" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" {% if not iam_settings.default_template %}selected{% endif %}>-- {{ lang.mailbox.template }} --</option>
+                  {% for mbox_template in mbox_templates %}
+                    <option {% if mbox_template.template == iam_settings.default_template %}selected{% endif %}>
+                      {{ mbox_template.template }}
+                    </option>
+                  {% endfor %}
+                  </select>
+                </div>
+                <div class="col-2 p-0 d-flex"></div>
+              </div>
+            </div>
             {% for key, role in iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
@@ -290,7 +331,7 @@
                   <input type="text" class="form-control me-2" name="mappers" value="{{ iam_settings.mappers[key] }}" required>
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --" required>
                   {% for mbox_template in mbox_templates %}
                     <option{% if mbox_template.template == iam_settings.templates[key] %} selected{% endif %}>
                       {{ mbox_template.template }}
@@ -304,14 +345,14 @@
               </div>
             </div>
             {% endfor %}
-            {% if not iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
                 <div class="col-5 p-0 pe-2">
-                  <input type="text" class="form-control me-2" name="mappers" value="" required>
+                  <input type="text" class="form-control me-2" name="mappers" value="">
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" selected>-- {{ lang.mailbox.template }} --</option>
                   {% for mbox_template in mbox_templates %}
                     <option>
                       {{ mbox_template.template }}
@@ -324,7 +365,6 @@
                 </div>
               </div>
             </div>
-            {% endif %}
           </div>
           <div class="row mb-4">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end">
@@ -463,6 +503,27 @@
             </div>
           </div>
           <div class="row mb-2" id="iam_ldap_mapping_list">
+            <input type="hidden" name="mappers" value="">
+            <input type="hidden" name="templates" value="">
+            <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
+              <div class="row px-2">
+                <div class="col-5 p-0 pe-2">
+                  <i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_default_template_description }}"></i>
+                  <span>{{ lang.admin.iam_default_template }}</span>
+                </div>
+                <div class="col-5 p-0 pe-2 align-content-end">
+                  <select data-live-search="true" name="default_template" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" {% if not iam_settings.default_template %}selected{% endif %}>-- {{ lang.mailbox.template }} --</option>
+                  {% for mbox_template in mbox_templates %}
+                    <option {% if mbox_template.template == iam_settings.default_template %}selected{% endif %}>
+                      {{ mbox_template.template }}
+                    </option>
+                  {% endfor %}
+                  </select>
+                </div>
+                <div class="col-2 p-0 d-flex"></div>
+              </div>
+            </div>
             {% for key, role in iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
@@ -470,7 +531,7 @@
                   <input type="text" class="form-control me-2" name="mappers" value="{{ iam_settings.mappers[key] }}" required>
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --" required>
                   {% for mbox_template in mbox_templates %}
                     <option{% if mbox_template.template == iam_settings.templates[key] %} selected{% endif %}>
                       {{ mbox_template.template }}
@@ -484,14 +545,14 @@
               </div>
             </div>
             {% endfor %}
-            {% if not iam_settings.mappers %}
             <div class="offset-md-3 col-12 col-md-9 col-lg-4 mb-2">
               <div class="row px-2">
                 <div class="col-5 p-0 pe-2">
-                  <input type="text" class="form-control me-2" name="mappers" value="" required>
+                  <input type="text" class="form-control me-2" name="mappers" value="">
                 </div>
                 <div class="col-5 p-0 pe-2">
-                  <select data-live-search="true" name="templates" class="form-control" title="{{ lang.mailbox.template }}" required>
+                  <select data-live-search="true" name="templates" class="form-control" title="-- {{ lang.mailbox.template }} --">
+                  <option value="" selected>-- {{ lang.mailbox.template }} --</option>
                   {% for mbox_template in mbox_templates %}
                     <option>
                       {{ mbox_template.template }}
@@ -504,7 +565,6 @@
                 </div>
               </div>
             </div>
-            {% endif %}
           </div>
           <div class="row mb-2">
             <div class="col-md-3 d-flex align-items-center justify-content-md-end">