mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
fix(plugin-authentication): user creation
CB-295
This commit is contained in:
@@ -26,7 +26,6 @@ type AdminUserNew = AdminUserInfo & { [NEW_USER_SYMBOL]: boolean }
|
||||
|
||||
type UserCreateOptions = {
|
||||
userId: string;
|
||||
newId?: string;
|
||||
roles: string[];
|
||||
credentials: Record<string, any>;
|
||||
grantedConnections: string[];
|
||||
@@ -59,22 +58,6 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
|
||||
return this.data.has(id);
|
||||
}
|
||||
|
||||
addNew() {
|
||||
const user = {
|
||||
userId: `new-${uuid()}`,
|
||||
grantedRoles: [],
|
||||
grantedConnections: [],
|
||||
configurationParameters: {},
|
||||
metaParameters: {},
|
||||
[NEW_USER_SYMBOL]: true,
|
||||
} as AdminUserNew;
|
||||
|
||||
this.data.set(user.userId, user);
|
||||
this.markUpdated(user.userId);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async loadConnections(userId: string): Promise<AdminConnectionGrantInfo[]> {
|
||||
if (this.isNew(userId)) {
|
||||
return [];
|
||||
@@ -90,15 +73,10 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
|
||||
}
|
||||
|
||||
async create({
|
||||
userId, newId, roles, credentials, grantedConnections,
|
||||
userId, roles, credentials, grantedConnections,
|
||||
}: UserCreateOptions): Promise<AdminUserInfo> {
|
||||
const { user } = await this.graphQLService.gql.createUser({ userId });
|
||||
|
||||
if (newId) {
|
||||
this.data.delete(newId);
|
||||
}
|
||||
this.set(userId, user as AdminUserInfo);
|
||||
|
||||
try {
|
||||
await this.updateCredentials(userId, credentials);
|
||||
for (const roleId of roles) {
|
||||
@@ -106,6 +84,8 @@ export class UsersResource extends CachedMapResource<string, AdminUserInfo> {
|
||||
}
|
||||
|
||||
await this.setConnections(userId, grantedConnections);
|
||||
const user = await this.refresh(userId)as AdminUserNew;
|
||||
user[NEW_USER_SYMBOL] = true;
|
||||
} catch (exception) {
|
||||
this.delete(userId);
|
||||
throw exception;
|
||||
|
||||
-6
@@ -33,12 +33,6 @@ const styles = composes(
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ErrorMessage {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 8px 24px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ const styles = composes(
|
||||
css`
|
||||
title-bar {
|
||||
composes: theme-typography--headline6 from global;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 24px;
|
||||
border-top: solid 1px;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
flex: 1;
|
||||
flex: auto 0 0;
|
||||
}
|
||||
`
|
||||
);
|
||||
@@ -47,7 +47,7 @@ export function CreateUser({
|
||||
return styled(useStyles(styles))(
|
||||
<user-create as='div'>
|
||||
<title-bar as='div'><Translate token='authentication_administration_user_connections_user_add'/></title-bar>
|
||||
<UserForm userId={user.userId} onCancel={onCancel} />
|
||||
<UserForm user={user} onCancel={onCancel} />
|
||||
</user-create>
|
||||
);
|
||||
}
|
||||
|
||||
+8
-9
@@ -21,6 +21,7 @@ import {
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { GrantedConnections } from './GrantedConnections';
|
||||
@@ -53,10 +54,6 @@ const styles = composes(
|
||||
}
|
||||
`,
|
||||
css`
|
||||
box {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
SubmittingForm {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -128,16 +125,18 @@ const styles = composes(
|
||||
);
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
user: AdminUserInfo;
|
||||
editing?: boolean;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export const UserForm = observer(function UserForm({
|
||||
userId,
|
||||
user,
|
||||
editing = false,
|
||||
onCancel,
|
||||
}: Props) {
|
||||
const translate = useTranslate();
|
||||
const controller = useController(UserFormController, userId, onCancel);
|
||||
const controller = useController(UserFormController, user, editing, onCancel);
|
||||
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
|
||||
|
||||
const handleLoginChange = useCallback(
|
||||
@@ -182,7 +181,7 @@ export const UserForm = observer(function UserForm({
|
||||
mod={['unelevated']}
|
||||
onClick={controller.save}
|
||||
>
|
||||
{translate(controller.isNew ? 'ui_processing_create' : 'ui_processing_save')}
|
||||
{translate(!editing ? 'ui_processing_create' : 'ui_processing_save')}
|
||||
</Button>
|
||||
</TabList>
|
||||
<content-box as='div'>
|
||||
@@ -199,7 +198,7 @@ export const UserForm = observer(function UserForm({
|
||||
name='login'
|
||||
value={controller.credentials.login}
|
||||
onChange={handleLoginChange}
|
||||
disabled={!controller.isNew || controller.isSaving}
|
||||
disabled={editing || controller.isSaving}
|
||||
mod='surface'
|
||||
required
|
||||
>
|
||||
|
||||
+21
-30
@@ -31,10 +31,6 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
roles: new Map<string, boolean>(),
|
||||
};
|
||||
|
||||
@computed get isNew() {
|
||||
return this.usersResource.isNew(this.userId);
|
||||
}
|
||||
|
||||
@computed get connections() {
|
||||
return Array.from(this.connectionsResource.data.values())
|
||||
.filter(connection => !this.connectionsResource.isNew(connection.id));
|
||||
@@ -44,16 +40,14 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
return Array.from(this.rolesManagerService.roles.data.values());
|
||||
}
|
||||
|
||||
@computed get user(): AdminUserInfo {
|
||||
return this.usersResource.get(this.userId)!;
|
||||
}
|
||||
user!: AdminUserInfo;
|
||||
|
||||
readonly error = new GQLErrorCatcher();
|
||||
private isDistructed = false;
|
||||
private userId!: string;
|
||||
private connectionAccessChanged = false;
|
||||
private connectionAccessLoaded = false;
|
||||
private collapse!: () => void;
|
||||
private editing!: boolean;
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
@@ -64,8 +58,9 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
private dbDriverResource: DBDriverResource
|
||||
) { }
|
||||
|
||||
init(id: string, collapse: () => void) {
|
||||
this.userId = id;
|
||||
init(user: AdminUserInfo, editing: boolean, collapse: () => void) {
|
||||
this.user = user;
|
||||
this.editing = editing;
|
||||
this.collapse = collapse;
|
||||
this.loadRoles();
|
||||
}
|
||||
@@ -84,10 +79,9 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
|
||||
this.isSaving = true;
|
||||
try {
|
||||
if (this.isNew) {
|
||||
if (!this.editing) {
|
||||
await this.usersResource.create({
|
||||
userId: this.credentials.login,
|
||||
newId: this.userId,
|
||||
credentials: { password: this.credentials.password },
|
||||
roles: this.getGrantedRoles(),
|
||||
grantedConnections: this.getGrantedConnections(),
|
||||
@@ -103,12 +97,9 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
await this.usersResource.refresh(this.user.userId);
|
||||
this.notificationService.logInfo({ title: 'authentication_administration_user_updated' });
|
||||
}
|
||||
|
||||
this.connectionAccessLoaded = false;
|
||||
await this.loadConnectionsAccess();
|
||||
} catch (exception) {
|
||||
if (!this.error.catch(exception) || this.isDistructed) {
|
||||
if (this.isNew) {
|
||||
if (!this.editing) {
|
||||
this.notificationService.logException(exception, 'Error creating new user');
|
||||
} else {
|
||||
this.notificationService.logException(exception, 'Error saving user');
|
||||
@@ -134,12 +125,14 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
|
||||
this.isLoading = true;
|
||||
try {
|
||||
this.grantedConnections = await this.usersResource.loadConnections(this.userId);
|
||||
if (this.editing) {
|
||||
this.grantedConnections = await this.usersResource.loadConnections(this.user.userId);
|
||||
|
||||
this.selectedConnections.clear();
|
||||
for (const connection of this.grantedConnections) {
|
||||
if (connection.subjectType !== AdminSubjectType.Role) {
|
||||
this.selectedConnections.set(connection.connectionId, true);
|
||||
this.selectedConnections.clear();
|
||||
for (const connection of this.grantedConnections) {
|
||||
if (connection.subjectType !== AdminSubjectType.Role) {
|
||||
this.selectedConnections.set(connection.connectionId, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.connectionAccessLoaded = true;
|
||||
@@ -161,12 +154,12 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.credentials.password && this.isNew) {
|
||||
if (!this.credentials.password && !this.editing) {
|
||||
this.notificationService.logError({ title: 'authentication_user_password_not_set' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.credentials.password && this.isNew) {
|
||||
if (!this.credentials.password && !this.editing) {
|
||||
this.notificationService.logError({ title: 'authentication_user_password_not_set' });
|
||||
return;
|
||||
}
|
||||
@@ -190,7 +183,7 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
if (!this.user.grantedRoles.includes(roleId)) {
|
||||
await this.usersResource.grantRole(this.user.userId, roleId, true);
|
||||
}
|
||||
} else if (!this.isNew) {
|
||||
} else {
|
||||
await this.usersResource.revokeRole(this.user.userId, roleId, true);
|
||||
}
|
||||
}
|
||||
@@ -215,8 +208,10 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
if (!this.connectionAccessChanged) {
|
||||
return;
|
||||
}
|
||||
await this.usersResource.setConnections(this.userId, this.getGrantedConnections());
|
||||
await this.usersResource.setConnections(this.user.userId, this.getGrantedConnections());
|
||||
this.connectionAccessChanged = false;
|
||||
this.connectionAccessLoaded = false;
|
||||
await this.loadConnectionsAccess();
|
||||
}
|
||||
|
||||
private async loadRoles() {
|
||||
@@ -232,11 +227,7 @@ export class UserFormController implements IInitializableController, IDestructib
|
||||
|
||||
private async loadUser() {
|
||||
try {
|
||||
await this.usersResource.load(this.userId);
|
||||
|
||||
if (!this.isNew) {
|
||||
this.credentials.login = this.user.userId;
|
||||
}
|
||||
this.credentials.login = this.user.userId;
|
||||
this.credentials.roles = new Map(this.user.grantedRoles.map(roleId => ([roleId, true])));
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, 'Can\'t load user');
|
||||
|
||||
+11
-9
@@ -13,7 +13,7 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialog } from '@cloudbeaver/core-dialogs';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ErrorDetailsDialog } from '@cloudbeaver/core-notifications';
|
||||
import { GQLErrorCatcher, resourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { GQLErrorCatcher, resourceKeyList, AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { UsersAdministrationNavigationService } from './UsersAdministrationNavigationService';
|
||||
|
||||
@@ -26,10 +26,9 @@ export class UsersAdministrationController {
|
||||
|
||||
@computed get users() {
|
||||
return Array.from(this.usersResource.data.values())
|
||||
.filter(user => !this.usersResource.isNew(user.userId))
|
||||
.sort((a, b) => {
|
||||
if (this.usersResource.isNew(a.userId) === this.usersResource.isNew(b.userId)) {
|
||||
return 0;
|
||||
return a.userId.localeCompare(b.userId);
|
||||
}
|
||||
if (this.usersResource.isNew(a.userId)) {
|
||||
return -1;
|
||||
@@ -38,10 +37,7 @@ export class UsersAdministrationController {
|
||||
});
|
||||
}
|
||||
|
||||
@computed get creatingUser() {
|
||||
return Array.from(this.usersResource.data.values())
|
||||
.find(user => this.usersResource.isNew(user.userId));
|
||||
}
|
||||
@observable creatingUser: AdminUserInfo | null = null;
|
||||
|
||||
get isLoading() {
|
||||
return this.usersResource.isLoading() || this.isDeleting;
|
||||
@@ -55,7 +51,13 @@ export class UsersAdministrationController {
|
||||
) { }
|
||||
|
||||
create = () => {
|
||||
this.usersResource.addNew();
|
||||
this.creatingUser = {
|
||||
userId: '',
|
||||
grantedRoles: [],
|
||||
grantedConnections: [],
|
||||
configurationParameters: {},
|
||||
metaParameters: {},
|
||||
} as AdminUserInfo;
|
||||
this.usersAdministrationNavigationService.navToAdd();
|
||||
}
|
||||
|
||||
@@ -64,7 +66,7 @@ export class UsersAdministrationController {
|
||||
return;
|
||||
}
|
||||
|
||||
this.usersResource.delete(this.creatingUser.userId);
|
||||
this.creatingUser = null;
|
||||
this.usersAdministrationNavigationService.navToRoot();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
TableItem, TableColumnValue, TableItemSelect, TableItemExpand
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
import { AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
@@ -31,9 +28,6 @@ type Props = {
|
||||
}
|
||||
|
||||
export const User = observer(function User({ user }: Props) {
|
||||
const translate = useTranslate();
|
||||
const usersResource = useService(UsersResource);
|
||||
const isNew = usersResource.isNew(user.userId);
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<TableItem item={user.userId} expandElement={UserEdit}>
|
||||
@@ -43,9 +37,9 @@ export const User = observer(function User({ user }: Props) {
|
||||
<TableColumnValue centerContent flex expand>
|
||||
<TableItemExpand />
|
||||
</TableColumnValue>
|
||||
<TableColumnValue expand>{isNew ? translate('authentication_administration_user_connections_user_new') : user.userId}</TableColumnValue>
|
||||
<TableColumnValue expand>{user.userId}</TableColumnValue>
|
||||
<TableColumnValue>{user.grantedRoles.join(', ')}</TableColumnValue>
|
||||
<TableColumnValue align='right'>{isNew && <tag as='div' {...use({ mod: 'positive' })}>{translate('ui_tag_new')}</tag>}</TableColumnValue>
|
||||
<TableColumnValue></TableColumnValue>
|
||||
</TableItem>
|
||||
);
|
||||
});
|
||||
|
||||
+30
-1
@@ -8,12 +8,34 @@
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import { useContext, useCallback } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import {
|
||||
TableContext,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useController } from '@cloudbeaver/core-di';
|
||||
import { composes, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { UserForm } from '../UserForm/UserForm';
|
||||
import { UserEditController } from './UserEditController';
|
||||
|
||||
const styles = composes(
|
||||
css`
|
||||
box {
|
||||
composes: theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
box {
|
||||
box-sizing: border-box;
|
||||
padding: 24px;
|
||||
min-height: 320px;
|
||||
max-height: 500px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
type Props = {
|
||||
item: string;
|
||||
@@ -22,8 +44,15 @@ type Props = {
|
||||
export const UserEdit = observer(function UserEdit({
|
||||
item,
|
||||
}: Props) {
|
||||
const controller = useController(UserEditController, item);
|
||||
const tableContext = useContext(TableContext);
|
||||
const collapse = useCallback(() => tableContext?.setItemExpand(item, false), [tableContext]);
|
||||
|
||||
return <UserForm userId={item} onCancel={collapse} />;
|
||||
return styled(useStyles(styles))(
|
||||
<box as='div'>
|
||||
{controller.user && (
|
||||
<UserForm user={controller.user} onCancel={collapse} editing/>
|
||||
)}
|
||||
</box>
|
||||
);
|
||||
});
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* cloudbeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observable, computed } from 'mobx';
|
||||
|
||||
import { UsersResource } from '@cloudbeaver/core-authentication';
|
||||
import {
|
||||
injectable, IInitializableController, IDestructibleController
|
||||
} from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { GQLErrorCatcher, AdminUserInfo } from '@cloudbeaver/core-sdk';
|
||||
|
||||
@injectable()
|
||||
export class UserEditController
|
||||
implements IInitializableController, IDestructibleController {
|
||||
@observable isLoading = true;
|
||||
@observable user: AdminUserInfo | null = null;
|
||||
|
||||
@computed get isDisabled() {
|
||||
return this.isLoading;
|
||||
}
|
||||
|
||||
userId!: string;
|
||||
|
||||
readonly error = new GQLErrorCatcher();
|
||||
|
||||
constructor(
|
||||
private notificationService: NotificationService,
|
||||
private usersResource: UsersResource,
|
||||
) { }
|
||||
|
||||
init(id: string) {
|
||||
this.userId = id;
|
||||
this.loadUser();
|
||||
}
|
||||
|
||||
destruct(): void { }
|
||||
|
||||
private async loadUser() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
// we create a copy to protect the current value from mutation
|
||||
this.user = JSON.parse(JSON.stringify(await this.usersResource.load(this.userId)));
|
||||
} catch (exception) {
|
||||
this.notificationService.logException(exception, `Can't load user ${this.userId}`);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user