Revert "refactor(settings): backport auth views (#5672)" (#5704)

This reverts commit 45af1f3d8b.
bug/EE-1734/Registry-page-displays-No-registry-available-despite-being-dockerhub-registry-available
Chaim Lev-Ari 2021-09-22 10:17:22 +03:00 committed by GitHub
parent 45af1f3d8b
commit 0b64250647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 536 additions and 2319 deletions

View File

@ -1,11 +0,0 @@
export const authenticationMethodTypesMap = {
INTERNAL: 1,
LDAP: 2,
OAUTH: 3,
};
export const authenticationMethodTypesLabels = {
[authenticationMethodTypesMap.INTERNAL]: 'Internal',
[authenticationMethodTypesMap.LDAP]: 'LDAP',
[authenticationMethodTypesMap.OAUTH]: 'OAuth',
};

View File

@ -1,11 +0,0 @@
export const authenticationActivityTypesMap = {
AuthSuccess: 1,
AuthFailure: 2,
Logout: 3,
};
export const authenticationActivityTypesLabels = {
[authenticationActivityTypesMap.AuthSuccess]: 'Authentication success',
[authenticationActivityTypesMap.AuthFailure]: 'Authentication failure',
[authenticationActivityTypesMap.Logout]: 'Logout',
};

View File

@ -1,14 +0,0 @@
<div class="col-sm-12 form-section-title">
Team membership
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small" ng-transclude="description"> </span>
</div>
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Automatic team membership
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.ngModel" /><i></i> </label>
</div>
</div>

View File

@ -1,9 +0,0 @@
export const autoTeamMembershipToggle = {
templateUrl: './auto-team-membership-toggle.html',
transclude: {
description: 'fieldDescription',
},
bindings: {
ngModel: '=',
},
};

View File

@ -1,14 +0,0 @@
<div class="col-sm-12 form-section-title">
Automatic user provisioning
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small" ng-transclude="description"> </span>
</div>
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Automatic user provisioning
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="$ctrl.ngModel" /><i></i> </label>
</div>
</div>

View File

@ -1,9 +0,0 @@
export const autoUserProvisionToggle = {
templateUrl: './auto-user-provision-toggle.html',
transclude: {
description: 'fieldDescription',
},
bindings: {
ngModel: '=',
},
};

View File

@ -1,12 +0,0 @@
import angular from 'angular';
import ldapModule from './ldap';
import { autoUserProvisionToggle } from './auto-user-provision-toggle';
import { autoTeamMembershipToggle } from './auto-team-membership-toggle';
export default angular
.module('portainer.settings.authentication', [ldapModule])
.component('autoUserProvisionToggle', autoUserProvisionToggle)
.component('autoTeamMembershipToggle', autoTeamMembershipToggle).name;

View File

@ -1,61 +0,0 @@
import _ from 'lodash-es';
export default class AdSettingsController {
/* @ngInject */
constructor(LDAPService) {
this.LDAPService = LDAPService;
this.domainSuffix = '';
this.onTlscaCertChange = this.onTlscaCertChange.bind(this);
this.searchUsers = this.searchUsers.bind(this);
this.searchGroups = this.searchGroups.bind(this);
this.parseDomainName = this.parseDomainName.bind(this);
this.onAccountChange = this.onAccountChange.bind(this);
}
parseDomainName(account) {
this.domainName = '';
if (!account || !account.includes('@')) {
return;
}
const [, domainName] = account.split('@');
if (!domainName) {
return;
}
const parts = _.compact(domainName.split('.'));
this.domainSuffix = parts.map((part) => `dc=${part}`).join(',');
}
onAccountChange(account) {
this.parseDomainName(account);
}
searchUsers() {
return this.LDAPService.users(this.settings);
}
searchGroups() {
return this.LDAPService.groups(this.settings);
}
onTlscaCertChange(file) {
this.tlscaCert = file;
}
addLDAPUrl() {
this.settings.URLs.push('');
}
removeLDAPUrl(index) {
this.settings.URLs.splice(index, 1);
}
$onInit() {
this.tlscaCert = this.settings.TLSCACert;
this.parseDomainName(this.settings.ReaderDN);
}
}

View File

@ -1,115 +0,0 @@
<auto-user-provision-toggle ng-model="$ctrl.settings.AutoCreateUsers">
<field-description>
With automatic user provisioning enabled, Portainer will create user(s) automatically with standard user role and assign them to team(s) which matches to LDAP group name(s). If
disabled, users must be created in Portainer beforehand.
</field-description>
</auto-user-provision-toggle>
<div>
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using Microsoft AD authentication, Portainer will delegate user authentication to the Domain Controller(s) configured below; if there is no connectivity, Portainer will
fallback to internal authentication.
</div>
</div>
<div class="col-sm-12 form-section-title">
AD configuration
</div>
<div class="form-group">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
You can configure multiple AD Controllers for authentication fallback. Make sure all servers are using the same configuration (i.e. if TLS is enabled, they should all use the
same certificates).
</p>
</div>
</div>
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
AD Controller
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
</button>
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<div class="form-group">
<label for="ldap_username" class="col-sm-3 control-label text-left">
Service Account
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
id="ldap_username"
ng-model="$ctrl.settings.ReaderDN"
placeholder="reader@domain.tld"
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
/>
</div>
</div>
<div class="form-group">
<label for="ldap_password" class="col-sm-3 control-label text-left">
Service Account Password
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
</div>
</div>
<ldap-connectivity-check
ng-if="!$ctrl.settings.TLSConfig.TLS && !$ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-settings-security
title="AD Connectivity Security"
settings="$ctrl.settings"
tlsca-cert="$ctrl.tlscaCert"
upload-in-progress="$ctrl.state.uploadInProgress"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
></ldap-settings-security>
<ldap-connectivity-check
ng-if="$ctrl.settings.TLSConfig.TLS || $ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-user-search
style="margin-top: 5px;"
show-username-format="true"
settings="$ctrl.settings.SearchSettings"
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=user)"
on-search-click="($ctrl.searchUsers)"
></ldap-user-search>
<ldap-group-search
style="margin-top: 5px;"
settings="$ctrl.settings.GroupSearchSettings"
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=group)"
on-search-click="($ctrl.searchGroups)"
></ldap-group-search>
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>

View File

@ -1,12 +0,0 @@
import controller from './ad-settings.controller';
export const adSettings = {
templateUrl: './ad-settings.html',
controller,
bindings: {
settings: '=',
tlscaCert: '=',
state: '=',
connectivityCheck: '<',
},
};

View File

@ -1,44 +0,0 @@
import angular from 'angular';
import { adSettings } from './ad-settings';
import { ldapSettings } from './ldap-settings';
import { ldapSettingsCustom } from './ldap-settings-custom';
import { ldapSettingsOpenLdap } from './ldap-settings-openldap';
import { ldapConnectivityCheck } from './ldap-connectivity-check';
import { ldapGroupsDatatable } from './ldap-groups-datatable';
import { ldapGroupSearch } from './ldap-group-search';
import { ldapGroupSearchItem } from './ldap-group-search-item';
import { ldapUserSearch } from './ldap-user-search';
import { ldapUserSearchItem } from './ldap-user-search-item';
import { ldapSettingsDnBuilder } from './ldap-settings-dn-builder';
import { ldapSettingsGroupDnBuilder } from './ldap-settings-group-dn-builder';
import { ldapCustomGroupSearch } from './ldap-custom-group-search';
import { ldapSettingsSecurity } from './ldap-settings-security';
import { ldapSettingsTestLogin } from './ldap-settings-test-login';
import { ldapCustomUserSearch } from './ldap-custom-user-search';
import { ldapUsersDatatable } from './ldap-users-datatable';
import { LDAPService } from './ldap.service';
import { LDAP } from './ldap.rest';
export default angular
.module('portainer.settings.authentication.ldap', [])
.service('LDAPService', LDAPService)
.service('LDAP', LDAP)
.component('ldapConnectivityCheck', ldapConnectivityCheck)
.component('ldapGroupsDatatable', ldapGroupsDatatable)
.component('ldapSettings', ldapSettings)
.component('adSettings', adSettings)
.component('ldapGroupSearch', ldapGroupSearch)
.component('ldapGroupSearchItem', ldapGroupSearchItem)
.component('ldapUserSearch', ldapUserSearch)
.component('ldapUserSearchItem', ldapUserSearchItem)
.component('ldapSettingsCustom', ldapSettingsCustom)
.component('ldapSettingsDnBuilder', ldapSettingsDnBuilder)
.component('ldapSettingsGroupDnBuilder', ldapSettingsGroupDnBuilder)
.component('ldapCustomGroupSearch', ldapCustomGroupSearch)
.component('ldapSettingsOpenLdap', ldapSettingsOpenLdap)
.component('ldapSettingsSecurity', ldapSettingsSecurity)
.component('ldapSettingsTestLogin', ldapSettingsTestLogin)
.component('ldapCustomUserSearch', ldapCustomUserSearch)
.component('ldapUsersDatatable', ldapUsersDatatable).name;

View File

@ -1,8 +0,0 @@
export const ldapConnectivityCheck = {
templateUrl: './ldap-connectivity-check.html',
bindings: {
settings: '<',
state: '<',
connectivityCheck: '<',
},
};

View File

@ -1,19 +0,0 @@
<div class="form-group">
<label for="ldap_password" class="col-sm-3 control-label text-left">
Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="$ctrl.state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="$ctrl.state.failedConnectivityCheck"></i>
</label>
<div class="col-sm-9">
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="($ctrl.state.connectivityCheckInProgress) || (!$ctrl.settings.URLs.length) || ((!$ctrl.settings.ReaderDN || !$ctrl.settings.Password) && !$ctrl.settings.AnonymousMode)"
ng-click="$ctrl.connectivityCheck()"
button-spinner="$ctrl.state.connectivityCheckInProgress"
>
<span ng-hide="$ctrl.state.connectivityCheckInProgress">Test connectivity</span>
<span ng-show="$ctrl.state.connectivityCheckInProgress">Testing connectivity...</span>
</button>
</div>
</div>

View File

@ -1,10 +0,0 @@
import controller from './ldap-custom-group-search.controller';
export const ldapCustomGroupSearch = {
templateUrl: './ldap-custom-group-search.html',
controller,
bindings: {
settings: '=',
onSearchClick: '<',
},
};

View File

@ -1,34 +0,0 @@
export default class LdapCustomGroupSearchController {
/* @ngInject */
constructor($async, Notifications) {
Object.assign(this, { $async, Notifications });
this.groups = null;
this.showTable = false;
this.onRemoveClick = this.onRemoveClick.bind(this);
this.onAddClick = this.onAddClick.bind(this);
this.search = this.search.bind(this);
}
onAddClick() {
this.settings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
}
onRemoveClick(index) {
this.settings.splice(index, 1);
}
search() {
return this.$async(async () => {
try {
this.groups = null;
this.showTable = true;
this.groups = await this.onSearchClick();
} catch (error) {
this.showTable = false;
this.Notifications.error('Failure', error, 'Failed to search users');
}
});
}
}

View File

@ -1,64 +0,0 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
Teams auto-population configurations
</div>
<rd-widget ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)" style="display: block; margin-bottom: 10px;">
<rd-widget-body>
<div class="form-group" ng-if="$index > 0" style="margin-bottom: 10px;">
<span class="col-sm-12 text-muted small">
Extra search configuration
</span>
</div>
<div class="form-group">
<label for="ldap_group_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Base DN
<portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for groups."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_group_basedn_{{ $index }}" ng-model="config.GroupBaseDN" placeholder="dc=ldap,dc=domain,dc=tld" />
</div>
<label for="ldap_group_att_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Membership Attribute
<portainer-tooltip position="bottom" message="LDAP attribute which denotes the group membership."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_group_att_{{ $index }}" ng-model="config.GroupAttribute" placeholder="member" />
</div>
</div>
<div class="form-group">
<label for="ldap_group_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Filter
<portainer-tooltip position="bottom" message="The LDAP search filter used to select group elements, optional."></portainer-tooltip>
</label>
<div ng-class="{ 'col-sm-7 col-md-9': $index, 'col-sm-8 col-md-10': !$index }">
<input type="text" class="form-control" id="ldap_group_filter_{{ $index }}" ng-model="config.GroupFilter" placeholder="(objectClass=account)" />
</div>
<div class="col-sm-1" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display User/Group matching
</button>
</div>
</div>
<div ng-if="$ctrl.showTable">
<div class="form-group">
<ldap-groups-datatable dataset="$ctrl.groups" title-text="Groups" title-icon="fa-users" table-key="ldapGroups"></ldap-groups-datatable>
</div>
</div>

View File

@ -1,10 +0,0 @@
import controller from './ldap-custom-user-search.controller';
export const ldapCustomUserSearch = {
templateUrl: './ldap-custom-user-search.html',
controller,
bindings: {
settings: '=',
onSearchClick: '<',
},
};

View File

@ -1,33 +0,0 @@
export default class LdapCustomUserSearchController {
/* @ngInject */
constructor($async, Notifications) {
Object.assign(this, { $async, Notifications });
this.users = null;
this.onRemoveClick = this.onRemoveClick.bind(this);
this.onAddClick = this.onAddClick.bind(this);
this.search = this.search.bind(this);
}
onAddClick() {
this.settings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
}
onRemoveClick(index) {
this.settings.splice(index, 1);
}
search() {
return this.$async(async () => {
try {
this.users = null;
this.showTable = true;
this.users = await this.onSearchClick();
} catch (error) {
this.showTable = false;
this.Notifications.error('Failure', error, 'Failed to search users');
}
});
}
}

View File

@ -1,64 +0,0 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
User search configurations
</div>
<rd-widget ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)" style="display: block; margin-bottom: 10px;">
<rd-widget-body>
<div class="form-group" ng-if="$index > 0" style="margin-bottom: 10px;">
<span class="col-sm-12 text-muted small">
Extra search configuration
</span>
</div>
<div class="form-group">
<label for="ldap_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Base DN
<portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for users."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_basedn_{{ $index }}" ng-model="config.BaseDN" placeholder="dc=ldap,dc=domain,dc=tld" />
</div>
<label for="ldap_username_att_{{ $index }}" class="col-sm-4 col-md-3 col-lg-2 control-label text-left">
Username attribute
<portainer-tooltip position="bottom" message="LDAP attribute which denotes the username."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-3 col-lg-4">
<input type="text" class="form-control" id="ldap_username_att_{{ $index }}" ng-model="config.UserNameAttribute" placeholder="uid" />
</div>
</div>
<div class="form-group">
<label for="ldap_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Filter
<portainer-tooltip position="bottom" message="The LDAP search filter used to select user elements, optional."></portainer-tooltip>
</label>
<div ng-class="{ 'col-sm-7 col-md-9': $index, 'col-sm-8 col-md-10': !$index }">
<input type="text" class="form-control" id="ldap_filter_{{ $index }}" ng-model="config.Filter" placeholder="(objectClass=account)" />
</div>
<div class="col-sm-1" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</rd-widget-body>
</rd-widget>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add user search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display Users
</button>
</div>
</div>
<div ng-if="$ctrl.showTable">
<div class="form-group">
<ldap-users-datatable dataset="$ctrl.users" title-text="Users" title-icon="fa-users" table-key="ldapUsers" order-by="" reverse-order=""></ldap-users-datatable>
</div>
</div>

View File

@ -1,14 +0,0 @@
import controller from './ldap-group-search-item.controller';
export const ldapGroupSearchItem = {
templateUrl: './ldap-group-search-item.html',
controller,
bindings: {
config: '=',
index: '<',
domainSuffix: '@',
baseFilter: '@',
onRemoveClick: '<',
},
};

View File

@ -1,51 +0,0 @@
export default class LdapSettingsAdGroupSearchItemController {
/* @ngInject */
constructor(Notifications) {
Object.assign(this, { Notifications });
this.groups = [];
this.onChangeBaseDN = this.onChangeBaseDN.bind(this);
}
onChangeBaseDN(baseDN) {
this.config.GroupBaseDN = baseDN;
}
addGroup() {
this.groups.push({ type: 'ou', value: '' });
}
removeGroup($index) {
this.groups.splice($index, 1);
this.onGroupsChange();
}
onGroupsChange() {
const groupsFilter = this.groups.map(({ type, value }) => `(${type}=${value})`).join('');
this.onFilterChange(groupsFilter ? `(&${this.baseFilter}(|${groupsFilter}))` : `${this.baseFilter}`);
}
onFilterChange(filter) {
this.config.GroupFilter = filter;
}
parseGroupFilter() {
const match = this.config.GroupFilter.match(/^\(&\(objectClass=(\w+)\)\(\|((\(\w+=.+\))+)\)\)$/);
if (!match) {
return;
}
const [, , groupFilter = ''] = match;
this.groups = groupFilter
.slice(1, -1)
.split(')(')
.map((str) => str.split('='))
.map(([type, value]) => ({ type, value }));
}
$onInit() {
this.parseGroupFilter();
}
}

View File

@ -1,68 +0,0 @@
<rd-widget>
<rd-widget-body>
<div ng-if="$ctrl.index > 0" style="margin-bottom: 10px;">
<span class="text-muted small">
Extra search configuration
</span>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
<ldap-settings-dn-builder
label="Group Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
ng-model="$ctrl.config.GroupBaseDN"
on-change="($ctrl.onChangeBaseDN)"
></ldap-settings-dn-builder>
<div class="form-group">
<label class="col-sm-4 col-md-2 control-label text-left">
Group Base DN
</label>
<div class="col-sm-8 col-md-10">
{{ $ctrl.config.GroupBaseDN }}
</div>
</div>
<div class="form-group">
<div class="col-sm-12" style="margin-bottom: 5px;">
<label class="control-label text-left">Groups</label>
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="$ctrl.addGroup()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another group
</span>
</div>
<div class="col-sm-12" ng-if="$ctrl.groups.length">
<rd-widget>
<rd-widget-body>
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.groups">
<div class="col-sm-4">
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onGroupsChange()">
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onGroupsChange()" />
</div>
<div class="col-sm-3 text-right">
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeGroup($index)">
<i class="fa fa-trash-alt" aria-hidden="true"></i>
</button>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>
<div class="form-group no-margin-last-child">
<label class="col-sm-4 col-md-2 control-label text-left">
Group Filter
</label>
<div class="col-sm-8 col-md-10">
{{ $ctrl.config.GroupFilter }}
</div>
</div>
</rd-widget-body>
</rd-widget>

View File

@ -1,13 +0,0 @@
import controller from './ldap-group-search.controller';
export const ldapGroupSearch = {
templateUrl: './ldap-group-search.html',
controller,
bindings: {
settings: '=',
domainSuffix: '@',
baseFilter: '@',
onSearchClick: '<',
},
};

View File

@ -1,36 +0,0 @@
import _ from 'lodash-es';
export default class LdapGroupSearchController {
/* @ngInject */
constructor($async, Notifications) {
Object.assign(this, { $async, Notifications });
this.groups = null;
this.onRemoveClick = this.onRemoveClick.bind(this);
this.onAddClick = this.onAddClick.bind(this);
this.search = this.search.bind(this);
}
onAddClick() {
const lastSetting = _.last(this.settings);
this.settings.push({ GroupBaseDN: this.domainSuffix, GroupAttribute: lastSetting.GroupAttribute, GroupFilter: this.baseFilter });
}
onRemoveClick(index) {
this.settings.splice(index, 1);
}
search() {
return this.$async(async () => {
try {
this.groups = null;
this.showTable = true;
this.groups = await this.onSearchClick();
} catch (error) {
this.showTable = false;
this.Notifications.error('Failure', error, 'Failed to search users');
}
});
}
}

View File

@ -1,32 +0,0 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
Teams auto-population configurations
</div>
<div style="margin-top: 10px;" ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)">
<ldap-group-search-item
config="config"
domain-suffix="{{ $ctrl.domainSuffix }}"
index="$index"
base-filter="{{ $ctrl.baseFilter }}"
on-remove-click="($ctrl.onRemoveClick)"
></ldap-group-search-item>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display User/Group matching
</button>
</div>
</div>
<div ng-if="$ctrl.showTable">
<div class="form-group">
<ldap-groups-datatable dataset="$ctrl.groups" title-text="Groups" title-icon="fa-users" table-key="ldapGroups"></ldap-groups-datatable>
</div>
</div>

View File

@ -1,12 +0,0 @@
export const ldapGroupsDatatable = {
templateUrl: './ldap-groups-datatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
},
};

View File

@ -1,77 +0,0 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th>
<a ng-click="$ctrl.changeOrderBy('Name')">
User Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a>
</th>
<th>
Groups
</th>
</tr>
</thead>
<tbody>
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
ng-class="{ active: item.Checked }"
>
<td>
{{ item.Name }}
</td>
<td>
<p ng-repeat="group in item.Groups" style="margin: 0;">{{ group }}</p>
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="3" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="5" class="text-center text-muted">No groups found.</td>
</tr>
</tbody>
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>

View File

@ -1,15 +0,0 @@
import controller from './ldap-settings-custom.controller';
export const ldapSettingsCustom = {
templateUrl: './ldap-settings-custom.html',
controller,
bindings: {
settings: '=',
tlscaCert: '=',
state: '=',
onTlscaCertChange: '<',
connectivityCheck: '<',
onSearchUsersClick: '<',
onSearchGroupsClick: '<',
},
};

View File

@ -1,9 +0,0 @@
export default class LdapSettingsCustomController {
addLDAPUrl() {
this.settings.URLs.push('');
}
removeLDAPUrl(index) {
this.settings.URLs.splice(index, 1);
}
}

View File

@ -1,99 +0,0 @@
<div>
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using LDAP authentication, Portainer will delegate user authentication to a LDAP server and fallback to internal authentication if LDAP authentication fails.
</div>
</div>
<div class="col-sm-12 form-section-title">
LDAP configuration
</div>
<div class="form-group">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
You can configure multiple LDAP Servers for authentication fallback. Make sure all servers are using the same configuration (i.e. if TLS is enabled, they should all use the
same certificates).
</p>
</div>
</div>
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
LDAP Server
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
</button>
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<!-- Anonymous mode-->
<div class="form-group">
<div class="col-sm-12">
<label for="anonymous_mode" class="control-label text-left">
Anonymous mode
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" /><i></i> </label>
</div>
</div>
<!-- !Anonymous mode-->
<div ng-if="!$ctrl.settings.AnonymousMode">
<div class="form-group">
<label for="ldap_username" class="col-sm-3 col-lg-2 control-label text-left">
Reader DN
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="ldap_username" ng-model="$ctrl.settings.ReaderDN" placeholder="{{ $ctrl.clickToSetValues.readerDNPlaceholder }}" />
</div>
</div>
<div class="form-group">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Password
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
</div>
</div>
</div>
<ldap-connectivity-check
ng-if="!$ctrl.settings.TLSConfig.TLS && !$ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-settings-security
settings="$ctrl.settings"
tlsca-cert="$ctrl.tlscaCert"
upload-in-progress="$ctrl.state.uploadInProgress"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
></ldap-settings-security>
<ldap-connectivity-check
ng-if="$ctrl.settings.TLSConfig.TLS || $ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-custom-user-search style="margin-top: 5px;" settings="$ctrl.settings.SearchSettings" on-search-click="($ctrl.onSearchUsersClick)"></ldap-custom-user-search>
<ldap-custom-group-search style="margin-top: 5px;" settings="$ctrl.settings.GroupSearchSettings" on-search-click="($ctrl.onSearchGroupsClick)"></ldap-custom-group-search>
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>

View File

@ -1,15 +0,0 @@
import controller from './ldap-settings-dn-builder.controller';
export const ldapSettingsDnBuilder = {
templateUrl: './ldap-settings-dn-builder.html',
controller,
bindings: {
// ngModel: string (dc=,cn=,)
ngModel: '<',
// onChange(string) => void
onChange: '<',
// suffix: string (dc=,dc=,)
suffix: '@',
label: '@',
},
};

View File

@ -1,84 +0,0 @@
export default class LdapSettingsBaseDnBuilderController {
/* @ngInject */
constructor() {
this.entries = [];
}
addEntry() {
this.entries.push({ type: 'ou', value: '' });
}
removeEntry($index) {
this.entries.splice($index, 1);
this.onEntriesChange();
}
moveUp($index) {
if ($index <= 0) {
return;
}
arrayMove(this.entries, $index, $index - 1);
this.onEntriesChange();
}
moveDown($index) {
if ($index >= this.entries.length - 1) {
return;
}
arrayMove(this.entries, $index, $index + 1);
this.onEntriesChange();
}
onEntriesChange() {
const dn = this.entries
.filter(({ value }) => value)
.map(({ type, value }) => `${type}=${value}`)
.concat(this.suffix)
.filter((value) => value)
.join(',');
this.onChange(dn);
}
getOUValues(dn, domainSuffix = '') {
const regex = /(\w+)=(\w*),?/;
let ouValues = [];
let left = dn;
let match = left.match(regex);
while (match && left !== domainSuffix) {
const [, type, value] = match;
ouValues.push({ type, value });
left = left.replace(regex, '');
match = left.match(/(\w+)=(\w+),?/);
}
return ouValues;
}
parseBaseDN() {
this.entries = this.getOUValues(this.ngModel, this.suffix);
}
$onChanges({ suffix, ngModel }) {
if ((!suffix && !ngModel) || (suffix && suffix.isFirstChange())) {
return;
}
this.onEntriesChange();
}
$onInit() {
this.parseBaseDN();
}
}
function arrayMove(array, fromIndex, toIndex) {
if (!checkValidIndex(array, fromIndex) || !checkValidIndex(array, toIndex)) {
throw new Error('index is out of bounds');
}
const [item] = array.splice(fromIndex, 1);
array.splice(toIndex, 0, item);
function checkValidIndex(array, index) {
return index >= 0 && index <= array.length;
}
}

View File

@ -1,36 +0,0 @@
<div class="form-group ldap-dn-builder">
<div class="col-sm-12" style="margin-bottom: 5px;">
<label class="control-label text-left">{{ $ctrl.label || 'DN entries' }}</label>
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addEntry()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another entry
</button>
</div>
<div class="col-sm-12" ng-if="$ctrl.entries.length">
<rd-widget>
<rd-widget-body>
<div class="form-group no-margin-last-child" ng-repeat="entry in $ctrl.entries">
<div class="col-sm-4">
<select class="form-control" ng-model="entry.type" ng-change="$ctrl.onEntriesChange()">
<option value="ou">OU Name</option>
<option value="cn">Folder Name</option>
</select>
</div>
<div class="col-sm-5">
<input class="form-control" ng-model="entry.value" ng-change="$ctrl.onEntriesChange()" />
</div>
<div class="col-sm-3 text-right">
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$first" ng-click="$ctrl.moveUp($index)">
<i class="fa fa-arrow-up" aria-hidden="true"></i>
</button>
<button class="btn btn-sm btn-primary" type="button" ng-disabled="$last" ng-click="$ctrl.moveDown($index)">
<i class="fa fa-arrow-down" aria-hidden="true"></i>
</button>
<button class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeEntry($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>
</div>

View File

@ -1,17 +0,0 @@
import controller from './ldap-settings-group-dn-builder.controller';
export const ldapSettingsGroupDnBuilder = {
templateUrl: './ldap-settings-group-dn-builder.html',
controller,
bindings: {
// ngModel: string (dc=,cn=,)
ngModel: '<',
// onChange(string) => void
onChange: '<',
// suffix: string (dc=,dc=,)
suffix: '@',
// index: int >= 0
index: '<',
onRemoveClick: '<',
},
};

View File

@ -1,55 +0,0 @@
export default class LdapSettingsGroupDnBuilderController {
/* @ngInject */
constructor() {
this.groupName = '';
this.entries = '';
this.onEntriesChange = this.onEntriesChange.bind(this);
this.onGroupNameChange = this.onGroupNameChange.bind(this);
this.onGroupChange = this.onGroupChange.bind(this);
this.removeGroup = this.removeGroup.bind(this);
}
onEntriesChange(entries) {
this.onGroupChange(this.groupName, entries);
}
onGroupNameChange() {
this.onGroupChange(this.groupName, this.entries);
}
onGroupChange(groupName, entries) {
if (!groupName) {
return;
}
const groupNameEntry = `cn=${groupName}`;
this.onChange(this.index, entries || this.suffix ? `${groupNameEntry},${entries || this.suffix}` : groupNameEntry);
}
removeGroup() {
this.onRemoveClick(this.index);
}
parseEntries(value, suffix) {
if (value === suffix) {
this.groupName = '';
this.entries = suffix;
return;
}
const [groupName, entries] = this.ngModel.split(/,(.+)/);
this.groupName = groupName.replace('cn=', '');
this.entries = entries || '';
}
$onChange({ ngModel, suffix }) {
if ((!suffix || suffix.isFirstChange()) && !ngModel) {
return;
}
this.parseEntries(ngModel.value, suffix.value);
}
$onInit() {
this.parseEntries(this.ngModel, this.suffix);
}
}

View File

@ -1,21 +0,0 @@
<div class="form-group">
<label for="group-name-input" class="col-sm-4 control-label text-left">
Group Name
</label>
<div class="col-sm-7" style="padding-left: 0;">
<input type="text" class="form-control" id="group-name-input" ng-model="$ctrl.groupName" ng-change="$ctrl.onGroupNameChange()" />
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-danger btn-sm" ng-if="$ctrl.onRemoveClick" ng-click="$ctrl.onRemoveClick()">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
<ldap-settings-dn-builder
ng-model="$ctrl.entries"
label="Path to group"
suffix="{{ $ctrl.suffix }}"
on-change="($ctrl.onEntriesChange)"
on-remove-click="($ctrl.removeGroup)"
></ldap-settings-dn-builder>

View File

@ -1,16 +0,0 @@
import controller from './ldap-settings-openldap.controller';
export const ldapSettingsOpenLdap = {
templateUrl: './ldap-settings-openldap.html',
controller,
bindings: {
settings: '=',
tlscaCert: '=',
state: '=',
connectivityCheck: '<',
onTlscaCertChange: '<',
onSearchUsersClick: '<',
onSearchGroupsClick: '<',
},
};

View File

@ -1,42 +0,0 @@
export default class LdapSettingsOpenLDAPController {
/* @ngInject */
constructor() {
this.domainSuffix = '';
this.findDomainSuffix = this.findDomainSuffix.bind(this);
this.parseDomainSuffix = this.parseDomainSuffix.bind(this);
this.onAccountChange = this.onAccountChange.bind(this);
}
findDomainSuffix() {
const serviceAccount = this.settings.ReaderDN;
let domainSuffix = this.parseDomainSuffix(serviceAccount);
if (!domainSuffix && this.settings.SearchSettings.length > 0) {
const searchSettings = this.settings.SearchSettings[0];
domainSuffix = this.parseDomainSuffix(searchSettings.BaseDN);
}
this.domainSuffix = domainSuffix;
}
parseDomainSuffix(string = '') {
const index = string.toLowerCase().indexOf('dc=');
return index !== -1 ? string.substring(index) : '';
}
onAccountChange(serviceAccount) {
this.domainSuffix = this.parseDomainSuffix(serviceAccount);
}
addLDAPUrl() {
this.settings.URLs.push('');
}
removeLDAPUrl(index) {
this.settings.URLs.splice(index, 1);
}
$onInit() {
this.findDomainSuffix();
}
}

View File

@ -1,129 +0,0 @@
<div>
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using LDAP authentication, Portainer will delegate user authentication to a LDAP server and fallback to internal authentication if LDAP authentication fails.
</div>
</div>
<div class="col-sm-12 form-section-title">
LDAP configuration
</div>
<div class="form-group">
<div class="col-sm-12 small text-muted">
<p>
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
You can configure multiple LDAP Servers for authentication fallback. Make sure all servers are using the same configuration (i.e. if TLS is enabled, they should all use the
same certificates).
</p>
</div>
</div>
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left" style="display: flex; flex-wrap: wrap;">
LDAP Server
<button type="button" class="label label-default interactive" style="border: 0;" ng-click="$ctrl.addLDAPUrl()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> Add additional server
</button>
</label>
<div class="col-sm-9 col-lg-10">
<div ng-repeat="url in $ctrl.settings.URLs track by $index" style="display: flex; margin-bottom: 10px;">
<input type="text" class="form-control" id="ldap_url" ng-model="$ctrl.settings.URLs[$index]" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
<button ng-if="$index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.removeLDAPUrl($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
</div>
<!-- Anonymous mode-->
<div class="form-group">
<label for="anonymous_mode" class="control-label text-left col-sm-3" style="padding-top: 0;">
Anonymous mode
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch"> <input type="checkbox" id="anonymous_mode" ng-model="$ctrl.settings.AnonymousMode" /><i></i> </label>
</div>
</div>
<!-- !Anonymous mode-->
<div ng-if="!$ctrl.settings.AnonymousMode">
<div class="form-group">
<label for="ldap_username" class="col-sm-3 control-label text-left">
Reader DN
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input
type="text"
class="form-control"
id="ldap_username"
ng-model="$ctrl.settings.ReaderDN"
placeholder="cn=user,dc=domain,dc=tld"
ng-change="$ctrl.onAccountChange($ctrl.settings.ReaderDN)"
/>
</div>
</div>
<div class="form-group">
<label for="ldap_password" class="col-sm-3 control-label text-left">
Password
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9">
<input type="password" class="form-control" id="ldap_password" ng-model="$ctrl.settings.Password" placeholder="password" autocomplete="new-password" />
</div>
</div>
</div>
<div class="form-group" ng-if="$ctrl.settings.AnonymousMode">
<label for="ldap_domain_root" class="col-sm-3 control-label text-left">
Domain root
</label>
<div class="col-sm-9">
<input type="text" class="form-control" id="ldap_domain_root" ng-model="$ctrl.domainSuffix" placeholder="dc=domain,dc=tld" />
</div>
</div>
<ldap-connectivity-check
ng-if="!$ctrl.settings.TLSConfig.TLS && !$ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-settings-security
title="Connectivity Security"
settings="$ctrl.settings"
tlsca-cert="$ctrl.tlscaCert"
upload-in-progress="$ctrl.state.uploadInProgress"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
></ldap-settings-security>
<ldap-connectivity-check
ng-if="$ctrl.settings.TLSConfig.TLS || $ctrl.settings.StartTLS"
settings="$ctrl.settings"
state="$ctrl.state"
connectivity-check="$ctrl.connectivityCheck"
></ldap-connectivity-check>
<ldap-user-search
style="margin-top: 5px;"
settings="$ctrl.settings.SearchSettings"
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=inetOrgPerson)"
on-search-click="($ctrl.onSearchUsersClick)"
></ldap-user-search>
<ldap-group-search
style="margin-top: 5px;"
settings="$ctrl.settings.GroupSearchSettings"
domain-suffix="{{ $ctrl.domainSuffix }}"
base-filter="(objectClass=groupOfNames)"
on-search-click="($ctrl.onSearchGroupsClick)"
></ldap-group-search>
<ldap-settings-test-login settings="$ctrl.settings"></ldap-settings-test-login>

