mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-24 11:35:54 +08:00
CB-3769-user-role-change-event (#1880)
* CB-3825 add role changed event * CB-3825 dc use event controller for sending events to mq * CB-3769 remove fields from gql subject event type * CB-3821 support CbSubjectPermissionUpdateEvent * CB-3769 session state job do not refresh permissions * CB-3769 change locale * CB-3769 use sm subject type * CB-3821 fix event name type * CB-3825 remove core-localization package * CB-3824 change imports * CB-3825 translate all messages --------- Co-authored-by: naumov <iamemptyhuh@gmail.com> Co-authored-by: Айнур Ягудин <ainur@MacBook-Air-Ajnur.local> Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> Co-authored-by: Alexey <wrouds@gmail.com>
This commit is contained in:
co-authored by
naumov
Айнур Ягудин
Daria Marutkina
Alexey
parent
f736dd4b46
commit
5206680590
+10
@@ -109,6 +109,16 @@ public abstract class BaseWebSession extends AbstractSessionPersistent {
|
||||
return userContext.refresh(smAuthInfo);
|
||||
}
|
||||
|
||||
public synchronized void refreshUserData() {
|
||||
try {
|
||||
userContext.refreshPermissions();
|
||||
userContext.refreshAccessibleProjects();
|
||||
} catch (DBException e) {
|
||||
addSessionError(e);
|
||||
log.error("Error refreshing accessible projects", e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SMAuthSpace getSessionSpace() {
|
||||
|
||||
@@ -232,7 +232,9 @@ public class WebSession extends BaseWebSession
|
||||
return userContext.getFileController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void refreshUserData() {
|
||||
super.refreshUserData();
|
||||
refreshSessionAuth();
|
||||
|
||||
initNavigatorModel();
|
||||
|
||||
+8
-4
@@ -123,14 +123,18 @@ public class WebUserContext implements SMCredentialsProvider {
|
||||
if (isSessionChanged) {
|
||||
this.smSessionId = smAuthPermissions.getSessionId();
|
||||
setUser(smAuthPermissions.getUserId() == null ? null : new WebUser(securityController.getCurrentUser()));
|
||||
this.accessibleProjectIds.clear();
|
||||
this.accessibleProjectIds.addAll(
|
||||
Arrays.stream(rmController.listAccessibleProjects()).map(RMProject::getId).collect(Collectors.toSet())
|
||||
);
|
||||
refreshAccessibleProjects();
|
||||
}
|
||||
return isSessionChanged;
|
||||
}
|
||||
|
||||
public synchronized void refreshAccessibleProjects() throws DBException {
|
||||
this.accessibleProjectIds.clear();
|
||||
this.accessibleProjectIds.addAll(
|
||||
Arrays.stream(rmController.listAccessibleProjects()).map(RMProject::getId).collect(Collectors.toSet())
|
||||
);
|
||||
}
|
||||
|
||||
public synchronized void refreshSMSession() throws DBException {
|
||||
if (smCredentials == null || refreshToken == null) {
|
||||
return;
|
||||
|
||||
@@ -58,8 +58,11 @@
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSRmResourceUpdatedEventHandlerImpl">
|
||||
<topic id="cb_scripts"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSPermissionUpdatedEventHandler">
|
||||
<topic id="cb_permissions"/>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSObjectPermissionUpdatedEventHandler">
|
||||
<topic id="cb_object_permissions"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSSubjectPermissionUpdatedEventHandler">
|
||||
<topic id="cb_subject_permissions"/>
|
||||
</eventHandler>
|
||||
<eventHandler class="io.cloudbeaver.server.events.WSProjectUpdatedEventHandler">
|
||||
<topic id="cb_projects"/>
|
||||
|
||||
@@ -25,7 +25,10 @@ enum CBServerEventId {
|
||||
cb_rm_resource_deleted,
|
||||
|
||||
cb_rm_project_added,
|
||||
cb_rm_project_removed
|
||||
cb_rm_project_removed,
|
||||
|
||||
cb_object_permissions_updated,
|
||||
cb_subject_permissions_updated
|
||||
}
|
||||
|
||||
# Events sent by client
|
||||
@@ -43,7 +46,9 @@ enum CBEventTopic {
|
||||
cb_datasource,
|
||||
cb_datasource_folder,
|
||||
cb_scripts,
|
||||
cb_projects
|
||||
cb_projects,
|
||||
cb_object_permissions,
|
||||
cb_subject_permissions
|
||||
}
|
||||
|
||||
# Base server event interface
|
||||
@@ -122,6 +127,12 @@ type CBProjectUpdateEvent implements CBServerEvent {
|
||||
projectId: String!
|
||||
}
|
||||
|
||||
# Subject permission update event
|
||||
type CBSubjectPermissionUpdateEvent implements CBServerEvent {
|
||||
id: CBServerEventId!
|
||||
topicId: CBEventTopic
|
||||
}
|
||||
|
||||
# Topic subscription event
|
||||
type CBTopicEvent implements CBClientEvent {
|
||||
id: CBClientEventId!
|
||||
|
||||
+2
-2
@@ -30,8 +30,8 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class WSPermissionUpdatedEventHandler extends WSDefaultEventHandler<WSObjectPermissionEvent> {
|
||||
private static final Log log = Log.getLog(WSPermissionUpdatedEventHandler.class);
|
||||
public class WSObjectPermissionUpdatedEventHandler extends WSDefaultEventHandler<WSObjectPermissionEvent> {
|
||||
private static final Log log = Log.getLog(WSObjectPermissionUpdatedEventHandler.class);
|
||||
private volatile Set<String> subjectsWithProjectAccess;
|
||||
|
||||
@Override
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.server.events;
|
||||
|
||||
import io.cloudbeaver.model.session.BaseWebSession;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.DBException;
|
||||
import org.jkiss.dbeaver.Log;
|
||||
import org.jkiss.dbeaver.model.websocket.event.permissions.WSSubjectPermissionEvent;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
public class WSSubjectPermissionUpdatedEventHandler extends WSDefaultEventHandler<WSSubjectPermissionEvent> {
|
||||
private static final Log log = Log.getLog(WSSubjectPermissionUpdatedEventHandler.class);
|
||||
|
||||
@Override
|
||||
protected void updateSessionData(@NotNull BaseWebSession activeUserSession, @NotNull WSSubjectPermissionEvent event) {
|
||||
try {
|
||||
activeUserSession.getUserContext().refreshSMSession();
|
||||
} catch (DBException e) {
|
||||
activeUserSession.addSessionError(e);
|
||||
log.error("Error refreshing session", e);
|
||||
}
|
||||
activeUserSession.refreshUserData();
|
||||
super.updateSessionData(activeUserSession, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isAcceptableInSession(@NotNull BaseWebSession activeUserSession, @NotNull WSSubjectPermissionEvent event) {
|
||||
if (!super.isAcceptableInSession(activeUserSession, event)) {
|
||||
return false;
|
||||
}
|
||||
var user = activeUserSession.getUserContext().getUser();
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
var subjectId = event.getSubjectId();
|
||||
switch (event.getSubjectType()) {
|
||||
case user:
|
||||
return CommonUtils.equalObjects(user.getUserId(), subjectId);
|
||||
case team:
|
||||
return ArrayUtils.containsIgnoreCase(user.getTeams(), subjectId);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -333,7 +333,6 @@ public class WebSessionManager {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
session.getUserContext().refreshPermissions();
|
||||
session.addSessionEvent(new WSSessionStateEvent(session.getRemainingTime(), session.isValid()));
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to refresh session state: " + session.getSessionId(), e);
|
||||
|
||||
+37
-3
@@ -51,6 +51,7 @@ import org.jkiss.dbeaver.model.security.exception.SMException;
|
||||
import org.jkiss.dbeaver.model.security.exception.SMRefreshTokenExpiredException;
|
||||
import org.jkiss.dbeaver.model.security.user.*;
|
||||
import org.jkiss.dbeaver.model.sql.SQLUtils;
|
||||
import org.jkiss.dbeaver.model.websocket.event.permissions.WSSubjectPermissionEvent;
|
||||
import org.jkiss.utils.ArrayUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
import org.jkiss.utils.SecurityUtils;
|
||||
@@ -214,8 +215,17 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error saving user teams in database", e);
|
||||
}
|
||||
var event = WSSubjectPermissionEvent.update(
|
||||
getSmSessionId(),
|
||||
getUserId(),
|
||||
SMSubjectType.user,
|
||||
userId
|
||||
);
|
||||
application.getEventController().addEvent(event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SMTeam[] getUserTeams(String userId) throws DBException {
|
||||
@@ -588,8 +598,17 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
} catch (SQLException e) {
|
||||
throw new DBCException("Error while updating user authentication role", e);
|
||||
}
|
||||
var event = WSSubjectPermissionEvent.update(
|
||||
getSmSessionId(),
|
||||
getUserId(),
|
||||
SMSubjectType.user,
|
||||
userId
|
||||
);
|
||||
application.getEventController().addEvent(event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////////////////////////////////
|
||||
// Credentials
|
||||
|
||||
@@ -1570,7 +1589,6 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
public SMTokens refreshSession(@NotNull String refreshToken) throws DBException {
|
||||
var currentUserCreds = getCurrentUserCreds();
|
||||
var currentUserAccessToken = currentUserCreds.getSmAccessToken();
|
||||
String currentUserAuthRole = readTokenAuthRole(currentUserAccessToken);
|
||||
|
||||
var smTokenInfo = readAccessTokenInfo(currentUserAccessToken);
|
||||
|
||||
@@ -1580,7 +1598,11 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
|
||||
try (var dbCon = database.openConnection()) {
|
||||
invalidateUserTokens(currentUserAccessToken);
|
||||
return generateNewSessionToken(smTokenInfo.getSessionId(), smTokenInfo.getUserId(), currentUserAuthRole, dbCon);
|
||||
return generateNewSessionToken(
|
||||
smTokenInfo.getSessionId(),
|
||||
smTokenInfo.getUserId(),
|
||||
updateUserAuthRoleIfNeeded(smTokenInfo.getUserId(), null),
|
||||
dbCon);
|
||||
} catch (SQLException e) {
|
||||
throw new DBException("Error refreshing sm session", e);
|
||||
}
|
||||
@@ -1937,7 +1959,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
}
|
||||
}
|
||||
|
||||
private String updateUserAuthRoleIfNeeded(@Nullable String userId, @Nullable String authRole) throws DBException {
|
||||
protected String updateUserAuthRoleIfNeeded(@Nullable String userId, @Nullable String authRole) throws DBException {
|
||||
if (userId == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -2574,4 +2596,16 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getSmSessionId() {
|
||||
var credentials = credentialsProvider.getActiveUserCredentials();
|
||||
return credentials == null ? null : credentials.getSmSessionId();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getUserId() {
|
||||
var credentials = credentialsProvider.getActiveUserCredentials();
|
||||
return credentials == null ? null : credentials.getUserId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,5 +113,6 @@ export default [
|
||||
['app_root_server_node_changed_title', 'The application was relaunched'],
|
||||
['app_root_server_node_changed_message', 'The application was relaunched. Please, reload the page.'],
|
||||
['app_root_quota_exceeded', 'Quota exceeded'],
|
||||
['app_root_event_permissions_changed_message', 'Your permissions have been changed'],
|
||||
['core_eventsLog_dbeaverErrorDetails', 'Error details'],
|
||||
];
|
||||
|
||||
@@ -92,5 +92,6 @@ export default [
|
||||
['app_root_session_expire_warning_button', 'Sono qua'],
|
||||
['app_root_session_expired_title', 'Sessione scaduta'],
|
||||
['app_root_session_expired_message', 'La sessione è scaduta. Vuoi ricaricare?'],
|
||||
['app_root_event_permissions_changed_message', 'Your permissions have been changed'],
|
||||
['core_eventsLog_dbeaverErrorDetails', "Dettagli dell'errore"],
|
||||
];
|
||||
|
||||
@@ -109,5 +109,6 @@ export default [
|
||||
['app_root_server_node_changed_title', 'Приложение было перезапущено'],
|
||||
['app_root_server_node_changed_message', 'Приложение было перезапущено. Пожалуйста, перезагрузите страницу.'],
|
||||
['app_root_quota_exceeded', 'Квота превышена'],
|
||||
['app_root_event_permissions_changed_message', 'Ваши права доступа были изменены'],
|
||||
['core_eventsLog_dbeaverErrorDetails', 'Детали ошибки'],
|
||||
];
|
||||
|
||||
@@ -107,5 +107,6 @@ export default [
|
||||
['app_root_session_expired_title', '会话已过期'],
|
||||
['app_root_session_expired_message', '会话已过期。您想重新加载吗?'],
|
||||
['app_root_quota_exceeded', '超出配额'],
|
||||
['app_root_event_permissions_changed_message', 'Your permissions have been changed'],
|
||||
['core_eventsLog_dbeaverErrorDetails', '错误详情'],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import type { CbSubjectPermissionUpdateEvent as ISessionPermissionEvent } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { TopicEventHandler } from './ServerEventEmitter/TopicEventHandler';
|
||||
import { ISessionEvent, SessionEventId, SessionEventSource, SessionEventTopic } from './SessionEventSource';
|
||||
|
||||
export { ISessionPermissionEvent };
|
||||
|
||||
@injectable()
|
||||
export class SessionPermissionEventHandler extends TopicEventHandler<ISessionPermissionEvent, ISessionEvent, SessionEventId, SessionEventTopic> {
|
||||
constructor(sessionEventSource: SessionEventSource) {
|
||||
super(SessionEventTopic.CbSubjectPermissions, sessionEventSource);
|
||||
}
|
||||
|
||||
map(event: any): ISessionPermissionEvent {
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,21 @@ import { injectable } from '@cloudbeaver/core-di';
|
||||
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
|
||||
import { CachedDataResource, CachedResource, GraphQLService } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { DataSynchronizationService } from './DataSynchronization/DataSynchronizationService';
|
||||
import { SessionDataResource } from './SessionDataResource';
|
||||
import { ServerEventId } from './SessionEventSource';
|
||||
import { ISessionPermissionEvent, SessionPermissionEventHandler } from './SessionPermissionEventHandler';
|
||||
import { SessionResource } from './SessionResource';
|
||||
|
||||
@injectable()
|
||||
export class SessionPermissionsResource extends CachedDataResource<Set<string>> {
|
||||
constructor(private readonly graphQLService: GraphQLService, sessionDataResource: SessionDataResource) {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
private readonly dataSynchronizationService: DataSynchronizationService,
|
||||
sessionDataResource: SessionDataResource,
|
||||
sessionResource: SessionResource,
|
||||
sessionPermissionEventHandler: SessionPermissionEventHandler,
|
||||
) {
|
||||
super(() => new Set());
|
||||
|
||||
this.sync(
|
||||
@@ -21,6 +31,19 @@ export class SessionPermissionsResource extends CachedDataResource<Set<string>>
|
||||
() => {},
|
||||
() => {},
|
||||
);
|
||||
|
||||
sessionPermissionEventHandler.onEvent<ISessionPermissionEvent>(
|
||||
ServerEventId.CbSubjectPermissionsUpdated,
|
||||
() => {
|
||||
this.dataSynchronizationService.requestSynchronization('permissions', 'app_root_event_permissions_changed_message').then(state => {
|
||||
if (state) {
|
||||
sessionResource.markOutdated();
|
||||
}
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
sessionResource,
|
||||
);
|
||||
}
|
||||
|
||||
require(resource: CachedResource<any, any, any, any, any>, ...permissions: string[]): this {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SessionEventSource } from './SessionEventSource';
|
||||
import { SessionExpireEventService } from './SessionExpireEventService';
|
||||
import { SessionExpireService } from './SessionExpireService';
|
||||
import { SessionInfoEventHandler } from './SessionInfoEventHandler';
|
||||
import { SessionPermissionEventHandler } from './SessionPermissionEventHandler';
|
||||
import { SessionPermissionsResource } from './SessionPermissionsResource';
|
||||
import { SessionResource } from './SessionResource';
|
||||
import { SessionService } from './SessionService';
|
||||
@@ -56,5 +57,6 @@ export const manifest: PluginManifest = {
|
||||
SessionEventSource,
|
||||
SessionInfoEventHandler,
|
||||
DataSynchronizationService,
|
||||
SessionPermissionEventHandler,
|
||||
],
|
||||
};
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ export const DataSynchronizationNotification = observer<NotificationComponentPro
|
||||
{notification.message && translate(notification.message)}
|
||||
<message>
|
||||
{groups.map(messages => {
|
||||
const message = uniq(messages.map(m => m.message)).join('\n');
|
||||
const message = uniq(messages.map(m => translate(m.message))).join('\n');
|
||||
|
||||
return <div key={messages[0].label}>{message}</div>;
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user