Files
cloudron-box/dashboard/public/views/user-settings.js
2024-10-04 14:30:44 +02:00

487 lines
19 KiB
JavaScript

'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();
}]);