mirror of https://github.com/portainer/portainer
This reverts commit 45af1f3d8b
.
bug/EE-1734/Registry-page-displays-No-registry-available-despite-being-dockerhub-registry-available
parent
45af1f3d8b
commit
0b64250647
|
@ -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',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -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>
|
|
@ -1,9 +0,0 @@
|
|||
export const autoTeamMembershipToggle = {
|
||||
templateUrl: './auto-team-membership-toggle.html',
|
||||
transclude: {
|
||||
description: 'fieldDescription',
|
||||
},
|
||||
bindings: {
|
||||
ngModel: '=',
|
||||
},
|
||||
};
|
|
@ -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>
|
|
@ -1,9 +0,0 @@
|
|||
export const autoUserProvisionToggle = {
|
||||
templateUrl: './auto-user-provision-toggle.html',
|
||||
transclude: {
|
||||
description: 'fieldDescription',
|
||||
},
|
||||
bindings: {
|
||||
ngModel: '=',
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,12 +0,0 @@
|
|||
import controller from './ad-settings.controller';
|
||||
|
||||
export const adSettings = {
|
||||
templateUrl: './ad-settings.html',
|
||||
controller,
|
||||
bindings: {
|
||||
settings: '=',
|
||||
tlscaCert: '=',
|
||||
state: '=',
|
||||
connectivityCheck: '<',
|
||||
},
|
||||
};
|
|
@ -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;
|
|
@ -1,8 +0,0 @@
|
|||
export const ldapConnectivityCheck = {
|
||||
templateUrl: './ldap-connectivity-check.html',
|
||||
bindings: {
|
||||
settings: '<',
|
||||
state: '<',
|
||||
connectivityCheck: '<',
|
||||
},
|
||||
};
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,12 +0,0 @@
|
|||
export const ldapGroupsDatatable = {
|
||||
templateUrl: './ldap-groups-datatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
},
|
||||
};
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
export default class LdapSettingsCustomController {
|
||||
addLDAPUrl() {
|
||||
this.settings.URLs.push('');
|
||||
}
|
||||
|
||||
removeLDAPUrl(index) {
|
||||
this.settings.URLs.splice(index, 1);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '@',
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,10 +0,0 @@
|
|||
export const ldapSettingsSecurity = {
|
||||
templateUrl: './ldap-settings-security.html',
|
||||
bindings: {
|
||||
settings: '=',
|
||||
tlscaCert: '<',
|
||||
onTlscaCertChange: '<',
|
||||
uploadInProgress: '<',
|
||||
title: '@',
|
||||
},
|
||||
};
|
|
@ -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 -->
|
|
@ -1,9 +0,0 @@
|
|||
import controller from './ldap-settings-test-login.controller';
|
||||
|
||||
export const ldapSettingsTestLogin = {
|
||||
templateUrl: './ldap-settings-test-login.html',
|
||||
controller,
|
||||
bindings: {
|
||||
settings: '=',
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import controller from './ldap-settings.controller';
|
||||
|
||||
export const ldapSettings = {
|
||||
templateUrl: './ldap-settings.html',
|
||||
controller,
|
||||
bindings: {
|
||||
settings: '=',
|
||||
state: '<',
|
||||
connectivityCheck: '<',
|
||||
},
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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: '<',
|
||||
},
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -1,12 +0,0 @@
|
|||
export const ldapUsersDatatable = {
|
||||
templateUrl: './ldap-users-datatable.html',
|
||||
controller: 'GenericDatatableController',
|
||||
bindings: {
|
||||
titleText: '@',
|
||||
titleIcon: '@',
|
||||
dataset: '<',
|
||||
tableKey: '@',
|
||||
orderBy: '@',
|
||||
reverseOrder: '<',
|
||||
},
|
||||
};
|
|
@ -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>
|
|
@ -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' } },
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import angular from 'angular';
|
||||
import _ from 'lodash-es';
|
||||
|
||||
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) {
|
||||
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: [
|
||||
{
|
||||
|
@ -36,69 +40,70 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
|||
$scope.formValues = {
|
||||
UserSessionTimeout: $scope.state.availableUserSessionTimeoutOptions[0],
|
||||
TLSCACert: '',
|
||||
ldap: {
|
||||
serverType: 0,
|
||||
adSettings: buildAdSettingsModel(),
|
||||
ldapSettings: buildLdapSettingsModel(),
|
||||
LDAPSettings: {
|
||||
AnonymousMode: true,
|
||||
ReaderDN: '',
|
||||
URL: '',
|
||||
TLSConfig: {
|
||||
TLS: false,
|
||||
TLSSkipVerify: false,
|
||||
},
|
||||
StartTLS: false,
|
||||
SearchSettings: [
|
||||
{
|
||||
BaseDN: '',
|
||||
Filter: '',
|
||||
UserNameAttribute: '',
|
||||
},
|
||||
],
|
||||
GroupSearchSettings: [
|
||||
{
|
||||
GroupBaseDN: '',
|
||||
GroupFilter: '',
|
||||
GroupAttribute: '',
|
||||
},
|
||||
],
|
||||
AutoCreateUsers: true,
|
||||
},
|
||||
};
|
||||
|
||||
$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.onChangeAuthMethod = function onChangeAuthMethod(value) {
|
||||
if (value === 4) {
|
||||
$scope.settings.AuthenticationMethod = 2;
|
||||
$scope.formValues.ldap.serverType = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
if (value === 2) {
|
||||
$scope.settings.AuthenticationMethod = 2;
|
||||
$scope.formValues.ldap.serverType = $scope.formValues.ldap.ldapSettings.ServerType;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.settings.AuthenticationMethod = value;
|
||||
};
|
||||
|
||||
$scope.authenticationMethodSelected = function authenticationMethodSelected(value) {
|
||||
if (!$scope.settings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
$scope.addSearchConfiguration = function () {
|
||||
$scope.formValues.LDAPSettings.SearchSettings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
|
||||
};
|
||||
|
||||
const { settings: ldapSettings, uploadRequired, tlscaFile } = prepareLDAPSettings();
|
||||
settings.LDAPSettings = ldapSettings;
|
||||
$scope.removeSearchConfiguration = function (index) {
|
||||
$scope.formValues.LDAPSettings.SearchSettings.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.addGroupSearchConfiguration = function () {
|
||||
$scope.formValues.LDAPSettings.GroupSearchSettings.push({ GroupBaseDN: '', GroupAttribute: '', GroupFilter: '' });
|
||||
};
|
||||
|
||||
$scope.removeGroupSearchConfiguration = function (index) {
|
||||
$scope.formValues.LDAPSettings.GroupSearchSettings.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.LDAPConnectivityCheck = function () {
|
||||
var settings = angular.copy($scope.settings);
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
if ($scope.formValues.LDAPSettings.AnonymousMode) {
|
||||
settings.LDAPSettings['ReaderDN'] = '';
|
||||
settings.LDAPSettings['Password'] = '';
|
||||
}
|
||||
|
||||
var uploadRequired = ($scope.formValues.LDAPSettings.TLSConfig.TLS || $scope.formValues.LDAPSettings.StartTLS) && !$scope.formValues.LDAPSettings.TLSConfig.TLSSkipVerify;
|
||||
$scope.state.uploadInProgress = uploadRequired;
|
||||
|
||||
$scope.state.connectivityCheckInProgress = true;
|
||||
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(tlscaFile, null, null))
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
|
||||
.then(function success() {
|
||||
return LDAPService.check(settings.LDAPSettings);
|
||||
addLDAPDefaultPort(settings, $scope.formValues.LDAPSettings.TLSConfig.TLS);
|
||||
return SettingsService.checkLDAPConnectivity(settings);
|
||||
})
|
||||
.then(function success() {
|
||||
$scope.state.failedConnectivityCheck = false;
|
||||
|
@ -114,19 +119,24 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
|||
$scope.state.uploadInProgress = false;
|
||||
$scope.state.connectivityCheckInProgress = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.saveSettings = function () {
|
||||
const settings = angular.copy($scope.settings);
|
||||
var settings = angular.copy($scope.settings);
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
const { settings: ldapSettings, uploadRequired, tlscaFile } = prepareLDAPSettings();
|
||||
settings.LDAPSettings = ldapSettings;
|
||||
if ($scope.formValues.LDAPSettings.AnonymousMode) {
|
||||
settings.LDAPSettings['ReaderDN'] = '';
|
||||
settings.LDAPSettings['Password'] = '';
|
||||
}
|
||||
|
||||
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))
|
||||
$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() {
|
||||
|
@ -141,62 +151,11 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
|||
});
|
||||
};
|
||||
|
||||
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 = '';
|
||||
// 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';
|
||||
}
|
||||
|
||||
if (isADServer) {
|
||||
settings.AnonymousMode = false;
|
||||
}
|
||||
|
||||
settings.URLs = settings.URLs.map((url) => {
|
||||
if (url.includes(':')) {
|
||||
return url;
|
||||
}
|
||||
return url + (settings.TLSConfig.TLS ? ':636' : ':389');
|
||||
});
|
||||
|
||||
const uploadRequired = (settings.TLSConfig.TLS || settings.StartTLS) && !settings.TLSConfig.TLSSkipVerify;
|
||||
|
||||
settings.URL = settings.URLs[0];
|
||||
|
||||
return { settings, uploadRequired, tlscaFile };
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
const hasInvalidMapping = $scope.settings.OAuthSettings.TeamMemberships.OAuthClaimMappings.some((m) => !(m.ClaimValRegex && m.Team));
|
||||
if (hasInvalidMapping) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function initView() {
|
||||
|
@ -208,32 +167,9 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
|||
var settings = data.settings;
|
||||
$scope.teams = data.teams;
|
||||
$scope.settings = settings;
|
||||
|
||||
$scope.formValues.LDAPSettings = settings.LDAPSettings;
|
||||
$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;
|
||||
}
|
||||
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
@ -241,4 +177,5 @@ function SettingsAuthenticationController($q, $scope, $state, Notifications, Set
|
|||
}
|
||||
|
||||
initView();
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
|
Loading…
Reference in New Issue