///////////////////////////////////////////////////////////// // // 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 { 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( , j[0]); }