From ac290d4bf6328b16b417bc119b5200f5a03e0cb3 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 10 Nov 2022 16:33:30 +0300 Subject: [PATCH 1/9] dbeaver/pro#138 Resource move support added. Properties move implementation. (#1269) --- .../io/cloudbeaver/model/rm/local/LocalResourceController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 1e6b103bef..31dbd07e81 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -426,6 +426,9 @@ public class LocalResourceController implements RMController { } catch (IOException e) { throw new DBException("Error moving resource '" + oldResourcePath + "'", e); } + // Move properties + getProjectMetadata(projectId).moveResourceProperties(oldResourcePath, newResourcePath); + fireRmResourceDeleteEvent(projectId, rmOldResourcePath); fireRmResourceAddEvent(projectId, newResourcePath); return DEFAULT_CHANGE_ID; From 537bfda887c7e2bff8787ea53a4f4e1cd3569745 Mon Sep 17 00:00:00 2001 From: Nikolay <46003534+riednyko@users.noreply.github.com> Date: Thu, 10 Nov 2022 18:54:48 +0300 Subject: [PATCH 2/9] dbeaver/dbeaver-devops#455 Added arg in Dockerfile for multiarch images (#1270) * dbeaver/dbeaver-devops#455 updated Dockerfile for multiarch images * dbeaver/dbeaver-devops#455 arch x64 default value in Dockerfile --- deploy/docker/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/docker/Dockerfile b/deploy/docker/Dockerfile index 2561dc9863..5e32885cae 100644 --- a/deploy/docker/Dockerfile +++ b/deploy/docker/Dockerfile @@ -1,5 +1,5 @@ -FROM eclipse-temurin:17.0.4.1_1-jre-focal -#adoptopenjdk/openjdk11:jdk-11.0.12_7-ubuntu-slim +ARG ARCH=amd64 +FROM ${ARCH}/eclipse-temurin:17.0.4.1_1-jre-focal COPY cloudbeaver /opt/cloudbeaver From bc4677ccde412bcd42b745d96764be53114898bf Mon Sep 17 00:00:00 2001 From: Alexander Skoblikov Date: Thu, 10 Nov 2022 20:20:21 +0300 Subject: [PATCH 3/9] CB-2830 send auth provider in auth info (#1263) * CB-2830 send auth provider in auth info * CB-1424 feat: provider / configuration logout * CB-1424 fix: provider logout Co-authored-by: Aleksey Potsetsuev --- .../model/session/WebAuthInfo.java | 17 ++-- .../session/WebSessionAuthProcessor.java | 20 ++-- .../CBEmbeddedSecurityController.java | 94 +++++++++++-------- .../src/AuthInfoService.ts | 11 ++- .../src/UserInfoResource.ts | 10 +- .../src/queries/authentication/authLogout.gql | 10 +- webapp/packages/core-sdk/src/sdk.ts | 50 ++-------- .../src/AuthenticationService.ts | 46 ++++----- .../AuthTokens/AuthTokenList.tsx | 4 +- 9 files changed, 119 insertions(+), 143 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java index a651608295..7fda561baf 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebAuthInfo.java @@ -19,7 +19,6 @@ package io.cloudbeaver.model.session; import io.cloudbeaver.DBWUserIdentity; import io.cloudbeaver.model.user.WebUser; import io.cloudbeaver.model.user.WebUserOriginInfo; -import io.cloudbeaver.registry.WebAuthProviderConfiguration; import io.cloudbeaver.registry.WebAuthProviderDescriptor; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; @@ -41,7 +40,7 @@ public class WebAuthInfo implements SMSessionPrincipal { private final WebSession session; private final WebUser user; private final WebAuthProviderDescriptor authProvider; - private WebAuthProviderConfiguration authProviderConfiguration; + private String authProviderConfigurationId; private SMSession authSession; private final OffsetDateTime loginTime; private final DBWUserIdentity userIdentity; @@ -101,7 +100,11 @@ public class WebAuthInfo implements SMSessionPrincipal { @Property public String getAuthConfiguration() { - return authProviderConfiguration == null ? null : authProviderConfiguration.getId(); + return authProviderConfigurationId; + } + + public void setAuthProviderConfigurationId(String authProviderConfigurationId) { + this.authProviderConfigurationId = authProviderConfigurationId; } public WebUser getUser() { @@ -116,14 +119,6 @@ public class WebAuthInfo implements SMSessionPrincipal { return authProvider; } - public WebAuthProviderConfiguration getAuthProviderConfiguration() { - return authProviderConfiguration; - } - - public void setAuthProviderConfiguration(WebAuthProviderConfiguration authProviderConfiguration) { - this.authProviderConfiguration = authProviderConfiguration; - } - public SMSession getAuthSession() { return authSession; } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java index 849b304422..2878925b5d 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSessionAuthProcessor.java @@ -29,6 +29,7 @@ import io.cloudbeaver.utils.WebAppUtils; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.auth.SMAuthConfigurationReference; import org.jkiss.dbeaver.model.auth.SMAuthInfo; import org.jkiss.dbeaver.model.auth.SMAuthProvider; import org.jkiss.dbeaver.model.auth.SMSession; @@ -87,8 +88,8 @@ public class WebSessionAuthProcessor { try { if (configMode && alreadyLoggedIn) { - for (String providerId : authInfo.getAuthData().keySet()) { - webSession.removeAuthInfo(providerId); + for (SMAuthConfigurationReference authConfiguration : authInfo.getAuthData().keySet()) { + webSession.removeAuthInfo(authConfiguration.getAuthProviderId()); } } webSession.updateSMAuthInfo(authInfo); @@ -102,8 +103,9 @@ public class WebSessionAuthProcessor { var securityController = webSession.getSecurityController(); Map providerConfig = Collections.emptyMap(); var newAuthInfos = new ArrayList(); - for (Map.Entry entry : authInfo.getAuthData().entrySet()) { - String providerId = entry.getKey(); + for (Map.Entry entry : authInfo.getAuthData().entrySet()) { + SMAuthConfigurationReference authConfiguration = entry.getKey(); + String providerId = authConfiguration.getAuthProviderId(); Map authAttrs = (Map) entry.getValue(); var authProviderDescriptor = getAuthProvider(providerId); @@ -111,15 +113,13 @@ public class WebSessionAuthProcessor { SMAuthProviderExternal authProviderExternal = authProviderInstance instanceof SMAuthProviderExternal ? (SMAuthProviderExternal) authProviderInstance : null; - boolean providerEnabled = isProviderEnabled(providerId); + boolean providerDisabled = !isProviderEnabled(providerId); if (configMode || webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) { // 1. Admin can authorize in any providers // 2. When it authorizes in non-local provider for the first time we force linkUser flag - if (!providerEnabled && webSession.getUser() != null) { + if (providerDisabled && webSession.getUser() != null) { linkWithActiveUser = true; } - } else if (!providerEnabled && !webSession.hasPermission(DBWConstants.PERMISSION_ADMIN)) { - throw new DBWebException("Authentication provider '" + providerId + "' is disabled"); } SMSession authSession; @@ -174,7 +174,9 @@ public class WebSessionAuthProcessor { authProviderDescriptor, userIdentity, authSession, - OffsetDateTime.now()); + OffsetDateTime.now() + ); + webAuthInfo.setAuthProviderConfigurationId(authConfiguration.getAuthProviderConfigurationId()); webAuthInfo.setMessage("Authenticated with " + authProviderDescriptor.getLabel() + " provider"); if (configMode) { webAuthInfo.setUserCredentials(authAttrs); diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index a0e9a62b59..29aa81d210 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -1037,17 +1037,17 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) { //async auth var authProviderFederated = (SMAuthProviderFederated) authProviderInstance; - var redirectUrl = buildRedirectLink( - authProviderFederated.getSignInLink(authProviderConfigurationId, Map.of()), + var redirectUrl = buildRedirectLink(authProviderFederated.getSignInLink(authProviderConfigurationId, Map.of()), authAttemptId); - return SMAuthInfo.inProgress(authAttemptId, redirectUrl, filteredUserCreds); + Map authData = Map.of(new SMAuthConfigurationReference(authProviderId, + authProviderConfigurationId), filteredUserCreds); + return SMAuthInfo.inProgress(authAttemptId, redirectUrl, authData); } txn.commit(); return finishAuthentication( SMAuthInfo.inProgress( authAttemptId, - null, - Map.of(authProviderId, securedUserIdentifyingCredentials) + null, Map.of(new SMAuthConfigurationReference(authProviderId, null), securedUserIdentifyingCredentials) ), true, false @@ -1125,10 +1125,12 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } @Override - public void updateAuthStatus(@NotNull String authId, - @NotNull SMAuthStatus authStatus, - @NotNull Map authInfo, - @Nullable String error) throws DBException { + public void updateAuthStatus( + @NotNull String authId, + @NotNull SMAuthStatus authStatus, + @NotNull Map authInfo, + @Nullable String error + ) throws DBException { var existAuthInfo = getAuthStatus(authId); if (existAuthInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) { throw new SMException("Authorization already finished and cannot be updated"); @@ -1137,13 +1139,14 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen updateAuthStatus(authId, authStatus, authInfo, error, authSessionInfo.getSmSessionId()); } - private void updateAuthStatus(@NotNull String authId, - @NotNull SMAuthStatus authStatus, - @NotNull Map authInfo, - @Nullable String error, - @Nullable String smSessionId) throws DBException { - try (Connection dbCon = database.openConnection(); - JDBCTransaction txn = new JDBCTransaction(dbCon)) { + private void updateAuthStatus( + @NotNull String authId, + @NotNull SMAuthStatus authStatus, + @NotNull Map authInfo, + @Nullable String error, + @Nullable String smSessionId + ) throws DBException { + try (Connection dbCon = database.openConnection(); JDBCTransaction txn = new JDBCTransaction(dbCon)) { try (PreparedStatement dbStat = dbCon.prepareStatement( "UPDATE CB_AUTH_ATTEMPT SET AUTH_STATUS=?,AUTH_ERROR=?,SESSION_ID=? WHERE AUTH_ID=?")) { dbStat.setString(1, authStatus.toString()); @@ -1155,20 +1158,29 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } } - for (Map.Entry entry : authInfo.entrySet()) { - String providerId = entry.getKey(); + for (Map.Entry entry : authInfo.entrySet()) { + SMAuthConfigurationReference providerId = entry.getKey(); String authJson = gson.toJson(entry.getValue()); - try (PreparedStatement dbStat = dbCon.prepareStatement( - "UPDATE CB_AUTH_ATTEMPT_INFO SET AUTH_STATE=? WHERE AUTH_ID=? AND AUTH_PROVIDER_ID=?")) { + boolean configIdExist = providerId.getAuthProviderConfigurationId() != null; + var sqlBuilder = new StringBuilder(); + sqlBuilder.append("UPDATE CB_AUTH_ATTEMPT_INFO SET AUTH_STATE=? ") + .append("WHERE AUTH_ID=? AND AUTH_PROVIDER_ID=? AND ") + .append(configIdExist ? "AUTH_PROVIDER_CONFIGURATION_ID=?" : "AUTH_PROVIDER_CONFIGURATION_ID IS NULL"); + try (PreparedStatement dbStat = dbCon.prepareStatement(sqlBuilder.toString())) { dbStat.setString(1, authJson); dbStat.setString(2, authId); - dbStat.setString(3, providerId); + dbStat.setString(3, providerId.getAuthProviderId()); + if (configIdExist) { + dbStat.setString(4, providerId.getAuthProviderConfigurationId()); + } if (dbStat.executeUpdate() <= 0) { try (PreparedStatement dbStatIns = dbCon.prepareStatement( - "INSERT INTO CB_AUTH_ATTEMPT_INFO (AUTH_ID,AUTH_PROVIDER_ID,AUTH_STATE) VALUES(?,?,?)")) { + "INSERT INTO CB_AUTH_ATTEMPT_INFO (AUTH_ID,AUTH_PROVIDER_ID,AUTH_PROVIDER_CONFIGURATION_ID,AUTH_STATE) " + + "VALUES(?,?,?,?)")) { dbStatIns.setString(1, authId); - dbStatIns.setString(2, providerId); - dbStatIns.setString(3, authJson); + dbStatIns.setString(2, providerId.getAuthProviderId()); + dbStatIns.setString(3, providerId.getAuthProviderConfigurationId()); + dbStatIns.setString(4, authJson); dbStatIns.execute(); } } @@ -1199,7 +1211,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen smSessionId = dbResult.getString(3); } } - Map authData = new LinkedHashMap<>(); + Map authData = new LinkedHashMap<>(); String redirectUrl = null; try (PreparedStatement dbStat = dbCon.prepareStatement( @@ -1217,14 +1229,13 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen WebAuthProviderDescriptor authProviderDescriptor = getAuthProvider(authProviderId); var authProviderInstance = authProviderDescriptor.getInstance(); if (SMAuthProviderFederated.class.isAssignableFrom(authProviderInstance.getClass())) { - redirectUrl = buildRedirectLink( - ((SMAuthProviderFederated) authProviderInstance).getRedirectLink(authProviderConfiguration, Map.of()), - authId - ); + redirectUrl = buildRedirectLink(((SMAuthProviderFederated) authProviderInstance).getRedirectLink( + authProviderConfiguration, + Map.of()), authId); } } - authData.put(authProviderId, authProviderData); + authData.put(new SMAuthConfigurationReference(authProviderId, authProviderConfiguration), authProviderData); } } } @@ -1368,7 +1379,7 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen if (authInfo.getAuthStatus() != SMAuthStatus.IN_PROGRESS) { throw new SMException("Authorization has already been completed with status: " + authInfo.getAuthStatus()); } - Set authProviderIds = authInfo.getAuthData().keySet(); + Set authProviderIds = authInfo.getAuthData().keySet(); if (authProviderIds.isEmpty()) { throw new SMException("Authorization providers are not defined"); } @@ -1386,20 +1397,19 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen } String activeUserId = permissions == null ? null : permissions.getUserId(); - Map storedUserData = new LinkedHashMap<>(); + Map storedUserData = new LinkedHashMap<>(); SMTeam[] allTeams = null; String detectedAuthRole = null; - for (String authProviderId : authProviderIds) { + for (SMAuthConfigurationReference authConfiguration : authProviderIds) { + String authProviderId = authConfiguration.getAuthProviderId(); WebAuthProviderDescriptor authProvider = getAuthProvider(authProviderId); - var userAuthData = (Map) authInfo.getAuthData().get(authProviderId); + var userAuthData = (Map) authInfo.getAuthData().get(authConfiguration); SMAutoAssign autoAssign = getAutoAssignUserData(authId, authProvider, userAuthData, finishAuthMonitor); if (autoAssign != null) { detectedAuthRole = autoAssign.getAuthRole(); } - var userIdFromCreds = findOrCreateExternalUserByCredentials( - authProvider, - authAttemptSessionInfo.getSessionParams(), + var userIdFromCreds = findOrCreateExternalUserByCredentials(authProvider, authAttemptSessionInfo.getSessionParams(), userAuthData, finishAuthMonitor, activeUserId, @@ -1423,10 +1433,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen if (activeUserId == null) { activeUserId = userIdFromCreds; } - storedUserData.put( - authProviderId, - saveSecuredCreds ? userAuthData : filterSecuredUserData(userAuthData, getAuthProvider(authProviderId)) - ); + storedUserData.put(authConfiguration, + saveSecuredCreds ? userAuthData : filterSecuredUserData(userAuthData, getAuthProvider(authProviderId))); } String tokenAuthRole = updateUserAuthRoleIfNeeded(activeUserId, detectedAuthRole); @@ -2156,4 +2164,8 @@ public class CBEmbeddedSecurityController implements SMAdminController, SMAuthen return activeUserCredentials.getUserId(); } + private boolean isProviderEnabled(@NotNull String providerId) { + WebAuthConfiguration appConfiguration = application.getAuthConfiguration(); + return appConfiguration.isAuthProviderEnabled(providerId); + } } diff --git a/webapp/packages/core-authentication/src/AuthInfoService.ts b/webapp/packages/core-authentication/src/AuthInfoService.ts index fe38e6c7eb..6ad4f359bd 100644 --- a/webapp/packages/core-authentication/src/AuthInfoService.ts +++ b/webapp/packages/core-authentication/src/AuthInfoService.ts @@ -15,15 +15,20 @@ import { uuid } from '@cloudbeaver/core-utils'; import { AuthProvidersResource, AuthProviderConfiguration } from './AuthProvidersResource'; import { type ILoginOptions, UserInfoResource } from './UserInfoResource'; +export interface IUserAuthConfiguration { + providerId: string; + configuration: AuthProviderConfiguration; +} + @injectable() export class AuthInfoService { get userInfo(): UserInfo | null { return this.userInfoResource.data; } - get userAuthConfigurations(): AuthProviderConfiguration[] { + get userAuthConfigurations(): IUserAuthConfiguration[] { const tokens = this.userInfo?.authTokens; - const result: AuthProviderConfiguration[] = []; + const result: IUserAuthConfiguration[] = []; if (!tokens) { return result; @@ -41,7 +46,7 @@ export class AuthInfoService { ); if (configuration) { - result.push(configuration); + result.push({ providerId: provider.id, configuration }); } } } diff --git a/webapp/packages/core-authentication/src/UserInfoResource.ts b/webapp/packages/core-authentication/src/UserInfoResource.ts index 4ae186dbd1..979b45780a 100644 --- a/webapp/packages/core-authentication/src/UserInfoResource.ts +++ b/webapp/packages/core-authentication/src/UserInfoResource.ts @@ -158,9 +158,13 @@ UserInfoIncludes }); } - async logout(): Promise { - if (this.data) { - await this.graphQLService.sdk.authLogout(); + async logout(provider?: string, configuration?: string): Promise { + await this.graphQLService.sdk.authLogout({ + provider, + configuration, + }); + + if (!provider || (this.data?.authTokens.length || 0) <= 1) { this.setData(null); this.resetIncludes(); } diff --git a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql index 842069abfa..86ec3b1489 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/authLogout.gql @@ -1,3 +1,9 @@ -query authLogout { - authLogout +query authLogout( + $provider: ID + $configuration: ID +) { + authLogout( + provider: $provider + configuration: $configuration + ) } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 1814514003..46fa5b8b2d 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -887,7 +887,6 @@ export interface ProjectInfo { export interface Query { activeUser?: Maybe; - allConnections: Array; authChangeLocalPassword: Scalars['Boolean']; authLogin: AuthInfo; authLogout?: Maybe; @@ -897,10 +896,6 @@ export interface Query { configureServer: Scalars['Boolean']; connectionFolders: Array; connectionInfo: ConnectionInfo; - /** @deprecated Field no longer supported */ - copyConnectionConfiguration: ConnectionInfo; - /** @deprecated Field no longer supported */ - createConnectionConfiguration: ConnectionInfo; createTeam: AdminTeamInfo; createUser: AdminUserInfo; dataTransferAvailableStreamProcessors: Array; @@ -909,8 +904,6 @@ export interface Query { dataTransferExportDataFromResults: AsyncTaskInfo; dataTransferRemoveDataFile?: Maybe; deleteAuthProviderConfiguration: Scalars['Boolean']; - /** @deprecated Field no longer supported */ - deleteConnectionConfiguration?: Maybe; deleteTeam?: Maybe; deleteUser?: Maybe; deleteUserMetaParameter: Scalars['Boolean']; @@ -969,19 +962,11 @@ export interface Query { sqlParseScript: SqlScriptInfo; sqlSupportedOperations: Array; templateConnections: Array; - /** @deprecated Field no longer supported */ - updateConnectionConfiguration: ConnectionInfo; updateTeam: AdminTeamInfo; userConnections: Array; } -export interface QueryAllConnectionsArgs { - id?: InputMaybe; - projectId: Scalars['ID']; -} - - export interface QueryAuthChangeLocalPasswordArgs { newPassword: Scalars['String']; oldPassword: Scalars['String']; @@ -1025,19 +1010,6 @@ export interface QueryConnectionInfoArgs { } -export interface QueryCopyConnectionConfigurationArgs { - config?: InputMaybe; - nodePath: Scalars['String']; - projectId: Scalars['ID']; -} - - -export interface QueryCreateConnectionConfigurationArgs { - config: ConnectionConfig; - projectId: Scalars['ID']; -} - - export interface QueryCreateTeamArgs { description?: InputMaybe; teamId: Scalars['ID']; @@ -1079,12 +1051,6 @@ export interface QueryDeleteAuthProviderConfigurationArgs { } -export interface QueryDeleteConnectionConfigurationArgs { - id: Scalars['ID']; - projectId: Scalars['ID']; -} - - export interface QueryDeleteTeamArgs { teamId: Scalars['ID']; } @@ -1372,13 +1338,6 @@ export interface QueryTemplateConnectionsArgs { } -export interface QueryUpdateConnectionConfigurationArgs { - config: ConnectionConfig; - id: Scalars['ID']; - projectId: Scalars['ID']; -} - - export interface QueryUpdateTeamArgs { description?: InputMaybe; teamId: Scalars['ID']; @@ -1711,7 +1670,10 @@ export type AuthLoginQueryVariables = Exact<{ export type AuthLoginQuery = { authInfo: { redirectLink?: string, authId?: string, authStatus: AuthStatus, userTokens?: Array<{ authProvider: string, authConfiguration?: string, loginTime: any, message?: string, origin: { type: string, subType?: string, displayName: string, icon?: string, details?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array, value?: any, length: ObjectPropertyLength, features: Array, order: number }> } }> } }; -export type AuthLogoutQueryVariables = Exact<{ [key: string]: never; }>; +export type AuthLogoutQueryVariables = Exact<{ + provider?: InputMaybe; + configuration?: InputMaybe; +}>; export type AuthLogoutQuery = { authLogout?: boolean }; @@ -3178,8 +3140,8 @@ export const AuthLoginDocument = ` } ${AuthTokenFragmentDoc}`; export const AuthLogoutDocument = ` - query authLogout { - authLogout + query authLogout($provider: ID, $configuration: ID) { + authLogout(provider: $provider, configuration: $configuration) } `; export const DeleteAuthProviderConfigurationDocument = ` diff --git a/webapp/packages/plugin-authentication/src/AuthenticationService.ts b/webapp/packages/plugin-authentication/src/AuthenticationService.ts index bb5cff443f..c0a3edb2b2 100644 --- a/webapp/packages/plugin-authentication/src/AuthenticationService.ts +++ b/webapp/packages/plugin-authentication/src/AuthenticationService.ts @@ -9,7 +9,7 @@ import { observable } from 'mobx'; import { AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { AppAuthService, AuthInfoService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, RequestedProvider, UserInfoResource } from '@cloudbeaver/core-authentication'; +import { AppAuthService, AuthInfoService, AuthProviderContext, AuthProviderService, AuthProvidersResource, AUTH_PROVIDER_LOCAL_ID, IUserAuthConfiguration, RequestedProvider, UserInfoResource } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import type { DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -71,25 +71,30 @@ export class AuthenticationService extends Bootstrap { await this.auth(false, { providerId, linkUser }); } - async logout(): Promise { + async logout(providerId?: string, configurationId?: string): Promise { const contexts = await this.onLogout.execute('before'); if (ExecutorInterrupter.isInterrupted(contexts)) { return; } - if (this.authInfoService.userAuthConfigurations.length > 0) { - const userAuthConfiguration = this.authInfoService.userAuthConfigurations[0]; + let userAuthConfiguration: IUserAuthConfiguration | undefined = undefined; - if (userAuthConfiguration.signOutLink) { - this.logoutConfiguration(userAuthConfiguration.id, true); - } + if (providerId) { + userAuthConfiguration = this.authInfoService.userAuthConfigurations + .find(c => c.providerId === providerId && c.configuration.id === configurationId); + } else if (this.authInfoService.userAuthConfigurations.length > 0) { + userAuthConfiguration = this.authInfoService.userAuthConfigurations[0]; + } + + if (userAuthConfiguration?.configuration.signOutLink) { + this.logoutConfiguration(userAuthConfiguration); } try { - await this.userInfoResource.logout(); + await this.userInfoResource.logout(providerId, configurationId); - if (!this.administrationScreenService.isConfigurationMode) { + if (!this.administrationScreenService.isConfigurationMode && !providerId) { this.screenService.navigateToRoot(); } @@ -99,14 +104,11 @@ export class AuthenticationService extends Bootstrap { } } - logoutConfiguration(configurationId: string, full: boolean): void { - const userAuthConfiguration = this.authInfoService.userAuthConfigurations - .find(configuration => configuration.id === configurationId); - - if (userAuthConfiguration?.signOutLink) { - const id = `${userAuthConfiguration.id}-sign-out`; + private async logoutConfiguration(configuration: IUserAuthConfiguration): Promise { + if (configuration.configuration.signOutLink) { + const id = `${configuration.configuration.id}-sign-out`; const popup = this.windowsService.open(id, { - url: userAuthConfiguration.signOutLink, + url: configuration.configuration.signOutLink, target: id, width: 600, height: 700, @@ -116,18 +118,6 @@ export class AuthenticationService extends Bootstrap { popup.blur(); window.focus(); } - - if (!full) { - let maxTime = 1000 / 100 * 10; - - const interval = setInterval(() => { - if (popup?.location.href !== userAuthConfiguration.signOutLink || maxTime === 0) { - this.userInfoResource.markOutdated(); - clearInterval(interval); - } - maxTime--; - }, 100); - } } } diff --git a/webapp/packages/plugin-user-profile/src/UserProfileForm/AuthTokens/AuthTokenList.tsx b/webapp/packages/plugin-user-profile/src/UserProfileForm/AuthTokens/AuthTokenList.tsx index 82930b5373..96c186ec03 100644 --- a/webapp/packages/plugin-user-profile/src/UserProfileForm/AuthTokens/AuthTokenList.tsx +++ b/webapp/packages/plugin-user-profile/src/UserProfileForm/AuthTokens/AuthTokenList.tsx @@ -95,10 +95,10 @@ export const AuthTokenList = observer(function AuthTokenList({ user, clas > : undefined} - after={configuration && ( + after={( From c25ce3e31e49e809d640f28dee18f1a7f0286ed4 Mon Sep 17 00:00:00 2001 From: Oksana Cherniavskaia <114406033+oksana-cherniavskaia@users.noreply.github.com> Date: Thu, 10 Nov 2022 20:28:23 +0300 Subject: [PATCH 4/9] CB-2797 TE. Support deleting connection folders (#1256) --- .../rm/local/LocalResourceController.java | 20 +++++++++++++++++++ .../navigator/impl/WebServiceNavigator.java | 2 ++ 2 files changed, 22 insertions(+) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 31dbd07e81..5fa715498e 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -29,6 +29,7 @@ import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataSourceConfigurationStorage; import org.jkiss.dbeaver.model.DBPDataSourceContainer; +import org.jkiss.dbeaver.model.DBPDataSourceFolder; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.auth.SMCredentials; @@ -332,6 +333,25 @@ public class LocalResourceController implements RMController { registry.checkForErrors(); } + @Override + public void deleteProjectConnectionFolders( + @NotNull String projectId, + @NotNull String[] folderPaths, + boolean dropContents + ) throws DBException { + DBPProject project = getProjectMetadata(projectId); + DBPDataSourceRegistry registry = project.getDataSourceRegistry(); + for (String folderPath : folderPaths) { + DBPDataSourceFolder folder = registry.getFolder(folderPath); + if (folder != null) { + registry.removeFolder(folder, dropContents); + } else { + log.warn("Can not find folder by path [" + folderPath + "] for deletion"); + } + } + registry.checkForErrors(); + } + @NotNull @Override public RMResource[] listResources( diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java index 7bc2d11d30..767f3563ac 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/navigator/impl/WebServiceNavigator.java @@ -415,7 +415,9 @@ public class WebServiceNavigator implements DBWServiceNavigator { ne.getValue().deleteObject(commandContext, object, options); commandContext.saveChanges(session.getProgressMonitor(), options); } else if (node instanceof DBNLocalFolder) { + node.getOwnerProject().getDataSourceRegistry().removeFolder(((DBNLocalFolder) node).getFolder(), false); + } else if (node instanceof DBNResourceManagerResource) { DBNResourceManagerResource rmResource = ((DBNResourceManagerResource) node); String resourceProjectId = rmResource.getResourceProject().getId(); From caa2d50614b5a6802f14c3613ddffe4125ccb6a6 Mon Sep 17 00:00:00 2001 From: Oksana Cherniavskaia <114406033+oksana-cherniavskaia@users.noreply.github.com> Date: Thu, 10 Nov 2022 20:37:31 +0300 Subject: [PATCH 5/9] CB-2638 Oracle wallet authentication (#1252) * CB-2794 support file form control * CB-2794 require name existing in state * CB-2794 support "file" feature in RenderField * CB-2794 code style fixes * CB-2794 review fixes * CB-2638 remove metadata from base64 string * CB-2794 treat file as password * CB-2794 pass accept as prop * CB-2638 do not rerender authentication dialog on submit Co-authored-by: naumov Co-authored-by: Ainur --- webapp/packages/core-blocks/src/Button.tsx | 12 +- .../src/FormControls/InputFile.tsx | 188 ++++++++++++++++++ .../ObjectPropertyInfoForm/RenderField.tsx | 33 ++- webapp/packages/core-blocks/src/index.ts | 1 + .../core-localization/src/locales/en.ts | 3 + .../core-localization/src/locales/it.ts | 3 + .../core-localization/src/locales/ru.ts | 3 + .../core-localization/src/locales/zh.ts | 3 + webapp/packages/core-utils/src/blobToData.ts | 2 +- webapp/packages/core-utils/src/index.ts | 3 +- .../src/removeMetadataFromBase64.test.ts | 27 +++ .../src/removeMetadataFromBase64.ts | 17 ++ .../Options/ConnectionOptionsTabService.ts | 4 +- .../DatabaseAuthDialog/DBAuthDialogFooter.tsx | 1 + .../DatabaseAuthDialog/DatabaseAuthDialog.tsx | 31 ++- 15 files changed, 303 insertions(+), 28 deletions(-) create mode 100644 webapp/packages/core-blocks/src/FormControls/InputFile.tsx create mode 100644 webapp/packages/core-utils/src/removeMetadataFromBase64.test.ts create mode 100644 webapp/packages/core-utils/src/removeMetadataFromBase64.ts diff --git a/webapp/packages/core-blocks/src/Button.tsx b/webapp/packages/core-blocks/src/Button.tsx index 95b17c143c..a88e8a4d2a 100644 --- a/webapp/packages/core-blocks/src/Button.tsx +++ b/webapp/packages/core-blocks/src/Button.tsx @@ -12,7 +12,7 @@ import styled, { css, use } from 'reshadow'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { Icon } from './Icon'; +import { IconOrImage } from './IconOrImage'; import { Loader } from './Loader/Loader'; import { useObservableRef } from './useObservableRef'; import { useStyles } from './useStyles'; @@ -31,6 +31,14 @@ const buttonStyles = css` composes: theme-button from global; display: flex; + & IconOrImage { + width: 100%; + } + + &[disabled] IconOrImage { + opacity: 0.5; + } + & Loader, & button-label { transition: opacity cubic-bezier(0.4, 0.0, 0.2, 1) 0.3s; } @@ -154,7 +162,7 @@ export const Button = observer(function Button({ onClick={state.click} > - {icon && } + {icon && } {children} diff --git a/webapp/packages/core-blocks/src/FormControls/InputFile.tsx b/webapp/packages/core-blocks/src/FormControls/InputFile.tsx new file mode 100644 index 0000000000..ebd57dfb34 --- /dev/null +++ b/webapp/packages/core-blocks/src/FormControls/InputFile.tsx @@ -0,0 +1,188 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 { observer } from 'mobx-react-lite'; +import { ReactNode, useContext, useState } from 'react'; +import styled, { css, use } from 'reshadow'; + +import type { ComponentStyle } from '@cloudbeaver/core-theming'; +import { blobToData, bytesToSize } from '@cloudbeaver/core-utils'; + +import { Button } from '../Button'; +import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps'; +import { IconButton } from '../IconButton'; +import { useTranslate } from '../localization/useTranslate'; +import { UploadArea } from '../UploadArea'; +import { useStyles } from '../useStyles'; +import { baseFormControlStyles, baseInvalidFormControlStyles, baseValidFormControlStyles } from './baseFormControlStyles'; +import { FormContext } from './FormContext'; + +const DEFAULT_MAX_FILE_SIZE = 2048; + +const INPUT_FILE_FIELD_STYLES = css` + field-label { + display: block; + composes: theme-typography--body1 from global; + font-weight: 500; + } + field-label:not(:empty) { + padding-bottom: 10px; + } + field-description { + display: flex; + align-items: center; + gap: 2px; + } + IconButton { + width: 12px; + height: 12px; + flex-shrink: 0; + cursor: pointer; + &:hover { + opacity: 0.5; + } + } +`; + +interface Props extends ILayoutSizeProps { + name: keyof TState; + state: TState; + accept?: string; + labelTooltip?: string; + tooltip?: string; + required?: boolean; + fileName?: string; + style?: ComponentStyle; + /** Max file size in KB */ + maxFileSize?: number; + disabled?: boolean; + className?: string; + children?: ReactNode; + onChange?: (value: string, name: keyof TState) => void; + mapValue?: (value: string) => string; +} + +type InputFileType = >(props: Props) => React.ReactElement; + +export const InputFile: InputFileType = observer(function InputFile({ + name, + state, + accept, + labelTooltip, + tooltip, + required, + fileName, + small, + medium, + large, + tiny, + style, + maxFileSize = DEFAULT_MAX_FILE_SIZE, + disabled, + className, + children, + onChange, + mapValue, +}: Props>) { + const translate = useTranslate(); + const context = useContext(FormContext); + + const [selected, setSelected] = useState(null); + const [error, setError] = useState(null); + + const styles = useStyles( + INPUT_FILE_FIELD_STYLES, + baseFormControlStyles, + style, + error ? baseInvalidFormControlStyles : baseValidFormControlStyles + ); + + const savedExternally = !!fileName && state[name] !== ''; + const saved = savedExternally || !!state[name]; + + let description; + + if (selected?.name || savedExternally) { + description = selected?.name ?? fileName; + } + + if (error?.message) { + description = error.message; + } + + if (!description) { + description = translate(saved ? 'ui_processing_saved' : 'ui_no_file_chosen'); + } + + function updateValue(value: string) { + const val = mapValue?.(value) ?? value; + + state[name] = val; + onChange?.(val, name); + context?.change(val, name); + } + + function removeFile() { + setSelected(null); + updateValue(''); + } + + function validateFileSize(size: number) { + const maxFileSizeBytes = maxFileSize * 1000; + + if (size > maxFileSizeBytes) { + throw new Error(translate('ui_file_size_exceeds', undefined, { + size: bytesToSize(size), + maxSize: bytesToSize(maxFileSizeBytes), + })); + } + } + + async function handleChange(event: React.ChangeEvent) { + const file = event.target.files?.[0]; + + if (!file) { + removeFile(); + } else { + try { + validateFileSize(file.size); + + const value = await blobToData(file); + + if (value) { + setSelected(file); + updateValue(value); + setError(null); + } + } catch (exception: any) { + removeFile(); + setError(exception); + } + } + } + + return styled(styles)( + + {children}{required && ' *'} + + + + + {description} + {(selected || saved) && } + + + ); +}); \ No newline at end of file diff --git a/webapp/packages/core-blocks/src/ObjectPropertyInfo/ObjectPropertyInfoForm/RenderField.tsx b/webapp/packages/core-blocks/src/ObjectPropertyInfo/ObjectPropertyInfoForm/RenderField.tsx index 49834b769d..39e49313d8 100644 --- a/webapp/packages/core-blocks/src/ObjectPropertyInfo/ObjectPropertyInfoForm/RenderField.tsx +++ b/webapp/packages/core-blocks/src/ObjectPropertyInfo/ObjectPropertyInfoForm/RenderField.tsx @@ -9,14 +9,17 @@ import { observer } from 'mobx-react-lite'; import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; +import { removeMetadataFromBase64 } from '@cloudbeaver/core-utils'; import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox'; import { Combobox } from '../../FormControls/Combobox'; import { FormFieldDescription } from '../../FormControls/FormFieldDescription'; import { InputField } from '../../FormControls/InputField'; +import { InputFile } from '../../FormControls/InputFile'; import { isControlPresented } from '../../FormControls/isControlPresented'; import { Textarea } from '../../FormControls/Textarea'; import { Link } from '../../Link'; +import { useTranslate } from '../../localization/useTranslate'; const RESERVED_KEYWORDS = ['no', 'off', 'new-password']; @@ -33,7 +36,7 @@ interface RenderFieldProps { className?: string; } -type ControlType = 'checkbox' | 'combobox' | 'link' | 'input' | 'textarea'; +type ControlType = 'checkbox' | 'combobox' | 'link' | 'input' | 'textarea' | 'file'; function getControlTypeFor(property: ObjectPropertyInfo): ControlType { const dataType = property.dataType?.toLowerCase(); @@ -46,6 +49,8 @@ function getControlTypeFor(property: ObjectPropertyInfo): ControlType { return 'link'; } else if (dataType === 'string' && property.length === 'MULTILINE') { return 'textarea'; + } else if (property.features.includes('file')) { + return 'file'; } return 'input'; @@ -77,12 +82,32 @@ export const RenderField = observer(function RenderField({ onFocus, className, }) { + const translate = useTranslate(); + const controltype = getControlTypeFor(property); const password = property.features.includes('password'); const value = getValue(property.value, controltype); const defaultValue = getValue(property.defaultValue, controltype); - let description: string | undefined; + const passwordSaved = showRememberTip && password && !!property.value; + const description = passwordSaved ? translate('ui_processing_saved') : undefined; + + if (controltype === 'file' && state) { + return ( + + {property.displayName} + + ); + } if (controltype === 'link') { return ( @@ -103,10 +128,6 @@ export const RenderField = observer(function RenderField({ ); } - if (showRememberTip && password && property.value) { - description = 'Password saved'; - } - if (controltype === 'checkbox') { if (state !== undefined) { return ( diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 9ea201b3dd..50ba81cde2 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -135,6 +135,7 @@ export * from './FormControls/FormContext'; export * from './FormControls/FormFieldDescription'; export * from './FormControls/FormGroup'; export * from './FormControls/InputField'; +export * from './FormControls/InputFile'; export * from './FormControls/Radio'; export * from './FormControls/RadioGroup'; export * from './FormControls/RadioGroupContext'; diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 5210ef268c..c369ca78ea 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -86,6 +86,9 @@ export const defaultENLocale = [ ['ui_limit', 'Limit'], ['ui_file_size', 'File size'], ['ui_processing_synchronization', 'Synchronization...'], + ['ui_no_file_chosen', 'No file chosen'], + ['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'], + ['ui_upload_file', 'Upload file'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index b4940df084..f731c69f99 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -73,6 +73,9 @@ export const defaultITLocale = [ ['ui_limit', 'Limit'], ['ui_file_size', 'File size'], ['ui_processing_synchronization', 'Synchronization...'], + ['ui_no_file_chosen', 'No file chosen'], + ['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'], + ['ui_upload_file', 'Upload file'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 83da44304f..4ad3ee337f 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -84,6 +84,9 @@ export const defaultRULocale = [ ['ui_limit', 'Лимит'], ['ui_file_size', 'Размер файла'], ['ui_processing_synchronization', 'Синхронизация...'], + ['ui_no_file_chosen', 'Файл не выбран'], + ['ui_file_size_exceeds', 'Максимальный размер файла превышен. Размер файла: {arg:size}. Максимальный размер: {arg:maxSize}'], + ['ui_upload_file', 'Загрузить файл'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 910967a46c..e3d5da7e59 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -86,6 +86,9 @@ export const defaultZHLocale = [ ['ui_limit', 'Limit'], ['ui_file_size', 'File size'], ['ui_processing_synchronization', 'Synchronization...'], + ['ui_no_file_chosen', 'No file chosen'], + ['ui_file_size_exceeds', 'Max file size exceeds. File size: {arg:size}. Max file size: {arg:maxSize}'], + ['ui_upload_file', 'Upload file'], ['root_permission_denied', '您没有权限'], ['root_permission_no_permission', '您没有权限执行此操作'], diff --git a/webapp/packages/core-utils/src/blobToData.ts b/webapp/packages/core-utils/src/blobToData.ts index 19134a7dd0..77328205ce 100644 --- a/webapp/packages/core-utils/src/blobToData.ts +++ b/webapp/packages/core-utils/src/blobToData.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -export function blobToData(blob: Blob): Promise { +export function blobToData(blob: Blob | File): Promise { return new Promise((resolve, reject) => { const fileReader = new FileReader(); fileReader.onload = () => { diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index f50c93e879..23e1b76d38 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -47,4 +47,5 @@ export * from './debounce'; export * from './svgToDataUri'; export * from './uriToBlob'; export * from './utf8ToBase64'; -export * from './createLastPromiseGetter'; \ No newline at end of file +export * from './createLastPromiseGetter'; +export * from './removeMetadataFromBase64'; \ No newline at end of file diff --git a/webapp/packages/core-utils/src/removeMetadataFromBase64.test.ts b/webapp/packages/core-utils/src/removeMetadataFromBase64.test.ts new file mode 100644 index 0000000000..d2bdf7cb8c --- /dev/null +++ b/webapp/packages/core-utils/src/removeMetadataFromBase64.test.ts @@ -0,0 +1,27 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 { removeMetadataFromBase64 } from './removeMetadataFromBase64'; + +const BASE64 = 'data:application/zip;base64,UEsDBBQACAgIAIB8CVUAAAAAAAAAAAAAAAALAAAAZXdhbGxldC5wZW3Nmceyq0qyhuc8Rc+JExiBgEEP8CC8NzMBEt4j3NNf1trH7D59um/c7slVhCJEVVKUsjK//FP65ZfrxfCirP+N11k7Ml2e+5tpyz7t8n9T+Ohr+hdAk2X+XtEpk/djUZcitcEMR/OcRmM8a4R9yZnBGawCncsdTzsso1WSGXc6voSWMeQpMO3R8lwYheqHxvfostBL22Fpwy29OiJqPLhNgQvd6bgdg7dgPWBkDDOsZrbbZHYVLwPudK1k79VpirZlyROasi04dWTJOr7fzNwbIhoC68nCKqpB4GzRMToanV0M9VcBCm0gsepQGcPZWZOzGCM70Txzy9rPS9P1eafhIG6Jw2my4gEKyWgYBv2IqZfSetjjHgjhG9BQO361HARiEoTf/bIpHpsXPTp6SF/e2mAoGT4/dSkXKJ0sFcTPybY2S0MgjuwjTUwANr0FNmiSqppurQ1SPYhPY6xBrzU4X6HX3hljtwzDkycpSJUIFqznp78LUXKaqy+/DWDAGnXw7aTcWVSR7/K8cH5LGKUzpvW0jfXqjzJW+DdY5620saGli5XG0ibNsIr7A+0AY3a6bJ5YWbDWUWA2Z36C7bRDiOQlchlrpPEKjKWAFK7a7XCa0VPEHcy6BTTfEFZNAki9BOOdeKx4t50aXGVgWLve+VDz5Z6IHk+2TZgGg3nnBsrcxdug7OKWrpqTremQYTlgrrU55QVXCcnHgMESdTMKFgx6zPjYRRM/TxLSbLD2QMxgZxg2RbXCRtRVeePZ5WkQ0Aa0kpy3wb35bI/Xl0Ye+BtpZXRvTnIHUy9PA5txFiN2fSwZ4noz1bGgitJuoWt7T0CcngXEv813HqmFR9JKj+exx3Iv4nlAkroF8wNlVjJkO1hooGg9lJuzzm1KTmllpdMA6PJq9oUEr7zsUqDZIjcVsZei7V9uPGLhiz2Xup9TYezPe0IX2CzuHQiLVWPf2/TZlcBoX9+zaHnbTLLuZsQ5pTs3TQpbxPMDPONEseHJlH5YueYd+vSZ3kXVxKbafZ7Z/HJCQF0gnKJK8Ei2TzEb/Avjl4WQiaisukF85Y3eRNWD3uUH8hJrbgPh/DztLpDKk5dFkQEejjvzxNiLmWQgReFhIDl7+xu8kWPygEaHJN9C1dqPnXCzwlcFthA0t24i7pnXBW2ogErgmuAUt2Ta7vAOIk97lzDNiYVnazSZNJxvZh4jsUgC0NXTFPcErqCsFM71V9LK/hvoY9Q5LOQ2xqxagSoZTU60mrx/F+8TnEvy04rld021haT2K3TibVzBLAjRdMp+AjnVAVUx0r1MHGOeVyJlaXdgn7e4GUBO8pPtwQpYstUPZh3NA0U/NrHj1r3nu2BeD39YIgnwSyxGqnBEEFOv2ed42EyGN+276BRjte5v4+iYVHIR3/PigS3w6bXx5Jw1C9h2Zqy/gOljLXVSqqXz6PeG52M8JX0HjjuuP6T47aOcqGw3vkyz0jTkqnbh11a4UqtPUWqbNwIQb+AAkWwmHjeEb0J+ZBA2KD+HcniVdWIDRjVu5lyOFQX1cLF6l1+61eqh9Qmi+PlpAJFH2S29zej+GtlwLk8iIeh2gSQ+50ZMfXqLHLo80R47Eb9LlulN2GEapa6ecuQ/RAJoOBa8S8LRivfCuMOxdI'; +const BASE64_NO_METADATA = 'UEsDBBQACAgIAIB8CVUAAAAAAAAAAAAAAAALAAAAZXdhbGxldC5wZW3Nmceyq0qyhuc8Rc+JExiBgEEP8CC8NzMBEt4j3NNf1trH7D59um/c7slVhCJEVVKUsjK//FP65ZfrxfCirP+N11k7Ml2e+5tpyz7t8n9T+Ohr+hdAk2X+XtEpk/djUZcitcEMR/OcRmM8a4R9yZnBGawCncsdTzsso1WSGXc6voSWMeQpMO3R8lwYheqHxvfostBL22Fpwy29OiJqPLhNgQvd6bgdg7dgPWBkDDOsZrbbZHYVLwPudK1k79VpirZlyROasi04dWTJOr7fzNwbIhoC68nCKqpB4GzRMToanV0M9VcBCm0gsepQGcPZWZOzGCM70Txzy9rPS9P1eafhIG6Jw2my4gEKyWgYBv2IqZfSetjjHgjhG9BQO361HARiEoTf/bIpHpsXPTp6SF/e2mAoGT4/dSkXKJ0sFcTPybY2S0MgjuwjTUwANr0FNmiSqppurQ1SPYhPY6xBrzU4X6HX3hljtwzDkycpSJUIFqznp78LUXKaqy+/DWDAGnXw7aTcWVSR7/K8cH5LGKUzpvW0jfXqjzJW+DdY5620saGli5XG0ibNsIr7A+0AY3a6bJ5YWbDWUWA2Z36C7bRDiOQlchlrpPEKjKWAFK7a7XCa0VPEHcy6BTTfEFZNAki9BOOdeKx4t50aXGVgWLve+VDz5Z6IHk+2TZgGg3nnBsrcxdug7OKWrpqTremQYTlgrrU55QVXCcnHgMESdTMKFgx6zPjYRRM/TxLSbLD2QMxgZxg2RbXCRtRVeePZ5WkQ0Aa0kpy3wb35bI/Xl0Ye+BtpZXRvTnIHUy9PA5txFiN2fSwZ4noz1bGgitJuoWt7T0CcngXEv813HqmFR9JKj+exx3Iv4nlAkroF8wNlVjJkO1hooGg9lJuzzm1KTmllpdMA6PJq9oUEr7zsUqDZIjcVsZei7V9uPGLhiz2Xup9TYezPe0IX2CzuHQiLVWPf2/TZlcBoX9+zaHnbTLLuZsQ5pTs3TQpbxPMDPONEseHJlH5YueYd+vSZ3kXVxKbafZ7Z/HJCQF0gnKJK8Ei2TzEb/Avjl4WQiaisukF85Y3eRNWD3uUH8hJrbgPh/DztLpDKk5dFkQEejjvzxNiLmWQgReFhIDl7+xu8kWPygEaHJN9C1dqPnXCzwlcFthA0t24i7pnXBW2ogErgmuAUt2Ta7vAOIk97lzDNiYVnazSZNJxvZh4jsUgC0NXTFPcErqCsFM71V9LK/hvoY9Q5LOQ2xqxagSoZTU60mrx/F+8TnEvy04rld021haT2K3TibVzBLAjRdMp+AjnVAVUx0r1MHGOeVyJlaXdgn7e4GUBO8pPtwQpYstUPZh3NA0U/NrHj1r3nu2BeD39YIgnwSyxGqnBEEFOv2ed42EyGN+276BRjte5v4+iYVHIR3/PigS3w6bXx5Jw1C9h2Zqy/gOljLXVSqqXz6PeG52M8JX0HjjuuP6T47aOcqGw3vkyz0jTkqnbh11a4UqtPUWqbNwIQb+AAkWwmHjeEb0J+ZBA2KD+HcniVdWIDRjVu5lyOFQX1cLF6l1+61eqh9Qmi+PlpAJFH2S29zej+GtlwLk8iIeh2gSQ+50ZMfXqLHLo80R47Eb9LlulN2GEapa6ecuQ/RAJoOBa8S8LRivfCuMOxdI'; + +describe('Remove metadata from base64', () => { + + test('should return base64 without metadata when base64 with metadata is passed', () => { + expect(removeMetadataFromBase64(BASE64)).toMatch(BASE64_NO_METADATA); + }); + + test('should return base64 when base64 with no metadata is passed', () => { + expect(removeMetadataFromBase64(BASE64_NO_METADATA)).toMatch(BASE64_NO_METADATA); + }); + + test('should return empty string when empty string is passed', () => { + expect(removeMetadataFromBase64('')).toMatch(''); + }); +}); \ No newline at end of file diff --git a/webapp/packages/core-utils/src/removeMetadataFromBase64.ts b/webapp/packages/core-utils/src/removeMetadataFromBase64.ts new file mode 100644 index 0000000000..13a543f72b --- /dev/null +++ b/webapp/packages/core-utils/src/removeMetadataFromBase64.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export function removeMetadataFromBase64(base64: string) { + const parts = base64.split(','); + + if (parts[1]) { + return parts[1]; + } + + return parts[0]; +} \ No newline at end of file diff --git a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts index d09e98bcf4..e78065de6a 100644 --- a/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts +++ b/webapp/packages/plugin-connections/src/ConnectionForm/Options/ConnectionOptionsTabService.ts @@ -399,8 +399,8 @@ export class ConnectionOptionsTabService extends Bootstrap { const value = credentials[property.id!]; if (property.features.includes('password')) { - if (value) { - return true; + if (value !== undefined) { + return property.features.includes('file') ? true : !!value; } } else if (value !== property.value) { return true; diff --git a/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DBAuthDialogFooter.tsx b/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DBAuthDialogFooter.tsx index 2699eb7beb..6a157e2ad9 100644 --- a/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DBAuthDialogFooter.tsx +++ b/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DBAuthDialogFooter.tsx @@ -49,6 +49,7 @@ export const DBAuthDialogFooter = observer>(funct type="button" mod={['unelevated']} disabled={isAuthenticating} + loading={isAuthenticating} onClick={onLogin} > {translate('authentication_login')} diff --git a/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx b/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx index 7fb8e8b8aa..d7a0708583 100644 --- a/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx +++ b/webapp/packages/plugin-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx @@ -12,7 +12,6 @@ import styled, { css } from 'reshadow'; import { useAdministrationSettings } from '@cloudbeaver/core-administration'; import { SubmittingForm, - Loader, useFocus, ErrorMessage, useStyles, @@ -87,21 +86,21 @@ export const DatabaseAuthDialog: DialogComponent = observer(function Da )} onReject={options?.persistent ? undefined : rejectDialog} > - {(!connection.isLoaded() || connection.isLoading() || !controller.configured) - ? - : ( - - - - )} + + + ); }); From bf64499be5979b034612e931081c37293456722a Mon Sep 17 00:00:00 2001 From: Aleksey Potsetsuev Date: Fri, 11 Nov 2022 18:18:05 +0300 Subject: [PATCH 6/9] CB-2875 fix: skip nav node open check --- .../src/NodesManager/NavNodeManagerService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts index 5b97895e29..06b397d508 100644 --- a/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts +++ b/webapp/packages/core-navigation-tree/src/NodesManager/NavNodeManagerService.ts @@ -174,6 +174,10 @@ export class NavNodeManagerService extends Bootstrap { } async canOpen(nodeId: string, parentId: string, folderId?: string): Promise { + if (!this.navTree.has(nodeId)) { + return false; + } + const contexts = await this.navigator.execute({ type: NavigationType.canOpen, nodeId, From 3823c61cf82671131d37d3a8c60fd3ecea9fdc67 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Fri, 11 Nov 2022 10:35:42 -0500 Subject: [PATCH 7/9] Cb 2798 show parameters tab on teams (#1249) * CB-2798 Meta parameters API + refactoring * CB-2798 Meta parameters API + refactoring * CB-2798 Team meta parameters save API * CB-2798 Team meta parameters read binding * CB-2798 show parameters in the team form * CB-2798 change locale token * CB-2798 reset includes in the loader method * CB-2798 Admin service merge * CB-2798 refresh team after update * CB-2798 mark outdated instead of the hard refresh Co-authored-by: naumov --- .../bundles/io.cloudbeaver.model/plugin.xml | 2 + .../io.cloudbeaver.metaParameters.exsd} | 2 +- .../registry/WebAuthProviderDescriptor.java | 10 +-- .../registry/WebMetaParametersRegistry.java} | 35 ++++++--- .../schema/service.admin.graphqls | 5 ++ .../service/admin/AdminTeamInfo.java | 9 +++ .../service/admin/DBWServiceAdmin.java | 5 ++ .../service/admin/WebServiceBindingAdmin.java | 7 ++ .../service/admin/impl/WebServiceAdmin.java | 35 ++++++++- .../io.cloudbeaver.service.auth/plugin.xml | 9 +-- .../schema/service.auth.graphqls | 7 +- .../cloudbeaver/service/auth/WebUserInfo.java | 5 ++ .../service/auth/impl/WebServiceAuthImpl.java | 9 +-- .../src/TeamMetaParametersResource.ts | 30 +++++++ .../core-authentication/src/TeamsResource.ts | 68 +++++++++++++--- .../packages/core-authentication/src/index.ts | 1 + .../core-authentication/src/manifest.ts | 2 + .../authentication/teams/createTeam.gql | 2 +- .../teams/getTeamMetaParameters.gql | 5 ++ .../authentication/teams/getTeamsList.gql | 2 +- .../teams/saveTeamMetaParameters.gql | 9 +++ .../authentication/teams/updateTeam.gql | 2 +- .../src/queries/fragments/AdminTeamInfo.gql | 1 + .../queries/fragments/ObjectPropertyInfo.gql | 13 ++++ webapp/packages/core-sdk/src/sdk.ts | 78 +++++++++++++++++-- .../Users/Teams/Contexts/teamContext.ts | 1 + .../Users/Teams/ITeamFormProps.ts | 5 +- .../Users/Teams/Options/TeamOptions.tsx | 18 ++++- .../Teams/Options/TeamOptionsTabService.ts | 12 +++ .../Users/Teams/TeamFormState.ts | 10 +-- .../Users/Teams/TeamsTable/TeamEdit.tsx | 2 +- .../Users/Teams/useTeamFormState.ts | 5 +- .../plugin-authentication/src/locales/en.ts | 1 + .../plugin-authentication/src/locales/it.ts | 1 + .../plugin-authentication/src/locales/ru.ts | 1 + .../plugin-authentication/src/locales/zh.ts | 1 + 36 files changed, 339 insertions(+), 71 deletions(-) rename server/bundles/{io.cloudbeaver.service.auth/schema/io.cloudbeaver.userProfile.exsd => io.cloudbeaver.model/schema/io.cloudbeaver.metaParameters.exsd} (85%) rename server/bundles/{io.cloudbeaver.service.auth/src/io/cloudbeaver/registry/WebUserProfileRegistry.java => io.cloudbeaver.model/src/io/cloudbeaver/registry/WebMetaParametersRegistry.java} (57%) create mode 100644 webapp/packages/core-authentication/src/TeamMetaParametersResource.ts create mode 100644 webapp/packages/core-sdk/src/queries/authentication/teams/getTeamMetaParameters.gql create mode 100644 webapp/packages/core-sdk/src/queries/authentication/teams/saveTeamMetaParameters.gql create mode 100644 webapp/packages/core-sdk/src/queries/fragments/ObjectPropertyInfo.gql diff --git a/server/bundles/io.cloudbeaver.model/plugin.xml b/server/bundles/io.cloudbeaver.model/plugin.xml index 96847f47d7..ec0dd5a019 100644 --- a/server/bundles/io.cloudbeaver.model/plugin.xml +++ b/server/bundles/io.cloudbeaver.model/plugin.xml @@ -7,4 +7,6 @@ + + diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/io.cloudbeaver.userProfile.exsd b/server/bundles/io.cloudbeaver.model/schema/io.cloudbeaver.metaParameters.exsd similarity index 85% rename from server/bundles/io.cloudbeaver.service.auth/schema/io.cloudbeaver.userProfile.exsd rename to server/bundles/io.cloudbeaver.model/schema/io.cloudbeaver.metaParameters.exsd index 5901571b6a..fe9b6f92fb 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/io.cloudbeaver.userProfile.exsd +++ b/server/bundles/io.cloudbeaver.model/schema/io.cloudbeaver.metaParameters.exsd @@ -2,7 +2,7 @@ - + diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java index a6c46bebc1..087dfda5b0 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebAuthProviderDescriptor.java @@ -46,7 +46,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor { private final IConfigurationElement cfg; private final ObjectType implType; - private final Map> metaProperties = new HashMap<>(); + private final Map> metaParameters = new HashMap<>(); private SMAuthProvider instance; private final DBPImage icon; private final Map configurationParameters = new LinkedHashMap<>(); @@ -76,13 +76,13 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor { for (IConfigurationElement credElement : cfg.getChildren("credentials")) { credentialProfiles.add(new SMAuthCredentialsProfile(credElement)); } - for (IConfigurationElement mpElement : cfg.getChildren("metaProperties")) { + for (IConfigurationElement mpElement : cfg.getChildren("metaParameters")) { SMSubjectType subjectType = CommonUtils.valueOf(SMSubjectType.class, mpElement.getAttribute("type"), SMSubjectType.user); List metaProps = new ArrayList<>(); for (IConfigurationElement propGroup : ArrayUtils.safeArray(mpElement.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) { metaProps.addAll(PropertyDescriptor.extractProperties(propGroup)); } - metaProperties.put(subjectType, metaProps); + metaParameters.put(subjectType, metaProps); } String rfList = cfg.getAttribute("requiredFeatures"); @@ -182,8 +182,8 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor { } @Nullable - public List getMetaProperties(SMSubjectType subjectType) { - return metaProperties.get(subjectType); + public List getMetaParameters(SMSubjectType subjectType) { + return metaParameters.get(subjectType); } } diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/registry/WebUserProfileRegistry.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebMetaParametersRegistry.java similarity index 57% rename from server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/registry/WebUserProfileRegistry.java rename to server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebMetaParametersRegistry.java index 796f62b3e2..c8f11f6211 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/registry/WebUserProfileRegistry.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/registry/WebMetaParametersRegistry.java @@ -27,36 +27,47 @@ import org.jkiss.utils.ArrayUtils; import java.util.ArrayList; import java.util.List; -public class WebUserProfileRegistry { +public class WebMetaParametersRegistry { - private static final Log log = Log.getLog(WebUserProfileRegistry.class); + private static final Log log = Log.getLog(WebMetaParametersRegistry.class); - public static final String EXTENSION_ID = "io.cloudbeaver.userProfile"; //$NON-NLS-1$ + public static final String EXTENSION_ID = "io.cloudbeaver.metaParameters"; //$NON-NLS-1$ - private static WebUserProfileRegistry instance = null; - private final List properties = new ArrayList<>(); + private static WebMetaParametersRegistry instance = null; + private final List userParameters = new ArrayList<>(); + private final List teamParameters = new ArrayList<>(); - public synchronized static WebUserProfileRegistry getInstance() { + public synchronized static WebMetaParametersRegistry getInstance() { if (instance == null) { - instance = new WebUserProfileRegistry(); + instance = new WebMetaParametersRegistry(); instance.loadExtensions(Platform.getExtensionRegistry()); } return instance; } - private WebUserProfileRegistry() { + private WebMetaParametersRegistry() { } - public List getProperties() { - return properties; + public List getUserParameters() { + return userParameters; + } + + public List getTeamParameters() { + return teamParameters; } private void loadExtensions(IExtensionRegistry registry) { IConfigurationElement[] extConfigs = registry.getConfigurationElementsFor(EXTENSION_ID); for (IConfigurationElement ext : extConfigs) { - if (ext.getName().equals("userProfileProperties")) { + if (ext.getName().equals("metaParameters")) { + boolean isUser = "user".equals(ext.getAttribute("type")); for (IConfigurationElement propGroup : ArrayUtils.safeArray(ext.getChildren(PropertyDescriptor.TAG_PROPERTY_GROUP))) { - properties.addAll(PropertyDescriptor.extractProperties(propGroup)); + List props = PropertyDescriptor.extractProperties(propGroup); + if (isUser) { + userParameters.addAll(props); + } else { + teamParameters.addAll(props); + } } } } diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index 895c8168f2..2b463c236d 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -50,6 +50,8 @@ type AdminTeamInfo { teamName: String description: String + metaParameters: Object! + grantedUsers: [ID!]! grantedConnections: [AdminConnectionGrantInfo!]! @@ -120,6 +122,7 @@ extend type Query { listTeams(teamId: ID): [AdminTeamInfo!]! listPermissions: [AdminPermissionInfo!]! listAuthRoles: [String!]! + listTeamMetaParameters: [ObjectPropertyInfo!]! createUser(userId: ID!, enabled: Boolean!, authRole: String): AdminUserInfo! deleteUser(userId: ID!): Boolean @@ -179,6 +182,8 @@ extend type Query { setUserMetaParameterValues(userId: ID!, parameters: Object!): Boolean! + setTeamMetaParameterValues(teamId: ID!, parameters: Object!): Boolean! + #### Global configuration configureServer(configuration: ServerConfigInput!): Boolean! diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminTeamInfo.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminTeamInfo.java index 5826d36239..37ea11b0e3 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminTeamInfo.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminTeamInfo.java @@ -25,6 +25,7 @@ import org.jkiss.dbeaver.model.security.user.SMTeam; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Web team info @@ -45,14 +46,22 @@ public class AdminTeamInfo { return team.getTeamId(); } + @Property public String getTeamName() { return team.getName(); } + @Property public String getDescription() { return team.getDescription(); } + @Property + public Map getMetaParameters() { + return team.getMetaParameters(); + } + + @Property public List getTeamPermissions() { return teamPermissions; } diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java index 315990ac04..9a183de67b 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java @@ -50,6 +50,9 @@ public interface DBWServiceAdmin extends DBWService { @NotNull List listPermissions(@NotNull WebSession webSession) throws DBWebException; + @WebAction() + WebPropertyInfo[] listTeamMetaParameters(@NotNull WebSession webSession); + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) @NotNull AdminUserInfo createUser( @@ -164,6 +167,8 @@ public interface DBWServiceAdmin extends DBWService { @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) Boolean setUserMetaParameterValues(WebSession webSession, String userId, Map parameters) throws DBWebException; @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) + Boolean setTeamMetaParameterValues(WebSession webSession, String teamId, Map parameters) throws DBWebException; + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) Boolean enableUser(WebSession webSession, String userId, Boolean enabled) throws DBWebException; @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java index ee6f46afce..3a0dabe8f7 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java @@ -50,6 +50,8 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase getService(env).listPermissions(getWebSession(env))) .dataFetcher("listAuthRoles", env -> getService(env).listAuthRoles()) + .dataFetcher("listTeamMetaParameters", + env -> getService(env).listTeamMetaParameters(getWebSession(env))) .dataFetcher("createUser", env -> getService(env).createUser( getWebSession(env), @@ -143,6 +145,11 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase getService(env).setTeamMetaParameterValues( + getWebSession(env), + env.getArgument("teamId"), + env.getArgument("parameters"))) .dataFetcher("configureServer", env -> getService(env).configureServer(getWebSession(env), env.getArgument("configuration"))) diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java index 5215b35597..85ef850d9a 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java @@ -41,10 +41,8 @@ import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.app.DBPProject; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; -import org.jkiss.dbeaver.model.security.SMAuthProviderCustomConfiguration; -import org.jkiss.dbeaver.model.security.SMConstants; -import org.jkiss.dbeaver.model.security.SMDataSourceGrant; -import org.jkiss.dbeaver.model.security.SMObjects; +import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; +import org.jkiss.dbeaver.model.security.*; import org.jkiss.dbeaver.model.security.user.SMTeam; import org.jkiss.dbeaver.model.security.user.SMUser; import org.jkiss.utils.CommonUtils; @@ -113,6 +111,25 @@ public class WebServiceAdmin implements DBWServiceAdmin { } } + @Override + public WebPropertyInfo[] listTeamMetaParameters(@NotNull WebSession webSession) { + // First add user profile properties + List props = new ArrayList<>( + WebMetaParametersRegistry.getInstance().getTeamParameters()); + + // Add metas from enabled auth providers + for (WebAuthProviderDescriptor ap : WebServiceUtils.getEnabledAuthProviders()) { + List metaProps = ap.getMetaParameters(SMSubjectType.team); + if (!CommonUtils.isEmpty(metaProps)) { + props.addAll(metaProps); + } + } + + return props.stream() + .map(p -> new WebPropertyInfo(webSession, p, null)) + .toArray(WebPropertyInfo[]::new); + } + @NotNull @Override public AdminUserInfo createUser( @@ -657,6 +674,16 @@ public class WebServiceAdmin implements DBWServiceAdmin { } } + @Override + public Boolean setTeamMetaParameterValues(WebSession webSession, String teamId, Map parameters) throws DBWebException { + try { + webSession.getAdminSecurityController().setSubjectMetas(teamId, parameters); + return true; + } catch (DBException e) { + throw new DBWebException("Error changing team '" + teamId + "' meta parameters", e); + } + } + private DBPDataSourceRegistry getDataSourceRegistry(WebSession session, String projectId) throws DBWebException { WebProjectImpl project = session.getProjectById(projectId); if (project == null) { diff --git a/server/bundles/io.cloudbeaver.service.auth/plugin.xml b/server/bundles/io.cloudbeaver.service.auth/plugin.xml index 3cbc26b47e..36baf6d3c9 100644 --- a/server/bundles/io.cloudbeaver.service.auth/plugin.xml +++ b/server/bundles/io.cloudbeaver.service.auth/plugin.xml @@ -3,21 +3,18 @@ - - - - - + + - + diff --git a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls index a48c41491d..e72b4b0d88 100644 --- a/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls +++ b/server/bundles/io.cloudbeaver.service.auth/schema/service.auth.graphqls @@ -109,16 +109,19 @@ type UserInfo { # Human readable display name. It is taken from the first auth provider which was used for user login. displayName: String + # User auth role ID. Optional. + authRole: ID + # All authentication tokens used during current session authTokens: [UserAuthToken!]! linkedAuthProviders: [String!]! # User profile properties map metaParameters: Object! - - # USer configuiraiton parameters + # User configuration parameters configurationParameters: Object! + } extend type Query { diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java index 7a78e7d5e0..dd2d72482b 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/WebUserInfo.java @@ -54,6 +54,11 @@ public class WebUserInfo { return user == null ? null : user.getDisplayName(); } + @Property + public String getAuthRole() { + return user == null ? null : user.getAuthRole(); + } + @Property public List getAuthTokens() { return session.getAllAuthInfo().stream() diff --git a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java index 7d31bb5268..949ba5fb1e 100644 --- a/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java +++ b/server/bundles/io.cloudbeaver.service.auth/src/io/cloudbeaver/service/auth/impl/WebServiceAuthImpl.java @@ -27,7 +27,7 @@ import io.cloudbeaver.model.user.WebAuthProviderInfo; import io.cloudbeaver.model.user.WebUser; import io.cloudbeaver.registry.WebAuthProviderDescriptor; import io.cloudbeaver.registry.WebAuthProviderRegistry; -import io.cloudbeaver.registry.WebUserProfileRegistry; +import io.cloudbeaver.registry.WebMetaParametersRegistry; import io.cloudbeaver.server.CBApplication; import io.cloudbeaver.service.auth.DBWServiceAuth; import io.cloudbeaver.service.auth.WebAuthStatus; @@ -191,14 +191,13 @@ public class WebServiceAuthImpl implements DBWServiceAuth { @Override public WebPropertyInfo[] listUserProfileProperties(@NotNull WebSession webSession) { - List props = new ArrayList<>(); - // First add user profile properties - props.addAll(WebUserProfileRegistry.getInstance().getProperties()); + List props = new ArrayList<>( + WebMetaParametersRegistry.getInstance().getUserParameters()); // Add metas from enabled auth providers for (WebAuthProviderDescriptor ap : WebServiceUtils.getEnabledAuthProviders()) { - List metaProps = ap.getMetaProperties(SMSubjectType.user); + List metaProps = ap.getMetaParameters(SMSubjectType.user); if (!CommonUtils.isEmpty(metaProps)) { props.addAll(metaProps); } diff --git a/webapp/packages/core-authentication/src/TeamMetaParametersResource.ts b/webapp/packages/core-authentication/src/TeamMetaParametersResource.ts new file mode 100644 index 0000000000..f5026e9f29 --- /dev/null +++ b/webapp/packages/core-authentication/src/TeamMetaParametersResource.ts @@ -0,0 +1,30 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 { SessionResource } from '@cloudbeaver/core-root'; +import { CachedDataResource, GraphQLService, ObjectPropertyInfo } from '@cloudbeaver/core-sdk'; + +export type TeamMetaParameter = ObjectPropertyInfo; + +@injectable() +export class TeamMetaParametersResource extends CachedDataResource { + constructor( + private readonly graphQLService: GraphQLService, + sessionResource: SessionResource, + ) { + super([]); + + this.sync(sessionResource, () => { }, () => { }); + } + + protected async loader(): Promise { + const { parameters } = await this.graphQLService.sdk.getTeamMetaParameters(); + return parameters; + } +} diff --git a/webapp/packages/core-authentication/src/TeamsResource.ts b/webapp/packages/core-authentication/src/TeamsResource.ts index 47cc793a75..ba3c8bb42d 100644 --- a/webapp/packages/core-authentication/src/TeamsResource.ts +++ b/webapp/packages/core-authentication/src/TeamsResource.ts @@ -16,17 +16,20 @@ import { ResourceKeyUtils, AdminTeamInfoFragment, AdminConnectionGrantInfo, - CachedMapAllKey + CachedMapAllKey, + GetTeamsListQueryVariables } from '@cloudbeaver/core-sdk'; import { isArraysEqual } from '@cloudbeaver/core-utils'; const NEW_TEAM_SYMBOL = Symbol('new-team'); export type TeamInfo = AdminTeamInfoFragment; + +type TeamResourceIncludes = Omit; type NewTeam = TeamInfo & { [NEW_TEAM_SYMBOL]: boolean; timestamp: number }; @injectable() -export class TeamsResource extends CachedMapResource { +export class TeamsResource extends CachedMapResource { constructor(private readonly graphQLService: GraphQLService) { super(); } @@ -41,8 +44,20 @@ export class TeamsResource extends CachedMapResource { return this.data; } - async createTeam(teamInfo: TeamInfo): Promise { - const response = await this.graphQLService.sdk.createTeam(teamInfo); + async createTeam({ + teamId, + teamPermissions, + teamName, + description, + metaParameters, + }: TeamInfo): Promise { + const response = await this.graphQLService.sdk.createTeam({ + teamId, + teamName, + description, + ...this.getDefaultIncludes(), + ...this.getIncludesMap(teamId), + }); const newTeam: NewTeam = { ...response.team, @@ -52,19 +67,35 @@ export class TeamsResource extends CachedMapResource { this.updateTeams(newTeam); - await this.setSubjectPermissions(newTeam.teamId, teamInfo.teamPermissions); + await this.setMetaParameters(newTeam.teamId, metaParameters); + await this.setSubjectPermissions(newTeam.teamId, teamPermissions); - return this.get(teamInfo.teamId)!; + return this.get(teamId)!; } - async updateTeam(teamInfo: TeamInfo): Promise { - const { team } = await this.graphQLService.sdk.updateTeam(teamInfo); + async updateTeam({ + teamId, + teamPermissions, + teamName, + description, + metaParameters, + }: TeamInfo): Promise { + const { team } = await this.graphQLService.sdk.updateTeam({ + teamId, + teamName, + description, + ...this.getDefaultIncludes(), + ...this.getIncludesMap(teamId), + }); this.updateTeams(team); - await this.setSubjectPermissions(team.teamId, teamInfo.teamPermissions); + await this.setMetaParameters(team.teamId, metaParameters); + await this.setSubjectPermissions(team.teamId, teamPermissions); - return this.get(teamInfo.teamId)!; + this.markOutdated(team.teamId); + + return this.get(teamId)!; } async deleteTeam(key: ResourceKey): Promise> { @@ -103,11 +134,15 @@ export class TeamsResource extends CachedMapResource { team.teamPermissions = newPermissions.map(permission => permission.id); } else { // TODO: update permissions for team instead - await this.loader(subjectId); + await this.loader(subjectId, []); } } - protected async loader(key: ResourceKey): Promise> { + async setMetaParameters(teamId: string, parameters: Record): Promise { + await this.graphQLService.sdk.saveTeamMetaParameters({ teamId, parameters }); + } + + protected async loader(key: ResourceKey, includes: string[] | undefined): Promise> { const all = ResourceKeyUtils.includes(key, CachedMapAllKey); await ResourceKeyUtils.forEachAsync(all ? CachedMapAllKey : key, async key => { @@ -115,9 +150,12 @@ export class TeamsResource extends CachedMapResource { const { teams } = await this.graphQLService.sdk.getTeamsList({ teamId, + ...this.getDefaultIncludes(), + ...this.getIncludesMap(teamId, includes), }); if (all) { + this.resetIncludes(); this.data.clear(); } @@ -141,6 +179,12 @@ export class TeamsResource extends CachedMapResource { return key; } + + private getDefaultIncludes(): TeamResourceIncludes { + return { + includeMetaParameters: false, + }; + } } function isNewTeam(team: TeamInfo | NewTeam): team is NewTeam { diff --git a/webapp/packages/core-authentication/src/index.ts b/webapp/packages/core-authentication/src/index.ts index a82c6a1b40..70c9325069 100644 --- a/webapp/packages/core-authentication/src/index.ts +++ b/webapp/packages/core-authentication/src/index.ts @@ -16,3 +16,4 @@ export * from './UserDataService'; export * from './UserInfoResource'; export * from './UserMetaParametersResource'; export * from './UsersResource'; +export * from './TeamMetaParametersResource'; \ No newline at end of file diff --git a/webapp/packages/core-authentication/src/manifest.ts b/webapp/packages/core-authentication/src/manifest.ts index e07c765bb2..4f08e36cb3 100644 --- a/webapp/packages/core-authentication/src/manifest.ts +++ b/webapp/packages/core-authentication/src/manifest.ts @@ -16,6 +16,7 @@ import { AuthProviderService } from './AuthProviderService'; import { AuthProvidersResource } from './AuthProvidersResource'; import { AuthRolesResource } from './AuthRolesResource'; import { AuthSettingsService } from './AuthSettingsService'; +import { TeamMetaParametersResource } from './TeamMetaParametersResource'; import { TeamsManagerService } from './TeamsManagerService'; import { TeamsResource } from './TeamsResource'; import { UserConfigurationBootstrap } from './UserConfigurationBootstrap'; @@ -45,5 +46,6 @@ export const manifest: PluginManifest = { UserMetaParametersResource, UserConfigurationBootstrap, AuthRolesResource, + TeamMetaParametersResource, ], }; diff --git a/webapp/packages/core-sdk/src/queries/authentication/teams/createTeam.gql b/webapp/packages/core-sdk/src/queries/authentication/teams/createTeam.gql index 6617ea956f..7654ea781e 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/teams/createTeam.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/teams/createTeam.gql @@ -1,4 +1,4 @@ -query createTeam($teamId: ID!, $teamName: String, $description: String ) { +query createTeam($teamId: ID!, $teamName: String, $description: String, $includeMetaParameters: Boolean!) { team: createTeam(teamId: $teamId, teamName: $teamName, description: $description) { ...AdminTeamInfo } diff --git a/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamMetaParameters.gql b/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamMetaParameters.gql new file mode 100644 index 0000000000..7e96985156 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamMetaParameters.gql @@ -0,0 +1,5 @@ +query getTeamMetaParameters { + parameters: listTeamMetaParameters { + ...ObjectPropertyInfo + } +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamsList.gql b/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamsList.gql index a7f4901739..fbbb425072 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamsList.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/teams/getTeamsList.gql @@ -1,4 +1,4 @@ -query getTeamsList($teamId: ID) { +query getTeamsList($teamId: ID, $includeMetaParameters: Boolean!) { teams: listTeams(teamId: $teamId) { ...AdminTeamInfo } diff --git a/webapp/packages/core-sdk/src/queries/authentication/teams/saveTeamMetaParameters.gql b/webapp/packages/core-sdk/src/queries/authentication/teams/saveTeamMetaParameters.gql new file mode 100644 index 0000000000..453fabd09b --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/authentication/teams/saveTeamMetaParameters.gql @@ -0,0 +1,9 @@ +query saveTeamMetaParameters( + $teamId: ID! + $parameters: Object! +) { + setTeamMetaParameterValues( + teamId: $teamId, + parameters: $parameters + ) +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/authentication/teams/updateTeam.gql b/webapp/packages/core-sdk/src/queries/authentication/teams/updateTeam.gql index 5b7d115b79..20bafa082b 100644 --- a/webapp/packages/core-sdk/src/queries/authentication/teams/updateTeam.gql +++ b/webapp/packages/core-sdk/src/queries/authentication/teams/updateTeam.gql @@ -1,4 +1,4 @@ -query updateTeam($teamId: ID!, $teamName: String, $description: String ) { +query updateTeam($teamId: ID!, $teamName: String, $description: String, $includeMetaParameters: Boolean!) { team: updateTeam(teamId: $teamId, teamName: $teamName, description: $description) { ...AdminTeamInfo } diff --git a/webapp/packages/core-sdk/src/queries/fragments/AdminTeamInfo.gql b/webapp/packages/core-sdk/src/queries/fragments/AdminTeamInfo.gql index d30dfb554d..29bfda19d0 100644 --- a/webapp/packages/core-sdk/src/queries/fragments/AdminTeamInfo.gql +++ b/webapp/packages/core-sdk/src/queries/fragments/AdminTeamInfo.gql @@ -3,4 +3,5 @@ fragment AdminTeamInfo on AdminTeamInfo { teamName description teamPermissions + metaParameters @include(if: $includeMetaParameters) } \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/queries/fragments/ObjectPropertyInfo.gql b/webapp/packages/core-sdk/src/queries/fragments/ObjectPropertyInfo.gql new file mode 100644 index 0000000000..e097d362b5 --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/fragments/ObjectPropertyInfo.gql @@ -0,0 +1,13 @@ +fragment ObjectPropertyInfo on ObjectPropertyInfo { + id + displayName + description + category + dataType + value + validValues + defaultValue + length + features + order +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index 46fa5b8b2d..70e0ece524 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -75,6 +75,7 @@ export interface AdminTeamInfo { description?: Maybe; grantedConnections: Array; grantedUsers: Array; + metaParameters: Scalars['Object']; teamId: Scalars['ID']; teamName?: Maybe; teamPermissions: Array; @@ -918,6 +919,7 @@ export interface Query { listFeatureSets: Array; listPermissions: Array; listProjects: Array; + listTeamMetaParameters: Array; listTeams: Array; listUserProfileProperties: Array; listUsers: Array; @@ -949,6 +951,7 @@ export interface Query { setDefaultNavigatorSettings: Scalars['Boolean']; setSubjectConnectionAccess?: Maybe; setSubjectPermissions: Array; + setTeamMetaParameterValues: Scalars['Boolean']; setUserAuthRole?: Maybe; setUserCredentials?: Maybe; setUserMetaParameterValues: Scalars['Boolean']; @@ -1246,6 +1249,12 @@ export interface QuerySetSubjectPermissionsArgs { } +export interface QuerySetTeamMetaParameterValuesArgs { + parameters: Scalars['Object']; + teamId: Scalars['ID']; +} + + export interface QuerySetUserAuthRoleArgs { authRole?: InputMaybe; userId: Scalars['ID']; @@ -1608,6 +1617,7 @@ export interface UserAuthToken { } export interface UserInfo { + authRole?: Maybe; authTokens: Array; configurationParameters: Scalars['Object']; displayName?: Maybe; @@ -1757,10 +1767,11 @@ export type CreateTeamQueryVariables = Exact<{ teamId: Scalars['ID']; teamName?: InputMaybe; description?: InputMaybe; + includeMetaParameters: Scalars['Boolean']; }>; -export type CreateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array } }; +export type CreateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array, metaParameters?: any } }; export type DeleteTeamQueryVariables = Exact<{ teamId: Scalars['ID']; @@ -1776,21 +1787,36 @@ export type GetTeamGrantedUsersQueryVariables = Exact<{ export type GetTeamGrantedUsersQuery = { team: Array<{ grantedUsers: Array }> }; +export type GetTeamMetaParametersQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetTeamMetaParametersQuery = { parameters: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array, defaultValue?: any, length: ObjectPropertyLength, features: Array, order: number }> }; + export type GetTeamsListQueryVariables = Exact<{ teamId?: InputMaybe; + includeMetaParameters: Scalars['Boolean']; }>; -export type GetTeamsListQuery = { teams: Array<{ teamId: string, teamName?: string, description?: string, teamPermissions: Array }> }; +export type GetTeamsListQuery = { teams: Array<{ teamId: string, teamName?: string, description?: string, teamPermissions: Array, metaParameters?: any }> }; + +export type SaveTeamMetaParametersQueryVariables = Exact<{ + teamId: Scalars['ID']; + parameters: Scalars['Object']; +}>; + + +export type SaveTeamMetaParametersQuery = { setTeamMetaParameterValues: boolean }; export type UpdateTeamQueryVariables = Exact<{ teamId: Scalars['ID']; teamName?: InputMaybe; description?: InputMaybe; + includeMetaParameters: Scalars['Boolean']; }>; -export type UpdateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array } }; +export type UpdateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array, metaParameters?: any } }; export type CreateUserQueryVariables = Exact<{ userId: Scalars['ID']; @@ -2219,7 +2245,7 @@ export type AdminObjectGrantInfoFragment = { subjectId: string, subjectType: Adm export type AdminPermissionInfoFragment = { id: string, label?: string, description?: string, category?: string }; -export type AdminTeamInfoFragment = { teamId: string, teamName?: string, description?: string, teamPermissions: Array }; +export type AdminTeamInfoFragment = { teamId: string, teamName?: string, description?: string, teamPermissions: Array, metaParameters?: any }; export type AdminUserInfoFragment = { userId: string, grantedTeams: Array, linkedAuthProviders: Array, metaParameters?: any, enabled: boolean, authRole?: string, origins: Array<{ type: string, subType?: string, displayName: string, icon?: string, details?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array, value?: any, length: ObjectPropertyLength, features: Array, order: number }> }> }; @@ -2251,6 +2277,8 @@ export type NavNodePropertiesFragment = { id?: string, category?: string, dataTy export type ObjectOriginInfoFragment = { type: string, subType?: string, displayName: string, icon?: string, details?: Array<{ id?: string, displayName?: string, description?: string, category?: string, dataType?: string, defaultValue?: any, validValues?: Array, value?: any, length: ObjectPropertyLength, features: Array, order: number }> }; +export type ObjectPropertyInfoFragment = { id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array, defaultValue?: any, length: ObjectPropertyLength, features: Array, order: number }; + export type SqlScriptInfoFragment = { queries: Array<{ start: number, end: number }> }; export type SessionStateFragment = { createTime: string, lastAccessTime: string, cacheExpired: boolean, locale: string, actionParameters?: any, valid: boolean, remainingTime: number }; @@ -2748,6 +2776,7 @@ export const AdminTeamInfoFragmentDoc = ` teamName description teamPermissions + metaParameters @include(if: $includeMetaParameters) } `; export const ObjectOriginInfoFragmentDoc = ` @@ -3051,6 +3080,21 @@ export const NavNodeInfoFragmentDoc = ` } } ${NavNodePropertiesFragmentDoc}`; +export const ObjectPropertyInfoFragmentDoc = ` + fragment ObjectPropertyInfo on ObjectPropertyInfo { + id + displayName + description + category + dataType + value + validValues + defaultValue + length + features + order +} + `; export const SqlScriptInfoFragmentDoc = ` fragment SQLScriptInfo on SQLScriptInfo { queries { @@ -3249,7 +3293,7 @@ export const SaveUserMetaParametersDocument = ` } `; export const CreateTeamDocument = ` - query createTeam($teamId: ID!, $teamName: String, $description: String) { + query createTeam($teamId: ID!, $teamName: String, $description: String, $includeMetaParameters: Boolean!) { team: createTeam( teamId: $teamId teamName: $teamName @@ -3271,15 +3315,27 @@ export const GetTeamGrantedUsersDocument = ` } } `; +export const GetTeamMetaParametersDocument = ` + query getTeamMetaParameters { + parameters: listTeamMetaParameters { + ...ObjectPropertyInfo + } +} + ${ObjectPropertyInfoFragmentDoc}`; export const GetTeamsListDocument = ` - query getTeamsList($teamId: ID) { + query getTeamsList($teamId: ID, $includeMetaParameters: Boolean!) { teams: listTeams(teamId: $teamId) { ...AdminTeamInfo } } ${AdminTeamInfoFragmentDoc}`; +export const SaveTeamMetaParametersDocument = ` + query saveTeamMetaParameters($teamId: ID!, $parameters: Object!) { + setTeamMetaParameterValues(teamId: $teamId, parameters: $parameters) +} + `; export const UpdateTeamDocument = ` - query updateTeam($teamId: ID!, $teamName: String, $description: String) { + query updateTeam($teamId: ID!, $teamName: String, $description: String, $includeMetaParameters: Boolean!) { team: updateTeam( teamId: $teamId teamName: $teamName @@ -4441,9 +4497,15 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = getTeamGrantedUsers(variables: GetTeamGrantedUsersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(GetTeamGrantedUsersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamGrantedUsers', 'query'); }, - getTeamsList(variables?: GetTeamsListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + getTeamMetaParameters(variables?: GetTeamMetaParametersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(GetTeamMetaParametersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamMetaParameters', 'query'); + }, + getTeamsList(variables: GetTeamsListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(GetTeamsListDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamsList', 'query'); }, + saveTeamMetaParameters(variables: SaveTeamMetaParametersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(SaveTeamMetaParametersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'saveTeamMetaParameters', 'query'); + }, updateTeam(variables: UpdateTeamQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise { return withWrapper((wrappedRequestHeaders) => client.request(UpdateTeamDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'updateTeam', 'query'); }, diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Contexts/teamContext.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Contexts/teamContext.ts index f5648ed9f6..48c269653d 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Contexts/teamContext.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Contexts/teamContext.ts @@ -12,5 +12,6 @@ export function teamContext(): TeamInfo { return { teamId: '', teamPermissions: [], + metaParameters: {}, }; } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/ITeamFormProps.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/ITeamFormProps.ts index 291a5d2f96..4a0cd269ad 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/ITeamFormProps.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/ITeamFormProps.ts @@ -6,9 +6,8 @@ * you may not use this file except in compliance with the License. */ -import type { TeamInfo } from '@cloudbeaver/core-authentication'; +import type { TeamInfo, TeamsResource } from '@cloudbeaver/core-authentication'; import type { IExecutorHandlersCollection } from '@cloudbeaver/core-executor'; -import type { CachedMapResource } from '@cloudbeaver/core-sdk'; import type { MetadataMap } from '@cloudbeaver/core-utils'; export type TeamFormMode = 'edit' | 'create'; @@ -25,7 +24,7 @@ export interface ITeamFormState { readonly loading: boolean; readonly submittingTask: IExecutorHandlersCollection; - readonly resource: CachedMapResource; + readonly resource: TeamsResource; readonly load: () => Promise; readonly loadTeamInfo: () => Promise; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptions.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptions.tsx index ee8ac23060..419fa8a9f8 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptions.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptions.tsx @@ -10,7 +10,8 @@ import { observer } from 'mobx-react-lite'; import { useRef } from 'react'; import styled, { css } from 'reshadow'; -import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, InputField, SubmittingForm, Textarea, useTranslate, useStyles, useDataResource } from '@cloudbeaver/core-blocks'; +import { TeamMetaParametersResource } from '@cloudbeaver/core-authentication'; +import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, InputField, SubmittingForm, Textarea, useTranslate, useStyles, useDataResource, Loader, GroupTitle, ObjectPropertyInfoForm } from '@cloudbeaver/core-blocks'; import { ServerConfigResource } from '@cloudbeaver/core-root'; import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; @@ -31,6 +32,7 @@ export const TeamOptions: TabContainerPanelComponent = observer( const translate = useTranslate(); const serverConfigResource = useDataResource(TeamOptions, ServerConfigResource, undefined); + const teamMetaParameters = useDataResource(TeamOptions, TeamMetaParametersResource, undefined); const style = useStyles(BASE_CONTAINERS_STYLES, styles); const edit = state.mode === 'edit'; @@ -72,6 +74,20 @@ export const TeamOptions: TabContainerPanelComponent = observer( {!serverConfigResource.data?.distributed && } + + {() => teamMetaParameters.data.length > 0 && styled(style)( + + {translate('authentication_team_meta_parameters')} + + + )} + ); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptionsTabService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptionsTabService.ts index c6614172f9..8888bb8fa6 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptionsTabService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/Options/TeamOptionsTabService.ts @@ -73,6 +73,10 @@ export class TeamOptionsTabService extends Bootstrap { config.description = state.config.description; } + if (state.config.metaParameters) { + config.metaParameters = state.config.metaParameters; + } + config.teamPermissions = [...state.config.teamPermissions]; } @@ -115,6 +119,7 @@ export class TeamOptionsTabService extends Bootstrap { status.info(team.teamId); } else { const team = await this.teamResource.updateTeam(config); + status.info('administration_teams_team_info_updated'); status.info(team.teamId); } @@ -142,12 +147,19 @@ export class TeamOptionsTabService extends Bootstrap { if (state.info.teamId) { state.config.teamId = state.info.teamId; } + if (state.info.teamName) { state.config.teamName = state.info.teamName; } + if (state.info.description) { state.config.description = state.info.description; } + + if (state.info.metaParameters) { + state.config.metaParameters = state.info.metaParameters; + } + state.config.teamPermissions = [...state.info.teamPermissions]; } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormState.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormState.ts index 5caff777aa..234b4efda1 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormState.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamFormState.ts @@ -8,9 +8,8 @@ import { computed, makeObservable, observable } from 'mobx'; -import type { TeamInfo } from '@cloudbeaver/core-authentication'; +import type { TeamInfo, TeamsResource } from '@cloudbeaver/core-authentication'; import { Executor, IExecutionContextProvider, IExecutor } from '@cloudbeaver/core-executor'; -import type { CachedMapResource } from '@cloudbeaver/core-sdk'; import { MetadataMap } from '@cloudbeaver/core-utils'; import { teamFormConfigureContext } from './Contexts/teamFormConfigureContext'; @@ -44,7 +43,7 @@ export class TeamFormState implements ITeamFormState { return false; } - readonly resource: CachedMapResource; + readonly resource: TeamsResource; readonly service: TeamFormService; readonly submittingTask: IExecutor; @@ -55,12 +54,13 @@ export class TeamFormState implements ITeamFormState { constructor( service: TeamFormService, - resource: CachedMapResource + resource: TeamsResource ) { this.resource = resource; this.config = { teamId: '', teamPermissions: [], + metaParameters: {}, }; this.stateInfo = null; @@ -156,6 +156,6 @@ export class TeamFormState implements ITeamFormState { return; } - await this.resource.load(data.config.teamId); + await this.resource.load(data.config.teamId, ['includeMetaParameters']); } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamEdit.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamEdit.tsx index b07650a3fa..8155d57dac 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamEdit.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamEdit.tsx @@ -59,7 +59,7 @@ export const TeamEdit = observer(function TeamEdit({ data.config.teamId = item; return styled(useStyles(styles))( - + , + resource: TeamsResource, configure?: (state: ITeamFormState) => any ): ITeamFormState { const service = useService(TeamFormService); diff --git a/webapp/packages/plugin-authentication/src/locales/en.ts b/webapp/packages/plugin-authentication/src/locales/en.ts index af8b302370..0b7cc179f0 100644 --- a/webapp/packages/plugin-authentication/src/locales/en.ts +++ b/webapp/packages/plugin-authentication/src/locales/en.ts @@ -16,6 +16,7 @@ export default [ ['authentication_user_role', 'Role'], ['authentication_user_credentials', 'Credentials'], ['authentication_user_meta_parameters', 'Parameters'], + ['authentication_team_meta_parameters', 'Parameters'], ['authentication_user_password', 'User password'], ['authentication_user_password_repeat', 'Repeat password'], ['authentication_user_team', 'User team'], diff --git a/webapp/packages/plugin-authentication/src/locales/it.ts b/webapp/packages/plugin-authentication/src/locales/it.ts index 03f7e1ba26..f60e06745e 100644 --- a/webapp/packages/plugin-authentication/src/locales/it.ts +++ b/webapp/packages/plugin-authentication/src/locales/it.ts @@ -10,6 +10,7 @@ export default [ ['authentication_user_role', 'Role'], ['authentication_user_credentials', 'Credenziali'], ['authentication_user_meta_parameters', 'Parameters'], + ['authentication_team_meta_parameters', 'Parameters'], ['authentication_user_password', 'Password utente'], ['authentication_user_password_repeat', 'Ripeti password'], ['authentication_user_status', 'User status'], diff --git a/webapp/packages/plugin-authentication/src/locales/ru.ts b/webapp/packages/plugin-authentication/src/locales/ru.ts index 744f305656..e2269f862b 100644 --- a/webapp/packages/plugin-authentication/src/locales/ru.ts +++ b/webapp/packages/plugin-authentication/src/locales/ru.ts @@ -16,6 +16,7 @@ export default [ ['authentication_user_role', 'Роль'], ['authentication_user_credentials', 'Учетные данные'], ['authentication_user_meta_parameters', 'Параметры'], + ['authentication_team_meta_parameters', 'Параметры'], ['authentication_user_password', 'Пароль'], ['authentication_user_password_repeat', 'Повторите пароль'], ['authentication_user_team', 'Команда'], diff --git a/webapp/packages/plugin-authentication/src/locales/zh.ts b/webapp/packages/plugin-authentication/src/locales/zh.ts index 9a00a5c34a..d6e29002ef 100644 --- a/webapp/packages/plugin-authentication/src/locales/zh.ts +++ b/webapp/packages/plugin-authentication/src/locales/zh.ts @@ -16,6 +16,7 @@ export default [ ['authentication_user_role', 'Role'], ['authentication_user_credentials', '凭据'], ['authentication_user_meta_parameters', '参数'], + ['authentication_team_meta_parameters', '参数'], ['authentication_user_password', '用户密码'], ['authentication_user_password_repeat', '重复密码'], ['authentication_user_status', 'User status'], From 15e2b7ea5fa46c7713f5cd9846c62f0fc9485fbe Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Fri, 11 Nov 2022 13:32:48 -0500 Subject: [PATCH 8/9] #1259 Security model refactoring. Move to model.sm. (#1273) --- server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF | 1 + .../service/security/CBEmbeddedSecurityController.java | 2 +- .../service/security/internal/AuthAttemptSessionInfo.java | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF index a99f1bcca3..b5d46e70fe 100644 --- a/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF +++ b/server/bundles/io.cloudbeaver.model/META-INF/MANIFEST.MF @@ -9,6 +9,7 @@ Bundle-RequiredExecutionEnvironment: JavaSE-11 Bundle-ActivationPolicy: lazy Bundle-ClassPath: . Require-Bundle: org.jkiss.dbeaver.model;visibility:=reexport, + org.jkiss.dbeaver.model.sm;visibility:=reexport, org.jkiss.dbeaver.registry;visibility:=reexport, org.jkiss.dbeaver.net.ssh, org.jkiss.bundle.graphql.java;visibility:=reexport, diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 29aa81d210..28d736399b 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -44,11 +44,11 @@ import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils; import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCTransaction; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.runtime.LoggingProgressMonitor; -import org.jkiss.dbeaver.model.security.*; import org.jkiss.dbeaver.model.security.exception.SMAccessTokenExpiredException; 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.security.*; import org.jkiss.dbeaver.runtime.DBWorkbench; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/AuthAttemptSessionInfo.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/AuthAttemptSessionInfo.java index 7c5aee36e3..28c972bdeb 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/AuthAttemptSessionInfo.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/internal/AuthAttemptSessionInfo.java @@ -19,7 +19,7 @@ package io.cloudbeaver.service.security.internal; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; -import org.jkiss.dbeaver.model.security.SMSessionType; +import org.jkiss.dbeaver.model.auth.SMSessionType; import java.util.Map; From 53ad274ad537ac9a9b74d445ed366545a6cb5399 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Sat, 12 Nov 2022 11:18:06 -0500 Subject: [PATCH 9/9] Tasks config save/load fix --- .../src/io/cloudbeaver/model/rm/RMUtils.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java index 4060ef878b..5b53c616e9 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java @@ -3,10 +3,12 @@ package io.cloudbeaver.model.rm; import io.cloudbeaver.DBWConstants; import io.cloudbeaver.model.app.BaseWebApplication; import org.jkiss.code.NotNull; +import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.rm.RMProject; import org.jkiss.dbeaver.model.rm.RMProjectPermission; import org.jkiss.dbeaver.model.rm.RMProjectType; import org.jkiss.dbeaver.runtime.DBWorkbench; +import org.jkiss.utils.CommonUtils; import java.nio.file.Path; import java.util.Objects; @@ -46,18 +48,25 @@ public class RMUtils { * @return path to the project based on the name of projects and prefixes that it contains */ @NotNull - public static Path getProjectPathByName(@NotNull String projectId) { - if (!projectId.contains("_") || projectId.startsWith("_")) { - return getRootPath().resolve(BaseWebApplication.getInstance().getDefaultProjectName()); + public static Path getProjectPathByName(@NotNull String projectId) throws DBException { + int divPos = projectId.indexOf("_"); + if (divPos <= 0) { + throw new DBException("Bad project ID"); } - String prefix = projectId.substring(0, projectId.indexOf("_")); + + String prefix = projectId.substring(0, divPos); + String projectName = projectId.substring(divPos + 1); switch (RMProjectType.getByPrefix(prefix)) { case GLOBAL: - return getRootPath().resolve(BaseWebApplication.getInstance().getDefaultProjectName()); + String defaultProjectName = BaseWebApplication.getInstance().getDefaultProjectName(); + if (CommonUtils.isEmpty(defaultProjectName)) { + throw new DBException("Global projects are not supported"); + } + return getRootPath().resolve(defaultProjectName); case SHARED: - return getSharedProjectsPath().resolve(projectId); + return getSharedProjectsPath().resolve(projectName); default: - return getUserProjectsPath().resolve(projectId); + return getUserProjectsPath().resolve(projectName); } }