diff --git a/dashboard/src/views/user-settings.html b/dashboard/src/views/user-settings.html
new file mode 100644
index 000000000..c31c80849
--- /dev/null
+++ b/dashboard/src/views/user-settings.html
@@ -0,0 +1,956 @@
+
+
+
+
+
+ {{ 'users.title' | tr }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{ 'users.users.user' | tr }} |
+ {{ 'users.users.groups' | tr }} |
+ {{ 'main.actions' | tr }} |
+
+
+
+
+ | {{ 'users.users.empty' | tr }} |
+
+
+ |
+
+
+
+
+
+ |
+
+ {{ user.displayName }} {{ user.username }}
+ |
+
+ {{ user.email }}
+ |
+
+
+ {{ groupsById[groupId].name }}
+
+ |
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ {{ 'users.users.count' | tr:{ count: allUsers.length } }}
+
+
+
+ {{ currentPage }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'users.groups.title' | tr }}
+
+
+
+
+
+
+
+
+
+
+
+
+ | {{ 'users.groups.name' | tr }} |
+ {{ 'users.groups.users' | tr }} |
+ {{ 'main.actions' | tr }} |
+
+
+
+
+ |
+ {{ group.name }}
+ |
+
+ {{ groupMembers(group) }}
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ profileConfig.errorMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ 'users.externalLdap.description' | tr }}
+
+
+
+
+
+
+
+
+
+
+ {{ 'users.externalLdap.noopInfo' | tr }}
+
+
+
+
+
+ {{ 'users.externalLdap.provider' | tr }}
+
+
+ {{ externalLdap.currentConfig.provider }}
+
+
+
+
+
+ {{ 'users.externalLdap.server' | tr }}
+
+
+ {{ externalLdap.currentConfig.url }}
+
+
+
+
+
+ {{ 'users.externalLdap.acceptSelfSignedCert' | tr }}
+
+
+ {{ externalLdap.currentConfig.acceptSelfSignedCerts ? 'Yes' : 'No' }}
+
+
+
+
+
+ {{ 'users.externalLdap.baseDn' | tr }}
+
+
+ {{ externalLdap.currentConfig.baseDn }}
+
+
+
+
+
+ {{ 'users.externalLdap.filter' | tr }}
+
+
+ {{ externalLdap.currentConfig.filter }}
+
+
+
+
+
+ {{ 'users.externalLdap.usernameField' | tr }}
+
+
+ {{ externalLdap.currentConfig.usernameField || 'uid' }}
+
+
+
+
+
+ {{ 'users.externalLdap.syncGroups' | tr }}
+
+
+ {{ externalLdap.currentConfig.syncGroups ? 'Yes' : 'No' }}
+
+
+
+
+
+ {{ 'users.externalLdap.groupBaseDn' | tr }}
+
+
+ {{ externalLdap.currentConfig.groupBaseDn }}
+
+
+
+
+
+ {{ 'users.externalLdap.groupFilter' | tr }}
+
+
+ {{ externalLdap.currentConfig.groupFilter }}
+
+
+
+
+
+ {{ 'users.externalLdap.groupnameField' | tr }}
+
+
+ {{ externalLdap.currentConfig.groupnameField }}
+
+
+
+
+
+ {{ 'users.externalLdap.auth' | tr }}
+
+
+ {{ externalLdap.currentConfig.bindDn ? 'Yes' : 'No' }}
+
+
+
+
+
+ {{ 'users.externalLdap.autocreateUsersOnLogin' | tr }}
+
+
+ {{ externalLdap.currentConfig.autoCreate ? 'Yes' : 'No' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ 'users.exposedLdap.description' | tr }}
+
+
+
+
+
+
+
+
+ {{ userDirectoryConfig.error.generic }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dashboard/src/views/user-settings.js b/dashboard/src/views/user-settings.js
new file mode 100644
index 000000000..1222ca49b
--- /dev/null
+++ b/dashboard/src/views/user-settings.js
@@ -0,0 +1,1308 @@
+'use strict';
+
+/* global angular */
+/* global Clipboard */
+/* global async */
+/* global ROLES */
+/* global $ */
+
+angular.module('Application').controller('UserSettingsController', ['$scope', '$location', '$translate', '$timeout', 'Client', function ($scope, $location, $translate, $timeout, Client) {
+ Client.onReady(function () { if (!Client.getUserInfo().isAtLeastAdmin) $location.path('/'); });
+
+ $scope.ldapProvider = [
+ { name: 'Active Directory', value: 'ad' },
+ { name: 'Cloudron', value: 'cloudron' },
+ { name: 'Jumpcloud', value: 'jumpcloud' },
+ { name: 'Okta', value: 'okta' },
+ { name: 'Univention Corporate Server (UCS)', value: 'univention' },
+ { name: 'Other', value: 'other' },
+ { name: 'Disabled', value: 'noop' }
+ ];
+
+ $translate(['users.externalLdap.providerOther', 'users.externalLdap.providerDisabled']).then(function (tr) {
+ if (tr['users.externalLdap.providerOther']) $scope.ldapProvider.find(function (p) { return p.value === 'other'; }).name = tr['users.externalLdap.providerOther'];
+ if (tr['users.externalLdap.providerDisabled']) $scope.ldapProvider.find(function (p) { return p.value === 'noop'; }).name = tr['users.externalLdap.providerDisabled'];
+ });
+
+ $scope.ready = false;
+ $scope.users = []; // users of current page
+ $scope.allUsersById = [];
+ $scope.groups = [];
+ $scope.groupsById = { };
+ $scope.config = Client.getConfig();
+ $scope.userInfo = Client.getUserInfo();
+ $scope.domains = [];
+
+ $scope.openSubscriptionSetup = function () {
+ Client.openSubscriptionSetup($scope.$parent.subscription);
+ };
+
+ $scope.roles = [];
+ $scope.allUsers = []; // all the users and not just current page, have to load this for group assignment
+
+ $scope.userSearchString = '';
+ $scope.currentPage = 1;
+ $scope.pageItems = localStorage.cloudronPageSize || 15;
+ $scope.userRefreshBusy = true;
+
+ $scope.userStates = [
+ { state: 'ALL', value: null, label: 'All Users' },
+ { state: 'ACTIVE', value: true, label: 'Active Users' },
+ { state: 'INACTIVE', value: false, label: 'Inactive Users' }
+ ];
+ $translate(['users.stateFilter.all', 'users.stateFilter.active', 'users.stateFilter.inactive']).then(function (tr) {
+ if (tr['users.stateFilter.all']) $scope.userStates.find(function (a) { return a.state === 'ALL'; }).label = tr['users.stateFilter.all'];
+ if (tr['users.stateFilter.active']) $scope.userStates.find(function (a) { return a.state === 'ACTIVE'; }).label = tr['users.stateFilter.active'];
+ if (tr['users.stateFilter.inactive']) $scope.userStates.find(function (a) { return a.state === 'INACTIVE'; }).label = tr['users.stateFilter.inactive'];
+ });
+
+ $scope.userStateFilter = $scope.userStates[0];
+ $scope.$watch('userStateFilter', function (newVal, oldVal) {
+ if (newVal === oldVal) return;
+ $scope.updateFilter();
+ });
+
+ $scope.groupMembers = function (group) {
+ return group.userIds.filter(function (uid) { return !!$scope.allUsersById[uid]; }).map(function (uid) { return $scope.allUsersById[uid].username || $scope.allUsersById[uid].email; }).join(' ');
+ };
+
+ $scope.canEdit = function (user) {
+ let roleInt1 = $scope.roles.findIndex(function (role) { return role.id === $scope.userInfo.role; });
+ let roleInt2 = $scope.roles.findIndex(function (role) { return role.id === user.role; });
+
+ return (roleInt1 - roleInt2) >= 0;
+ };
+
+ $scope.canImpersonate = function (user) {
+ // only admins can impersonate
+ if (!$scope.userInfo.isAtLeastAdmin) return false;
+
+ // only users with username can be impersonated
+ if (!user.username) return false;
+
+ // normal admins cannot impersonate owners
+ if (!$scope.userInfo.isAtLeastOwner && [ ROLES.OWNER ].indexOf(user.role) !== -1) return false;
+
+ return true;
+ };
+
+ $scope.userImport = {
+ busy: false,
+ done: false,
+ error: null,
+ percent: 0,
+ success: 0,
+ users: [],
+ sendInvite: false,
+
+ reset: function () {
+ $scope.userImport.busy = false;
+ $scope.userImport.error = null;
+ $scope.userImport.users = [];
+ $scope.userImport.percent = 0;
+ $scope.userImport.success = 0;
+ $scope.userImport.done = false;
+ $scope.userImport.sendInvite = false;
+ },
+
+ handleFileChanged: function () {
+ $scope.userImport.reset();
+
+ var fileInput = document.getElementById('userImportFileInput');
+ if (!fileInput.files || !fileInput.files[0]) return;
+
+ var file = fileInput.files[0];
+ if (file.type !== 'application/json' && file.type !== 'text/csv') return console.log('Unsupported file type.');
+
+ const reader = new FileReader();
+ reader.addEventListener('load', function () {
+ $scope.$apply(function () {
+ $scope.userImport.users = [];
+ var users = [];
+ if (file.type === 'text/csv') {
+ var lines = reader.result.split('\n');
+ if (lines.length === 0) return $scope.userImport.error = { file: 'Imported file has no lines' };
+
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].trim();
+ if (!line) continue;
+ var items = line.split(',');
+ if (items.length !== 5) {
+ $scope.userImport.error = { file: 'Line ' + (i+1) + ' has wrong column count. Expecting 5' };
+ return;
+ }
+ users.push({
+ username: items[0].trim(),
+ email: items[1].trim(),
+ fallbackEmail: items[2].trim(),
+ displayName: items[3].trim(),
+ role: items[4].trim()
+ });
+ }
+ } else {
+ try {
+ users = JSON.parse(reader.result).map(function (user) {
+ return {
+ username: user.username,
+ email: user.email,
+ fallbackEmail: user.fallbackEmail,
+ displayName: user.displayName,
+ role: user.role
+ };
+ });
+ } catch (e) {
+ console.error('Failed to parse users.', e);
+ $scope.userImport.error = { file: 'Imported file is not valid JSON:' + e.message };
+ }
+ }
+ $scope.userImport.users = users;
+ });
+ }, false);
+ reader.readAsText(file);
+ },
+
+ show: function () {
+ $scope.userImport.reset();
+
+ // named so no duplactes
+ document.getElementById('userImportFileInput').addEventListener('change', $scope.userImport.handleFileChanged);
+
+ $('#userImportModal').modal('show');
+ },
+
+ openFileInput: function () {
+ $('#userImportFileInput').click();
+ },
+
+ import: function () {
+ $scope.userImport.percent = 0;
+ $scope.userImport.success = 0;
+ $scope.userImport.done = false;
+ $scope.userImport.error = { import: [] };
+ $scope.userImport.busy = true;
+
+ var processed = 0;
+
+ async.eachSeries($scope.userImport.users, function (user, callback) {
+ Client.addUser(user, function (error, userId) {
+ if (error) $scope.userImport.error.import.push({ error: error, user: user });
+ else ++$scope.userImport.success;
+
+ ++processed;
+ $scope.userImport.percent = 100 * processed / $scope.userImport.users.length;
+
+ if (!error && $scope.userImport.sendInvite) {
+ console.log('sending', userId, user.email);
+ Client.sendInviteEmail(userId, user.email, function (error) {
+ if (error) console.error('Failed to send invite.', error);
+ });
+ }
+
+ callback();
+ });
+ }, function (error) {
+ if (error) return console.error(error);
+
+ $scope.userImport.busy = false;
+ $scope.userImport.done = true;
+ if ($scope.userImport.success) {
+ refresh();
+ refreshAllUsers();
+ }
+ });
+ }
+ };
+
+ // supported types are 'json' and 'csv'
+ $scope.userExport = function (type) {
+ Client.getAllUsers(function (error, result) {
+ if (error) {
+ Client.error('Failed to list users. Full error in the webinspector.');
+ return console.error('Failed to list users.', error);
+ }
+
+ var content = '';
+ if (type === 'json') {
+ content = JSON.stringify(result.map(function (user) {
+ return {
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ fallbackEmail: user.fallbackEmail,
+ displayName: user.displayName,
+ role: user.role,
+ active: user.active
+ };
+ }), null, 2);
+ } else if (type === 'csv') {
+ content = result.map(function (user) {
+ return [ user.id, user.username, user.email, user.fallbackEmail, user.displayName, user.role, user.active ].join(',');
+ }).join('\n');
+ } else {
+ return;
+ }
+
+ var file = new Blob([ content ], { type: type === 'json' ? 'application/json' : 'text/csv' });
+ var a = document.createElement('a');
+ a.href = URL.createObjectURL(file);
+ a.download = type === 'json' ? 'users.json' : 'users.csv';
+ document.body.appendChild(a);
+ a.click();
+ });
+ };
+
+ $scope.userremove = {
+ busy: false,
+ error: null,
+ userInfo: {},
+
+ show: function (userInfo) {
+ $scope.userremove.error = null;
+ $scope.userremove.userInfo = userInfo;
+
+ $('#userRemoveModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.userremove.busy = true;
+
+ Client.removeUser($scope.userremove.userInfo.id, function (error) {
+ $scope.userremove.busy = false;
+
+ if (error && error.statusCode === 403) return $scope.userremove.error = error.message;
+ else if (error) return console.error('Unable to delete user.', error);
+
+ $scope.userremove.userInfo = {};
+
+ refresh();
+ refreshAllUsers();
+
+ $('#userRemoveModal').modal('hide');
+ });
+ }
+ };
+
+ $scope.useradd = {
+ busy: false,
+ alreadyTaken: false,
+ error: {},
+ email: '',
+ fallbackEmail: '',
+ username: '',
+ displayName: '',
+ selectedGroups: [],
+ role: 'user',
+ sendInvite: false,
+
+ show: function () {
+ $scope.useradd.error = {};
+ $scope.useradd.email = '';
+ $scope.useradd.fallbackEmail = '';
+ $scope.useradd.username = '';
+ $scope.useradd.displayName = '';
+ $scope.useradd.selectedGroups = [];
+ $scope.useradd.role = 'user';
+ $scope.useradd.sendInvite = false;
+
+ $scope.useraddForm.$setUntouched();
+ $scope.useraddForm.$setPristine();
+
+ $('#userAddModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.useradd.busy = true;
+
+ $scope.useradd.alreadyTaken = false;
+ $scope.useradd.error.email = null;
+ $scope.useradd.error.fallbackEmail = null;
+ $scope.useradd.error.username = null;
+ $scope.useradd.error.displayName = null;
+
+ var user = {
+ username: $scope.useradd.username || null,
+ email: $scope.useradd.email,
+ fallbackEmail: $scope.useradd.fallbackEmail,
+ displayName: $scope.useradd.displayName,
+ role: $scope.useradd.role
+ };
+
+ Client.addUser(user, function (error, userId) {
+ if (error) {
+ $scope.useradd.busy = false;
+
+ if (error.statusCode === 409) {
+ if (error.message.toLowerCase().indexOf('email') !== -1) {
+ $scope.useradd.error.email = 'Email already taken';
+ $scope.useraddForm.email.$setPristine();
+ $('#inputUserAddEmail').focus();
+ } else if (error.message.toLowerCase().indexOf('username') !== -1 || error.message.toLowerCase().indexOf('mailbox') !== -1) {
+ $scope.useradd.error.username = 'Username already taken';
+ $scope.useraddForm.username.$setPristine();
+ $('#inputUserAddUsername').focus();
+ } else {
+ // should not happen!!
+ console.error(error.message);
+ }
+ return;
+ } else if (error.statusCode === 400) {
+ if (error.message.toLowerCase().indexOf('email') !== -1) {
+ $scope.useradd.error.email = 'Invalid Email';
+ $scope.useradd.error.emailAttempted = $scope.useradd.email;
+ $scope.useraddForm.email.$setPristine();
+ $('#inputUserAddEmail').focus();
+ } else if (error.message.toLowerCase().indexOf('username') !== -1) {
+ $scope.useradd.error.username = error.message;
+ $scope.useraddForm.username.$setPristine();
+ $('#inputUserAddUsername').focus();
+ } else {
+ console.error('Unable to create user.', error.statusCode, error.message);
+ }
+ return;
+ } else {
+ return console.error('Unable to create user.', error.statusCode, error.message);
+ }
+ }
+
+ var groupIds = $scope.useradd.selectedGroups.map(function (g) { return g.id; });
+
+ Client.setGroups(userId, groupIds, function (error) {
+ $scope.useradd.busy = false;
+
+ if (error) return console.error(error);
+
+ if ($scope.useradd.sendInvite) Client.sendInviteEmail(userId, user.email, function (error) { if (error) console.error('Failed to send invite.', error); });
+
+ refresh();
+ refreshAllUsers();
+
+ $('#userAddModal').modal('hide');
+ });
+ });
+ }
+ };
+
+ $scope.useredit = {
+ busy: false,
+ reset2FABusy: false,
+ error: {},
+ userInfo: {},
+
+ // form fields
+ username: '',
+ email: '',
+ fallbackEmail: '',
+ aliases: {},
+ displayName: '',
+ active: false,
+ source: '',
+ selectedGroups: [],
+ role: '',
+
+ show: function (userInfo) {
+ $scope.useredit.error = {};
+ $scope.useredit.username = userInfo.username;
+ $scope.useredit.email = userInfo.email;
+ $scope.useredit.displayName = userInfo.displayName;
+ $scope.useredit.fallbackEmail = userInfo.fallbackEmail;
+ $scope.useredit.userInfo = userInfo;
+ $scope.useredit.selectedGroups = userInfo.groupIds.map(function (gid) { return $scope.groupsById[gid]; });
+ $scope.useredit.active = userInfo.active;
+ $scope.useredit.source = userInfo.source;
+ $scope.useredit.role = userInfo.role;
+
+ $scope.useredit_form.$setPristine();
+ $scope.useredit_form.$setUntouched();
+
+ $('#userEditModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.useredit.error = {};
+ $scope.useredit.busy = true;
+
+ var userId = $scope.useredit.userInfo.id;
+ var data = {
+ id: userId
+ };
+
+ // only send if not the current active user
+ if (userId !== $scope.userInfo.id) {
+ data.active = $scope.useredit.active;
+ data.role = $scope.useredit.role;
+ }
+
+ // only change those if it is a local user
+ if (!$scope.useredit.source) {
+ // username is settable only if it was empty previously. it's editable for the "lock" profiles feature
+ if (!$scope.useredit.userInfo.username) data.username = $scope.useredit.username;
+ data.email = $scope.useredit.email;
+ data.displayName = $scope.useredit.displayName;
+ data.fallbackEmail = $scope.useredit.fallbackEmail;
+ }
+
+ Client.updateUser(data, function (error) {
+ if (error) {
+ $scope.useredit.busy = false;
+
+ if (error.statusCode === 409) {
+ if (error.message.toLowerCase().indexOf('email') !== -1) {
+ $scope.useredit.error.email = 'Email already taken';
+ } else if (error.message.toLowerCase().indexOf('username') !== -1) {
+ $scope.useredit.error.username = 'Username already taken';
+ }
+ $scope.useredit_form.email.$setPristine();
+ $('#inputUserEditEmail').focus();
+ } else {
+ $scope.useredit.error.generic = error.message;
+ console.error('Unable to update user:', error);
+ }
+
+ return;
+ }
+
+ var groupIds = $scope.useredit.selectedGroups.map(function (g) { return g.id; });
+
+ Client.setGroups(data.id, groupIds, function (error) {
+ $scope.useredit.busy = false;
+
+ if (error) return console.error('Unable to update groups for user:', error);
+
+ refreshUsers(false);
+
+ $('#userEditModal').modal('hide');
+ });
+ });
+ },
+
+ reset2FA: function () {
+ $scope.useredit.reset2FABusy = true;
+
+ Client.disableTwoFactorAuthenticationByUserId($scope.useredit.userInfo.id, function (error) {
+ if (error) return console.error(error);
+
+ $timeout(function () {
+ $scope.useredit.userInfo.twoFactorAuthenticationEnabled = false;
+ $scope.useredit.reset2FABusy = false;
+ }, 3000);
+ });
+ }
+ };
+
+ $scope.groupAdd = {
+ busy: false,
+ error: {},
+ name: '',
+ selectedUsers: [],
+
+ show: function () {
+ $scope.groupAdd.busy = false;
+
+ $scope.groupAdd.error = {};
+ $scope.groupAdd.name = '';
+
+ $scope.groupAddForm.$setUntouched();
+ $scope.groupAddForm.$setPristine();
+
+ $('#groupAddModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.groupAdd.busy = true;
+ $scope.groupAdd.error = {};
+
+ Client.createGroup($scope.groupAdd.name, function (error, result) {
+ if (error) {
+ $scope.groupAdd.busy = false;
+
+ if (error.statusCode === 409) {
+ $scope.groupAdd.error.name = 'Name already taken';
+ $scope.groupAddForm.name.$setPristine();
+ $('#groupAddName').focus();
+ return;
+ } else if (error.statusCode === 400) {
+ $scope.groupAdd.error.name = error.message;
+ $scope.groupAddForm.name.$setPristine();
+ $('#groupAddName').focus();
+ return;
+ } else {
+ return console.error('Unable to create group.', error.statusCode, error.message);
+ }
+ }
+
+ var userIds = $scope.groupAdd.selectedUsers.map(function (u) { return u.id; });
+
+ Client.setGroupMembers(result.id, userIds, function (error) {
+ $scope.groupAdd.busy = false;
+
+ if (error) return console.error('Unable to add memebers.', error.statusCode, error.message);
+
+ refresh();
+
+ $('#groupAddModal').modal('hide');
+ });
+ });
+ }
+ };
+
+ $scope.groupEdit = {
+ busy: false,
+ error: {},
+ groupInfo: {},
+ name: '',
+ source: '',
+ selectedUsers: [],
+ selectedApps: [],
+ selectedAppsOriginal: [],
+ apps: [],
+
+ show: function (groupInfo) {
+ $scope.groupEdit.error = {};
+ $scope.groupEdit.groupInfo = groupInfo;
+ $scope.groupEdit.name = groupInfo.name;
+ $scope.groupEdit.source = groupInfo.source;
+ $scope.groupEdit.selectedUsers = groupInfo.userIds.map(function (uid) { return $scope.allUsersById[uid]; });
+ $scope.groupEdit.apps = Client.getInstalledApps();
+
+ $scope.groupEdit.selectedApps = Client.getInstalledApps().filter(function (app) {
+ if (app.accessRestriction === null || !Array.isArray(app.accessRestriction.groups)) return false;
+ return app.accessRestriction.groups.indexOf(groupInfo.id) !== -1;
+ });
+ angular.copy($scope.groupEdit.selectedApps, $scope.groupEdit.selectedAppsOriginal);
+
+ $scope.groupEdit_form.$setPristine();
+ $scope.groupEdit_form.$setUntouched();
+
+ $('#groupEditModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.groupEdit.busy = true;
+ $scope.groupEdit.error = {};
+
+ Client.updateGroup($scope.groupEdit.groupInfo.id, $scope.groupEdit.name, function (error) {
+ if (error) {
+ $scope.groupEdit.busy = false;
+
+ if (error.statusCode === 409) {
+ $scope.groupEdit.error.name = 'Name already taken';
+ $scope.groupEditForm.name.$setPristine();
+ $('#groupEditName').focus();
+ return;
+ } else if (error.statusCode === 400) {
+ $scope.groupEdit.error.name = error.message;
+ $scope.groupEditForm.name.$setPristine();
+ $('#groupEditName').focus();
+ return;
+ } else {
+ return console.error('Unable to edit group.', error.statusCode, error.message);
+ }
+ }
+
+ var userIds = $scope.groupEdit.selectedUsers.map(function (u) { return u.id; });
+
+ Client.setGroupMembers($scope.groupEdit.groupInfo.id, userIds, function (error) {
+ if (error) {
+ $scope.groupEdit.busy = false;
+ return console.error('Unable to set group members.', error.statusCode, error.message);
+ }
+
+ // find apps where ACL has changed
+ var addedApps = $scope.groupEdit.selectedApps.filter(function (a) {
+ return !$scope.groupEdit.selectedAppsOriginal.find(function (b) { return b.id === a.id; });
+ });
+ var removedApps = $scope.groupEdit.selectedAppsOriginal.filter(function (a) {
+ return !$scope.groupEdit.selectedApps.find(function (b) { return b.id === a.id; });
+ });
+
+ async.eachSeries(addedApps, function (app, callback) {
+ var accessRestriction = app.accessRestriction;
+ if (!accessRestriction) accessRestriction = { users: [], groups: [] };
+ if (!Array.isArray(accessRestriction.groups)) accessRestriction.groups = [];
+
+ accessRestriction.groups.push($scope.groupEdit.groupInfo.id);
+
+ Client.configureApp(app.id, 'access_restriction', { accessRestriction: accessRestriction }, callback);
+ }, function (error) {
+ if (error) {
+ $scope.groupEdit.busy = false;
+ return console.error('Unable to set added app access.', error.statusCode, error.message);
+ }
+
+ async.eachSeries(removedApps, function (app, callback) {
+ var accessRestriction = app.accessRestriction;
+ if (!accessRestriction) accessRestriction = { users: [], groups: [] };
+ if (!Array.isArray(accessRestriction.groups)) accessRestriction.groups = [];
+
+ var deleted = accessRestriction.groups.splice(accessRestriction.groups.indexOf($scope.groupEdit.groupInfo.id), 1);
+
+ // if not found return early
+ if (deleted.length === 0) return callback();
+
+ Client.configureApp(app.id, 'access_restriction', { accessRestriction: accessRestriction }, callback);
+ }, function (error) {
+ $scope.groupEdit.busy = false;
+ if (error) return console.error('Unable to set removed app access.', error.statusCode, error.message);
+
+ refresh();
+
+ // refresh apps to reflect change
+ Client.refreshInstalledApps();
+
+ $('#groupEditModal').modal('hide');
+ });
+ });
+ });
+ });
+ }
+ };
+
+ $scope.groupRemove = {
+ busy: false,
+ error: {},
+ group: null,
+ memberCount: 0,
+
+ show: function (group) {
+ $scope.groupRemove.busy = false;
+
+ $scope.groupRemove.error = {};
+
+ $scope.groupRemove.group = angular.copy(group);
+
+ Client.getGroup(group.id, function (error, result) {
+ if (error) return console.error('Unable to fetch group information.', error.statusCode, error.message);
+
+ $scope.groupRemove.memberCount = result.userIds.length;
+
+ $('#groupRemoveModal').modal('show');
+ });
+ },
+
+ submit: function () {
+ $scope.groupRemove.busy = true;
+ $scope.groupRemove.error = {};
+
+ Client.removeGroup($scope.groupRemove.group.id, function (error) {
+ $scope.groupRemove.busy = false;
+
+ if (error) return console.error('Unable to remove group.', error.statusCode, error.message);
+
+ refresh();
+ $('#groupRemoveModal').modal('hide');
+ });
+ }
+ };
+
+ $scope.isMe = function (user) {
+ return user.username === Client.getUserInfo().username;
+ };
+
+ $scope.passwordReset = {
+ busy: false,
+ resetLink: '',
+ user: null,
+ email: '',
+ emailError: null,
+
+ show: function (user) {
+ $scope.passwordReset.busy = false;
+ $scope.passwordReset.resetLink = '';
+ $scope.passwordReset.user = user;
+ $scope.passwordReset.email = user.fallbackEmail || user.email;
+ $scope.passwordReset.emailError = null;
+
+ Client.getPasswordResetLink(user.id, function (error, result) {
+ if (error) return console.error('Failed to get password reset link.', error);
+
+ $scope.passwordReset.resetLink = result.passwordResetLink;
+
+ $('#passwordResetModal').modal('show');
+ });
+ },
+
+ sendEmail: function () {
+ $scope.passwordReset.busy = true;
+ $scope.passwordReset.emailError = null;
+
+ Client.sendPasswordResetEmail($scope.passwordReset.user.id, $scope.passwordReset.email, function (error) {
+ $scope.passwordReset.busy = false;
+
+ if (error) {
+ $scope.passwordReset.emailError = error.message;
+ } else {
+ $scope.passwordReset.emailError = '';
+ }
+ });
+ }
+ };
+
+ $scope.makeLocal = {
+ busy: false,
+ user: null,
+
+ show: function (user) {
+ $scope.makeLocal.busy = false;
+ $scope.makeLocal.user = user;
+
+ $('#makeLocalModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.makeLocal.busy = false;
+
+ Client.makeUserLocal($scope.makeLocal.user.id, function (error) {
+ if (error) return console.error('Failed to make user local.', error);
+
+ $scope.makeLocal.busy = false;
+
+ refreshUsers();
+
+ $('#makeLocalModal').modal('hide');
+ });
+ }
+ };
+
+ $scope.invitation = {
+ busy: false,
+ inviteLink: '',
+ user: null,
+ email: '',
+
+ show: function (user) {
+ $scope.invitation.busy = false;
+ $scope.invitation.inviteLink = '';
+ $scope.invitation.user = user;
+ $scope.invitation.email = user.email;
+
+ Client.getInviteLink(user.id, function (error, result) {
+ if (error) return console.error('Failed to get invite link.', error);
+
+ $scope.invitation.inviteLink = result.inviteLink;
+
+ $('#invitationModal').modal('show');
+ });
+ },
+
+ sendEmail: function () {
+ $scope.invitation.busy = true;
+
+ Client.sendInviteEmail($scope.invitation.user.id, $scope.invitation.email, function (error) {
+ if (error) return console.error('Failed to send invite email.', error);
+
+ $scope.invitation.busy = false;
+
+ Client.notify($translate.instant('users.invitationNotification.title'), $translate.instant('users.invitationNotification.body', { email: $scope.invitation.email }), false, 'success');
+ });
+ }
+ };
+
+ // https://stackoverflow.com/questions/1497481/javascript-password-generator
+ function generatePassword() {
+ var length = 12,
+ charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
+ retVal = '';
+ for (var i = 0, n = charset.length; i < length; ++i) {
+ retVal += charset.charAt(Math.floor(Math.random() * n));
+ }
+ return retVal;
+ }
+
+ $scope.setGhost = {
+ busy: false,
+ error: null,
+ success: false,
+ user: null,
+ password: '',
+
+ show: function (user) {
+ $scope.setGhost.busy = false;
+ $scope.setGhost.success = false;
+ $scope.setGhost.error = null;
+ $scope.setGhost.user = user;
+ $scope.setGhost.password = '';
+
+ $('#setGhostModal').modal('show');
+ },
+
+ generatePassword: function () {
+ $scope.setGhost.password = generatePassword();
+ },
+
+ submit: function () {
+ $scope.setGhost.busy = true;
+
+ Client.setGhost($scope.setGhost.user.id, $scope.setGhost.password, null, function (error) {
+ $scope.setGhost.busy = false;
+
+ if (error) {
+ $scope.setGhost.error = error.message;
+ return console.error(error);
+ }
+
+ $scope.setGhost.success = true;
+ });
+ }
+ };
+
+ $scope.profileConfig = {
+ editableUserProfiles: true,
+ mandatory2FA: false,
+ errorMessage: '',
+
+ refresh: function () {
+ Client.getProfileConfig(function (error, result) {
+ if (error) return console.error('Unable to get directory config.', error);
+
+ $scope.profileConfig.editableUserProfiles = !result.lockUserProfiles;
+ $scope.profileConfig.mandatory2FA = !!result.mandatory2FA;
+ });
+ },
+
+ submit: function () {
+ // prevent the current user from getting locked out
+ if ($scope.profileConfig.mandatory2FA && !$scope.userInfo.twoFactorAuthenticationEnabled) return Client.notify('', $translate.instant('users.settings.require2FAWarning'), true, 'error', '#/profile');
+
+ $scope.profileConfig.error = '';
+ $scope.profileConfig.busy = true;
+ $scope.profileConfig.success = false;
+
+ var data = {
+ lockUserProfiles: !$scope.profileConfig.editableUserProfiles,
+ mandatory2FA: $scope.profileConfig.mandatory2FA
+ };
+
+ Client.setProfileConfig(data, function (error) {
+ if (error) $scope.profileConfig.errorMessage = error.message;
+
+ $scope.profileConfig.success = true;
+
+ $scope.profileConfigForm.$setUntouched();
+ $scope.profileConfigForm.$setPristine();
+
+ Client.refreshConfig(); // refresh the $scope.config
+
+ $timeout(function () {
+ $scope.profileConfig.busy = false;
+ }, 500);
+ });
+ }
+ };
+
+ $scope.userDirectoryConfig = {
+ enabled: false,
+ secret: '',
+ allowlist: '',
+ error: null,
+
+ refresh: function () {
+ Client.getUserDirectoryConfig(function (error, result) {
+ if (error) return console.error('Unable to get exposed ldap config.', error);
+
+ $scope.userDirectoryConfig.enabled = !!result.enabled;
+ $scope.userDirectoryConfig.allowlist = result.allowlist;
+ $scope.userDirectoryConfig.secret = result.secret;
+ });
+ },
+
+ submit: function () {
+ $scope.userDirectoryConfig.error = null;
+ $scope.userDirectoryConfig.busy = true;
+ $scope.userDirectoryConfig.success = false;
+
+ var data = {
+ enabled: $scope.userDirectoryConfig.enabled,
+ secret: $scope.userDirectoryConfig.secret,
+ allowlist: $scope.userDirectoryConfig.allowlist
+ };
+
+ Client.setUserDirectoryConfig(data, function (error) {
+ $scope.userDirectoryConfig.busy = false;
+
+ if (error && error.statusCode === 400) {
+ if (error.message.indexOf('secret') !== -1) return $scope.userDirectoryConfig.error = { secret: error.message };
+ else return $scope.userDirectoryConfig.error = { allowlist: error.message };
+ }
+ if (error) return $scope.userDirectoryConfig.error = { generic: error.message };
+
+ $scope.userDirectoryConfigForm.$setUntouched();
+ $scope.userDirectoryConfigForm.$setPristine();
+
+ $scope.userDirectoryConfig.success = true;
+ });
+ }
+ };
+
+ $scope.externalLdap = {
+ busy: false,
+ percent: 0,
+ message: '',
+ errorMessage: '',
+ error: {},
+ taskId: 0,
+
+ syncBusy: false,
+
+ // fields
+ provider: 'noop',
+ autoCreate: false,
+ url: '',
+ acceptSelfSignedCerts: false,
+ baseDn: '',
+ filter: '',
+ groupBaseDn: '',
+ bindDn: '',
+ bindPassword: '',
+ usernameField: '',
+
+ currentConfig: {},
+
+ checkStatus: function () {
+ Client.getLatestTaskByType('syncExternalLdap', function (error, task) {
+ if (error) return console.error(error);
+
+ if (!task) return;
+
+ $scope.externalLdap.taskId = task.id;
+ $scope.externalLdap.updateStatus();
+ });
+ },
+
+ sync: function () {
+ $scope.externalLdap.syncBusy = true;
+
+ Client.startExternalLdapSync(function (error, taskId) {
+ if (error) {
+ $scope.externalLdap.syncBusy = false;
+ console.error('Unable to start ldap syncer task.', error);
+ return;
+ }
+
+ $scope.externalLdap.taskId = taskId;
+ $scope.externalLdap.updateStatus();
+ });
+ },
+
+ updateStatus: function () {
+ Client.getTask($scope.externalLdap.taskId, function (error, data) {
+ if (error) return window.setTimeout($scope.externalLdap.updateStatus, 5000);
+
+ if (!data.active) {
+ $scope.externalLdap.syncBusy = false;
+ $scope.externalLdap.message = '';
+ $scope.externalLdap.percent = 100; // indicates that 'result' is valid
+ $scope.externalLdap.errorMessage = data.success ? '' : data.error.message;
+
+ refreshGroups();
+ refreshUsers();
+
+ return;
+ }
+
+ $scope.externalLdap.syncBusy = true;
+ $scope.externalLdap.percent = data.percent;
+ $scope.externalLdap.message = data.message;
+ window.setTimeout($scope.externalLdap.updateStatus, 3000);
+ });
+ },
+
+ show: function () {
+ $scope.externalLdap.busy = false;
+ $scope.externalLdap.error = {};
+
+ $scope.externalLdap.provider = $scope.externalLdap.currentConfig.provider;
+ $scope.externalLdap.url = $scope.externalLdap.currentConfig.url;
+ $scope.externalLdap.acceptSelfSignedCerts = $scope.externalLdap.currentConfig.acceptSelfSignedCerts;
+ $scope.externalLdap.baseDn = $scope.externalLdap.currentConfig.baseDn;
+ $scope.externalLdap.filter = $scope.externalLdap.currentConfig.filter;
+ $scope.externalLdap.syncGroups = $scope.externalLdap.currentConfig.syncGroups;
+ $scope.externalLdap.groupBaseDn = $scope.externalLdap.currentConfig.groupBaseDn;
+ $scope.externalLdap.groupFilter = $scope.externalLdap.currentConfig.groupFilter;
+ $scope.externalLdap.groupnameField = $scope.externalLdap.currentConfig.groupnameField;
+ $scope.externalLdap.bindDn = $scope.externalLdap.currentConfig.bindDn;
+ $scope.externalLdap.bindPassword = $scope.externalLdap.currentConfig.bindPassword;
+ $scope.externalLdap.usernameField = $scope.externalLdap.currentConfig.usernameField;
+ $scope.externalLdap.autoCreate = $scope.externalLdap.currentConfig.autoCreate;
+
+ $('#externalLdapModal').modal('show');
+ },
+
+ submit: function () {
+ $scope.externalLdap.busy = true;
+ $scope.externalLdap.error = {};
+
+ var config = {
+ provider: $scope.externalLdap.provider
+ };
+
+ if ($scope.externalLdap.provider === 'cloudron') {
+ config.url = $scope.externalLdap.url;
+ config.acceptSelfSignedCerts = $scope.externalLdap.acceptSelfSignedCerts;
+ config.autoCreate = $scope.externalLdap.autoCreate;
+ config.syncGroups = $scope.externalLdap.syncGroups;
+ config.bindPassword = $scope.externalLdap.bindPassword;
+
+ // those values are known and thus overwritten
+ config.baseDn = 'ou=users,dc=cloudron';
+ config.filter = '(objectClass=inetOrgPerson)';
+ config.usernameField = 'username';
+ config.groupBaseDn = 'ou=groups,dc=cloudron';
+ config.groupFilter = '(objectClass=group)';
+ config.groupnameField = 'cn';
+ config.bindDn = 'cn=admin,ou=system,dc=cloudron';
+ } else if ($scope.externalLdap.provider !== 'noop') {
+ config.url = $scope.externalLdap.url;
+ config.acceptSelfSignedCerts = $scope.externalLdap.acceptSelfSignedCerts;
+ config.baseDn = $scope.externalLdap.baseDn;
+ config.filter = $scope.externalLdap.filter;
+ config.usernameField = $scope.externalLdap.usernameField;
+ config.syncGroups = $scope.externalLdap.syncGroups;
+ config.groupBaseDn = $scope.externalLdap.groupBaseDn;
+ config.groupFilter = $scope.externalLdap.groupFilter;
+ config.groupnameField = $scope.externalLdap.groupnameField;
+ config.autoCreate = $scope.externalLdap.autoCreate;
+
+ if ($scope.externalLdap.bindDn) {
+ config.bindDn = $scope.externalLdap.bindDn;
+ config.bindPassword = $scope.externalLdap.bindPassword;
+ }
+ }
+
+ Client.setExternalLdapConfig(config, function (error) {
+ $scope.externalLdap.busy = false;
+
+ if (error) {
+ if (error.statusCode === 424) {
+ if (error.code === 'SELF_SIGNED_CERT_IN_CHAIN') $scope.externalLdap.error.acceptSelfSignedCerts = true;
+ else $scope.externalLdap.error.url = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid baseDn') {
+ $scope.externalLdap.error.baseDn = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid filter') {
+ $scope.externalLdap.error.filter = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid groupBaseDn') {
+ $scope.externalLdap.error.groupBaseDn = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid groupFilter') {
+ $scope.externalLdap.error.groupFilter = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid groupnameField') {
+ $scope.externalLdap.error.groupnameField = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid bind credentials') {
+ $scope.externalLdap.error.credentials = true;
+ } else if (error.statusCode === 400 && error.message === 'invalid usernameField') {
+ $scope.externalLdap.error.usernameField = true;
+ } else {
+ console.error('Failed to set external LDAP config:', error);
+ $scope.externalLdap.error.generic = error.message;
+ }
+ } else {
+ $('#externalLdapModal').modal('hide');
+
+ loadExternalLdapConfig();
+ }
+ });
+ }
+ };
+
+ function getUsers(callback) {
+ var users = [];
+
+ Client.getUsers($scope.userSearchString, $scope.userStateFilter.value, $scope.currentPage, $scope.pageItems, function (error, results) {
+ if (error) return console.error(error);
+
+ async.eachOf(results, function (result, index, iteratorDone) {
+ Client.getUser(result.id, function (error, user) {
+ if (error) return iteratorDone(error);
+
+ users[index] = user; // keep the sorting order
+
+ iteratorDone();
+ });
+ }, function (error) {
+ callback(error, users);
+ });
+ });
+ }
+
+ function refreshUsers(showBusy) { // loads users on current page only
+ if (showBusy) $scope.userRefreshBusy = true;
+
+ getUsers(function (error, result) {
+ if (error) return console.error('Unable to get user listing.', error);
+
+ angular.copy(result, $scope.users);
+
+ $scope.ready = true;
+ $scope.userRefreshBusy = false;
+ });
+ }
+
+ function refreshGroups(callback) {
+ Client.getGroups(function (error, result) {
+ if (error) {
+ if (callback) return callback(error);
+ else return console.error('Unable to get group listing.', error);
+ }
+
+ angular.copy(result, $scope.groups);
+ $scope.groupsById = { };
+ for (var i = 0; i < result.length; i++) {
+ $scope.groupsById[result[i].id] = result[i];
+ }
+
+ if (callback) callback();
+ });
+ }
+
+ function refresh() {
+ refreshGroups(function (error) {
+ if (error) return console.error('Unable to get group listing.', error);
+ refreshUsers(true);
+ });
+ }
+
+ function loadExternalLdapConfig() {
+ Client.getExternalLdapConfig(function (error, result) {
+ if (error) return console.error('Unable to get external ldap config.', error);
+
+ $scope.externalLdap.currentConfig = result;
+ $scope.externalLdap.checkStatus();
+ });
+ }
+
+ $scope.showNextPage = function () {
+ $scope.currentPage++;
+ refreshUsers();
+ };
+
+ $scope.showPrevPage = function () {
+ if ($scope.currentPage > 1) $scope.currentPage--;
+ else $scope.currentPage = 1;
+ refreshUsers();
+ };
+
+ $scope.updateFilter = function () {
+ refreshUsers();
+ };
+
+ function refreshAllUsers() { // this loads all users on Cloudron, not just current page
+ Client.getAllUsers(function (error, results) {
+ if (error) return console.error(error);
+
+ $scope.allUsers = results;
+
+ $scope.allUsersById = {};
+ for (var i = 0; i < results.length; i++) {
+ $scope.allUsersById[results[i].id] = results[i];
+ }
+ });
+ }
+
+ function getAllDomains() {
+ Client.getDomains(function (error, domains) {
+ if (error) return console.error('Unable to get domains:', error);
+
+ $scope.domains = domains;
+ });
+ }
+
+ Client.onReady(function () {
+ refresh();
+ if ($scope.user.isAtLeastAdmin) loadExternalLdapConfig();
+ if ($scope.user.isAtLeastAdmin) $scope.profileConfig.refresh();
+ if ($scope.user.isAtLeastAdmin) $scope.userDirectoryConfig.refresh();
+ if ($scope.user.isAtLeastAdmin) getAllDomains();
+ refreshAllUsers();
+
+ // Order matters for permissions used in canEdit
+ $scope.roles = [
+ { id: 'user', name: $translate.instant('users.role.user'), disabled: false },
+ { id: 'usermanager', name: $translate.instant('users.role.usermanager'), disabled: false },
+ { id: 'mailmanager', name: $translate.instant('users.role.mailmanager'), disabled: false },
+ { id: 'admin', name: $translate.instant('users.role.admin'), disabled: !$scope.user.isAtLeastAdmin },
+ { id: 'owner', name: $translate.instant('users.role.owner'), disabled: !$scope.user.isAtLeastOwner }
+ ];
+
+ // give search the initial focus
+ setTimeout(function () { $('#userSearchInput').focus(); }, 1);
+ });
+
+ // setup all the dialog focus handling
+ ['userAddModal', 'userRemoveModal', 'userEditModal', 'groupAddModal', 'groupEditModal', 'groupRemoveModal'].forEach(function (id) {
+ $('#' + id).on('shown.bs.modal', function () {
+ $(this).find("[autofocus]:first").focus();
+ });
+ });
+
+ new Clipboard('#passwordResetLinkClipboardButton').on('success', function(e) {
+ $('#passwordResetLinkClipboardButton').tooltip({
+ title: 'Copied!',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#passwordResetLinkClipboardButton').tooltip('hide'); }, 2000);
+
+ e.clearSelection();
+ }).on('error', function(/*e*/) {
+ $('#passwordResetLinkClipboardButton').tooltip({
+ title: 'Press Ctrl+C to copy',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#passwordResetLinkClipboardButton').tooltip('hide'); }, 2000);
+ });
+
+ new Clipboard('#invitationLinkClipboardButton').on('success', function(e) {
+ $('#invitationLinkClipboardButton').tooltip({
+ title: 'Copied!',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#invitationLinkClipboardButton').tooltip('hide'); }, 2000);
+
+ e.clearSelection();
+ }).on('error', function(/*e*/) {
+ $('#invitationLinkClipboardButton').tooltip({
+ title: 'Press Ctrl+C to copy',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#invitationLinkClipboardButton').tooltip('hide'); }, 2000);
+ });
+
+ new Clipboard('#setGhostClipboardButton').on('success', function(e) {
+ $('#setGhostClipboardButton').tooltip({
+ title: 'Copied!',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#setGhostClipboardButton').tooltip('hide'); }, 2000);
+
+ e.clearSelection();
+ }).on('error', function(/*e*/) {
+ $('#setGhostClipboardButton').tooltip({
+ title: 'Press Ctrl+C to copy',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#setGhostClipboardButton').tooltip('hide'); }, 2000);
+ });
+
+ new Clipboard('#userDirectoryUrlClipboardButton').on('success', function(e) {
+ $('#userDirectoryUrlClipboardButton').tooltip({
+ title: 'Copied!',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#userDirectoryUrlClipboardButton').tooltip('hide'); }, 2000);
+
+ e.clearSelection();
+ }).on('error', function(/*e*/) {
+ $('#userDirectoryUrlClipboardButton').tooltip({
+ title: 'Press Ctrl+C to copy',
+ trigger: 'manual'
+ }).tooltip('show');
+
+ $timeout(function () { $('#userDirectoryUrlClipboardButton').tooltip('hide'); }, 2000);
+ });
+
+ $('.modal-backdrop').remove();
+}]);
diff --git a/dashboard/src/views/users.html b/dashboard/src/views/users.html
index c31c80849..ab878435b 100644
--- a/dashboard/src/views/users.html
+++ b/dashboard/src/views/users.html
@@ -447,107 +447,6 @@
-
-
@@ -727,165 +626,6 @@
-
-
-
-
-
{{ 'users.externalLdap.description' | tr }}
-
-
-
-
-
-
-
-
-
-
- {{ 'users.externalLdap.noopInfo' | tr }}
-
-
-
-
-
- {{ 'users.externalLdap.provider' | tr }}
-
-
- {{ externalLdap.currentConfig.provider }}
-
-
-
-
-
- {{ 'users.externalLdap.server' | tr }}
-
-
- {{ externalLdap.currentConfig.url }}
-
-
-
-
-
- {{ 'users.externalLdap.acceptSelfSignedCert' | tr }}
-
-
- {{ externalLdap.currentConfig.acceptSelfSignedCerts ? 'Yes' : 'No' }}
-
-
-
-
-
- {{ 'users.externalLdap.baseDn' | tr }}
-
-
- {{ externalLdap.currentConfig.baseDn }}
-
-
-
-
-
- {{ 'users.externalLdap.filter' | tr }}
-
-
- {{ externalLdap.currentConfig.filter }}
-
-
-
-
-
- {{ 'users.externalLdap.usernameField' | tr }}
-
-
- {{ externalLdap.currentConfig.usernameField || 'uid' }}
-
-
-
-
-
- {{ 'users.externalLdap.syncGroups' | tr }}
-
-
- {{ externalLdap.currentConfig.syncGroups ? 'Yes' : 'No' }}
-
-
-
-
-
- {{ 'users.externalLdap.groupBaseDn' | tr }}
-
-
- {{ externalLdap.currentConfig.groupBaseDn }}
-
-
-
-
-
- {{ 'users.externalLdap.groupFilter' | tr }}
-
-
- {{ externalLdap.currentConfig.groupFilter }}
-
-
-
-
-
- {{ 'users.externalLdap.groupnameField' | tr }}
-
-
- {{ externalLdap.currentConfig.groupnameField }}
-
-
-
-
-
- {{ 'users.externalLdap.auth' | tr }}
-
-
- {{ externalLdap.currentConfig.bindDn ? 'Yes' : 'No' }}
-
-
-
-
-
- {{ 'users.externalLdap.autocreateUsersOnLogin' | tr }}
-
-
- {{ externalLdap.currentConfig.autoCreate ? 'Yes' : 'No' }}
-
-
-
-
-
-
-
-
-
diff --git a/dashboard/src/views/users.js b/dashboard/src/views/users.js
index ff83b5b5f..968efa0ec 100644
--- a/dashboard/src/views/users.js
+++ b/dashboard/src/views/users.js
@@ -19,11 +19,6 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
{ name: 'Disabled', value: 'noop' }
];
- $translate(['users.externalLdap.providerOther', 'users.externalLdap.providerDisabled']).then(function (tr) {
- if (tr['users.externalLdap.providerOther']) $scope.ldapProvider.find(function (p) { return p.value === 'other'; }).name = tr['users.externalLdap.providerOther'];
- if (tr['users.externalLdap.providerDisabled']) $scope.ldapProvider.find(function (p) { return p.value === 'noop'; }).name = tr['users.externalLdap.providerDisabled'];
- });
-
$scope.ready = false;
$scope.users = []; // users of current page
$scope.allUsersById = [];
@@ -933,175 +928,6 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
}
};
- $scope.externalLdap = {
- busy: false,
- percent: 0,
- message: '',
- errorMessage: '',
- error: {},
- taskId: 0,
-
- syncBusy: false,
-
- // fields
- provider: 'noop',
- autoCreate: false,
- url: '',
- acceptSelfSignedCerts: false,
- baseDn: '',
- filter: '',
- groupBaseDn: '',
- bindDn: '',
- bindPassword: '',
- usernameField: '',
-
- currentConfig: {},
-
- checkStatus: function () {
- Client.getLatestTaskByType('syncExternalLdap', function (error, task) {
- if (error) return console.error(error);
-
- if (!task) return;
-
- $scope.externalLdap.taskId = task.id;
- $scope.externalLdap.updateStatus();
- });
- },
-
- sync: function () {
- $scope.externalLdap.syncBusy = true;
-
- Client.startExternalLdapSync(function (error, taskId) {
- if (error) {
- $scope.externalLdap.syncBusy = false;
- console.error('Unable to start ldap syncer task.', error);
- return;
- }
-
- $scope.externalLdap.taskId = taskId;
- $scope.externalLdap.updateStatus();
- });
- },
-
- updateStatus: function () {
- Client.getTask($scope.externalLdap.taskId, function (error, data) {
- if (error) return window.setTimeout($scope.externalLdap.updateStatus, 5000);
-
- if (!data.active) {
- $scope.externalLdap.syncBusy = false;
- $scope.externalLdap.message = '';
- $scope.externalLdap.percent = 100; // indicates that 'result' is valid
- $scope.externalLdap.errorMessage = data.success ? '' : data.error.message;
-
- refreshGroups();
- refreshUsers();
-
- return;
- }
-
- $scope.externalLdap.syncBusy = true;
- $scope.externalLdap.percent = data.percent;
- $scope.externalLdap.message = data.message;
- window.setTimeout($scope.externalLdap.updateStatus, 3000);
- });
- },
-
- show: function () {
- $scope.externalLdap.busy = false;
- $scope.externalLdap.error = {};
-
- $scope.externalLdap.provider = $scope.externalLdap.currentConfig.provider;
- $scope.externalLdap.url = $scope.externalLdap.currentConfig.url;
- $scope.externalLdap.acceptSelfSignedCerts = $scope.externalLdap.currentConfig.acceptSelfSignedCerts;
- $scope.externalLdap.baseDn = $scope.externalLdap.currentConfig.baseDn;
- $scope.externalLdap.filter = $scope.externalLdap.currentConfig.filter;
- $scope.externalLdap.syncGroups = $scope.externalLdap.currentConfig.syncGroups;
- $scope.externalLdap.groupBaseDn = $scope.externalLdap.currentConfig.groupBaseDn;
- $scope.externalLdap.groupFilter = $scope.externalLdap.currentConfig.groupFilter;
- $scope.externalLdap.groupnameField = $scope.externalLdap.currentConfig.groupnameField;
- $scope.externalLdap.bindDn = $scope.externalLdap.currentConfig.bindDn;
- $scope.externalLdap.bindPassword = $scope.externalLdap.currentConfig.bindPassword;
- $scope.externalLdap.usernameField = $scope.externalLdap.currentConfig.usernameField;
- $scope.externalLdap.autoCreate = $scope.externalLdap.currentConfig.autoCreate;
-
- $('#externalLdapModal').modal('show');
- },
-
- submit: function () {
- $scope.externalLdap.busy = true;
- $scope.externalLdap.error = {};
-
- var config = {
- provider: $scope.externalLdap.provider
- };
-
- if ($scope.externalLdap.provider === 'cloudron') {
- config.url = $scope.externalLdap.url;
- config.acceptSelfSignedCerts = $scope.externalLdap.acceptSelfSignedCerts;
- config.autoCreate = $scope.externalLdap.autoCreate;
- config.syncGroups = $scope.externalLdap.syncGroups;
- config.bindPassword = $scope.externalLdap.bindPassword;
-
- // those values are known and thus overwritten
- config.baseDn = 'ou=users,dc=cloudron';
- config.filter = '(objectClass=inetOrgPerson)';
- config.usernameField = 'username';
- config.groupBaseDn = 'ou=groups,dc=cloudron';
- config.groupFilter = '(objectClass=group)';
- config.groupnameField = 'cn';
- config.bindDn = 'cn=admin,ou=system,dc=cloudron';
- } else if ($scope.externalLdap.provider !== 'noop') {
- config.url = $scope.externalLdap.url;
- config.acceptSelfSignedCerts = $scope.externalLdap.acceptSelfSignedCerts;
- config.baseDn = $scope.externalLdap.baseDn;
- config.filter = $scope.externalLdap.filter;
- config.usernameField = $scope.externalLdap.usernameField;
- config.syncGroups = $scope.externalLdap.syncGroups;
- config.groupBaseDn = $scope.externalLdap.groupBaseDn;
- config.groupFilter = $scope.externalLdap.groupFilter;
- config.groupnameField = $scope.externalLdap.groupnameField;
- config.autoCreate = $scope.externalLdap.autoCreate;
-
- if ($scope.externalLdap.bindDn) {
- config.bindDn = $scope.externalLdap.bindDn;
- config.bindPassword = $scope.externalLdap.bindPassword;
- }
- }
-
- Client.setExternalLdapConfig(config, function (error) {
- $scope.externalLdap.busy = false;
-
- if (error) {
- if (error.statusCode === 424) {
- if (error.code === 'SELF_SIGNED_CERT_IN_CHAIN') $scope.externalLdap.error.acceptSelfSignedCerts = true;
- else $scope.externalLdap.error.url = true;
- } else if (error.statusCode === 400 && error.message === 'invalid baseDn') {
- $scope.externalLdap.error.baseDn = true;
- } else if (error.statusCode === 400 && error.message === 'invalid filter') {
- $scope.externalLdap.error.filter = true;
- } else if (error.statusCode === 400 && error.message === 'invalid groupBaseDn') {
- $scope.externalLdap.error.groupBaseDn = true;
- } else if (error.statusCode === 400 && error.message === 'invalid groupFilter') {
- $scope.externalLdap.error.groupFilter = true;
- } else if (error.statusCode === 400 && error.message === 'invalid groupnameField') {
- $scope.externalLdap.error.groupnameField = true;
- } else if (error.statusCode === 400 && error.message === 'invalid bind credentials') {
- $scope.externalLdap.error.credentials = true;
- } else if (error.statusCode === 400 && error.message === 'invalid usernameField') {
- $scope.externalLdap.error.usernameField = true;
- } else {
- console.error('Failed to set external LDAP config:', error);
- $scope.externalLdap.error.generic = error.message;
- }
- } else {
- $('#externalLdapModal').modal('hide');
-
- loadExternalLdapConfig();
- }
- });
- }
- };
-
function getUsers(callback) {
var users = [];
@@ -1159,15 +985,6 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
});
}
- function loadExternalLdapConfig() {
- Client.getExternalLdapConfig(function (error, result) {
- if (error) return console.error('Unable to get external ldap config.', error);
-
- $scope.externalLdap.currentConfig = result;
- $scope.externalLdap.checkStatus();
- });
- }
-
$scope.showNextPage = function () {
$scope.currentPage++;
refreshUsers();
@@ -1206,7 +1023,6 @@ angular.module('Application').controller('UsersController', ['$scope', '$locatio
Client.onReady(function () {
refresh();
- if ($scope.user.isAtLeastAdmin) loadExternalLdapConfig();
if ($scope.user.isAtLeastAdmin) $scope.profileConfig.refresh();
if ($scope.user.isAtLeastAdmin) $scope.userDirectoryConfig.refresh();
if ($scope.user.isAtLeastAdmin) getAllDomains();