View File

@ -1,10 +0,0 @@
export const ldapSettingsSecurity = {
templateUrl: './ldap-settings-security.html',
bindings: {
settings: '=',
tlscaCert: '<',
onTlscaCertChange: '<',
uploadInProgress: '<',
title: '@',
},
};

View File

@ -1,57 +0,0 @@
<div class="col-sm-12 form-section-title">
{{ $ctrl.title || 'LDAP security' }}
</div>
<!-- starttls -->
<div class="form-group" ng-if="!$ctrl.settings.TLSConfig.TLS">
<label for="tls" class="control-label col-sm-3 text-left" style="padding-top: 0;">
Use StartTLS
<portainer-tooltip
position="bottom"
message="Enable this option if want to use StartTLS to secure the connection to the server. Ignored if Use TLS is selected."
></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.StartTLS" /><i></i> </label>
</div>
</div>
<!-- !starttls -->
<!-- tls-checkbox -->
<div class="form-group" ng-if="!$ctrl.settings.StartTLS">
<label for="tls" class="control-label col-sm-3 text-left" style="padding-top: 0;">
Use TLS
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the LDAP server."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
<!-- tls-skip-verify -->
<div class="form-group">
<label for="tls" class="control-label col-sm-3 text-left" style="padding-top: 0;">
Skip verification of server certificate
<portainer-tooltip position="bottom" message="Skip the verification of the server TLS certificate. Not recommended on unsecured networks."></portainer-tooltip>
</label>
<div class="col-sm-9">
<label class="switch"> <input type="checkbox" ng-model="$ctrl.settings.TLSConfig.TLSSkipVerify" /><i></i> </label>
</div>
</div>
<!-- !tls-skip-verify -->
<!-- ca-input -->
<div class="form-group" ng-if="$ctrl.settings.TLSConfig.TLS || ($ctrl.settings.StartTLS && !$ctrl.settings.TLSConfig.TLSSkipVerify)">
<label class="col-sm-3 control-label text-left">TLS CA certificate</label>
<div class="col-sm-9">
<button type="button" class="btn btn-sm btn-primary" ngf-select="$ctrl.onTlscaCertChange($file)" ng-model="$ctrl.tlscaCert">Select file</button>
<span style="margin-left: 5px;">
{{ $ctrl.tlscaCert.name }}
<i class="fa fa-check green-icon" ng-if="$ctrl.tlscaCert && $ctrl.tlscaCert === $ctrl.settings.TLSConfig.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!$ctrl.tlscaCert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="$ctrl.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !ca-input -->

View File

@ -1,9 +0,0 @@
import controller from './ldap-settings-test-login.controller';
export const ldapSettingsTestLogin = {
templateUrl: './ldap-settings-test-login.html',
controller,
bindings: {
settings: '=',
},
};

View File

@ -1,31 +0,0 @@
const TEST_STATUS = {
LOADING: 'LOADING',
SUCCESS: 'SUCCESS',
FAILURE: 'FAILURE',
};
export default class LdapSettingsTestLogin {
/* @ngInject */
constructor($async, LDAPService, Notifications) {
Object.assign(this, { $async, LDAPService, Notifications });
this.TEST_STATUS = TEST_STATUS;
this.state = {
testStatus: '',
};
}
async testLogin(username, password) {
return this.$async(async () => {
this.state.testStatus = TEST_STATUS.LOADING;
try {
const response = await this.LDAPService.testLogin(this.settings, username, password);
this.state.testStatus = response.valid ? TEST_STATUS.SUCCESS : TEST_STATUS.FAILURE;
} catch (err) {
this.Notifications.error('Failure', err, 'Unable to test login');
this.state.testStatus = TEST_STATUS.FAILURE;
}
});
}
}

View File

@ -1,32 +0,0 @@
<div class="col-sm-12 form-section-title">
Test login
</div>
<div class="form-inline">
<div class="form-group" style="margin: 0;">
<label for="ldap_test_username" style="font-size: 0.9em; margin-right: 5px;">
Username
</label>
<input type="text" class="form-control" id="ldap_test_username" ng-model="$ctrl.username" />
</div>
<div class="form-group" style="margin: 0;">
<label for="ldap_test_password" style="font-size: 0.9em; margin-right: 5px;">
Password
</label>
<input type="password" class="form-control" id="ldap_test_password" ng-model="$ctrl.password" autocomplete="new-password" />
</div>
<div class="form-group" style="margin: 0;">
<button
type="submit"
class="btn btn-primary"
ng-disabled="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING || !$ctrl.username || !$ctrl.password"
ng-click="$ctrl.testLogin($ctrl.username, $ctrl.password)"
>
<span ng-if="$ctrl.state.testStatus !== $ctrl.TEST_STATUS.LOADING">Test</span>
<span ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.LOADING">Testing...</span>
</button>
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.SUCCESS" class="fa fa-check green-icon"></i>
<i ng-if="$ctrl.state.testStatus === $ctrl.TEST_STATUS.FAILURE" class="fa fa-times red-icon"></i>
</div>
</div>

View File

@ -1,54 +0,0 @@
export function buildLdapSettingsModel() {
return {
AnonymousMode: true,
ReaderDN: '',
URLs: [''],
ServerType: 0,
TLSConfig: {
TLS: false,
TLSSkipVerify: false,
},
StartTLS: false,
SearchSettings: [
{
BaseDN: '',
Filter: '',
UserNameAttribute: '',
},
],
GroupSearchSettings: [
{
GroupBaseDN: '',
GroupFilter: '',
GroupAttribute: '',
},
],
AutoCreateUsers: true,
};
}
export function buildAdSettingsModel() {
const settings = buildLdapSettingsModel();
settings.ServerType = 2;
settings.AnonymousMode = false;
settings.SearchSettings[0].UserNameAttribute = 'sAMAccountName';
settings.SearchSettings[0].Filter = '(objectClass=user)';
settings.GroupSearchSettings[0].GroupAttribute = 'member';
settings.GroupSearchSettings[0].GroupFilter = '(objectClass=group)';
return settings;
}
export function buildOpenLDAPSettingsModel() {
const settings = buildLdapSettingsModel();
settings.ServerType = 1;
settings.AnonymousMode = false;
settings.SearchSettings[0].UserNameAttribute = 'uid';
settings.SearchSettings[0].Filter = '(objectClass=inetOrgPerson)';
settings.GroupSearchSettings[0].GroupAttribute = 'member';
settings.GroupSearchSettings[0].GroupFilter = '(objectClass=groupOfNames)';
return settings;
}

View File

@ -1,11 +0,0 @@
import controller from './ldap-settings.controller';
export const ldapSettings = {
templateUrl: './ldap-settings.html',
controller,
bindings: {
settings: '=',
state: '<',
connectivityCheck: '<',
},
};

View File

