1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2024-12-21 01:49:22 +02:00

[Web] support multiple ldap hosts separated by comma

This commit is contained in:
FreddleSpl0it 2024-11-29 18:59:07 +01:00
parent b2db8e6b31
commit ec4b9b088c
No known key found for this signature in database
GPG Key ID: 00E14E7634F4BEC5
3 changed files with 116 additions and 43 deletions

View File

@ -2240,6 +2240,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
$required_settings = array('authsource', 'authorize_url', 'token_url', 'client_id', 'client_secret', 'redirect_url', 'userinfo_url', 'client_scopes');
break;
case "ldap":
$_data['host'] = (!empty($_data['host'])) ? str_replace(" ", "", $_data['host']) : "";
$_data['port'] = (!empty($_data['port'])) ? intval($_data['port']) : 389;
$_data['username_field'] = (!empty($_data['username_field'])) ? strtolower($_data['username_field']) : "mail";
$_data['attribute_field'] = (!empty($_data['attribute_field'])) ? strtolower($_data['attribute_field']) : "";
@ -2356,7 +2357,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
$options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER;
}
$provider = new \LdapRecord\Connection([
'hosts' => [$_data['host']],
'hosts' => explode(",", $_data['host']),
'port' => $_data['port'],
'base_dn' => $_data['basedn'],
'username' => $_data['binddn'],
@ -2450,7 +2451,7 @@ function identity_provider($_action = null, $_data = null, $_extra = null) {
$options[LDAP_OPT_X_TLS_REQUIRE_CERT] = LDAP_OPT_X_TLS_NEVER;
}
$provider = new \LdapRecord\Connection([
'hosts' => [$settings['host']],
'hosts' => explode(",", $settings['host']),
'port' => $settings['port'],
'base_dn' => $settings['basedn'],
'username' => $settings['binddn'],

View File

@ -225,6 +225,7 @@
"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",
"iam_host_info": "Enter one or more LDAP hosts, separated by commas.",
"iam_import_users": "Import Users",
"iam_mapping": "Attribute Mapping",
"iam_bindpass": "Bind Password",

View File

@ -9,7 +9,9 @@
<div id="collapse-tab-config-identity-provider" class="card-body collapse" data-bs-parent="#admin-content">
<p class="offset-sm-3 mb-4">{{ lang.admin.iam_description|raw }}</p>
<div class="row mb-4">
<label class="control-label col-md-3 text-sm-end" for="iam_realm">{{ lang.admin.iam }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_realm">{{ lang.admin.iam }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<select
data-style="btn btn-secondary"
@ -26,25 +28,33 @@
<form class="form-horizontal" autocapitalize="none" data-id="iam_keycloak" autocorrect="off" role="form" method="post">
<input type="hidden" name="authsource" value="keycloak">
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_url">{{ lang.admin.iam_server_url }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_url">{{ lang.admin.iam_server_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_keycloak_url" name="server_url" value="{{ iam_settings.server_url }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_realm">{{ lang.admin.iam_realm }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_realm">{{ lang.admin.iam_realm }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_keycloak_realm" name="realm" value="{{ iam_settings.realm }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_clientid">{{ lang.admin.iam_client_id }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_clientid">{{ lang.admin.iam_client_id }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_keycloak_clientid" name="client_id" value="{{ iam_settings.client_id }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_clientsecret">{{ lang.admin.iam_client_secret }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_clientsecret">{{ lang.admin.iam_client_secret }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="reveal-password-input input-group">
<input type="password" class="password-field form-control" id="iam_keycloak_clientsecret" name="client_secret" value="{{ iam_settings.client_secret }}" required>
@ -53,19 +63,25 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_redirecturl">{{ lang.admin.iam_redirect_url }}:</label>
<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>
</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>
</div>
<div class="row mb-4">
<label class="control-label col-md-3 text-sm-end" for="iam_keycloak_version">{{ lang.admin.iam_version }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_keycloak_version">{{ lang.admin.iam_version }}:</label>
</div>
<div class="col-sm-4">
<input type="text" class="form-control" id="iam_keycloak_version" name="version" value="{{ iam_settings.version }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_mapping }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_mapping }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="row px-2 align-items-center">
<span class="col-5 p-0 pe-2">Attribute</span>
@ -121,13 +137,15 @@
{% endif %}
</div>
<div class="row mb-2 mt-4">
<label class="control-label col-md-3 text-sm-end"></label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end"></div>
<div class="col-12 col-md-9">
<span>{{ lang.admin.iam_extra_permission|raw }}</span>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_rest_flow }}</label>
<div class="col-md-3 d-flex align-items-start justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_rest_flow }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="mailpassword_flow" value="1" {% if iam_settings.mailpassword_flow == 1 %}checked{% endif %}>
@ -140,7 +158,9 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_periodic_full_sync }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_periodic_full_sync }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="periodic_sync" value="1" {% if iam_settings.periodic_sync == 1 %}checked{% endif %}>
@ -148,7 +168,9 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_import_users }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_import_users }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="import_users" value="1" {% if iam_settings.import_users == 1 %}checked{% endif %}>
@ -156,14 +178,16 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_sync_interval }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_sync_interval }}</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input class="form-control" type="number" min="1" name="sync_interval" style="width: 80px;" {% if iam_settings.sync_interval %}value="{{ iam_settings.sync_interval }}"{% else %}value="15"{% endif %}>
</div>
</div>
<div class="row mt-4 mb-2">
<div class="offset-md-3 col-12 col-md-9 d-flex flex-wrap">
<div class="btn-group mb-2">
<div class="btn-group mb-2">
<button class="btn btn-sm d-block d-sm-inline btn-secondary iam_test_connection iam_test_connection" data-id="iam_keycloak"><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="identity-provider" data-action="edit_selected" data-id="iam_keycloak" data-api-url='edit/identity-provider' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div>
@ -176,31 +200,41 @@
<form class="form-horizontal" autocapitalize="none" data-id="iam_generic" autocorrect="off" role="form" method="post">
<input type="hidden" name="authsource" value="generic-oidc">
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_authorize_url">{{ lang.admin.iam_authorize_url }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_authorize_url">{{ lang.admin.iam_authorize_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_authorize_url" name="authorize_url" value="{{ iam_settings.authorize_url }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_token_url">{{ lang.admin.iam_token_url }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_token_url">{{ lang.admin.iam_token_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_token_url" name="token_url" value="{{ iam_settings.token_url }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_userinfo_url">{{ lang.admin.iam_userinfo_url }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_userinfo_url">{{ lang.admin.iam_userinfo_url }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_userinfo_url" name="userinfo_url" value="{{ iam_settings.userinfo_url }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_client_id">{{ lang.admin.iam_client_id }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_client_id">{{ lang.admin.iam_client_id }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_client_id" name="client_id" value="{{ iam_settings.client_id }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_client_secret">{{ lang.admin.iam_client_secret }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_client_secret">{{ lang.admin.iam_client_secret }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="reveal-password-input input-group">
<input type="password" class="password-field form-control" id="iam_client_secret" name="client_secret" value="{{ iam_settings.client_secret }}" required>
@ -209,19 +243,25 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_redirect_url">{{ lang.admin.iam_redirect_url }}:</label>
<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>
</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>
</div>
<div class="row mb-4">
<label class="control-label col-md-3 text-sm-end" for="iam_client_scopes">{{ lang.admin.iam_client_scopes }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_client_scopes">{{ lang.admin.iam_client_scopes }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" placeholder="openid profile email" class="form-control" id="iam_client_scopes" name="client_scopes" value="{{ iam_settings.client_scopes }}">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_mapping }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_mapping }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="row px-2 align-items-center">
<span class="col-5 p-0 pe-2">Attribute</span>
@ -278,7 +318,7 @@
</div>
<div class="row mt-4 mb-2">
<div class="offset-md-3 col-12 col-md-9 d-flex flex-wrap">
<div class="btn-group mb-2">
<div class="btn-group mb-2">
<button class="btn btn-sm d-block d-sm-inline btn-secondary iam_test_connection" data-id="iam_generic"><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="identity-provider" data-action="edit_selected" data-id="iam_generic" data-api-url='edit/identity-provider' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div>
@ -291,19 +331,26 @@
<form class="form-horizontal" autocapitalize="none" data-id="iam_ldap" autocorrect="off" role="form" method="post">
<input type="hidden" name="authsource" value="ldap">
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_host">{{ lang.admin.iam_host }}:</label>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_ldap_host" name="host" value="{{ iam_settings.host }}" required>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<i style="font-size: 16px; cursor: pointer;" class="bi bi-patch-question-fill m-2 ms-0" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="bottom" title="{{ lang.admin.iam_host_info }}"></i>
<label class="control-label" for="iam_ldap_host">{{ lang.admin.iam_host }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4 d-flex">
<input type="text" class="form-control" id="iam_ldap_host" name="host" value="{{ iam_settings.host }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_port">{{ lang.admin.iam_port }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_port">{{ lang.admin.iam_port }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="number" class="form-control" id="iam_ldap_port" name="port" value="{{ iam_settings.port }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_use_ssl }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_use_ssl }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="use_ssl" value="1" {% if iam_settings.use_ssl == 1 %}checked{% endif %}>
@ -311,7 +358,9 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_use_tls }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_use_tls }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="use_tls" value="1" {% if iam_settings.use_tls == 1 %}checked{% endif %}>
@ -319,7 +368,9 @@
</div>
</div>
<div class="row mb-4">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.ignore_ssl_error }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.ignore_ssl_error }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="ignore_ssl_error" value="1" {% if iam_settings.ignore_ssl_error == 1 %}checked{% endif %}>
@ -327,37 +378,49 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_basedn">{{ lang.admin.iam_basedn }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_basedn">{{ lang.admin.iam_basedn }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_ldap_basedn" name="basedn" value="{{ iam_settings.basedn }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_username_field">{{ lang.admin.iam_username_field }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_username_field">{{ lang.admin.iam_username_field }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" placeholder="mail" id="iam_ldap_username_field" name="username_field" value="{{ iam_settings.username_field }}">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_filter">{{ lang.admin.filter }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_filter">{{ lang.admin.filter }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" placeholder="" id="iam_ldap_filter" name="filter" value="{{ iam_settings.filter }}">
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_attribute_field">{{ lang.admin.iam_attribute_field }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_attribute_field">{{ lang.admin.iam_attribute_field }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_ldap_attribute_field" name="attribute_field" value="{{ iam_settings.attribute_field }}" required>
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_binddn">{{ lang.admin.iam_binddn }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_binddn">{{ lang.admin.iam_binddn }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input type="text" class="form-control" id="iam_ldap_binddn" name="binddn" value="{{ iam_settings.binddn }}" required>
</div>
</div>
<div class="row mb-4">
<label class="control-label col-md-3 text-sm-end" for="iam_ldap_bindpass">{{ lang.admin.iam_bindpass }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label" for="iam_ldap_bindpass">{{ lang.admin.iam_bindpass }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="reveal-password-input input-group">
<input type="password" class="password-field form-control" id="iam_ldap_bindpass" name="bindpass" value="{{ iam_settings.bindpass }}" required>
@ -366,7 +429,9 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_mapping }}:</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_mapping }}:</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<div class="row px-2 align-items-center">
<span class="col-5 p-0 pe-2">Attribute</span>
@ -422,7 +487,9 @@
{% endif %}
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_periodic_full_sync }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_periodic_full_sync }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="periodic_sync" value="1" {% if iam_settings.periodic_sync == 1 %}checked{% endif %}>
@ -430,7 +497,9 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_import_users }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_import_users }}</label>
</div>
<div class="col-12 col-md-9">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="import_users" value="1" {% if iam_settings.import_users == 1 %}checked{% endif %}>
@ -438,14 +507,16 @@
</div>
</div>
<div class="row mb-2">
<label class="control-label col-md-3 text-sm-end">{{ lang.admin.iam_sync_interval }}</label>
<div class="col-md-3 d-flex align-items-center justify-content-md-end">
<label class="control-label">{{ lang.admin.iam_sync_interval }}</label>
</div>
<div class="col-12 col-md-9 col-lg-4">
<input class="form-control" type="number" min="1" name="sync_interval" style="width: 80px;" {% if iam_settings.sync_interval %}value="{{ iam_settings.sync_interval }}"{% else %}value="15"{% endif %}>
</div>
</div>
<div class="row mt-4 mb-2">
<div class="offset-md-3 col-12 col-md-9 d-flex flex-wrap">
<div class="btn-group mb-2">
<div class="btn-group mb-2">
<button class="btn btn-sm d-block d-sm-inline btn-secondary iam_test_connection iam_test_connection" data-id="iam_ldap"><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="identity-provider" data-action="edit_selected" data-id="iam_ldap" data-api-url='edit/identity-provider' data-api-attr='{}'><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
</div>