'use strict'; /* global angular */ /* global Clipboard */ /* global $, TASK_TYPES */ 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.config = Client.getConfig(); $scope.userInfo = Client.getUserInfo(); $scope.adminDomain = null; $scope.oidcClients = []; $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 () { $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; // prevent the current user from getting locked out. if user ignores this, they have to use cloudron-support --admin-login if ($scope.profileConfig.mandatory2FA && !$scope.userInfo.twoFactorAuthenticationEnabled) { if ($scope.userInfo.source && $scope.config.external2FA) return; // no need for warning if 2fa is external Client.notify('', $translate.instant('users.settings.require2FAWarning'), true, 'error', '#/profile'); } }, 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: '', // last task error tasks: [], error: {}, // save error saveBusy: false, // fields provider: 'noop', autoCreate: true, url: '', acceptSelfSignedCerts: false, baseDn: '', filter: '', groupBaseDn: '', bindDn: '', bindPassword: '', usernameField: '', currentConfig: {}, init: function () { Client.getExternalLdapConfig(function (error, result) { if (error) return console.error('Unable to get external ldap config.', error); $scope.externalLdap.currentConfig = result; $scope.externalLdap.refreshTasks(); }); }, refreshTasks: function () { Client.getTasksByType(TASK_TYPES.TASK_SYNC_EXTERNAL_LDAP, function (error, tasks) { if (error) return console.error(error); $scope.externalLdap.tasks = tasks.slice(0, 10); if ($scope.externalLdap.tasks.length && $scope.externalLdap.tasks[0].active) $scope.externalLdap.updateStatus(); }); }, updateStatus: function () { var taskId = $scope.externalLdap.tasks[0].id; Client.getTask(taskId, function (error, data) { if (error) return window.setTimeout($scope.externalLdap.updateStatus, 5000); if (!data.active) { $scope.externalLdap.busy = false; $scope.externalLdap.message = ''; $scope.externalLdap.percent = 100; // indicates that 'result' is valid $scope.externalLdap.errorMessage = data.success ? '' : data.error.message; $scope.externalLdap.refreshTasks(); // update the tasks list dropdown return; } $scope.externalLdap.busy = true; $scope.externalLdap.percent = data.percent; $scope.externalLdap.message = data.message; window.setTimeout($scope.externalLdap.updateStatus, 500); }); }, 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'); }, sync: function () { $scope.externalLdap.busy = true; $scope.externalLdap.percent = 0; $scope.externalLdap.message = ''; $scope.externalLdap.errorMessage = ''; Client.startExternalLdapSync(function (error) { if (error) { console.error(error); $scope.externalLdap.errorMessage = error.message; $scope.externalLdap.busy = false; } else { $scope.externalLdap.refreshTasks(); } }); }, submit: function () { $scope.externalLdap.saveBusy = 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.saveBusy = 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; $scope.externalLdap.error.generic = error.message; } 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'); $scope.externalLdap.init(); } }); } }; $scope.refreshOIDCClients = function () { Client.getOidcClients(function (error, result) { if (error) return console.error('Failed to load oidc clients', error); $scope.oidcClients = result; }); }; $scope.clientAdd = { busy: false, error: null, name: '', loginRedirectUri: '', tokenSignatureAlgorithm: '', show: function () { $scope.clientAdd.name = ''; $scope.clientAdd.loginRedirectUri = ''; $scope.clientAdd.tokenSignatureAlgorithm = 'RS256'; $scope.clientAdd.busy = false; $scope.clientAdd.error = null; $scope.clientAddForm.$setPristine(); $('#oidcClientAddModal').modal('show'); }, submit: function () { $scope.clientAdd.busy = true; $scope.clientAdd.error = null; Client.addOidcClient($scope.clientAdd.name, $scope.clientAdd.loginRedirectUri, $scope.clientAdd.tokenSignatureAlgorithm, function (error) { if (error) { $scope.clientAdd.error = error.message; console.error('Unable to add openid client.', error); $scope.clientAdd.busy = false; return; } $scope.refreshOIDCClients(); $scope.clientAdd.busy = false; $('#oidcClientAddModal').modal('hide'); }); } }; $scope.clientEdit = { busy: false, error: null, id: '', secret: '', name: '', loginRedirectUri: '', tokenSignatureAlgorithm: '', show: function (client) { $scope.clientEdit.id = client.id; $scope.clientEdit.secret = client.secret; $scope.clientEdit.name = client.name; $scope.clientEdit.loginRedirectUri = client.loginRedirectUri; $scope.clientEdit.tokenSignatureAlgorithm = client.tokenSignatureAlgorithm; $scope.clientEdit.busy = false; $scope.clientEdit.error = null; $scope.clientEditForm.$setPristine(); $('#oidcClientEditModal').modal('show'); }, submit: function () { $scope.clientEdit.busy = true; $scope.clientEdit.error = null; Client.updateOidcClient($scope.clientEdit.id, $scope.clientEdit.name, $scope.clientEdit.loginRedirectUri, $scope.clientEdit.tokenSignatureAlgorithm, function (error) { if (error) { $scope.clientEdit.error = error.message; console.error('Unable to edit openid client.', error); $scope.clientEdit.busy = false; return; } $scope.refreshOIDCClients(); $scope.clientEdit.busy = false; $('#oidcClientEditModal').modal('hide'); }); } }; $scope.deleteClient = { busy: false, error: {}, id: '', show: function (client) { $scope.deleteClient.busy = false; $scope.deleteClient.id = client.id; $('#oidcClientDeleteModal').modal('show'); }, submit: function () { Client.delOidcClient($scope.deleteClient.id, function (error) { $scope.deleteClient.busy = false; if (error) return console.error('Failed to delete openid client', error); $scope.refreshOIDCClients(); $('#oidcClientDeleteModal').modal('hide'); }); } }; Client.onReady(function () { $scope.externalLdap.init(); $scope.profileConfig.refresh(); $scope.userDirectoryConfig.refresh(); $scope.refreshOIDCClients(); Client.getDomains(function (error, result) { if (error) return console.error('Unable to list domains.', error); $scope.adminDomain = result.filter(function (d) { return d.domain === $scope.config.adminDomain; })[0]; }); }); // setup all the dialog focus handling ['oidcClientAddModal', 'oidcClientEditModal'].forEach(function (id) { $('#' + id).on('shown.bs.modal', function () { $(this).find('[autofocus]:first').focus(); }); }); 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); }); new Clipboard('#clientIdInputClipboardButton').on('success', function(e) { $('#clientIdInputClipboardButton').tooltip({ title: 'Copied!', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#clientIdInputClipboardButton').tooltip('hide'); }, 2000); e.clearSelection(); }).on('error', function(/*e*/) { $('#clientIdInputClipboardButton').tooltip({ title: 'Press Ctrl+C to copy', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#clientIdInputClipboardButton').tooltip('hide'); }, 2000); }); new Clipboard('#clientSecretInputClipboardButton').on('success', function(e) { $('#clientSecretInputClipboardButton').tooltip({ title: 'Copied!', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#clientSecretInputClipboardButton').tooltip('hide'); }, 2000); e.clearSelection(); }).on('error', function(/*e*/) { $('#clientSecretInputClipboardButton').tooltip({ title: 'Press Ctrl+C to copy', trigger: 'manual' }).tooltip('show'); $timeout(function () { $('#clientSecretInputClipboardButton').tooltip('hide'); }, 2000); }); $('.modal-backdrop').remove(); }]);