2022-08-10 23:55:52 -05:00
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
2024-01-01 02:43:48 -06:00
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
2022-08-10 23:55:52 -05:00
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import React from 'react' ;
2024-06-06 06:43:12 -05:00
import { styled } from '@mui/material/styles' ;
2022-08-10 23:55:52 -05:00
import SchemaView from '../../../../static/js/SchemaView' ;
import BaseUISchema from '../../../../static/js/SchemaView/base_schema.ui' ;
import pgAdmin from 'sources/pgadmin' ;
import gettext from 'sources/gettext' ;
import url _for from 'sources/url_for' ;
import PropTypes from 'prop-types' ;
2022-08-13 22:18:58 -05:00
import getApiInstance , { parseApiError } from '../../../../static/js/api_instance' ;
2023-10-23 07:13:17 -05:00
import { AUTH _METHODS } from 'pgadmin.browser.constants' ;
2022-08-10 23:55:52 -05:00
import current _user from 'pgadmin.user_management.current_user' ;
import { isEmptyString } from '../../../../static/js/validators' ;
import { showChangeOwnership } from '../../../../static/js/Dialogs/index' ;
2023-10-23 07:13:17 -05:00
import { BROWSER _PANELS } from '../../../../browser/static/js/constants' ;
2023-11-27 02:46:49 -06:00
import _ from 'lodash' ;
2022-08-10 23:55:52 -05:00
2024-06-06 06:43:12 -05:00
const StyledSchemaView = styled ( SchemaView ) ( ( { theme } ) => ( {
'& .UserManagementDialog-root' : {
... theme . mixins . tabPanel ,
padding : 0 ,
}
} ) ) ;
2022-08-10 23:55:52 -05:00
class UserManagementCollection extends BaseUISchema {
constructor ( authSources , roleOptions ) {
super ( {
id : undefined ,
username : undefined ,
email : undefined ,
active : true ,
role : '2' ,
newPassword : undefined ,
confirmPassword : undefined ,
2022-08-16 04:55:25 -05:00
locked : false ,
2023-10-23 07:13:17 -05:00
auth _source : AUTH _METHODS [ 'INTERNAL' ]
2022-08-10 23:55:52 -05:00
} ) ;
this . authOnlyInternal = ( current _user [ 'auth_sources' ] . length == 1 &&
2024-04-08 06:49:51 -05:00
current _user [ 'auth_sources' ] . includes ( AUTH _METHODS [ 'INTERNAL' ] ) ) ;
2022-08-10 23:55:52 -05:00
this . authSources = authSources ;
this . roleOptions = roleOptions ;
}
get idAttribute ( ) {
return 'id' ;
}
isUserNameEnabled ( state ) {
2023-10-23 07:13:17 -05:00
return ! ( this . authOnlyInternal || state . auth _source == AUTH _METHODS [ 'INTERNAL' ] ) ;
2022-08-10 23:55:52 -05:00
}
isEditable ( state ) {
return state . id != current _user [ 'id' ] ;
}
get baseFields ( ) {
let obj = this ;
return [
{
2023-11-27 02:46:49 -06:00
id : 'auth_source' , label : gettext ( 'Authentication source' ) ,
cell : ( state ) => {
return {
cell : 'select' ,
options : ( ) => {
if ( obj . isNew ( state ) ) {
return Promise . resolve ( obj . authSources . filter ( ( s ) => current _user [ 'auth_sources' ] . includes ( s . value ) ) ) ;
}
return Promise . resolve ( obj . authSources ) ;
} ,
optionsReloadBasis : obj . isNew ( state )
} ;
} ,
minWidth : 110 , width : 110 ,
2022-08-10 23:55:52 -05:00
controlProps : {
allowClear : false ,
openOnEnter : false ,
first _empty : false ,
} ,
visible : function ( ) {
2022-09-08 09:26:02 -05:00
return ! obj . authOnlyInternal ;
2022-08-10 23:55:52 -05:00
} ,
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 ) ) {
2022-08-11 00:19:45 -05:00
return { username : undefined } ;
2022-08-10 23:55:52 -05:00
}
} ,
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 ;
2023-10-23 07:13:17 -05:00
return obj . isEditable ( state ) && state . auth _source != AUTH _METHODS [ 'INTERNAL' ] ;
2022-08-10 23:55:52 -05:00
}
} , {
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 ) ;
}
} , {
2024-05-07 06:01:04 -05:00
id : 'active' , label : gettext ( 'Active' ) , cell : 'switch' , width : 60 , enableResizing : false ,
2022-08-10 23:55:52 -05:00
editable : ( state ) => {
return obj . isEditable ( state ) ;
}
} , {
id : 'newPassword' , label : gettext ( 'New password' ) , cell : 'password' ,
2023-12-28 04:59:15 -06:00
minWidth : 90 , width : 90 , deps : [ 'auth_source' ] , controlProps : {
autoComplete : 'new-password' ,
} ,
2022-08-10 23:55:52 -05:00
editable : ( state ) => {
2023-10-23 07:13:17 -05:00
return obj . isEditable ( state ) && state . auth _source == AUTH _METHODS [ 'INTERNAL' ] ;
2022-08-10 23:55:52 -05:00
}
} , {
id : 'confirmPassword' , label : gettext ( 'Confirm password' ) , cell : 'password' ,
2023-12-28 04:59:15 -06:00
minWidth : 90 , width : 90 , deps : [ 'auth_source' ] , controlProps : {
autoComplete : 'new-password' ,
} ,
2022-08-10 23:55:52 -05:00
editable : ( state ) => {
2023-10-23 07:13:17 -05:00
return obj . isEditable ( state ) && state . auth _source == AUTH _METHODS [ 'INTERNAL' ] ;
2022-08-10 23:55:52 -05:00
}
} , {
2024-05-07 06:01:04 -05:00
id : 'locked' , label : gettext ( 'Locked' ) , cell : 'switch' , width : 60 , enableResizing : false ,
2022-08-10 23:55:52 -05:00
editable : ( state ) => {
2022-08-16 04:55:25 -05:00
return state . locked ;
2022-08-10 23:55:52 -05:00
}
}
] ;
}
validate ( state , setError ) {
2024-04-08 06:49:51 -05:00
let msg ;
2022-08-10 23:55:52 -05:00
let obj = this ;
2023-02-01 02:54:49 -06:00
let minPassLen = pgAdmin . password _length _min ;
2022-08-10 23:55:52 -05:00
if ( obj . isUserNameEnabled ( state ) && isEmptyString ( state . username ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Username cannot be empty' ) ;
2022-08-10 23:55:52 -05:00
setError ( 'username' , msg ) ;
return true ;
} else {
setError ( 'username' , null ) ;
}
2023-10-23 07:13:17 -05:00
if ( state . auth _source != AUTH _METHODS [ 'INTERNAL' ] ) {
2022-08-16 06:58:45 -05:00
if ( obj . isNew ( state ) && obj . top ? . _sessData ? . userManagement ) {
for ( let i = 0 ; i < obj . top . _sessData . userManagement . length ; i ++ ) {
if ( obj . top . _sessData . userManagement [ i ] ? . id &&
2022-09-22 02:43:29 -05:00
obj . top . _sessData . userManagement [ i ] . username . toLowerCase ( ) == state . username . toLowerCase ( ) &&
2022-08-16 06:58:45 -05:00
obj . top . _sessData . userManagement [ i ] . auth _source == state . auth _source ) {
msg = gettext ( 'User name \'%s\' already exists' , state . username ) ;
setError ( 'username' , msg ) ;
return true ;
}
}
}
}
2023-10-23 07:13:17 -05:00
if ( state . auth _source == AUTH _METHODS [ 'INTERNAL' ] ) {
2022-08-10 23:55:52 -05:00
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 ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Email cannot be empty' ) ;
2022-08-10 23:55:52 -05:00
setError ( 'email' , msg ) ;
return true ;
} else if ( ! email _filter . test ( state . email ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Invalid email address: %s' , state . email ) ;
2022-08-10 23:55:52 -05:00
setError ( 'email' , msg ) ;
return true ;
} else {
setError ( 'email' , null ) ;
}
2022-08-13 22:18:58 -05:00
2022-08-16 04:55:25 -05:00
if ( obj . isNew ( state ) && obj . top ? . _sessData ? . userManagement ) {
2022-08-13 22:18:58 -05:00
for ( let i = 0 ; i < obj . top . _sessData . userManagement . length ; i ++ ) {
2022-08-16 04:55:25 -05:00
if ( obj . top . _sessData . userManagement [ i ] ? . id &&
2023-01-16 08:03:17 -06:00
obj . top . _sessData . userManagement [ i ] . email ? . toLowerCase ( ) == state . email ? . toLowerCase ( ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Email address \'%s\' already exists' , state . email ) ;
setError ( 'email' , msg ) ;
return true ;
}
}
}
2022-08-10 23:55:52 -05:00
if ( obj . isNew ( state ) && isEmptyString ( state . newPassword ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Password cannot be empty for user %s' , state . email ) ;
2022-08-10 23:55:52 -05:00
setError ( 'newPassword' , msg ) ;
return true ;
2023-02-01 02:54:49 -06:00
} else if ( state . newPassword ? . length < minPassLen ) {
msg = gettext ( 'Password must be at least %s characters for user %s' , minPassLen , state . email ) ;
2022-08-10 23:55:52 -05:00
setError ( 'newPassword' , msg ) ;
return true ;
} else {
setError ( 'newPassword' , null ) ;
}
if ( obj . isNew ( state ) && isEmptyString ( state . confirmPassword ) ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Confirm Password cannot be empty for user %s' , state . email ) ;
2022-08-10 23:55:52 -05:00
setError ( 'confirmPassword' , msg ) ;
return true ;
} else {
setError ( 'confirmPassword' , null ) ;
}
if ( state . newPassword !== state . confirmPassword ) {
2022-08-13 22:18:58 -05:00
msg = gettext ( 'Passwords do not match for user %s' , state . email ) ;
2022-08-10 23:55:52 -05:00
setError ( 'confirmPassword' , msg ) ;
return true ;
} else {
setError ( 'confirmPassword' , null ) ;
}
}
return false ;
}
}
class UserManagementSchema extends BaseUISchema {
constructor ( authSources , roleOptions ) {
2022-09-14 04:38:05 -05:00
super ( { refreshBrowserTree : false } ) ;
2022-08-10 23:55:52 -05:00
this . userManagementCollObj = new UserManagementCollection ( authSources , roleOptions ) ;
2022-09-14 04:38:05 -05:00
this . changeOwnership = false ;
2022-08-10 23:55:52 -05:00
}
deleteUser ( deleteRow ) {
2023-10-23 07:13:17 -05:00
pgAdmin . Browser . notifier . confirm (
2022-08-10 23:55:52 -05:00
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' ,
2023-05-18 02:56:47 -05:00
addOnTop : true ,
2022-08-10 23:55:52 -05:00
canDeleteRow : ( row ) => {
2022-09-08 09:26:02 -05:00
return row [ 'id' ] != current _user [ 'id' ] ;
2022-08-10 23:55:52 -05:00
} ,
onDelete : ( row , deleteRow ) => {
2023-11-27 02:46:49 -06:00
if ( _ . isUndefined ( row [ 'id' ] ) ) {
deleteRow ( ) ;
return ;
}
2022-08-10 23:55:52 -05:00
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 ,
2022-09-14 04:38:05 -05:00
( ) => {
this . changeOwnership = true ;
deleteRow ( ) ;
}
2022-08-10 23:55:52 -05:00
) ;
} )
. catch ( ( err ) => {
2023-11-27 02:46:49 -06:00
pgAdmin . Browser . notifier . error ( parseApiError ( err ) ) ;
2022-08-10 23:55:52 -05:00
} ) ;
} else {
obj . deleteUser ( deleteRow ) ;
}
} )
. catch ( ( err ) => {
2023-11-27 02:46:49 -06:00
pgAdmin . Browser . notifier . error ( parseApiError ( err ) ) ;
2022-08-10 23:55:52 -05:00
obj . deleteUser ( deleteRow ) ;
} ) ;
} ,
canSearch : true
} ,
2022-09-14 04:38:05 -05:00
{
2024-05-20 06:24:49 -05:00
id : 'refreshBrowserTree' , visible : false , type : 'switch' ,
2022-09-14 04:38:05 -05:00
deps : [ 'userManagement' ] , depChange : ( ) => {
2022-09-14 04:45:52 -05:00
return { refreshBrowserTree : this . changeOwnership } ;
2022-09-14 04:38:05 -05:00
}
}
2022-08-10 23:55:52 -05:00
] ;
}
}
function UserManagementDialog ( { onClose } ) {
2024-06-06 06:43:12 -05:00
2022-08-10 23:55:52 -05:00
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 ) => {
2023-10-23 07:13:17 -05:00
pgAdmin . Browser . notifier . error ( err ) ;
2022-08-10 23:55:52 -05:00
} ) ;
api . get ( url _for ( 'user_management.roles' ) )
. then ( res => {
setRoles ( res . data ) ;
} )
. catch ( ( err ) => {
2024-01-03 04:39:42 -06:00
pgAdmin . Browser . notifier . error ( parseApiError ( err ) ) ;
2022-08-10 23:55:52 -05:00
} ) ;
} catch ( error ) {
2023-10-23 07:13:17 -05:00
pgAdmin . Browser . notifier . error ( parseApiError ( error ) ) ;
2022-08-10 23:55:52 -05:00
}
} , [ ] ) ;
const onSaveClick = ( _isNew , changeData ) => {
return new Promise ( ( resolve , reject ) => {
try {
2022-09-14 04:38:05 -05:00
if ( changeData [ 'refreshBrowserTree' ] ) {
// Confirmation dialog to refresh the browser tree.
2023-10-23 07:13:17 -05:00
pgAdmin . Browser . notifier . confirm (
2023-03-28 11:50:14 -05:00
gettext ( 'Object explorer tree refresh required' ) ,
gettext ( 'The ownership of the shared server was changed or the shared server was deleted, so the object explorer tree refresh is required. Do you wish to refresh the tree?' ) ,
2022-09-14 04:38:05 -05:00
function ( ) {
pgAdmin . Browser . tree . destroy ( ) ;
} ,
function ( ) {
return true ;
} ,
gettext ( 'Refresh' ) ,
gettext ( 'Later' )
) ;
}
2022-08-10 23:55:52 -05:00
api . post ( url _for ( 'user_management.save' ) , changeData [ 'userManagement' ] )
. then ( ( ) => {
2023-10-23 07:13:17 -05:00
pgAdmin . Browser . notifier . success ( 'Users Saved Successfully' ) ;
2022-08-13 22:18:58 -05:00
resolve ( ) ;
onClose ( ) ;
2022-08-10 23:55:52 -05:00
} )
. catch ( ( err ) => {
2024-06-10 07:34:32 -05:00
reject ( new Error ( err ) ) ;
2022-08-10 23:55:52 -05:00
} ) ;
} catch ( error ) {
2022-08-13 22:18:58 -05:00
reject ( parseApiError ( error ) ) ;
2022-08-10 23:55:52 -05:00
}
} ) ;
} ;
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' ) ;
} ;
2024-06-06 06:43:12 -05:00
return < StyledSchemaView
2022-08-10 23:55:52 -05:00
formType = { 'dialog' }
getInitData = { ( ) => { return new Promise ( ( resolve , reject ) => {
api . get ( url _for ( 'user_management.users' ) )
. then ( ( res ) => {
resolve ( { userManagement : res . data } ) ;
} )
. catch ( ( err ) => {
2024-06-10 07:34:32 -05:00
reject ( new Error ( err ) ) ;
2022-08-10 23:55:52 -05:00
} ) ;
} ) ; } }
schema = { new UserManagementSchema ( authSourcesOptions , roleOptions ) }
viewHelperProps = { {
mode : 'edit' ,
} }
onSave = { onSaveClick }
onClose = { onClose }
onHelp = { onDialogHelp }
hasSQL = { false }
disableSqlHelp = { true }
isTabView = { false }
2024-06-06 06:43:12 -05:00
formClassName = 'UserManagementDialog-root'
2022-08-10 23:55:52 -05:00
/ > ;
}
UserManagementDialog . propTypes = {
onClose : PropTypes . func
} ;
2022-08-30 03:51:33 -05:00
export function showUserManagement ( ) {
2023-10-23 07:13:17 -05:00
const panelTitle = gettext ( 'User Management' ) ;
const panelId = BROWSER _PANELS . USER _MANAGEMENT ;
pgAdmin . Browser . docker . openDialog ( {
id : panelId ,
title : panelTitle ,
manualClose : false ,
content : (
2022-08-10 23:55:52 -05:00
< UserManagementDialog
2023-10-23 07:13:17 -05:00
onClose = { ( ) => { pgAdmin . Browser . docker . close ( panelId ) ; } }
2022-08-10 23:55:52 -05:00
/ >
2023-10-23 07:13:17 -05:00
)
2023-11-27 02:46:49 -06:00
} , pgAdmin . Browser . stdW . lg , pgAdmin . Browser . stdH . md ) ;
2022-08-13 22:18:58 -05:00
}