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:
Ainur
2023-08-16 15:19:31 +03:00
committed by GitHub
co-authored by naumov Айнур Ягудин Daria Marutkina Alexey
parent f736dd4b46
commit 5206680590
17 changed files with 194 additions and 16 deletions
@@ -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();
@@ -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!
@@ -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
@@ -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;
}
}
}
@@ -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);
@@ -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,
],
};
@@ -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>;
})}