@ -1,66 +0,0 @@
const SERVER_TYPES = {
CUSTOM: 0,
OPEN_LDAP: 1,
AD: 2,
};
import { buildOpenLDAPSettingsModel } from '@/portainer/settings/authentication/ldap/ldap-settings.model';
const DEFAULT_GROUP_FILTER = '(objectClass=groupOfNames)';
const DEFAULT_USER_FILTER = '(objectClass=inetOrgPerson)';
export default class LdapSettingsController {
/* @ngInject */
constructor(LDAPService) {
Object.assign(this, { LDAPService, SERVER_TYPES });
this.tlscaCert = null;
this.boxSelectorOptions = [
{ id: 'ldap_custom', value: SERVER_TYPES.CUSTOM, label: 'Custom', icon: 'fa fa-server' },
{ id: 'ldap_openldap', value: SERVER_TYPES.OPEN_LDAP, label: 'OpenLDAP', icon: 'fa fa-server' },
];
this.onTlscaCertChange = this.onTlscaCertChange.bind(this);
this.searchUsers = this.searchUsers.bind(this);
this.searchGroups = this.searchGroups.bind(this);
this.onChangeServerType = this.onChangeServerType.bind(this);
}
onTlscaCertChange(file) {
this.tlscaCert = file;
}
$onInit() {
this.tlscaCert = this.settings.TLSCACert;
}
onChangeServerType(serverType) {
switch (serverType) {
case SERVER_TYPES.OPEN_LDAP:
return this.onChangeToOpenLDAP();
default:
break;
}
}
onChangeToOpenLDAP() {
this.settings = buildOpenLDAPSettingsModel();
}
searchUsers() {
const settings = {
...this.settings,
SearchSettings: this.settings.SearchSettings.map((search) => ({ ...search, Filter: search.Filter || DEFAULT_USER_FILTER })),
};
return this.LDAPService.users(settings);
}
searchGroups() {
const settings = {
...this.settings,
GroupSearchSettings: this.settings.GroupSearchSettings.map((search) => ({ ...search, GroupFilter: search.GroupFilter || DEFAULT_GROUP_FILTER })),
};
return this.LDAPService.groups(settings);
}
}

View File

@ -1,35 +0,0 @@
<div>
<auto-user-provision-toggle ng-model="$ctrl.settings.AutoCreateUsers">
<field-description>
With automatic user provisioning enabled, Portainer will create user(s) automatically with standard user role and assign them to team(s) which matches to LDAP group name(s).
If disabled, users must be created in Portainer beforehand.
</field-description>
</auto-user-provision-toggle>
<div class="col-sm-12 form-section-title">
Server Type
</div>
<box-selector style="margin-bottom: 0;" ng-model="$ctrl.settings.ServerType" options="$ctrl.boxSelectorOptions" on-change="($ctrl.onChangeServerType)"></box-selector>
<ldap-settings-custom
ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.CUSTOM"
settings="$ctrl.settings"
tlsca-cert="$ctrl.tlscaCert"
state="$ctrl.state"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
connectivity-check="$ctrl.connectivityCheck"
on-search-users-click="($ctrl.searchUsers)"
on-search-groups-click="($ctrl.searchGroups)"
></ldap-settings-custom>
<ldap-settings-open-ldap
ng-if="$ctrl.settings.ServerType === $ctrl.SERVER_TYPES.OPEN_LDAP"
settings="$ctrl.settings"
tlsca-cert="$ctrl.tlscaCert"
state="$ctrl.state"
on-tlsca-cert-change="($ctrl.onTlscaCertChange)"
connectivity-check="$ctrl.connectivityCheck"
on-search-users-click="($ctrl.searchUsers)"
on-search-groups-click="($ctrl.searchGroups)"
></ldap-settings-open-ldap>
</div>

View File

@ -1,14 +0,0 @@
import controller from './ldap-user-search-item.controller';
export const ldapUserSearchItem = {
templateUrl: './ldap-user-search-item.html',
controller,
bindings: {
config: '=',
index: '<',
showUsernameFormat: '<',
domainSuffix: '@',
baseFilter: '@',
onRemoveClick: '<',
},
};

View File

@ -1,67 +0,0 @@
export default class LdapUserSearchItemController {
/* @ngInject */
constructor() {
this.groups = [];
this.onBaseDNChange = this.onBaseDNChange.bind(this);
this.onGroupChange = this.onGroupChange.bind(this);
this.onGroupsChange = this.onGroupsChange.bind(this);
this.removeGroup = this.removeGroup.bind(this);
}
onBaseDNChange(baseDN) {
this.config.BaseDN = baseDN;
}
onGroupChange(index, group) {
this.groups[index] = group;
this.onGroupsChange(this.groups);
}
onGroupsChange(groups) {
this.config.Filter = this.generateUserFilter(groups);
}
removeGroup(index) {
this.groups.splice(index, 1);
this.onGroupsChange(this.groups);
}
addGroup() {
this.groups.push(this.domainSuffix ? `cn=,${this.domainSuffix}` : 'cn=');
}
generateUserFilter(groups) {
const filteredGroups = groups.filter((group) => group !== this.domainSuffix);
if (!filteredGroups.length) {
return this.baseFilter;
}
const groupsFilter = filteredGroups.map((group) => `(memberOf=${group})`);
return `(&${this.baseFilter}${groupsFilter.length > 1 ? `(|${groupsFilter.join('')})` : groupsFilter[0]})`;
}
parseFilter() {
const filter = this.config.Filter;
if (filter === this.baseFilter) {
return;
}
if (!filter.includes('|')) {
const index = filter.indexOf('memberOf=');
if (index > -1) {
this.groups = [filter.slice(index + 9, -2)];
}
return;
}
const members = filter.slice(filter.indexOf('|') + 2, -3);
this.groups = members.split(')(').map((member) => member.replace('memberOf=', ''));
}
$onInit() {
this.parseFilter();
}
}

View File

@ -1,75 +0,0 @@
<rd-widget>
<rd-widget-body>
<div ng-if="$ctrl.index > 0" style="margin-bottom: 10px;">
<span class="text-muted small">
Extra search configuration
</span>
<button ng-if="$ctrl.index > 0" class="btn btn-sm btn-danger" type="button" ng-click="$ctrl.onRemoveClick($ctrl.index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
<div class="form-group" ng-if="$ctrl.showUsernameFormat">
<div class="col-sm-4" style="margin-bottom: 5px;">
<label class="control-label text-left">Username Format</label>
</div>
<div class="col-sm-8">
<div class="input-group">
<div class="input-group-btn">
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'sAMAccountName'" style="margin-left: 0px;">username</button>
<button class="btn btn-primary" ng-model="$ctrl.config.UserNameAttribute" uib-btn-radio="'userPrincipalName'">user@domainname</button>
</div>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label text-left">
Root Domain
</label>
<div class="col-sm-8">
{{ $ctrl.config.BaseDN }}
</div>
</div>
<ldap-settings-dn-builder
ng-model="$ctrl.config.BaseDN"
label="User Search Path (optional)"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onBaseDNChange)"
></ldap-settings-dn-builder>
<div class="form-group no-margin-last-child">
<div class="col-sm-12" style="margin-bottom: 5px;">
<label class="control-label text-left">Allowed Groups (optional)</label>
<button type="button" class="label label-default interactive" style="margin-left: 10px; border: 0;" ng-click="$ctrl.addGroup()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add another group
</button>
</div>
<div class="col-sm-12">
<div ng-repeat="group in $ctrl.groups track by $index" style="margin-bottom: 10px;">
<rd-widget>
<rd-widget-body>
<ldap-settings-group-dn-builder
ng-model="group"
index="$index"
suffix="{{ $ctrl.domainSuffix }}"
on-change="($ctrl.onGroupChange)"
on-remove-click="($ctrl.removeGroup)"
></ldap-settings-group-dn-builder>
</rd-widget-body>
</rd-widget>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label text-left">
User Filter
</label>
<div class="col-sm-8">
{{ $ctrl.config.Filter }}
</div>
</div>
</rd-widget-body>
</rd-widget>

View File

@ -1,14 +0,0 @@
import controller from './ldap-user-search.controller';
export const ldapUserSearch = {
templateUrl: './ldap-user-search.html',
controller,
bindings: {
settings: '=',
domainSuffix: '@',
showUsernameFormat: '<',
baseFilter: '@',
onSearchClick: '<',
},
};

View File

@ -1,38 +0,0 @@
import _ from 'lodash';
export default class LdapUserSearchController {
/* @ngInject */
constructor($async, Notifications) {
Object.assign(this, { $async, Notifications });
this.users = null;
this.showTable = false;
this.onRemoveClick = this.onRemoveClick.bind(this);
this.onAddClick = this.onAddClick.bind(this);
this.search = this.search.bind(this);
}
onAddClick() {
const lastSetting = _.last(this.settings);
this.settings.push({ BaseDN: this.domainSuffix, UserNameAttribute: lastSetting.UserNameAttribute, Filter: this.baseFilter });
}
onRemoveClick(index) {
this.settings.splice(index, 1);
}
search() {
return this.$async(async () => {
try {
this.users = null;
this.showTable = true;
const users = await this.onSearchClick();
this.users = _.compact(users);
} catch (error) {
this.Notifications.error('Failure', error, 'Failed to search users');
this.showTable = false;
}
});
}
}

View File

@ -1,33 +0,0 @@
<div class="col-sm-12 form-section-title" style="float: initial;">
User search configurations
</div>
<div style="margin-top: 10px;" ng-repeat="config in $ctrl.settings | limitTo: (1 - $ctrl.settings)">
<ldap-user-search-item
index="$index"
config="config"
domain-suffix="{{ $ctrl.domainSuffix }}"
show-username-format="$ctrl.showUsernameFormat"
base-filter="{{ $ctrl.baseFilter }}"
on-remove-click="($ctrl.onRemoveClick)"
></ldap-user-search-item>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-12">
<button class="label label-default interactive" style="border: 0;" ng-click="$ctrl.onAddClick()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add user search configuration
</button>
</div>
<div class="col-sm-12" style="margin-top: 10px;">
<button class="btn btm-sm btn-primary" type="button" ng-click="$ctrl.search()">
Display Users
</button>
</div>
</div>
<div ng-if="$ctrl.showTable">
<div class="form-group">
<ldap-users-datatable dataset="$ctrl.users" title-text="Users" title-icon="fa-users" table-key="ldapUsers" order-by="" reverse-order=""></ldap-users-datatable>
</div>
</div>

View File

@ -1,12 +0,0 @@
export const ldapUsersDatatable = {
templateUrl: './ldap-users-datatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
titleIcon: '@',
dataset: '<',
tableKey: '@',
orderBy: '@',
reverseOrder: '<',
},
};

