mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into tech/CB-2666
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -7,4 +7,6 @@
|
||||
<extension-point id="io.cloudbeaver.valueSerializer" name="Web value serializers" schema="schema/io.cloudbeaver.valueSerializer.exsd"/>
|
||||
<extension-point id="org.jkiss.dbeaver.auth.provider" name="%extension-point.org.jkiss.dbeaver.authProvider.name" schema="schema/org.jkiss.dbeaver.authProvider.exsd"/>
|
||||
<extension-point id="io.cloudbeaver.event.handler" name="Wev event handler" schema="schema/io.cloudbeaver.event.handler.exsd"/>
|
||||
<extension-point id="io.cloudbeaver.metaParameters" name="Meta parameters" schema="schema/io.cloudbeaver.metaParameters.exsd"/>
|
||||
|
||||
</plugin>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<schema targetNamespace="org.jkiss.dbeaver.core" xmlns="http://www.w3.org/2001/XMLSchema">
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.schema plugin="io.cloudbeaver.auth" id="io.cloudbeaver.userProfile" name="User profile properties"/>
|
||||
<meta.schema plugin="io.cloudbeaver.model" id="io.cloudbeaver.metaParameters" name="Subject meta parameters"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
</documentation>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
@@ -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(
|
||||
@@ -426,6 +446,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;
|
||||
|
||||
+6
-11
@@ -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;
|
||||
}
|
||||
|
||||
+11
-9
@@ -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<String, Object> providerConfig = Collections.emptyMap();
|
||||
var newAuthInfos = new ArrayList<WebAuthInfo>();
|
||||
for (Map.Entry<String, Object> entry : authInfo.getAuthData().entrySet()) {
|
||||
String providerId = entry.getKey();
|
||||
for (Map.Entry<SMAuthConfigurationReference, Object> entry : authInfo.getAuthData().entrySet()) {
|
||||
SMAuthConfigurationReference authConfiguration = entry.getKey();
|
||||
String providerId = authConfiguration.getAuthProviderId();
|
||||
Map<String, Object> authAttrs = (Map<String, Object>) 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);
|
||||
|
||||
+5
-5
@@ -46,7 +46,7 @@ public class WebAuthProviderDescriptor extends AbstractDescriptor {
|
||||
private final IConfigurationElement cfg;
|
||||
|
||||
private final ObjectType implType;
|
||||
private final Map<SMSubjectType, List<DBPPropertyDescriptor>> metaProperties = new HashMap<>();
|
||||
private final Map<SMSubjectType, List<DBPPropertyDescriptor>> metaParameters = new HashMap<>();
|
||||
private SMAuthProvider<?> instance;
|
||||
private final DBPImage icon;
|
||||
private final Map<String, PropertyDescriptor> 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<DBPPropertyDescriptor> 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<DBPPropertyDescriptor> getMetaProperties(SMSubjectType subjectType) {
|
||||
return metaProperties.get(subjectType);
|
||||
public List<DBPPropertyDescriptor> getMetaParameters(SMSubjectType subjectType) {
|
||||
return metaParameters.get(subjectType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-12
@@ -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<DBPPropertyDescriptor> properties = new ArrayList<>();
|
||||
private static WebMetaParametersRegistry instance = null;
|
||||
private final List<DBPPropertyDescriptor> userParameters = new ArrayList<>();
|
||||
private final List<DBPPropertyDescriptor> 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<DBPPropertyDescriptor> getProperties() {
|
||||
return properties;
|
||||
public List<DBPPropertyDescriptor> getUserParameters() {
|
||||
return userParameters;
|
||||
}
|
||||
|
||||
public List<DBPPropertyDescriptor> 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<DBPPropertyDescriptor> props = PropertyDescriptor.extractProperties(propGroup);
|
||||
if (isUser) {
|
||||
userParameters.addAll(props);
|
||||
} else {
|
||||
teamParameters.addAll(props);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
@@ -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();
|
||||
|
||||
@@ -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!
|
||||
|
||||
+9
@@ -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<String, String> getMetaParameters() {
|
||||
return team.getMetaParameters();
|
||||
}
|
||||
|
||||
@Property
|
||||
public List<String> getTeamPermissions() {
|
||||
return teamPermissions;
|
||||
}
|
||||
|
||||
+5
@@ -50,6 +50,9 @@ public interface DBWServiceAdmin extends DBWService {
|
||||
@NotNull
|
||||
List<AdminPermissionInfo> 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<String, String> parameters) throws DBWebException;
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean setTeamMetaParameterValues(WebSession webSession, String teamId, Map<String, String> parameters) throws DBWebException;
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
Boolean enableUser(WebSession webSession, String userId, Boolean enabled) throws DBWebException;
|
||||
|
||||
@WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN)
|
||||
|
||||
+7
@@ -50,6 +50,8 @@ public class WebServiceBindingAdmin extends WebServiceBindingBase<DBWServiceAdmi
|
||||
env -> 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<DBWServiceAdmi
|
||||
getWebSession(env),
|
||||
env.getArgument("userId"),
|
||||
env.getArgument("parameters")))
|
||||
.dataFetcher("setTeamMetaParameterValues",
|
||||
env -> getService(env).setTeamMetaParameterValues(
|
||||
getWebSession(env),
|
||||
env.getArgument("teamId"),
|
||||
env.getArgument("parameters")))
|
||||
|
||||
.dataFetcher("configureServer",
|
||||
env -> getService(env).configureServer(getWebSession(env), env.getArgument("configuration")))
|
||||
|
||||
+31
-4
@@ -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<DBPPropertyDescriptor> props = new ArrayList<>(
|
||||
WebMetaParametersRegistry.getInstance().getTeamParameters());
|
||||
|
||||
// Add metas from enabled auth providers
|
||||
for (WebAuthProviderDescriptor ap : WebServiceUtils.getEnabledAuthProviders()) {
|
||||
List<DBPPropertyDescriptor> 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<String, String> 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) {
|
||||
|
||||
@@ -3,21 +3,18 @@
|
||||
|
||||
<plugin>
|
||||
|
||||
<extension-point id="io.cloudbeaver.userProfile" name="User profiles" schema="schema/io.cloudbeaver.userProfile.exsd"/>
|
||||
|
||||
<extension point="io.cloudbeaver.service">
|
||||
<service id="auth" label="User authentication" description="User authentication services" class="io.cloudbeaver.service.auth.WebServiceBindingAuth">
|
||||
|
||||
</service>
|
||||
</extension>
|
||||
|
||||
<extension point="io.cloudbeaver.userProfile">
|
||||
<userProfileProperties>
|
||||
<extension point="io.cloudbeaver.metaParameters">
|
||||
<metaParameters type="user">
|
||||
<propertyGroup label="Main">
|
||||
<property id="firstName" label="First Name" type="string" description="User first name"/>
|
||||
<property id="lastName" label="Last Name" type="string" description="User last name"/>
|
||||
</propertyGroup>
|
||||
</userProfileProperties>
|
||||
</metaParameters>
|
||||
</extension>
|
||||
<extension point="io.cloudbeaver.handler">
|
||||
<sessionHandler id="RPSessionHandler" class="io.cloudbeaver.service.auth.RPSessionHandler"/>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+5
@@ -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<WebUserAuthToken> getAuthTokens() {
|
||||
return session.getAllAuthInfo().stream()
|
||||
|
||||
+4
-5
@@ -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<DBPPropertyDescriptor> props = new ArrayList<>();
|
||||
|
||||
// First add user profile properties
|
||||
props.addAll(WebUserProfileRegistry.getInstance().getProperties());
|
||||
List<DBPPropertyDescriptor> props = new ArrayList<>(
|
||||
WebMetaParametersRegistry.getInstance().getUserParameters());
|
||||
|
||||
// Add metas from enabled auth providers
|
||||
for (WebAuthProviderDescriptor ap : WebServiceUtils.getEnabledAuthProviders()) {
|
||||
List<DBPPropertyDescriptor> metaProps = ap.getMetaProperties(SMSubjectType.user);
|
||||
List<DBPPropertyDescriptor> metaProps = ap.getMetaParameters(SMSubjectType.user);
|
||||
if (!CommonUtils.isEmpty(metaProps)) {
|
||||
props.addAll(metaProps);
|
||||
}
|
||||
|
||||
+54
-42
@@ -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;
|
||||
@@ -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<SMAuthConfigurationReference, Object> 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<String, Object> authInfo,
|
||||
@Nullable String error) throws DBException {
|
||||
public void updateAuthStatus(
|
||||
@NotNull String authId,
|
||||
@NotNull SMAuthStatus authStatus,
|
||||
@NotNull Map<SMAuthConfigurationReference, Object> 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<String, Object> 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<SMAuthConfigurationReference, Object> 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<String, Object> entry : authInfo.entrySet()) {
|
||||
String providerId = entry.getKey();
|
||||
for (Map.Entry<SMAuthConfigurationReference, Object> 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<String, Object> authData = new LinkedHashMap<>();
|
||||
Map<SMAuthConfigurationReference, Object> 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<String> authProviderIds = authInfo.getAuthData().keySet();
|
||||
Set<SMAuthConfigurationReference> 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<String, Object> storedUserData = new LinkedHashMap<>();
|
||||
Map<SMAuthConfigurationReference, Object> 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<String, Object>) authInfo.getAuthData().get(authProviderId);
|
||||
var userAuthData = (Map<String, Object>) 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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<TeamMetaParameter[]> {
|
||||
constructor(
|
||||
private readonly graphQLService: GraphQLService,
|
||||
sessionResource: SessionResource,
|
||||
) {
|
||||
super([]);
|
||||
|
||||
this.sync(sessionResource, () => { }, () => { });
|
||||
}
|
||||
|
||||
protected async loader(): Promise<TeamMetaParameter[]> {
|
||||
const { parameters } = await this.graphQLService.sdk.getTeamMetaParameters();
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@@ -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<GetTeamsListQueryVariables, 'teamId'>;
|
||||
type NewTeam = TeamInfo & { [NEW_TEAM_SYMBOL]: boolean; timestamp: number };
|
||||
|
||||
@injectable()
|
||||
export class TeamsResource extends CachedMapResource<string, TeamInfo> {
|
||||
export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResourceIncludes> {
|
||||
constructor(private readonly graphQLService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
@@ -41,8 +44,20 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo> {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
async createTeam(teamInfo: TeamInfo): Promise<TeamInfo> {
|
||||
const response = await this.graphQLService.sdk.createTeam(teamInfo);
|
||||
async createTeam({
|
||||
teamId,
|
||||
teamPermissions,
|
||||
teamName,
|
||||
description,
|
||||
metaParameters,
|
||||
}: TeamInfo): Promise<TeamInfo> {
|
||||
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<string, TeamInfo> {
|
||||
|
||||
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<TeamInfo> {
|
||||
const { team } = await this.graphQLService.sdk.updateTeam(teamInfo);
|
||||
async updateTeam({
|
||||
teamId,
|
||||
teamPermissions,
|
||||
teamName,
|
||||
description,
|
||||
metaParameters,
|
||||
}: TeamInfo): Promise<TeamInfo> {
|
||||
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<string>): Promise<Map<string, TeamInfo>> {
|
||||
@@ -103,11 +134,15 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo> {
|
||||
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<string>): Promise<Map<string, TeamInfo>> {
|
||||
async setMetaParameters(teamId: string, parameters: Record<string, any>): Promise<void> {
|
||||
await this.graphQLService.sdk.saveTeamMetaParameters({ teamId, parameters });
|
||||
}
|
||||
|
||||
protected async loader(key: ResourceKey<string>, includes: string[] | undefined): Promise<Map<string, TeamInfo>> {
|
||||
const all = ResourceKeyUtils.includes(key, CachedMapAllKey);
|
||||
|
||||
await ResourceKeyUtils.forEachAsync(all ? CachedMapAllKey : key, async key => {
|
||||
@@ -115,9 +150,12 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo> {
|
||||
|
||||
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<string, TeamInfo> {
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private getDefaultIncludes(): TeamResourceIncludes {
|
||||
return {
|
||||
includeMetaParameters: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function isNewTeam(team: TeamInfo | NewTeam): team is NewTeam {
|
||||
|
||||
@@ -158,9 +158,13 @@ UserInfoIncludes
|
||||
});
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
if (this.data) {
|
||||
await this.graphQLService.sdk.authLogout();
|
||||
async logout(provider?: string, configuration?: string): Promise<void> {
|
||||
await this.graphQLService.sdk.authLogout({
|
||||
provider,
|
||||
configuration,
|
||||
});
|
||||
|
||||
if (!provider || (this.data?.authTokens.length || 0) <= 1) {
|
||||
this.setData(null);
|
||||
this.resetIncludes();
|
||||
}
|
||||
|
||||
@@ -16,3 +16,4 @@ export * from './UserDataService';
|
||||
export * from './UserInfoResource';
|
||||
export * from './UserMetaParametersResource';
|
||||
export * from './UsersResource';
|
||||
export * from './TeamMetaParametersResource';
|
||||
@@ -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,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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<ButtonProps>(function Button({
|
||||
onClick={state.click}
|
||||
>
|
||||
<ripple />
|
||||
{icon && <button-icon><Icon name={icon} viewBox={viewBox} /></button-icon>}
|
||||
{icon && <button-icon><IconOrImage icon={icon} viewBox={viewBox} /></button-icon>}
|
||||
<button-label as='span'>{children}</button-label>
|
||||
<Loader small />
|
||||
</Button>
|
||||
|
||||
@@ -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<TState> 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 = <TState extends Record<string, any>>(props: Props<TState>) => React.ReactElement<any, any>;
|
||||
|
||||
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<Record<any, any>>) {
|
||||
const translate = useTranslate();
|
||||
const context = useContext(FormContext);
|
||||
|
||||
const [selected, setSelected] = useState<File | null>(null);
|
||||
const [error, setError] = useState<Error | null>(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<HTMLInputElement>) {
|
||||
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)(
|
||||
<field className={className} {...use({ small, medium, large, tiny })}>
|
||||
<field-label title={labelTooltip}>{children}{required && ' *'}</field-label>
|
||||
<UploadArea title={tooltip} disabled={disabled} accept={accept} reset onChange={handleChange}>
|
||||
<Button
|
||||
icon='/icons/import.svg'
|
||||
tag='div'
|
||||
mod={['outlined']}
|
||||
disabled={disabled}
|
||||
>
|
||||
{translate('ui_upload_file')}
|
||||
</Button>
|
||||
</UploadArea>
|
||||
<field-description>
|
||||
{description}
|
||||
{(selected || saved) && <IconButton disabled={disabled} name='cross' onClick={removeFile} />}
|
||||
</field-description>
|
||||
</field>
|
||||
);
|
||||
});
|
||||
+27
-6
@@ -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<RenderFieldProps>(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 (
|
||||
<InputFile
|
||||
tooltip={property.description}
|
||||
labelTooltip={property.displayName || property.description}
|
||||
name={property.id!}
|
||||
state={state}
|
||||
disabled={disabled}
|
||||
fileName={description}
|
||||
className={className}
|
||||
mapValue={removeMetadataFromBase64}
|
||||
>
|
||||
{property.displayName}
|
||||
</InputFile>
|
||||
);
|
||||
}
|
||||
|
||||
if (controltype === 'link') {
|
||||
return (
|
||||
@@ -103,10 +128,6 @@ export const RenderField = observer<RenderFieldProps>(function RenderField({
|
||||
);
|
||||
}
|
||||
|
||||
if (showRememberTip && password && property.value) {
|
||||
description = 'Password saved';
|
||||
}
|
||||
|
||||
if (controltype === 'checkbox') {
|
||||
if (state !== undefined) {
|
||||
return (
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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', 'У вас нет разрешения на это действие'],
|
||||
|
||||
@@ -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', '您没有权限执行此操作'],
|
||||
|
||||
@@ -174,6 +174,10 @@ export class NavNodeManagerService extends Bootstrap {
|
||||
}
|
||||
|
||||
async canOpen(nodeId: string, parentId: string, folderId?: string): Promise<boolean> {
|
||||
if (!this.navTree.has(nodeId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const contexts = await this.navigator.execute({
|
||||
type: NavigationType.canOpen,
|
||||
nodeId,
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
query authLogout {
|
||||
authLogout
|
||||
query authLogout(
|
||||
$provider: ID
|
||||
$configuration: ID
|
||||
) {
|
||||
authLogout(
|
||||
provider: $provider
|
||||
configuration: $configuration
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
query getTeamMetaParameters {
|
||||
parameters: listTeamMetaParameters {
|
||||
...ObjectPropertyInfo
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
query getTeamsList($teamId: ID) {
|
||||
query getTeamsList($teamId: ID, $includeMetaParameters: Boolean!) {
|
||||
teams: listTeams(teamId: $teamId) {
|
||||
...AdminTeamInfo
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
query saveTeamMetaParameters(
|
||||
$teamId: ID!
|
||||
$parameters: Object!
|
||||
) {
|
||||
setTeamMetaParameterValues(
|
||||
teamId: $teamId,
|
||||
parameters: $parameters
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -3,4 +3,5 @@ fragment AdminTeamInfo on AdminTeamInfo {
|
||||
teamName
|
||||
description
|
||||
teamPermissions
|
||||
metaParameters @include(if: $includeMetaParameters)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fragment ObjectPropertyInfo on ObjectPropertyInfo {
|
||||
id
|
||||
displayName
|
||||
description
|
||||
category
|
||||
dataType
|
||||
value
|
||||
validValues
|
||||
defaultValue
|
||||
length
|
||||
features
|
||||
order
|
||||
}
|
||||
@@ -75,6 +75,7 @@ export interface AdminTeamInfo {
|
||||
description?: Maybe<Scalars['String']>;
|
||||
grantedConnections: Array<AdminConnectionGrantInfo>;
|
||||
grantedUsers: Array<Scalars['ID']>;
|
||||
metaParameters: Scalars['Object'];
|
||||
teamId: Scalars['ID'];
|
||||
teamName?: Maybe<Scalars['String']>;
|
||||
teamPermissions: Array<Scalars['ID']>;
|
||||
@@ -887,7 +888,6 @@ export interface ProjectInfo {
|
||||
|
||||
export interface Query {
|
||||
activeUser?: Maybe<UserInfo>;
|
||||
allConnections: Array<ConnectionInfo>;
|
||||
authChangeLocalPassword: Scalars['Boolean'];
|
||||
authLogin: AuthInfo;
|
||||
authLogout?: Maybe<Scalars['Boolean']>;
|
||||
@@ -897,10 +897,6 @@ export interface Query {
|
||||
configureServer: Scalars['Boolean'];
|
||||
connectionFolders: Array<ConnectionFolderInfo>;
|
||||
connectionInfo: ConnectionInfo;
|
||||
/** @deprecated Field no longer supported */
|
||||
copyConnectionConfiguration: ConnectionInfo;
|
||||
/** @deprecated Field no longer supported */
|
||||
createConnectionConfiguration: ConnectionInfo;
|
||||
createTeam: AdminTeamInfo;
|
||||
createUser: AdminUserInfo;
|
||||
dataTransferAvailableStreamProcessors: Array<DataTransferProcessorInfo>;
|
||||
@@ -909,8 +905,6 @@ export interface Query {
|
||||
dataTransferExportDataFromResults: AsyncTaskInfo;
|
||||
dataTransferRemoveDataFile?: Maybe<Scalars['Boolean']>;
|
||||
deleteAuthProviderConfiguration: Scalars['Boolean'];
|
||||
/** @deprecated Field no longer supported */
|
||||
deleteConnectionConfiguration?: Maybe<Scalars['Boolean']>;
|
||||
deleteTeam?: Maybe<Scalars['Boolean']>;
|
||||
deleteUser?: Maybe<Scalars['Boolean']>;
|
||||
deleteUserMetaParameter: Scalars['Boolean'];
|
||||
@@ -925,6 +919,7 @@ export interface Query {
|
||||
listFeatureSets: Array<WebFeatureSet>;
|
||||
listPermissions: Array<AdminPermissionInfo>;
|
||||
listProjects: Array<ProjectInfo>;
|
||||
listTeamMetaParameters: Array<ObjectPropertyInfo>;
|
||||
listTeams: Array<AdminTeamInfo>;
|
||||
listUserProfileProperties: Array<ObjectPropertyInfo>;
|
||||
listUsers: Array<AdminUserInfo>;
|
||||
@@ -956,6 +951,7 @@ export interface Query {
|
||||
setDefaultNavigatorSettings: Scalars['Boolean'];
|
||||
setSubjectConnectionAccess?: Maybe<Scalars['Boolean']>;
|
||||
setSubjectPermissions: Array<AdminPermissionInfo>;
|
||||
setTeamMetaParameterValues: Scalars['Boolean'];
|
||||
setUserAuthRole?: Maybe<Scalars['Boolean']>;
|
||||
setUserCredentials?: Maybe<Scalars['Boolean']>;
|
||||
setUserMetaParameterValues: Scalars['Boolean'];
|
||||
@@ -969,19 +965,11 @@ export interface Query {
|
||||
sqlParseScript: SqlScriptInfo;
|
||||
sqlSupportedOperations: Array<DataTypeLogicalOperation>;
|
||||
templateConnections: Array<ConnectionInfo>;
|
||||
/** @deprecated Field no longer supported */
|
||||
updateConnectionConfiguration: ConnectionInfo;
|
||||
updateTeam: AdminTeamInfo;
|
||||
userConnections: Array<ConnectionInfo>;
|
||||
}
|
||||
|
||||
|
||||
export interface QueryAllConnectionsArgs {
|
||||
id?: InputMaybe<Scalars['ID']>;
|
||||
projectId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryAuthChangeLocalPasswordArgs {
|
||||
newPassword: Scalars['String'];
|
||||
oldPassword: Scalars['String'];
|
||||
@@ -1025,19 +1013,6 @@ export interface QueryConnectionInfoArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface QueryCopyConnectionConfigurationArgs {
|
||||
config?: InputMaybe<ConnectionConfig>;
|
||||
nodePath: Scalars['String'];
|
||||
projectId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryCreateConnectionConfigurationArgs {
|
||||
config: ConnectionConfig;
|
||||
projectId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryCreateTeamArgs {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
teamId: Scalars['ID'];
|
||||
@@ -1079,12 +1054,6 @@ export interface QueryDeleteAuthProviderConfigurationArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface QueryDeleteConnectionConfigurationArgs {
|
||||
id: Scalars['ID'];
|
||||
projectId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryDeleteTeamArgs {
|
||||
teamId: Scalars['ID'];
|
||||
}
|
||||
@@ -1280,6 +1249,12 @@ export interface QuerySetSubjectPermissionsArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface QuerySetTeamMetaParameterValuesArgs {
|
||||
parameters: Scalars['Object'];
|
||||
teamId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QuerySetUserAuthRoleArgs {
|
||||
authRole?: InputMaybe<Scalars['String']>;
|
||||
userId: Scalars['ID'];
|
||||
@@ -1372,13 +1347,6 @@ export interface QueryTemplateConnectionsArgs {
|
||||
}
|
||||
|
||||
|
||||
export interface QueryUpdateConnectionConfigurationArgs {
|
||||
config: ConnectionConfig;
|
||||
id: Scalars['ID'];
|
||||
projectId: Scalars['ID'];
|
||||
}
|
||||
|
||||
|
||||
export interface QueryUpdateTeamArgs {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
teamId: Scalars['ID'];
|
||||
@@ -1649,6 +1617,7 @@ export interface UserAuthToken {
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
authRole?: Maybe<Scalars['ID']>;
|
||||
authTokens: Array<UserAuthToken>;
|
||||
configurationParameters: Scalars['Object'];
|
||||
displayName?: Maybe<Scalars['String']>;
|
||||
@@ -1711,7 +1680,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<any>, value?: any, length: ObjectPropertyLength, features: Array<string>, order: number }> } }> } };
|
||||
|
||||
export type AuthLogoutQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
export type AuthLogoutQueryVariables = Exact<{
|
||||
provider?: InputMaybe<Scalars['ID']>;
|
||||
configuration?: InputMaybe<Scalars['ID']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type AuthLogoutQuery = { authLogout?: boolean };
|
||||
@@ -1795,10 +1767,11 @@ export type CreateTeamQueryVariables = Exact<{
|
||||
teamId: Scalars['ID'];
|
||||
teamName?: InputMaybe<Scalars['String']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
includeMetaParameters: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array<string> } };
|
||||
export type CreateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array<string>, metaParameters?: any } };
|
||||
|
||||
export type DeleteTeamQueryVariables = Exact<{
|
||||
teamId: Scalars['ID'];
|
||||
@@ -1814,21 +1787,36 @@ export type GetTeamGrantedUsersQueryVariables = Exact<{
|
||||
|
||||
export type GetTeamGrantedUsersQuery = { team: Array<{ grantedUsers: Array<string> }> };
|
||||
|
||||
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<any>, defaultValue?: any, length: ObjectPropertyLength, features: Array<string>, order: number }> };
|
||||
|
||||
export type GetTeamsListQueryVariables = Exact<{
|
||||
teamId?: InputMaybe<Scalars['ID']>;
|
||||
includeMetaParameters: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type GetTeamsListQuery = { teams: Array<{ teamId: string, teamName?: string, description?: string, teamPermissions: Array<string> }> };
|
||||
export type GetTeamsListQuery = { teams: Array<{ teamId: string, teamName?: string, description?: string, teamPermissions: Array<string>, 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<Scalars['String']>;
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
includeMetaParameters: Scalars['Boolean'];
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array<string> } };
|
||||
export type UpdateTeamQuery = { team: { teamId: string, teamName?: string, description?: string, teamPermissions: Array<string>, metaParameters?: any } };
|
||||
|
||||
export type CreateUserQueryVariables = Exact<{
|
||||
userId: Scalars['ID'];
|
||||
@@ -2257,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<string> };
|
||||
export type AdminTeamInfoFragment = { teamId: string, teamName?: string, description?: string, teamPermissions: Array<string>, metaParameters?: any };
|
||||
|
||||
export type AdminUserInfoFragment = { userId: string, grantedTeams: Array<string>, linkedAuthProviders: Array<string>, 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<any>, value?: any, length: ObjectPropertyLength, features: Array<string>, order: number }> }> };
|
||||
|
||||
@@ -2289,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<any>, value?: any, length: ObjectPropertyLength, features: Array<string>, order: number }> };
|
||||
|
||||
export type ObjectPropertyInfoFragment = { id?: string, displayName?: string, description?: string, category?: string, dataType?: string, value?: any, validValues?: Array<any>, defaultValue?: any, length: ObjectPropertyLength, features: Array<string>, 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 };
|
||||
@@ -2786,6 +2776,7 @@ export const AdminTeamInfoFragmentDoc = `
|
||||
teamName
|
||||
description
|
||||
teamPermissions
|
||||
metaParameters @include(if: $includeMetaParameters)
|
||||
}
|
||||
`;
|
||||
export const ObjectOriginInfoFragmentDoc = `
|
||||
@@ -3089,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 {
|
||||
@@ -3178,8 +3184,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 = `
|
||||
@@ -3287,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
|
||||
@@ -3309,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
|
||||
@@ -4479,9 +4497,15 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
|
||||
getTeamGrantedUsers(variables: GetTeamGrantedUsersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTeamGrantedUsersQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetTeamGrantedUsersQuery>(GetTeamGrantedUsersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamGrantedUsers', 'query');
|
||||
},
|
||||
getTeamsList(variables?: GetTeamsListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTeamsListQuery> {
|
||||
getTeamMetaParameters(variables?: GetTeamMetaParametersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTeamMetaParametersQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetTeamMetaParametersQuery>(GetTeamMetaParametersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamMetaParameters', 'query');
|
||||
},
|
||||
getTeamsList(variables: GetTeamsListQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetTeamsListQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<GetTeamsListQuery>(GetTeamsListDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getTeamsList', 'query');
|
||||
},
|
||||
saveTeamMetaParameters(variables: SaveTeamMetaParametersQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<SaveTeamMetaParametersQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<SaveTeamMetaParametersQuery>(SaveTeamMetaParametersDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'saveTeamMetaParameters', 'query');
|
||||
},
|
||||
updateTeam(variables: UpdateTeamQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<UpdateTeamQuery> {
|
||||
return withWrapper((wrappedRequestHeaders) => client.request<UpdateTeamQuery>(UpdateTeamDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'updateTeam', 'query');
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export function blobToData(blob: Blob): Promise<string | null> {
|
||||
export function blobToData(blob: Blob | File): Promise<string | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
|
||||
@@ -47,4 +47,5 @@ export * from './debounce';
|
||||
export * from './svgToDataUri';
|
||||
export * from './uriToBlob';
|
||||
export * from './utf8ToBase64';
|
||||
export * from './createLastPromiseGetter';
|
||||
export * from './createLastPromiseGetter';
|
||||
export * from './removeMetadataFromBase64';
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
@@ -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];
|
||||
}
|
||||
+1
@@ -12,5 +12,6 @@ export function teamContext(): TeamInfo {
|
||||
return {
|
||||
teamId: '',
|
||||
teamPermissions: [],
|
||||
metaParameters: {},
|
||||
};
|
||||
}
|
||||
|
||||
+2
-3
@@ -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<ITeamFormSubmitData>;
|
||||
readonly resource: CachedMapResource<string, TeamInfo>;
|
||||
readonly resource: TeamsResource;
|
||||
|
||||
readonly load: () => Promise<void>;
|
||||
readonly loadTeamInfo: () => Promise<TeamInfo | undefined>;
|
||||
|
||||
+17
-1
@@ -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<ITeamFormProps> = 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<ITeamFormProps> = observer(
|
||||
</Textarea>
|
||||
</Group>
|
||||
{!serverConfigResource.data?.distributed && <Permissions state={state} />}
|
||||
<Loader state={teamMetaParameters} inline>
|
||||
{() => teamMetaParameters.data.length > 0 && styled(style)(
|
||||
<Group small gap vertical overflow>
|
||||
<GroupTitle keepSize>{translate('authentication_team_meta_parameters')}</GroupTitle>
|
||||
<ObjectPropertyInfoForm
|
||||
state={state.config.metaParameters}
|
||||
properties={teamMetaParameters.data}
|
||||
disabled={state.disabled}
|
||||
keepSize
|
||||
tiny
|
||||
/>
|
||||
</Group>
|
||||
)}
|
||||
</Loader>
|
||||
</ColoredContainer>
|
||||
</SubmittingForm>
|
||||
);
|
||||
|
||||
+12
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -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<string, TeamInfo>;
|
||||
readonly resource: TeamsResource;
|
||||
|
||||
readonly service: TeamFormService;
|
||||
readonly submittingTask: IExecutor<ITeamFormSubmitData>;
|
||||
@@ -55,12 +54,13 @@ export class TeamFormState implements ITeamFormState {
|
||||
|
||||
constructor(
|
||||
service: TeamFormService,
|
||||
resource: CachedMapResource<string, TeamInfo>
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ export const TeamEdit = observer<Props>(function TeamEdit({
|
||||
data.config.teamId = item;
|
||||
|
||||
return styled(useStyles(styles))(
|
||||
<box ref={boxRef} as='div'>
|
||||
<box ref={boxRef}>
|
||||
<TeamForm
|
||||
state={data}
|
||||
onCancel={collapse}
|
||||
|
||||
+2
-3
@@ -8,16 +8,15 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import type { TeamInfo } from '@cloudbeaver/core-authentication';
|
||||
import type { TeamsResource } from '@cloudbeaver/core-authentication';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { CachedMapResource } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { ITeamFormState } from './ITeamFormProps';
|
||||
import { TeamFormService } from './TeamFormService';
|
||||
import { TeamFormState } from './TeamFormState';
|
||||
|
||||
export function useTeamFormState(
|
||||
resource: CachedMapResource<string, TeamInfo>,
|
||||
resource: TeamsResource,
|
||||
configure?: (state: ITeamFormState) => any
|
||||
): ITeamFormState {
|
||||
const service = useService(TeamFormService);
|
||||
|
||||
@@ -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<void> {
|
||||
async logout(providerId?: string, configurationId?: string): Promise<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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', 'Команда'],
|
||||
|
||||
@@ -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'],
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
@@ -49,6 +49,7 @@ export const DBAuthDialogFooter = observer<React.PropsWithChildren<Props>>(funct
|
||||
type="button"
|
||||
mod={['unelevated']}
|
||||
disabled={isAuthenticating}
|
||||
loading={isAuthenticating}
|
||||
onClick={onLogin}
|
||||
>
|
||||
{translate('authentication_login')}
|
||||
|
||||
@@ -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<Payload> = observer(function Da
|
||||
)}
|
||||
onReject={options?.persistent ? undefined : rejectDialog}
|
||||
>
|
||||
{(!connection.isLoaded() || connection.isLoading() || !controller.configured)
|
||||
? <Loader />
|
||||
: (
|
||||
<SubmittingForm ref={focusedRef} onSubmit={controller.login}>
|
||||
<ConnectionAuthenticationForm
|
||||
config={controller.config}
|
||||
authModelId={authModelId}
|
||||
authProperties={connection.connectionInfo?.authProperties}
|
||||
networkHandlers={payload.networkHandlers}
|
||||
formId={`${payload.connection.projectId}:${payload.connection.connectionId}`}
|
||||
allowSaveCredentials={credentialsSavingEnabled}
|
||||
disabled={controller.isAuthenticating}
|
||||
/>
|
||||
</SubmittingForm>
|
||||
)}
|
||||
<SubmittingForm
|
||||
ref={focusedRef}
|
||||
disabled={!connection.isLoaded() || connection.isLoading() || !controller.configured}
|
||||
onSubmit={controller.login}
|
||||
>
|
||||
<ConnectionAuthenticationForm
|
||||
config={controller.config}
|
||||
authModelId={authModelId}
|
||||
authProperties={connection.connectionInfo?.authProperties}
|
||||
networkHandlers={payload.networkHandlers}
|
||||
formId={`${payload.connection.projectId}:${payload.connection.connectionId}`}
|
||||
allowSaveCredentials={credentialsSavingEnabled}
|
||||
disabled={controller.isAuthenticating}
|
||||
/>
|
||||
</SubmittingForm>
|
||||
</CommonDialogWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
+2
-2
@@ -95,10 +95,10 @@ export const AuthTokenList = observer<Props>(function AuthTokenList({ user, clas
|
||||
>
|
||||
<Cell
|
||||
before={icon ? <IconOrImage icon={icon} /> : undefined}
|
||||
after={configuration && (
|
||||
after={(
|
||||
<Button
|
||||
mod={['outlined']}
|
||||
onClick={() => authenticationService.logoutConfiguration(configuration!.id, false)}
|
||||
onClick={() => authenticationService.logout(provider.id, configuration?.id)}
|
||||
>
|
||||
{translate('authentication_logout')}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user