mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin 4 - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
//////////////////////////////////////////////////////////////
|
|
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
import { makeStyles } from '@material-ui/core';
|
|
import SchemaView from '../../../../static/js/SchemaView';
|
|
import BaseUISchema from '../../../../static/js/SchemaView/base_schema.ui';
|
|
import pgAdmin from 'sources/pgadmin';
|
|
import Theme from 'sources/Theme';
|
|
import gettext from 'sources/gettext';
|
|
import url_for from 'sources/url_for';
|
|
import PropTypes from 'prop-types';
|
|
import getApiInstance from '../../../../static/js/api_instance';
|
|
import authConstant from 'pgadmin.browser.constants';
|
|
import current_user from 'pgadmin.user_management.current_user';
|
|
import { isEmptyString } from '../../../../static/js/validators';
|
|
import Notify from '../../../../static/js/helpers/Notifier';
|
|
import { showChangeOwnership } from '../../../../static/js/Dialogs/index';
|
|
|
|
class UserManagementCollection extends BaseUISchema {
|
|
constructor(authSources, roleOptions) {
|
|
super({
|
|
id: undefined,
|
|
username: undefined,
|
|
email: undefined,
|
|
active: true,
|
|
role: '2',
|
|
newPassword: undefined,
|
|
confirmPassword: undefined,
|
|
auth_source: authConstant['INTERNAL']
|
|
});
|
|
|
|
this.authOnlyInternal = (current_user['auth_sources'].length == 1 &&
|
|
current_user['auth_sources'].includes(authConstant['INTERNAL'])) ? true : false;
|
|
this.authSources = authSources;
|
|
this.roleOptions = roleOptions;
|
|
}
|
|
|
|
get idAttribute() {
|
|
return 'id';
|
|
}
|
|
|
|
isUserNameEnabled(state) {
|
|
if (this.authOnlyInternal || state.auth_source == authConstant['INTERNAL']) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
isEditable(state) {
|
|
return state.id != current_user['id'];
|
|
}
|
|
|
|
get baseFields() {
|
|
let obj = this;
|
|
return [
|
|
{
|
|
id: 'auth_source', label: gettext('Authentication source'), cell: 'select',
|
|
options: obj.authSources, minWidth: 110, width: 110,
|
|
controlProps: {
|
|
allowClear: false,
|
|
openOnEnter: false,
|
|
first_empty: false,
|
|
},
|
|
visible: function() {
|
|
if (obj.authOnlyInternal)
|
|
return false;
|
|
return true;
|
|
},
|
|
editable: function(state) {
|
|
return (obj.isNew(state) && !obj.authOnlyInternal);
|
|
}
|
|
}, {
|
|
id: 'username', label: gettext('Username'), cell: 'text',
|
|
minWidth: 90, width: 90,
|
|
deps: ['auth_source'],
|
|
depChange: (state)=>{
|
|
if (obj.isUserNameEnabled(state) && obj.isNew(state) && !isEmptyString(obj.username)) {
|
|
return {username: undefined};
|
|
}
|
|
},
|
|
editable: (state)=> {
|
|
return obj.isUserNameEnabled(state);
|
|
}
|
|
}, {
|
|
id: 'email', label: gettext('Email'), cell: 'text',
|
|
minWidth: 90, width: 90, deps: ['id'],
|
|
editable: (state)=> {
|
|
if (obj.isNew(state))
|
|
return true;
|
|
|
|
if (obj.isEditable(state) && state.auth_source != authConstant['INTERNAL'])
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
}, {
|
|
id: 'role', label: gettext('Role'), cell: 'select',
|
|
options: obj.roleOptions, minWidth: 95, width: 95,
|
|
controlProps: {
|
|
allowClear: false,
|
|
openOnEnter: false,
|
|
first_empty: false,
|
|
},
|
|
editable: (state)=> {
|
|
return obj.isEditable(state);
|
|
}
|
|
}, {
|
|
id: 'active', label: gettext('Active'), cell: 'switch', width: 60, disableResizing: true,
|
|
editable: (state)=> {
|
|
return obj.isEditable(state);
|
|
}
|
|
}, {
|
|
id: 'newPassword', label: gettext('New password'), cell: 'password',
|
|
minWidth: 90, width: 90, deps: ['auth_source'],
|
|
editable: (state)=> {
|
|
return obj.isEditable(state) && state.auth_source == authConstant['INTERNAL'];
|
|
}
|
|
}, {
|
|
id: 'confirmPassword', label: gettext('Confirm password'), cell: 'password',
|
|
minWidth: 90, width: 90, deps: ['auth_source'],
|
|
editable: (state)=> {
|
|
return obj.isEditable(state) && state.auth_source == authConstant['INTERNAL'];
|
|
}
|
|
}, {
|
|
id: 'locked', label: gettext('Locked'), cell: 'switch', width: 60, disableResizing: true,
|
|
editable: (state)=> {
|
|
return obj.isEditable(state);
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
validate(state, setError) {
|
|
let msg = undefined;
|
|
let obj = this;
|
|
if (obj.isUserNameEnabled(state) && isEmptyString(state.username)) {
|
|
msg = gettext('Username cannot be empty.');
|
|
setError('username', msg);
|
|
return true;
|
|
} else {
|
|
setError('username', null);
|
|
}
|
|
|
|
if (state.auth_source == authConstant['INTERNAL']) {
|
|
let email_filter = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
if (isEmptyString(state.email)) {
|
|
msg = gettext('Email cannot be empty.');
|
|
setError('email', msg);
|
|
return true;
|
|
} else if (!email_filter.test(state.email)) {
|
|
msg = gettext('Invalid email address: %s.', state.email);
|
|
setError('email', msg);
|
|
return true;
|
|
} else {
|
|
setError('email', null);
|
|
}
|
|
// TODO: Check for duplicate email address errmsg = gettext('The email address %s already exists.'
|
|
|
|
if (obj.isNew(state) && isEmptyString(state.newPassword)) {
|
|
msg = gettext('Password cannot be empty for user %s.', state.email);
|
|
setError('newPassword', msg);
|
|
return true;
|
|
} else if (state.newPassword?.length < 6) {
|
|
msg = gettext('Password must be at least 6 characters for user %s.', state.email);
|
|
setError('newPassword', msg);
|
|
return true;
|
|
} else {
|
|
setError('newPassword', null);
|
|
}
|
|
|
|
if (obj.isNew(state) && isEmptyString(state.confirmPassword)) {
|
|
msg = gettext('Confirm Password cannot be empty for user %s.', state.email);
|
|
setError('confirmPassword', msg);
|
|
return true;
|
|
} else {
|
|
setError('confirmPassword', null);
|
|
}
|
|
|
|
if (state.newPassword !== state.confirmPassword) {
|
|
msg = gettext('Passwords do not match for user %s.', state.email);
|
|
setError('confirmPassword', msg);
|
|
return true;
|
|
} else {
|
|
setError('confirmPassword', null);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class UserManagementSchema extends BaseUISchema {
|
|
constructor(authSources, roleOptions) {
|
|
super();
|
|
this.userManagementCollObj = new UserManagementCollection(authSources, roleOptions);
|
|
}
|
|
|
|
deleteUser(deleteRow) {
|
|
Notify.confirm(
|
|
gettext('Delete user?'),
|
|
gettext('Are you sure you wish to delete this user?'),
|
|
deleteRow,
|
|
function() {
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
get baseFields() {
|
|
let obj = this;
|
|
const api = getApiInstance();
|
|
return [
|
|
{
|
|
id: 'userManagement', label: '', type: 'collection', schema: obj.userManagementCollObj,
|
|
canAdd: true, canDelete: true, isFullTab: true, group: 'temp_user',
|
|
canDeleteRow: (row)=>{
|
|
if (row['id'] == current_user['id'])
|
|
return false;
|
|
return true;
|
|
},
|
|
onDelete: (row, deleteRow)=> {
|
|
let deletedUser = {'id': row['id'], 'name': !isEmptyString(row['email']) ? row['email'] : row['username']};
|
|
api.get(url_for('user_management.shared_servers', {'uid': row['id']}))
|
|
.then((res)=>{
|
|
if (res.data?.data?.shared_servers > 0) {
|
|
api.get(url_for('user_management.admin_users', {'uid': row['id']}))
|
|
.then((result)=>{
|
|
showChangeOwnership(gettext('Change ownership'),
|
|
result?.data?.data?.result?.data,
|
|
res?.data?.data?.shared_servers,
|
|
deletedUser,
|
|
deleteRow
|
|
);
|
|
})
|
|
.catch((err)=>{
|
|
Notify.error(err);
|
|
});
|
|
} else {
|
|
obj.deleteUser(deleteRow);
|
|
}
|
|
})
|
|
.catch((err)=>{
|
|
Notify.error(err);
|
|
obj.deleteUser(deleteRow);
|
|
});
|
|
},
|
|
canSearch: true
|
|
},
|
|
];
|
|
}
|
|
}
|
|
|
|
const useStyles = makeStyles((theme)=>({
|
|
root: {
|
|
...theme.mixins.tabPanel,
|
|
padding: 0,
|
|
},
|
|
}));
|
|
|
|
function UserManagementDialog({onClose}) {
|
|
const classes = useStyles();
|
|
const [authSources, setAuthSources] = React.useState([]);
|
|
const [roles, setRoles] = React.useState([]);
|
|
const api = getApiInstance();
|
|
|
|
React.useEffect(async ()=>{
|
|
try {
|
|
api.get(url_for('user_management.auth_sources'))
|
|
.then(res=>{
|
|
setAuthSources(res.data);
|
|
})
|
|
.catch((err)=>{
|
|
Notify.error(err);
|
|
});
|
|
|
|
api.get(url_for('user_management.roles'))
|
|
.then(res=>{
|
|
setRoles(res.data);
|
|
})
|
|
.catch((err)=>{
|
|
Notify.error(err);
|
|
});
|
|
} catch (error) {
|
|
Notify.error(error);
|
|
}
|
|
}, []);
|
|
|
|
const onSaveClick = (_isNew, changeData)=>{
|
|
return new Promise((resolve, reject)=>{
|
|
try {
|
|
api.post(url_for('user_management.save'), changeData['userManagement'])
|
|
.then(()=>{
|
|
Notify.success('Users Saved Successfully');
|
|
})
|
|
.catch((err)=>{
|
|
Notify.error(err);
|
|
});
|
|
resolve();
|
|
onClose();
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
};
|
|
|
|
const authSourcesOptions = authSources.map((m)=>({
|
|
label: m.label,
|
|
value: m.value,
|
|
}));
|
|
|
|
if(authSourcesOptions.length <= 0) {
|
|
return <></>;
|
|
}
|
|
|
|
const roleOptions = roles.map((m)=>({
|
|
label: m.name,
|
|
value: m.id,
|
|
}));
|
|
|
|
if(roleOptions.length <= 0) {
|
|
return <></>;
|
|
}
|
|
|
|
const onDialogHelp = () => {
|
|
window.open(url_for('help.static', { 'filename': 'user_management.html' }), 'pgadmin_help');
|
|
};
|
|
|
|
return <SchemaView
|
|
formType={'dialog'}
|
|
getInitData={()=>{ return new Promise((resolve, reject)=>{
|
|
api.get(url_for('user_management.users'))
|
|
.then((res)=>{
|
|
resolve({userManagement:res.data});
|
|
})
|
|
.catch((err)=>{
|
|
reject(err);
|
|
});
|
|
}); }}
|
|
schema={new UserManagementSchema(authSourcesOptions, roleOptions)}
|
|
viewHelperProps={{
|
|
mode: 'edit',
|
|
}}
|
|
onSave={onSaveClick}
|
|
onClose={onClose}
|
|
onHelp={onDialogHelp}
|
|
hasSQL={false}
|
|
disableSqlHelp={true}
|
|
isTabView={false}
|
|
formClassName={classes.root}
|
|
/>;
|
|
}
|
|
|
|
UserManagementDialog.propTypes = {
|
|
onClose: PropTypes.func
|
|
};
|
|
|
|
export default function showUserManagement() {
|
|
pgAdmin.Browser.Node.registerUtilityPanel();
|
|
let panel = pgAdmin.Browser.Node.addUtilityPanel(980, pgAdmin.Browser.stdH.md),
|
|
j = panel.$container.find('.obj_properties').first();
|
|
panel.title(gettext('User Management'));
|
|
|
|
const onClose = ()=> {
|
|
ReactDOM.unmountComponentAtNode(j[0]);
|
|
panel.close();
|
|
};
|
|
|
|
ReactDOM.render(
|
|
<Theme>
|
|
<UserManagementDialog
|
|
onClose={onClose}
|
|
/>
|
|
</Theme>, j[0]);
|
|
} |