View File

@ -1,71 +0,0 @@
<div class="datatable">
<rd-widget>
<rd-widget-body classes="no-padding">
<div class="toolBar">
<div class="toolBarTitle"> <i class="fa" ng-class="$ctrl.titleIcon" aria-hidden="true" style="margin-right: 2px;"></i> {{ $ctrl.titleText }} </div>
</div>
<div class="searchBar">
<i class="fa fa-search searchIcon" aria-hidden="true"></i>
<input
type="text"
class="searchInput"
ng-model="$ctrl.state.textFilter"
ng-change="$ctrl.onTextFilterChange()"
placeholder="Search..."
auto-focus
ng-model-options="{ debounce: 300 }"
/>
</div>
<div class="table-responsive">
<table class="table table-hover nowrap-cells">
<thead>
<tr>
<th>
<a ng-click="$ctrl.changeOrderBy('Name')">
Name
<i class="fa fa-sort-alpha-down" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && !$ctrl.state.reverseOrder"></i>
<i class="fa fa-sort-alpha-up" aria-hidden="true" ng-if="$ctrl.state.orderBy === 'Name' && $ctrl.state.reverseOrder"></i>
</a>
</th>
</tr>
</thead>
<tbody>
<tr
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit)) track by $index"
ng-class="{ active: item.Checked }"
>
<td>
<a ui-sref="portainer.groups.group({id: item.Id})">{{ item }}</a>
</td>
</tr>
<tr ng-if="!$ctrl.dataset">
<td colspan="3" class="text-center text-muted">Loading...</td>
</tr>
<tr ng-if="$ctrl.state.filteredDataSet.length === 0">
<td colspan="5" class="text-center text-muted">No users found.</td>
</tr>
</tbody>
</table>
</div>
<div class="footer" ng-if="$ctrl.dataset">
<div class="paginationControls">
<form class="form-inline">
<span class="limitSelector">
<span style="margin-right: 5px;">
Items per page
</span>
<select class="form-control" ng-model="$ctrl.state.paginatedItemLimit" ng-change="$ctrl.changePaginationLimit()">
<option value="0">All</option>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</span>
<dir-pagination-controls max-size="5"></dir-pagination-controls>
</form>
</div>
</div>
</rd-widget-body>
</rd-widget>
</div>

View File

@ -1,15 +0,0 @@
const API_ENDPOINT_LDAP = 'api/ldap';
/* @ngInject */
export function LDAP($resource) {
return $resource(
`${API_ENDPOINT_LDAP}/:action`,
{},
{
check: { method: 'POST', params: { action: 'check' } },
users: { method: 'POST', isArray: true, params: { action: 'users' } },
groups: { method: 'POST', isArray: true, params: { action: 'groups' } },
testLogin: { method: 'POST', params: { action: 'test' } },
}
);
}

View File

@ -1,29 +0,0 @@
/* @ngInject */
export function LDAPService(LDAP) {
return { users, groups, check, testLogin };
function users(ldapSettings) {
return LDAP.users({ ldapSettings }).$promise;
}
async function groups(ldapSettings) {
const userGroups = await LDAP.groups({ ldapSettings }).$promise;
return userGroups.map(({ Name, Groups }) => {
let name = Name;
if (Name.includes(',') && Name.includes('=')) {
const [cnName] = Name.split(',');
const split = cnName.split('=');
name = split[1];
}
return { Groups, Name: name };
});
}
function check(ldapSettings) {
return LDAP.check({ ldapSettings }).$promise;
}
function testLogin(ldapSettings, username, password) {
return LDAP.testLogin({ ldapSettings, username, password }).$promise;
}
}

View File

@ -1,6 +1,5 @@
import angular from 'angular';
import authenticationModule from './authentication';
import generalModule from './general';
export default angular.module('portainer.settings', [authenticationModule, generalModule]).name;
export default angular.module('portainer.settings', [generalModule]).name;

View File

