mirror of
https://github.com/portainer/portainer.git
synced 2025-08-05 13:55:21 +02:00
feat(authentication): add LDAP authentication support (#1093)
This commit is contained in:
parent
04ea81e7cd
commit
d27528a771
37 changed files with 922 additions and 166 deletions
|
@ -98,7 +98,7 @@
|
|||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
</div>
|
||||
<!-- !port-mapping -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
<!-- !driver-options -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<!-- actions -->
|
||||
<div class="col-sm-12 form-section-title">
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="Authentication settings">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>
|
||||
<a ui-sref="settings">Settings</a> > Authentication
|
||||
</rd-header-content>
|
||||
</rd-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-users" title="Authentication"></rd-widget-header>
|
||||
<rd-widget-body>
|
||||
<form class="form-horizontal">
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-sm-12 form-section-title">
|
||||
Information
|
||||
</div>
|
||||
<div class="form-group" ng-if="settings.AuthenticationMethod === 1">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
When using internal authentication, Portainer will encrypt user passwords and store credentials locally.
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group" ng-if="settings.AuthenticationMethod === 2">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
When using LDAP authentication, Portainer will delegate user authentication to a LDAP server (exception for the <b>admin</b> user that always use internal authentication).
|
||||
<p style="margin-top:5px;">
|
||||
<i class="fa fa-exclamation-triangle orange-icon" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
<u>Users still need to be created in Portainer beforehand.</u>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div ng-if="settings.AuthenticationMethod === 2">
|
||||
<div class="col-sm-12 form-section-title">
|
||||
LDAP configuration
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ldap_url" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
LDAP URL
|
||||
<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="LDAPSettings.URL" placeholder="e.g. 10.0.0.10:389 or myldap.domain.tld:389">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="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
|
||||
</label>
|
||||
<div class="col-sm-9 col-lg-10">
|
||||
<input type="password" class="form-control" id="ldap_password" ng-model="LDAPSettings.Password" placeholder="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="!LDAPSettings.TLSConfig.TLS && !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="!LDAPSettings.URL || !LDAPSettings.ReaderDN || !LDAPSettings.Password" ng-click="LDAPConnectivityCheck()">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">
|
||||
LDAP security
|
||||
</div>
|
||||
|
||||
<!-- starttls -->
|
||||
<div class="form-group" ng-if="!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="LDAPSettings.StartTLS"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !starttls -->
|
||||
|
||||
<!-- tls-checkbox -->
|
||||
<div class="form-group" ng-if="!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="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="LDAPSettings.TLSConfig.TLSSkipVerify"><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !tls-skip-verify -->
|
||||
|
||||
<!-- tls-certs -->
|
||||
<div ng-if="LDAPSettings.TLSConfig.TLS || LDAPSettings.StartTLS">
|
||||
<!-- ca-input -->
|
||||
<div class="form-group" ng-if="!LDAPSettings.TLSConfig.TLSSkipVerify">
|
||||
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
|
||||
<div class="col-sm-10">
|
||||
<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 === 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-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- !ca-input -->
|
||||
</div>
|
||||
<!-- !tls-certs -->
|
||||
|
||||
<div class="form-group" ng-if="LDAPSettings.TLSConfig.TLS || 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="!LDAPSettings.URL || !LDAPSettings.ReaderDN || !LDAPSettings.Password || (!formValues.TLSCACert && !LDAPSettings.TLSConfig.TLSSkipVerify)">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">
|
||||
User search configurations
|
||||
</div>
|
||||
|
||||
<!-- search-settings -->
|
||||
<div ng-repeat="config in LDAPSettings.SearchSettings | limitTo: (1 - 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 search configuration
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- !search-settings -->
|
||||
</div>
|
||||
|
||||
<!-- actions -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-click="saveSettings()">Save</button>
|
||||
<i id="updateSettingsSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<!-- <span class="text-danger" ng-if="state.formValidationError" style="margin-left: 5px;">{{ state.formValidationError }}</span> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- !actions -->
|
||||
|
||||
</form>
|
||||
</rd-widget-body>
|
||||
</rd-widget>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,93 @@
|
|||
angular.module('settingsAuthentication', [])
|
||||
.controller('SettingsAuthenticationController', ['$q', '$scope', 'Notifications', 'SettingsService', 'FileUploadService',
|
||||
function ($q, $scope, Notifications, SettingsService, FileUploadService) {
|
||||
|
||||
$scope.state = {
|
||||
successfulConnectivityCheck: false,
|
||||
failedConnectivityCheck: false,
|
||||
uploadInProgress: false
|
||||
};
|
||||
|
||||
$scope.formValues = {
|
||||
TLSCACert: ''
|
||||
};
|
||||
|
||||
$scope.addSearchConfiguration = function() {
|
||||
$scope.LDAPSettings.SearchSettings.push({ BaseDN: '', UserNameAttribute: '', Filter: '' });
|
||||
};
|
||||
|
||||
$scope.removeSearchConfiguration = function(index) {
|
||||
$scope.LDAPSettings.SearchSettings.splice(index, 1);
|
||||
};
|
||||
|
||||
$scope.LDAPConnectivityCheck = function() {
|
||||
$('#connectivityCheckSpinner').show();
|
||||
var settings = $scope.settings;
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
|
||||
$scope.state.uploadInProgress = uploadRequired;
|
||||
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
|
||||
.then(function success(data) {
|
||||
return SettingsService.checkLDAPConnectivity(settings);
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.state.failedConnectivityCheck = false;
|
||||
$scope.state.successfulConnectivityCheck = true;
|
||||
Notifications.success('Connection to LDAP successful');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
$scope.state.failedConnectivityCheck = true;
|
||||
$scope.state.successfulConnectivityCheck = false;
|
||||
Notifications.error('Failure', err, 'Connection to LDAP failed');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.uploadInProgress = false;
|
||||
$('#connectivityCheckSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.saveSettings = function() {
|
||||
$('#updateSettingsSpinner').show();
|
||||
var settings = $scope.settings;
|
||||
var TLSCAFile = $scope.formValues.TLSCACert !== settings.LDAPSettings.TLSConfig.TLSCACert ? $scope.formValues.TLSCACert : null;
|
||||
|
||||
var uploadRequired = ($scope.LDAPSettings.TLSConfig.TLS || $scope.LDAPSettings.StartTLS) && !$scope.LDAPSettings.TLSConfig.TLSSkipVerify;
|
||||
$scope.state.uploadInProgress = uploadRequired;
|
||||
|
||||
$q.when(!uploadRequired || FileUploadService.uploadLDAPTLSFiles(TLSCAFile, null, null))
|
||||
.then(function success(data) {
|
||||
return SettingsService.update(settings);
|
||||
})
|
||||
.then(function success(data) {
|
||||
Notifications.success('Authentication settings updated');
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to update authentication settings');
|
||||
})
|
||||
.finally(function final() {
|
||||
$scope.state.uploadInProgress = false;
|
||||
$('#updateSettingsSpinner').hide();
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
SettingsService.settings()
|
||||
.then(function success(data) {
|
||||
var settings = data;
|
||||
$scope.settings = settings;
|
||||
$scope.LDAPSettings = settings.LDAPSettings;
|
||||
$scope.formValues.TLSCACert = settings.LDAPSettings.TLSConfig.TLSCACert;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
|
@ -69,6 +69,9 @@
|
|||
</li>
|
||||
<li class="sidebar-list" ng-if="!applicationState.application.authentication || isAdmin">
|
||||
<a ui-sref="settings" ui-sref-active="active">Settings <span class="menu-icon fa fa-cogs"></span></a>
|
||||
<div class="sidebar-sublist" ng-if="toggle && ($state.current.name === 'settings' || $state.current.name === 'settings_authentication') && applicationState.application.authentication && isAdmin">
|
||||
<a ui-sref="settings_authentication" ui-sref-active="active">Authentication</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
|
|
|
@ -68,7 +68,7 @@
|
|||
</div>
|
||||
<!-- !env -->
|
||||
<!-- access-control -->
|
||||
<por-access-control-form form-data="formValues.AccessControlData"></por-access-control-form>
|
||||
<por-access-control-form form-data="formValues.AccessControlData" ng-if="applicationState.application.authentication"></por-access-control-form>
|
||||
<!-- !access-control -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
|
|
|
@ -32,29 +32,6 @@
|
|||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- <tr ng-if="!formValues.Administrator">
|
||||
<td colspan="2">
|
||||
<label for="teams" class="control-label text-left">
|
||||
Teams
|
||||
</label>
|
||||
<span class="small text-muted" style="margin-left: 20px;" ng-if="teams.length === 0">
|
||||
You have not yet created any team. Head over the <a ui-sref="teams">teams view</a> to manage user teams.</span>
|
||||
</span>
|
||||
<span isteven-multi-select
|
||||
ng-if="teams.length > 0"
|
||||
input-model="teams"
|
||||
output-model="formValues.Teams"
|
||||
button-label="Name"
|
||||
item-label="Name"
|
||||
tick-property="ticked"
|
||||
helper-elements="filter"
|
||||
search-property="Name"
|
||||
translation="{nothingSelected: 'Select one or more teams', search: 'Search...'}"
|
||||
style="margin-left: 20px;"
|
||||
on-item-click="onTeamClick(data)"
|
||||
</span>
|
||||
</td>
|
||||
</tr> -->
|
||||
</tbody>
|
||||
</table>
|
||||
</rd-widget-body>
|
||||
|
@ -62,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row" ng-if="AuthenticationMethod === 1">
|
||||
<div class="col-lg-12 col-md-12 col-xs-12">
|
||||
<rd-widget>
|
||||
<rd-widget-header icon="fa-lock" title="Change user password"></rd-widget-header>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('user', [])
|
||||
.controller('UserController', ['$q', '$scope', '$state', '$stateParams', 'UserService', 'ModalService', 'Notifications',
|
||||
function ($q, $scope, $state, $stateParams, UserService, ModalService, Notifications) {
|
||||
.controller('UserController', ['$q', '$scope', '$state', '$stateParams', 'UserService', 'ModalService', 'Notifications', 'SettingsService',
|
||||
function ($q, $scope, $state, $stateParams, UserService, ModalService, Notifications, SettingsService) {
|
||||
|
||||
$scope.state = {
|
||||
updatePasswordError: ''
|
||||
|
@ -72,12 +72,14 @@ function ($q, $scope, $state, $stateParams, UserService, ModalService, Notificat
|
|||
function initView() {
|
||||
$('#loadingViewSpinner').show();
|
||||
$q.all({
|
||||
user: UserService.user($stateParams.id)
|
||||
user: UserService.user($stateParams.id),
|
||||
settings: SettingsService.publicSettings()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var user = data.user;
|
||||
$scope.user = user;
|
||||
$scope.formValues.Administrator = user.Role === 1 ? true : false;
|
||||
$scope.AuthenticationMethod = data.settings.AuthenticationMethod;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve user information');
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<rd-header>
|
||||
<rd-header-title title="User settings">
|
||||
<i id="loadingViewSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px;"></i>
|
||||
</rd-header-title>
|
||||
<rd-header-content>User settings</rd-header-content>
|
||||
</rd-header>
|
||||
|
@ -58,7 +59,11 @@
|
|||
<!-- !confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.currentPassword || formValues.newPassword.length < 8 || formValues.newPassword !== formValues.confirmPassword" ng-click="updatePassword()">Update password</button>
|
||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="AuthenticationMethod !== 1 || !formValues.currentPassword || formValues.newPassword.length < 8 || formValues.newPassword !== formValues.confirmPassword" ng-click="updatePassword()">Update password</button>
|
||||
<span class="text-muted small" style="margin-left: 5px;" ng-if="AuthenticationMethod === 2">
|
||||
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
|
||||
You cannot change your password when using LDAP authentication.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('userSettings', [])
|
||||
.controller('UserSettingsController', ['$scope', '$state', '$sanitize', 'Authentication', 'UserService', 'Notifications',
|
||||
function ($scope, $state, $sanitize, Authentication, UserService, Notifications) {
|
||||
.controller('UserSettingsController', ['$scope', '$state', '$sanitize', 'Authentication', 'UserService', 'Notifications', 'SettingsService',
|
||||
function ($scope, $state, $sanitize, Authentication, UserService, Notifications, SettingsService) {
|
||||
$scope.formValues = {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
|
@ -26,4 +26,19 @@ function ($scope, $state, $sanitize, Authentication, UserService, Notifications)
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
function initView() {
|
||||
SettingsService.publicSettings()
|
||||
.then(function success(data) {
|
||||
$scope.AuthenticationMethod = data.AuthenticationMethod;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
})
|
||||
.finally(function final() {
|
||||
$('#loadingViewSpinner').hide();
|
||||
});
|
||||
}
|
||||
|
||||
initView();
|
||||
}]);
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
<form class="form-horizontal">
|
||||
<!-- name-input -->
|
||||
<div class="form-group">
|
||||
<label for="username" class="col-sm-2 control-label text-left">Username</label>
|
||||
<label for="username" class="col-sm-3 col-lg-2 control-label text-left">
|
||||
Username
|
||||
<portainer-tooltip ng-if="AuthenticationMethod === 2" position="bottom" message="Username must exactly match username defined in external LDAP source."></portainer-tooltip>
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="username" ng-model="formValues.Username" ng-change="checkUsernameValidity()" placeholder="e.g. jdoe">
|
||||
|
@ -27,8 +30,8 @@
|
|||
</div>
|
||||
<!-- !name-input -->
|
||||
<!-- new-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="password" class="col-sm-2 control-label text-left">Password</label>
|
||||
<div class="form-group" ng-if="AuthenticationMethod === 1">
|
||||
<label for="password" class="col-sm-3 col-lg-2 control-label text-left">Password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
|
@ -38,8 +41,8 @@
|
|||
</div>
|
||||
<!-- !new-password-input -->
|
||||
<!-- confirm-password-input -->
|
||||
<div class="form-group">
|
||||
<label for="confirm_password" class="col-sm-2 control-label text-left">Confirm password</label>
|
||||
<div class="form-group" ng-if="AuthenticationMethod === 1">
|
||||
<label for="confirm_password" class="col-sm-3 col-lg-2 control-label text-left">Confirm password</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
|
||||
|
@ -95,7 +98,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!state.validUsername || formValues.Username === '' || formValues.Password === '' || formValues.Password !== formValues.ConfirmPassword" ng-click="addUser()"><i class="fa fa-user-plus" aria-hidden="true"></i> Add user</button>
|
||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!state.validUsername || formValues.Username === '' || (AuthenticationMethod === 1 && formValues.Password === '') || (AuthenticationMethod === 1 && formValues.Password !== formValues.ConfirmPassword)" ng-click="addUser()"><i class="fa fa-user-plus" aria-hidden="true"></i> Add user</button>
|
||||
<i id="createUserSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||
<span class="text-danger" ng-if="state.userCreationError" style="margin: 5px;">
|
||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.userCreationError }}
|
||||
|
@ -140,19 +143,26 @@
|
|||
<input type="checkbox" ng-model="allSelected" ng-change="selectItems(allSelected)" />
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="users" ng-click="order('Username')">
|
||||
<a ng-click="order('Username')">
|
||||
Name
|
||||
<span ng-show="sortType == 'Username' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'Username' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ui-sref="users" ng-click="order('RoleName')">
|
||||
<a ng-click="order('RoleName')">
|
||||
Role
|
||||
<span ng-show="sortType == 'RoleName' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'RoleName' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a ng-click="order('AuthenticationMethod')">
|
||||
Authentication
|
||||
<span ng-show="sortType == 'AuthenticationMethod' && !sortReverse" class="glyphicon glyphicon-chevron-down"></span>
|
||||
<span ng-show="sortType == 'AuthenticationMethod' && sortReverse" class="glyphicon glyphicon-chevron-up"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th ng-if="isAdmin"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -166,6 +176,10 @@
|
|||
<i ng-if="user.isTeamLeader" class="fa fa-user-plus" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||
{{ user.RoleName }}
|
||||
</td>
|
||||
<td>
|
||||
<span ng-if="AuthenticationMethod === 1 || user.Id === 1">Internal</span>
|
||||
<span ng-if="AuthenticationMethod === 2 && user.Id !== 1">LDAP</span>
|
||||
</td>
|
||||
<td ng-if="isAdmin">
|
||||
<a ui-sref="user({id: user.Id})"><i class="fa fa-pencil-square-o" aria-hidden="true"></i> Edit</a>
|
||||
</td>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
angular.module('users', [])
|
||||
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication',
|
||||
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication) {
|
||||
.controller('UsersController', ['$q', '$scope', '$state', '$sanitize', 'UserService', 'TeamService', 'TeamMembershipService', 'ModalService', 'Notifications', 'Pagination', 'Authentication', 'SettingsService',
|
||||
function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershipService, ModalService, Notifications, Pagination, Authentication, SettingsService) {
|
||||
$scope.state = {
|
||||
userCreationError: '',
|
||||
selectedItemCount: 0,
|
||||
|
@ -140,13 +140,15 @@ function ($q, $scope, $state, $sanitize, UserService, TeamService, TeamMembershi
|
|||
$q.all({
|
||||
users: UserService.users(true),
|
||||
teams: isAdmin ? TeamService.teams() : UserService.userLeadingTeams(userDetails.ID),
|
||||
memberships: TeamMembershipService.memberships()
|
||||
memberships: TeamMembershipService.memberships(),
|
||||
settings: SettingsService.publicSettings()
|
||||
})
|
||||
.then(function success(data) {
|
||||
var users = data.users;
|
||||
assignTeamLeaders(users, data.memberships);
|
||||
$scope.users = users;
|
||||
$scope.teams = data.teams;
|
||||
$scope.AuthenticationMethod = data.settings.AuthenticationMethod;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve users and teams');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue