238 lines
7.5 KiB
JavaScript
238 lines
7.5 KiB
JavaScript
/////////////////////////////////////////////////////////////
|
|
//
|
|
// pgAdmin 4 - PostgreSQL Tools
|
|
//
|
|
// Copyright (C) 2013 - 2025, The pgAdmin Development Team
|
|
// This software is released under the PostgreSQL Licence
|
|
//
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
import React, { useMemo } from 'react';
|
|
import SchemaView from '../../../../static/js/SchemaView';
|
|
import BaseUISchema from '../../../../static/js/SchemaView/base_schema.ui';
|
|
import gettext from 'sources/gettext';
|
|
import url_for from 'sources/url_for';
|
|
import getApiInstance, { parseApiError } from '../../../../static/js/api_instance';
|
|
import {AUTH_METHODS} from 'pgadmin.browser.constants';
|
|
import current_user from 'pgadmin.user_management.current_user';
|
|
import { isEmptyString } from '../../../../static/js/validators';
|
|
import ErrorBoundary from '../../../../static/js/helpers/ErrorBoundary';
|
|
import PropTypes from 'prop-types';
|
|
import { usePgAdmin } from '../../../../static/js/PgAdminProvider';
|
|
|
|
class UserSchema extends BaseUISchema {
|
|
constructor(options, pgAdmin) {
|
|
super({
|
|
auth_source: 'internal',
|
|
role: 1,
|
|
active: true,
|
|
refreshBrowserTree: false
|
|
});
|
|
this.options = options;
|
|
this.pgAdmin = pgAdmin;
|
|
|
|
this.authOnlyInternal = (current_user['auth_sources'].length == 1 &&
|
|
current_user['auth_sources'].includes(AUTH_METHODS['INTERNAL']));
|
|
}
|
|
|
|
isUserNameEnabled(state) {
|
|
return this.isNew(state) && state.auth_source != AUTH_METHODS['INTERNAL'];
|
|
}
|
|
|
|
isNotCurrentUser(state) {
|
|
return state.id != current_user['id'];
|
|
}
|
|
|
|
get baseFields() {
|
|
let obj = this;
|
|
return [
|
|
{
|
|
id: 'auth_source', label: gettext('Authentication source'),
|
|
type: (state) => {
|
|
return {
|
|
type: 'select',
|
|
options: () => {
|
|
if (obj.isNew(state)) {
|
|
return Promise.resolve(obj.options.authSources.filter((s) => current_user['auth_sources'].includes(s.value)));
|
|
}
|
|
return Promise.resolve(obj.options.authSources);
|
|
},
|
|
optionsReloadBasis: obj.isNew(state)
|
|
};
|
|
},
|
|
controlProps: {
|
|
allowClear: false,
|
|
openOnEnter: false,
|
|
},
|
|
readonly: function (state) {
|
|
return !obj.isNew(state) || obj.authOnlyInternal;
|
|
}
|
|
}, {
|
|
id: 'username', label: gettext('Username'), type: 'text',
|
|
deps: ['auth_source'],
|
|
depChange: (state) => {
|
|
if (!obj.isUserNameEnabled(state)) {
|
|
return { username: undefined };
|
|
}
|
|
},
|
|
readonly: (state) => {
|
|
return !obj.isUserNameEnabled(state);
|
|
}
|
|
}, {
|
|
id: 'email', label: gettext('Email'), type: 'text',
|
|
deps: ['id'],
|
|
readonly: (state) => {
|
|
if (obj.isNew(state)) {
|
|
return false;
|
|
} else {
|
|
return !obj.isNotCurrentUser(state) || state.auth_source == AUTH_METHODS['INTERNAL'];
|
|
}
|
|
}
|
|
}, {
|
|
id: 'role', label: gettext('Role'), type: 'select',
|
|
options: obj.options.roles,
|
|
controlProps: {
|
|
allowClear: false,
|
|
openOnEnter: false,
|
|
},
|
|
readonly: (state) => {
|
|
if (obj.isNew(state)) {
|
|
return false;
|
|
}
|
|
return !obj.isNotCurrentUser(state);
|
|
}
|
|
}, {
|
|
id: 'active', label: gettext('Active'), type: 'switch',
|
|
readonly: (state) => {
|
|
if (obj.isNew(state)) {
|
|
return false;
|
|
}
|
|
return !obj.isNotCurrentUser(state);
|
|
}
|
|
}, {
|
|
id: 'newPassword', label: gettext('New password'), type: 'password',
|
|
deps: ['auth_source'], controlProps: {
|
|
autoComplete: 'new-password',
|
|
},
|
|
visible: (state)=>obj.isNotCurrentUser(state) && state.auth_source == AUTH_METHODS['INTERNAL'],
|
|
}, {
|
|
id: 'confirmPassword', label: gettext('Confirm password'), type: 'password',
|
|
deps: ['auth_source'], controlProps: {
|
|
autoComplete: 'new-password',
|
|
},
|
|
visible: (state)=>obj.isNotCurrentUser(state) && state.auth_source == AUTH_METHODS['INTERNAL'],
|
|
}, {
|
|
id: 'locked', label: gettext('Locked'), type: 'switch',
|
|
readonly: (state) => {
|
|
return !state.locked;
|
|
}
|
|
}
|
|
];
|
|
}
|
|
|
|
validate(state, setError) {
|
|
let msg;
|
|
let obj = this;
|
|
let minPassLen = this.pgAdmin.password_length_min;
|
|
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 == AUTH_METHODS['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);
|
|
}
|
|
|
|
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 < minPassLen) {
|
|
msg = gettext('Password must be at least %s characters for user %s', minPassLen, 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;
|
|
}
|
|
}
|
|
|
|
export default function UserDialog({user, options, onClose}) {
|
|
const pgAdmin = usePgAdmin();
|
|
const schema = useMemo(() => new UserSchema(options, pgAdmin), []);
|
|
const isEdit = Boolean(user.id);
|
|
const api = getApiInstance();
|
|
|
|
const onSaveClick = (_isNew, changeData)=>{
|
|
return new Promise((resolve, reject)=>{
|
|
try {
|
|
api.post(url_for('user_management.save'), changeData)
|
|
.then(()=>{
|
|
pgAdmin.Browser.notifier.success(gettext('Users Saved Successfully'));
|
|
resolve();
|
|
onClose(null, true);
|
|
})
|
|
.catch((err)=>{
|
|
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
|
|
});
|
|
} catch (error) {
|
|
reject(Error(parseApiError(error)));
|
|
}
|
|
});
|
|
};
|
|
|
|
return <ErrorBoundary>
|
|
<SchemaView
|
|
formType={'dialog'}
|
|
getInitData={()=>{ return Promise.resolve(user); }}
|
|
schema={schema}
|
|
viewHelperProps={{
|
|
mode: isEdit ? 'edit' : 'create',
|
|
}}
|
|
onSave={onSaveClick}
|
|
onClose={onClose}
|
|
hasSQL={false}
|
|
disableSqlHelp={true}
|
|
disableDialogHelp={true}
|
|
isTabView={false}
|
|
/>
|
|
</ErrorBoundary>;
|
|
}
|
|
|
|
UserDialog.propTypes = {
|
|
user: PropTypes.object,
|
|
options: PropTypes.object,
|
|
onClose: PropTypes.func,
|
|
}; |