@ -34,10 +34,43 @@
<div class="col-sm-12 form-section-title">
Authentication method
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div>
<input type="radio" id="registry_quay" ng-model="settings.AuthenticationMethod" ng-value="1" />
<label for="registry_quay">
<div class="boxselector_header">
<i class="fa fa-users" aria-hidden="true" style="margin-right: 2px;"></i>
Internal
</div>
<p>Internal authentication mechanism</p>
</label>
</div>
<div>
<input type="radio" id="registry_custom" ng-model="settings.AuthenticationMethod" ng-value="2" />
<label for="registry_custom">
<div class="boxselector_header">
<i class="fa fa-users" aria-hidden="true" style="margin-right: 2px;"></i>
LDAP
</div>
<p>LDAP authentication</p>
</label>
</div>
<div>
<input type="radio" id="registry_auth" ng-model="settings.AuthenticationMethod" ng-value="3" />
<label for="registry_auth">
<div class="boxselector_header">
<i class="fa fa-users" aria-hidden="true" style="margin-right: 2px;"></i>
OAuth
</div>
<p>OAuth authentication</p>
</label>
</div>
</div>
</div>
<box-selector radio-name="authOptions" ng-model="authMethod" options="authOptions" on-change="(onChangeAuthMethod)"></box-selector>
<div ng-if="authenticationMethodSelected(1)">
<div ng-if="settings.AuthenticationMethod === 1">
<div class="col-sm-12 form-section-title">
Information
</div>
@ -46,23 +79,345 @@
</div>
</div>
<ldap-settings
ng-if="authenticationMethodSelected(2)"
settings="formValues.ldap.ldapSettings"
tlsca-cert="formValues.TLSCACert"
state="state"
connectivity-check="LDAPConnectivityCheck"
></ldap-settings>
<div ng-if="settings.AuthenticationMethod === 2">
<div>
<div class="col-sm-12 form-section-title">
Information
</div>
<div class="form-group col-sm-12 text-muted small">
When using LDAP authentication, Portainer will delegate user authentication to a LDAP server and fallback to internal authentication if LDAP authentication fails.
</div>
</div>
<ad-settings
ng-if="authenticationMethodSelected(4)"
settings="formValues.ldap.adSettings"
tlsca-cert="formValues.TLSCACert"
state="state"
connectivity-check="LDAPConnectivityCheck"
></ad-settings>
<div class="col-sm-12 form-section-title">
LDAP configuration
</div>
<oauth-settings ng-if="authenticationMethodSelected(3)" settings="OAuthSettings" teams="teams"></oauth-settings>
<div class="form-group">
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left">
LDAP Server
<portainer-tooltip position="bottom" message="URL or IP address of the LDAP server."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="text" class="form-control" id="ldap_url" ng-model="formValues.LDAPSettings.URL" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389" />
</div>
</div>
<!-- Anonymous mode-->
<div class="form-group">
<div class="col-sm-12">
<label for="anonymous_mode" class="control-label text-left">
Anonymous mode
<portainer-tooltip position="bottom" message="Enable this option if the server is configured for Anonymous access."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" id="anonymous_mode" ng-model="formValues.LDAPSettings.AnonymousMode" /><i></i> </label>
</div>
</div>
<!-- !Anonymous mode-->
<div ng-if="!formValues.LDAPSettings.AnonymousMode">
<div class="form-group">
<label for="ldap_username" class="col-sm-3 col-lg-2 control-label text-left">
Reader DN
<portainer-tooltip position="bottom" message="Account that will be used to search for users."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input
type="text"
class="form-control"
id="ldap_username"
ng-model="formValues.LDAPSettings.ReaderDN"
placeholder="cn=readonly-account,dc=ldap,dc=domain,dc=tld"
/>
</div>
</div>
<div class="form-group">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Password
<portainer-tooltip position="bottom" message="If you do not enter a password, Portainer will leave the current password unchanged."></portainer-tooltip>
</label>
<div class="col-sm-9 col-lg-10">
<input type="password" class="form-control" id="ldap_password" ng-model="formValues.LDAPSettings.Password" placeholder="password" />
</div>
</div>
</div>
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLS && !formValues.LDAPSettings.StartTLS">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="state.failedConnectivityCheck"></i>
</label>
<div class="col-sm-9 col-lg-10">
<button
type="button"
class="btn btn-primary btn-sm"
ng-disabled="(state.connectivityCheckInProgress) || (!formValues.LDAPSettings.URL) || ((!formValues.LDAPSettings.ReaderDN || !formValues.LDAPSettings.Password) && !formValues.LDAPSettings.AnonymousMode)"
ng-click="LDAPConnectivityCheck()"
button-spinner="state.connectivityCheckInProgress"
>
<span ng-hide="state.connectivityCheckInProgress">Test connectivity</span>
<span ng-show="state.connectivityCheckInProgress">Testing connectivity...</span>
</button>
</div>
</div>
<div class="col-sm-12 form-section-title">
LDAP security
</div>
<!-- starttls -->
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLS">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Use StartTLS
<portainer-tooltip
position="bottom"
message="Enable this option if want to use StartTLS to secure the connection to the server. Ignored if Use TLS is selected."
></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.LDAPSettings.StartTLS" /><i></i> </label>
</div>
</div>
<!-- !starttls -->
<!-- tls-checkbox -->
<div class="form-group" ng-if="!formValues.LDAPSettings.StartTLS">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Use TLS
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the LDAP server."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.LDAPSettings.TLSConfig.TLS" /><i></i> </label>
</div>
</div>
<!-- !tls-checkbox -->
<!-- tls-skip-verify -->
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Skip verification of server certificate
<portainer-tooltip position="bottom" message="Skip the verification of the server TLS certificate. Not recommended on unsecured networks."></portainer-tooltip>
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.LDAPSettings.TLSConfig.TLSSkipVerify" /><i></i> </label>
</div>
</div>
<!-- !tls-skip-verify -->
<!-- tls-certs -->
<div ng-if="formValues.LDAPSettings.TLSConfig.TLS || formValues.LDAPSettings.StartTLS">
<!-- ca-input -->
<div class="form-group" ng-if="!formValues.LDAPSettings.TLSConfig.TLSSkipVerify">
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
<div class="col-sm-10">
<button type="button" class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
<span style="margin-left: 5px;">
{{ formValues.TLSCACert.name }}
<i class="fa fa-check green-icon" ng-if="formValues.TLSCACert && formValues.TLSCACert === formValues.LDAPSettings.TLSConfig.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
<i class="fa fa-circle-notch fa-spin" ng-if="state.uploadInProgress"></i>
</span>
</div>
</div>
<!-- !ca-input -->
</div>
<!-- !tls-certs -->
<div class="form-group" ng-if="formValues.LDAPSettings.TLSConfig.TLS || formValues.LDAPSettings.StartTLS">
<label for="ldap_password" class="col-sm-3 col-lg-2 control-label text-left">
Connectivity check
<i class="fa fa-check green-icon" style="margin-left: 5px;" ng-if="state.successfulConnectivityCheck"></i>
<i class="fa fa-times red-icon" style="margin-left: 5px;" ng-if="state.failedConnectivityCheck"></i>
</label>
<div class="col-sm-9 col-lg-10">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="LDAPConnectivityCheck()"
ng-disabled="(!formValues.LDAPSettings.URL) || (!formValues.TLSCACert && !formValues.LDAPSettings.TLSConfig.TLSSkipVerify) || ((!formValues.LDAPSettings.ReaderDN || !formValues.LDAPSettings.Password) && !formValues.LDAPSettings.AnonymousMode)"
>Test connectivity</button
>
<i id="connectivityCheckSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
</div>
</div>
<div class="col-sm-12 form-section-title">
Automatic user provisioning
</div>
<div class="form-group">
<span class="col-sm-12 text-muted small">
With automatic user provisioning enabled, Portainer will create user(s) automatically with standard user role and assign them to team(s) which matches to LDAP group
name(s). If disabled, users must be created in Portainer beforehand.
</span>
</div>
<div class="form-group">
<div class="col-sm-12">
<label for="tls" class="control-label text-left">
Automatic user provisioning
</label>
<label class="switch" style="margin-left: 20px;"> <input type="checkbox" ng-model="formValues.LDAPSettings.AutoCreateUsers" /><i></i> </label>
</div>
</div>
<div class="col-sm-12 form-section-title">
User search configurations
</div>
<!-- search-settings -->
<div ng-repeat="config in formValues.LDAPSettings.SearchSettings | limitTo: (1 - formValues.LDAPSettings.SearchSettings)" style="margin-top: 5px;">
<div class="form-group" ng-if="$index > 0">
<span class="col-sm-12 text-muted small">
Extra search configuration
</span>
</div>
<div class="form-group">
<label for="ldap_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Base DN
<portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for users."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_basedn_{{ $index }}" ng-model="config.BaseDN" placeholder="dc=ldap,dc=domain,dc=tld" />
</div>
<label for="ldap_username_att_{{ $index }}" class="col-sm-4 col-md-3 col-lg-2 margin-sm-top control-label text-left">
Username attribute
<portainer-tooltip position="bottom" message="LDAP attribute which denotes the username."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-3 col-lg-4 margin-sm-top">
<input type="text" class="form-control" id="ldap_username_att_{{ $index }}" ng-model="config.UserNameAttribute" placeholder="uid" />
</div>
</div>
<div class="form-group">
<label for="ldap_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Filter
<portainer-tooltip position="bottom" message="The LDAP search filter used to select user elements, optional."></portainer-tooltip>
</label>
<div class="col-sm-7 col-md-9">
<input type="text" class="form-control" id="ldap_filter_{{ $index }}" ng-model="config.Filter" placeholder="(objectClass=account)" />
</div>
<div class="col-sm-1" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeSearchConfiguration($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="form-group">
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addSearchConfiguration()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add user search configuration
</span>
</div>
</div>
<!-- !search-settings -->
<div class="col-sm-12 form-section-title">
Group search configurations
</div>
<!-- group-search-settings -->
<div ng-repeat="groupConfig in formValues.LDAPSettings.GroupSearchSettings | limitTo: (1 - formValues.LDAPSettings.GroupSearchSettings)" style="margin-top: 5px;">
<div class="form-group" ng-if="$index > 0">
<span class="col-sm-12 text-muted small">
Extra search configuration
</span>
</div>
<div class="form-group">
<label for="ldap_group_basedn_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Base DN
<portainer-tooltip position="bottom" message="The distinguished name of the element from which the LDAP server will search for groups."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-4">
<input type="text" class="form-control" id="ldap_group_basedn_{{ $index }}" ng-model="groupConfig.GroupBaseDN" placeholder="dc=ldap,dc=domain,dc=tld" />
</div>
<label for="ldap_group_att_{{ $index }}" class="col-sm-4 col-md-3 col-lg-2 margin-sm-top control-label text-left">
Group Membership Attribute
<portainer-tooltip position="bottom" message="LDAP attribute which denotes the group membership."></portainer-tooltip>
</label>
<div class="col-sm-8 col-md-3 col-lg-4 margin-sm-top">
<input type="text" class="form-control" id="ldap_group_att_{{ $index }}" ng-model="groupConfig.GroupAttribute" placeholder="member" />
</div>
</div>
<div class="form-group">
<label for="ldap_group_filter_{{ $index }}" class="col-sm-4 col-md-2 control-label text-left">
Group Filter
<portainer-tooltip position="bottom" message="The LDAP search filter used to select group elements, optional."></portainer-tooltip>
</label>
<div class="col-sm-7 col-md-9">
<input type="text" class="form-control" id="ldap_group_filter_{{ $index }}" ng-model="groupConfig.GroupFilter" placeholder="(objectClass=account)" />
</div>
<div class="col-sm-1" ng-if="$index > 0">
<button class="btn btn-sm btn-danger" type="button" ng-click="removeGroupSearchConfiguration($index)">
<i class="fa fa-trash" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="form-group">
<span class="label label-default interactive" style="margin-left: 10px;" ng-click="addGroupSearchConfiguration()">
<i class="fa fa-plus-circle" aria-hidden="true"></i> add group search configuration
</span>
</div>
</div>
<!-- !group-search-settings -->
</div>
<div ng-if="isOauthEnabled()">
<div class="col-sm-12 form-section-title">
Provider
</div>
<div class="form-group"></div>
<div class="form-group" style="margin-bottom: 0;">
<div class="boxselector_wrapper">
<div data-toggle="tooltip" title="This feature is available in Portainer Business Edition" style="color: #767676;">
<input type="radio" id="microsoft" disabled />
<label for="microsoft" style="cursor: pointer; border-color: #767676;">
<div class="boxselector_header">
<i class="fab fa-microsoft" aria-hidden="true" style="margin-right: 2px;"></i>
Microsoft
</div>
<p>Microsoft OAuth provider</p>
</label>
</div>
<div data-toggle="tooltip" title="This feature is available in Portainer Business Edition" style="color: #767676;">
<input type="radio" id="google" disabled />
<label for="google" style="cursor: pointer; border-color: #767676;">
<div class="boxselector_header">
<i class="fab fa-google" aria-hidden="true" style="margin-right: 2px;"></i>
Google
</div>
<p>Google OAuth provider</p>
</label>
</div>
<div data-toggle="tooltip" title="This feature is available in Portainer Business Edition" style="color: #767676;">
<input type="radio" id="registry_auth" disabled />
<label for="Github" style="cursor: pointer; border-color: #767676;">
<div class="boxselector_header">
<i class="fab fa-github" aria-hidden="true" style="margin-right: 2px;"></i>
Github
</div>
<p>Github OAuth provider</p>
</label>
</div>
<div>
<input type="radio" id="custom" ng-model="settings.AuthenticationMethod" ng-value="3" />
<label for="custom">
<div class="boxselector_header">
<i class="fa fa-user-check" aria-hidden="true" style="margin-right: 2px;"></i>
Custom
</div>
<p>Custom OAuth provider</p>
</label>
</div>
</div>
</div>
</div>
<oauth-settings ng-if="isOauthEnabled()" settings="OAuthSettings" teams="teams"></oauth-settings>
<!-- actions -->
<div class="col-sm-12 form-section-title">
@ -70,13 +425,7 @@
</div>
<div class="form-group">
<div class="col-sm-12">
<button
type="button"
class="btn btn-primary btn-sm"
ng-click="saveSettings()"
ng-disabled="state.actionInProgress || (settings.AuthenticationMethod === 2 && !isLDAPFormValid()) || (settings.AuthenticationMethod === 3 && !isOAuthTeamMembershipFormValid())"
button-spinner="state.actionInProgress"
>
<button type="button" class="btn btn-primary btn-sm" ng-click="saveSettings()" ng-disabled="state.actionInProgress" button-spinner="state.actionInProgress">
<span ng-hide="state.actionInProgress">Save settings</span>
<span ng-show="state.actionInProgress">Saving...</span>
</button>

View File

@ -1,244 +1,181 @@
import angular from 'angular';
import _ from 'lodash-es';
angular.module('portainer.app').controller('SettingsAuthenticationController', [
'$q',
'$scope',
'$state',
'Notifications',
'SettingsService',
'FileUploadService',
'TeamService',
function ($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService) {
$scope.state = {
successfulConnectivityCheck: false,
failedConnectivityCheck: false,
uploadInProgress: false,
connectivityCheckInProgress: false,
actionInProgress: false,
availableUserSessionTimeoutOptions: [
{
key: '1 hour',
value: '1h',
},
{
key: '4 hours',
value: '4h',
},
{
key: '8 hours',
value: '8h',
},
{
key: '24 hours',
value: '24h',
},
{ key: '1 week', value: `${24 * 7}h` },
{ key: '1 month', value: `${24 * 30}h` },
{ key: '6 months', value: `${24 * 30 * 6}h` },
{ key: '1 year', value: `${24 * 30 * 12}h` },
],
};
import { buildLdapSettingsModel, buildAdSettingsModel } from '@/portainer/settings/authentication/ldap/ldap-settings.model';
angular.module('portainer.app').controller('SettingsAuthenticationController', SettingsAuthenticationController);
function SettingsAuthenticationController($q, $scope, $state, Notifications, SettingsService, FileUploadService, TeamService, LDAPService) {
$scope.state = {
uploadInProgress: false,
actionInProgress: false,
availableUserSessionTimeoutOptions: [
{
key: '1 hour',
value: '1h',
$scope.formValues = {
UserSessionTimeout: $scope.state.availableUserSessionTimeoutOptions[0],
TLSCACert: '',
LDAPSettings: {
AnonymousMode: true,
ReaderDN: '',
URL: '',
TLSConfig: {
TLS: false,
TLSSkipVerify: false,
},
StartTLS: false,
SearchSettings: [
{
BaseDN: '',
Filter: '',
UserNameAttribute: '',
},
],
GroupSearchSettings: [
{
GroupBaseDN: '',
GroupFilter: '',
GroupAttribute: '',
},
],
AutoCreateUsers: true,
},
{
key: '4 hours',
value: '4h',
},
{
key: '8 hours',
value: '8h',
},
{
key: '24 hours',
value: '24h',
},
{ key: '1 week', value: `${24 * 7}h` },
{ key: '1 month', value: `${24 * 30}h` },
{ key: '6 months', value: `${24 * 30 * 6}h` },
{ key: '1 year', value: `${24 * 30 * 12}h` },
],
};
};
$scope.formValues = {
UserSessionTimeout: $scope.state.availableUserSessionTimeoutOptions[0],
TLSCACert: '',
ldap: {
serverType: 0,
adSettings: buildAdSettingsModel(),
ldapSettings: buildLdapSettingsModel(),
},
};
$scope.isOauthEnabled = function isOauthEnabled() {
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
};
$scope.authOptions = [
{ id: 'auth_internal', icon: 'fa fa-users', label: 'Internal', description: 'Internal authentication mechanism', value: 1 },
{ id: 'auth_ldap', icon: 'fa fa-users', label: 'LDAP', description: 'LDAP authentication', value: 2 },
{ id: 'auth_ad', icon: 'fab fa-microsoft', label: 'Microsoft Active Directory', description: 'AD authentication', value: 4 },
{ id: 'auth_oauth', icon: 'fa fa-users', label: 'OAuth', description: 'OAuth authentication', value: 3 },
];
$scope.addSearchConfiguration = function () {
$scope.formValues.LDAPSettings.SearchSettings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
};
$scope.onChangeAuthMethod = function onChangeAuthMethod(value) {
if (value === 4) {
$scope.settings.AuthenticationMethod = 2;
$scope.formValues.ldap.serverType = 2;
return;
}
$scope.removeSearchConfiguration = function (index) {
$scope.formValues.LDAPSettings.SearchSettings.splice(index, 1);
};
if (value === 2) {
$scope.settings.AuthenticationMethod = 2;
$scope.formValues.ldap.serverType = $scope.formValues.ldap.ldapSettings.ServerType;
return;
}
$scope.addGroupSearchConfiguration = function () {
$scope.formValues.LDAPSettings.GroupSearchSettings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
};
$scope.settings.AuthenticationMethod = value;
};
$scope.removeGroupSearchConfiguration = function (index) {
$scope.formValues.LDAPSettings.GroupSearchSettings.splice(index, 1);
};
$scope.authenticationMethodSelected = function authenticationMethodSelected(value) {
if (!$scope.settings) {
return false;
}
$scope.LDAPConnectivityCheck = function () {
var settings = angular.copy($scope.settings);
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
if (value === 4) {
return $scope.settings.AuthenticationMethod === 2 && $scope.formValues.ldap.serverType === 2;
}
if (value === 2) {
return $scope.settings.AuthenticationMethod === 2 && $scope.formValues.ldap.serverType !== 2;
}
return $scope.settings.AuthenticationMethod === value;
};
$scope.isOauthEnabled = function isOauthEnabled() {
return $scope.settings && $scope.settings.AuthenticationMethod === 3;
};
$scope.LDAPConnectivityCheck = LDAPConnectivityCheck;
function LDAPConnectivityCheck() {
const settings = angular.copy($scope.settings);
const { settings: ldapSettings, uploadRequired, tlscaFile } = prepareLDAPSettings();
settings.LDAPSettings = ldapSettings;
$scope.state.uploadInProgress = uploadRequired;
$scope.state.connectivityCheckInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(tlscaFile, null, null))
.then(function success() {
return LDAPService.check(settings.LDAPSettings);
})
.then(function success() {
$scope.state.failedConnectivityCheck = false;
$scope.state.successfulConnectivityCheck = true;
Notifications.success('Connection to LDAP successful');
})
.catch(function error(err) {
$scope.state.failedConnectivityCheck = true;
$scope.state.successfulConnectivityCheck = false;
Notifications.error('Failure', err, 'Connection to LDAP failed');
})
.finally(function final() {
$scope.state.uploadInProgress = false;
$scope.state.connectivityCheckInProgress = false;
});
}
$scope.saveSettings = function () {
const settings = angular.copy($scope.settings);
const { settings: ldapSettings, uploadRequired, tlscaFile } = prepareLDAPSettings();
settings.LDAPSettings = ldapSettings;
$scope.state.uploadInProgress = uploadRequired;
$scope.state.actionInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(tlscaFile, null, null))
.then(function success() {
return SettingsService.update(settings);
})
.then(function success() {
Notifications.success('Authentication settings updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update authentication settings');
})
.finally(function final() {
$scope.state.uploadInProgress = false;
$scope.state.actionInProgress = false;
});
};
function prepareLDAPSettings() {
const tlscaCert = $scope.formValues.TLSCACert;
const tlscaFile = tlscaCert !== $scope.settings.LDAPSettings.TLSConfig.TLSCACert ? tlscaCert : null;
const isADServer = $scope.formValues.ldap.serverType === 2;
const settings = isADServer ? $scope.formValues.ldap.adSettings : $scope.formValues.ldap.ldapSettings;
if (settings.AnonymousMode && !isADServer) {
settings.ReaderDN = '';
settings.Password = '';
}
if (isADServer) {
settings.AnonymousMode = false;
}
settings.URLs = settings.URLs.map((url) => {
if (url.includes(':')) {
return url;
if ($scope.formValues.LDAPSettings.AnonymousMode) {
settings.LDAPSettings['ReaderDN'] = '';
settings.LDAPSettings['Password'] = '';
}
return url + (settings.TLSConfig.TLS ? ':636' : ':389');
});
const uploadRequired = (settings.TLSConfig.TLS || settings.StartTLS) && !settings.TLSConfig.TLSSkipVerify;
var uploadRequired = ($scope.formValues.LDAPSettings.TLSConfig.TLS || $scope.formValues.LDAPSettings.StartTLS) && !$scope.formValues.LDAPSettings.TLSConfig.TLSSkipVerify;
$scope.state.uploadInProgress = uploadRequired;
settings.URL = settings.URLs[0];
$scope.state.connectivityCheckInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success() {
addLDAPDefaultPort(settings, $scope.formValues.LDAPSettings.TLSConfig.TLS);
return SettingsService.checkLDAPConnectivity(settings);
})
.then(function success() {
$scope.state.failedConnectivityCheck = false;
$scope.state.successfulConnectivityCheck = true;
Notifications.success('Connection to LDAP successful');
})
.catch(function error(err) {
$scope.state.failedConnectivityCheck = true;
$scope.state.successfulConnectivityCheck = false;
Notifications.error('Failure', err, 'Connection to LDAP failed');
})
.finally(function final() {
$scope.state.uploadInProgress = false;
$scope.state.connectivityCheckInProgress = false;
});
};
return { settings, uploadRequired, tlscaFile };
}
$scope.saveSettings = function () {
var settings = angular.copy($scope.settings);
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
$scope.isLDAPFormValid = isLDAPFormValid;
function isLDAPFormValid() {
const ldapSettings = $scope.formValues.ldap.serverType === 2 ? $scope.formValues.ldap.adSettings : $scope.formValues.ldap.ldapSettings;
const isTLSMode = ldapSettings.TLSConfig.TLS || ldapSettings.StartTLS;
return (
_.compact(ldapSettings.URLs).length &&
(ldapSettings.AnonymousMode || (ldapSettings.ReaderDN && ldapSettings.Password)) &&
(!isTLSMode || $scope.formValues.TLSCACert || ldapSettings.TLSConfig.TLSSkipVerify)
);
}
$scope.isOAuthTeamMembershipFormValid = isOAuthTeamMembershipFormValid;
function isOAuthTeamMembershipFormValid() {
if ($scope.settings && $scope.settings.OAuthSettings.OAuthAutoMapTeamMemberships) {
if (!$scope.settings.OAuthSettings.TeamMemberships.OAuthClaimName) {
return false;
if ($scope.formValues.LDAPSettings.AnonymousMode) {
settings.LDAPSettings['ReaderDN'] = '';
settings.LDAPSettings['Password'] = '';
}
const hasInvalidMapping = $scope.settings.OAuthSettings.TeamMemberships.OAuthClaimMappings.some((m) => !(m.ClaimValRegex && m.Team));
if (hasInvalidMapping) {
return false;
var uploadRequired = ($scope.formValues.LDAPSettings.TLSConfig.TLS || $scope.formValues.LDAPSettings.StartTLS) && !$scope.formValues.LDAPSettings.TLSConfig.TLSSkipVerify;
$scope.state.uploadInProgress = uploadRequired;
$scope.state.actionInProgress = true;
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
.then(function success() {
addLDAPDefaultPort(settings, $scope.formValues.LDAPSettings.TLSConfig.TLS);
return SettingsService.update(settings);
})
.then(function success() {
Notifications.success('Authentication settings updated');
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to update authentication settings');
})
.finally(function final() {
$scope.state.uploadInProgress = false;
$scope.state.actionInProgress = false;
});
};
// Add default port if :port is not defined in URL
function addLDAPDefaultPort(settings, tlsEnabled) {
if (settings.LDAPSettings.URL.indexOf(':') === -1) {
settings.LDAPSettings.URL += tlsEnabled ? ':636' : ':389';
}
}
return true;
}
function initView() {
$q.all({
settings: SettingsService.settings(),
teams: TeamService.teams(),
})
.then(function success(data) {
var settings = data.settings;
$scope.teams = data.teams;
$scope.settings = settings;
$scope.OAuthSettings = settings.OAuthSettings;
$scope.authMethod = settings.AuthenticationMethod;
if (settings.AuthenticationMethod === 2 && settings.LDAPSettings.ServerType === 2) {
$scope.authMethod = 4;
}
$scope.formValues.ldap.serverType = settings.LDAPSettings.ServerType;
if (settings.LDAPSettings.ServerType === 2) {
$scope.formValues.ldap.adSettings = settings.LDAPSettings;
} else {
$scope.formValues.ldap.ldapSettings = settings.LDAPSettings;
}
if (settings.LDAPSettings.URL) {
settings.LDAPSettings.URLs = [settings.LDAPSettings.URL];
}
if (!settings.LDAPSettings.URLs) {
settings.LDAPSettings.URLs = [];
}
if (!settings.LDAPSettings.URLs.length) {
settings.LDAPSettings.URLs.push('');
}
if (!settings.LDAPSettings.ServerType) {
settings.LDAPSettings.ServerType = 0;
}
function initView() {
$q.all({
settings: SettingsService.settings(),
teams: TeamService.teams(),
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');
});
}
.then(function success(data) {
var settings = data.settings;
$scope.teams = data.teams;
$scope.settings = settings;
$scope.formValues.LDAPSettings = settings.LDAPSettings;
$scope.OAuthSettings = settings.OAuthSettings;
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
})
.catch(function error(err) {
Notifications.error('Failure', err, 'Unable to retrieve application settings');
});
}
initView();
}
initView();
},
]);