diff --git a/server/bundles/graphql.config.yml b/server/bundles/graphql.config.yml new file mode 100644 index 0000000000..9dd806b67e --- /dev/null +++ b/server/bundles/graphql.config.yml @@ -0,0 +1,9 @@ +projects: + schema: + include: ./**/*.graphqls +schema: "gql.ce" +extensions: + endpoints: + Default GraphQL Endpoint: + url: http://localhost:8080/api/gql + introspect: false diff --git a/server/bundles/io.cloudbeaver.server/schema/schema.graphqls b/server/bundles/io.cloudbeaver.server/schema/schema.graphqls index 873430e94e..afffe35fef 100644 --- a/server/bundles/io.cloudbeaver.server/schema/schema.graphqls +++ b/server/bundles/io.cloudbeaver.server/schema/schema.graphqls @@ -8,6 +8,8 @@ input PageInput { offset: Int } +directive @since(version: String!) on OBJECT|SCALAR|QUERY|MUTATION|FIELD|VARIABLE_DEFINITION|OBJECT|FIELD_DEFINITION|ARGUMENT_DEFINITION|INTERFACE|ENUM|ENUM_VALUE|INPUT_OBJECT|INPUT_FIELD_DEFINITION + type Query type Mutation diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index 67e8da9fa6..a0978f5a02 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -157,9 +157,15 @@ extend type Query { # Permissions getConnectionSubjectAccess(projectId: ID!, connectionId: ID): [AdminConnectionGrantInfo!]! + @deprecated(reason: "23.2.2") setConnectionSubjectAccess(projectId: ID!, connectionId: ID!, subjects: [ID!]!): Boolean + @since(version: "23.2.2") + addConnectionsAccess(projectId: ID!, connectionIds: [ID!]!, subjects: [ID!]!): Boolean + @since(version: "23.2.2") + deleteConnectionsAccess(projectId: ID!, connectionIds: [ID!]!, subjects: [ID!]!): Boolean getSubjectConnectionAccess(subjectId: ID!): [AdminConnectionGrantInfo!]! + @deprecated(reason: "23.2.2") setSubjectConnectionAccess(subjectId: ID!, connections: [ID!]!): Boolean #### Feature sets diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java index 05bc52993e..faf258d081 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/DBWServiceAdmin.java @@ -147,6 +147,7 @@ SMDataSourceGrant[] getConnectionSubjectAccess( @Nullable String projectId, String connectionId) throws DBWebException; + @Deprecated @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) boolean setConnectionSubjectAccess( @NotNull WebSession webSession, @@ -154,6 +155,22 @@ boolean setConnectionSubjectAccess( @NotNull String connectionId, @NotNull List subjects) throws DBWebException; + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) + boolean addConnectionsAccess( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull List connectionIds, + @NotNull List subjects + ) throws DBWebException; + + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) + boolean deleteConnectionAccess( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull List connectionIds, + @NotNull List subjects + ) throws DBWebException; + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) SMDataSourceGrant[] getSubjectConnectionAccess(@NotNull WebSession webSession, @NotNull String subjectId) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java index 551c292581..53c8e3f515 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/WebServiceBindingAdmin.java @@ -95,27 +95,41 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { env -> getService(env).revokeUserTeam(getWebSession(env), env.getArgument("userId"), env.getArgument("teamId"))) .dataFetcher("setSubjectPermissions", env -> getService(env).setSubjectPermissions(getWebSession(env), env.getArgument("subjectId"), env.getArgument("permissions"))) - .dataFetcher("setUserCredentials", - env -> getService(env).setUserCredentials(getWebSession(env), env.getArgument("userId"), env.getArgument("providerId"), env.getArgument("credentials"))) - .dataFetcher("deleteUserCredentials", - env -> getService(env).deleteUserCredentials(getWebSession(env), env.getArgument("userId"), env.getArgument("providerId"))) - .dataFetcher("enableUser", - env -> getService(env).enableUser(getWebSession(env), env.getArgument("userId"), env.getArgument("enabled"))) - .dataFetcher("setUserAuthRole", - env -> getService(env).setUserAuthRole(getWebSession(env), env.getArgument("userId"), env.getArgument("authRole"))) - .dataFetcher("searchConnections", env -> getService(env).searchConnections(getWebSession(env), env.getArgument("hostNames"))) - - .dataFetcher("getConnectionSubjectAccess", - env -> getService(env).getConnectionSubjectAccess( - getWebSession(env), - getProjectReference(env), - env.getArgument("connectionId"))) - .dataFetcher("setConnectionSubjectAccess", - env -> getService(env).setConnectionSubjectAccess( - getWebSession(env), - getProjectReference(env), - env.getArgument("connectionId"), - env.getArgument("subjects"))) + .dataFetcher("setUserCredentials", + env -> getService(env).setUserCredentials(getWebSession(env), + env.getArgument("userId"), + env.getArgument("providerId"), + env.getArgument("credentials"))) + .dataFetcher("deleteUserCredentials", + env -> getService(env).deleteUserCredentials(getWebSession(env), env.getArgument("userId"), env.getArgument("providerId"))) + .dataFetcher("enableUser", + env -> getService(env).enableUser(getWebSession(env), env.getArgument("userId"), env.getArgument("enabled"))) + .dataFetcher("setUserAuthRole", + env -> getService(env).setUserAuthRole(getWebSession(env), env.getArgument("userId"), env.getArgument("authRole"))) + .dataFetcher("searchConnections", env -> getService(env).searchConnections(getWebSession(env), env.getArgument("hostNames"))) + .dataFetcher("getConnectionSubjectAccess", + env -> getService(env).getConnectionSubjectAccess( + getWebSession(env), + getProjectReference(env), + env.getArgument("connectionId"))) + .dataFetcher("setConnectionSubjectAccess", + env -> getService(env).setConnectionSubjectAccess( + getWebSession(env), + getProjectReference(env), + env.getArgument("connectionId"), + env.getArgument("subjects"))) + .dataFetcher("addConnectionsAccess", + env -> getService(env).addConnectionsAccess( + getWebSession(env), + getProjectReference(env), + env.getArgument("connectionIds"), + env.getArgument("subjects"))) + .dataFetcher("deleteConnectionAccess", + env -> getService(env).deleteConnectionAccess( + getWebSession(env), + getProjectReference(env), + env.getArgument("connectionIds"), + env.getArgument("subjects"))) .dataFetcher("getSubjectConnectionAccess", env -> getService(env).getSubjectConnectionAccess(getWebSession(env), env.getArgument("subjectId"))) diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java index 039fed41f4..7da8ee75a8 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/impl/WebServiceAdmin.java @@ -610,14 +610,7 @@ public boolean setConnectionSubjectAccess( @NotNull String connectionId, @NotNull List subjects ) throws DBWebException { - DBPProject globalProject = webSession.getProjectById(projectId); - if (!WebServiceUtils.isGlobalProject(globalProject)) { - throw new DBWebException("Project '" + projectId + "'is not global"); - } - DBPDataSourceContainer dataSource = getDataSourceRegistry(webSession, projectId).getDataSource(connectionId); - if (dataSource == null) { - throw new DBWebException("Connection '" + connectionId + "' not found"); - } + validateThatConnectionGlobal(webSession, projectId, List.of(connectionId)); WebUser grantor = webSession.getUser(); if (grantor == null) { throw new DBWebException("Cannot grant connection access in anonymous mode"); @@ -635,6 +628,72 @@ public boolean setConnectionSubjectAccess( return true; } + void validateThatConnectionGlobal(WebSession webSession, String projectId, Collection connectionIds) throws DBWebException { + DBPProject globalProject = webSession.getProjectById(projectId); + if (!WebServiceUtils.isGlobalProject(globalProject)) { + throw new DBWebException("Project '" + projectId + "'is not global"); + } + for (String connectionId : connectionIds) { + DBPDataSourceContainer dataSource = getDataSourceRegistry(webSession, projectId).getDataSource(connectionId); + if (dataSource == null) { + throw new DBWebException("Connection '" + connectionId + "' not found"); + } + } + } + + @Override + public boolean addConnectionsAccess( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull List connectionIds, + @NotNull List subjects + ) throws DBWebException { + validateThatConnectionGlobal(webSession, projectId, connectionIds); + WebUser grantor = webSession.getUser(); + if (grantor == null) { + throw new DBWebException("Cannot grant connection access in anonymous mode"); + } + try { + var adminSM = webSession.getAdminSecurityController(); + adminSM.addObjectPermissions( + new HashSet<>(connectionIds), + SMObjectType.datasource, + new HashSet<>(subjects), + Set.of(SMConstants.DATA_SOURCE_ACCESS_PERMISSION), + grantor.getUserId() + ); + } catch (DBException e) { + throw new DBWebException("Error adding connection subject access", e); + } + return true; + } + + @Override + public boolean deleteConnectionAccess( + @NotNull WebSession webSession, + @Nullable String projectId, + @NotNull List connectionIds, + @NotNull List subjects + ) throws DBWebException { + validateThatConnectionGlobal(webSession, projectId, connectionIds); + WebUser grantor = webSession.getUser(); + if (grantor == null) { + throw new DBWebException("Cannot grant connection access in anonymous mode"); + } + try { + var adminSM = webSession.getAdminSecurityController(); + adminSM.deleteObjectPermissions( + new HashSet<>(connectionIds), + SMObjectType.datasource, + new HashSet<>(subjects), + Set.of(SMConstants.DATA_SOURCE_ACCESS_PERMISSION) + ); + } catch (DBException e) { + throw new DBWebException("Error adding connection subject access", e); + } + return true; + } + @Override public SMDataSourceGrant[] getSubjectConnectionAccess(@NotNull WebSession webSession, @NotNull String subjectId) throws DBWebException { try { diff --git a/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls b/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls index a3797f5a70..42eeea19e9 100644 --- a/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls +++ b/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls @@ -98,9 +98,17 @@ extend type Mutation { rmDeleteProject(projectId: ID!): Boolean! + @deprecated rmSetProjectPermissions(projectId: String!, permissions: [RMSubjectProjectPermissions!]!): Boolean! + @deprecated rmSetSubjectProjectPermissions(subjectId: String!, permissions: [RMProjectPermissions!]!): Boolean! + @since(version: "23.2.2") + rmAddProjectsPermissions(projectIds: [ID!]!, subjectIds: [ID!]!, permissions:[String!]! ): Boolean + @since(version: "23.2.2") + rmDeleteProjectsPermissions(projectIds: [ID!]!, subjectIds: [ID!]!, permissions:[String!]!): Boolean + + rmSetResourceProperty(projectId: String!, resourcePath: String!, name: ID!, value: String): Boolean! } \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java index 355fe25f12..f52a60ec7e 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/DBWServiceRM.java @@ -136,6 +136,7 @@ boolean deleteProject( @WebAction(requirePermissions = {RMConstants.PERMISSION_RM_ADMIN}) List listProjectPermissions() throws DBWebException; + @Deprecated @WebProjectAction( requireProjectPermissions = RMConstants.PERMISSION_PROJECT_ADMIN ) @@ -145,6 +146,7 @@ boolean setProjectPermissions( @NotNull RMSubjectProjectPermissions projectPermissions ) throws DBWebException; + @Deprecated @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) boolean setSubjectProjectPermissions( @NotNull WebSession webSession, @@ -152,6 +154,22 @@ boolean setSubjectProjectPermissions( @NotNull RMProjectPermissions projectPermissions ) throws DBWebException; + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) + boolean deleteProjectsPermissions( + @NotNull WebSession webSession, + @NotNull List projectIds, + @NotNull List subjectIds, + @NotNull List permissions + ) throws DBWebException; + + @WebAction(requirePermissions = DBWConstants.PERMISSION_ADMIN) + boolean addProjectsPermissions( + @NotNull WebSession webSession, + @NotNull List projectIds, + @NotNull List subjectIds, + @NotNull List permissions + ) throws DBWebException; + @WebProjectAction( requireProjectPermissions = RMConstants.PERMISSION_PROJECT_ADMIN ) diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java index fac21fac06..1909e3126b 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/WebServiceBindingRM.java @@ -112,6 +112,18 @@ public void bindWiring(DBWBindingContext model) throws DBWebException { env.getArgument("subjectId"), new RMProjectPermissions(env.getArgument("permissions")) )) + .dataFetcher("rmAddProjectsPermissions", env -> getService(env).addProjectsPermissions( + getWebSession(env), + env.getArgument("projectIds"), + env.getArgument("subjectIds"), + env.getArgument("permissions") + )) + .dataFetcher("rmDeleteProjectsPermissions", env -> getService(env).addProjectsPermissions( + getWebSession(env), + env.getArgument("projectIds"), + env.getArgument("subjectIds"), + env.getArgument("permissions") + )) ; } } diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java index 96ade14e07..0f88521937 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java +++ b/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/impl/WebServiceRM.java @@ -38,6 +38,7 @@ import org.jkiss.dbeaver.model.websocket.event.resource.WSResourceProperty; import java.nio.charset.StandardCharsets; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -349,6 +350,49 @@ public boolean setSubjectProjectPermissions( } } + @Override + public boolean deleteProjectsPermissions( + @NotNull WebSession webSession, + @NotNull List projectIds, + @NotNull List subjectIds, + @NotNull List permissions + ) throws DBWebException { + try { + SMAdminController smAdminController = webSession.getAdminSecurityController(); + smAdminController.deleteObjectPermissions( + new HashSet<>(projectIds), + SMObjectType.project, + new HashSet<>(subjectIds), + new HashSet<>(permissions) + ); + return true; + } catch (Exception e) { + throw new DBWebException("Error deleting project permissions", e); + } + } + + @Override + public boolean addProjectsPermissions( + @NotNull WebSession webSession, + @NotNull List projectIds, + @NotNull List subjectIds, + @NotNull List permissions + ) throws DBWebException { + try { + SMAdminController smAdminController = webSession.getAdminSecurityController(); + smAdminController.addObjectPermissions( + new HashSet<>(projectIds), + SMObjectType.project, + new HashSet<>(subjectIds), + new HashSet<>(permissions), + webSession.getUserId() + ); + return true; + } catch (Exception e) { + throw new DBWebException("Error adding project permissions", e); + } + } + @Override public List listProjectGrantedPermissions(@NotNull WebSession webSession, @NotNull String projectId diff --git a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java index 875de6b57d..25a568f544 100644 --- a/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java +++ b/server/bundles/io.cloudbeaver.service.security/src/io/cloudbeaver/service/security/CBEmbeddedSecurityController.java @@ -2265,6 +2265,89 @@ public void setObjectPermissions( } + @Override + public void addObjectPermissions( + @NotNull Set objectIds, + @NotNull SMObjectType objectType, + @NotNull Set subjectIds, + @NotNull Set permissions, + @NotNull String grantor + ) throws DBException { + if (CommonUtils.isEmpty(objectIds) || CommonUtils.isEmpty(subjectIds) || CommonUtils.isEmpty(permissions)) { + return; + } + Set filteredSubjects = getFilteredSubjects(subjectIds); + try (Connection dbCon = database.openConnection(); + JDBCTransaction txn = new JDBCTransaction(dbCon); + PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames( + "INSERT INTO {table_prefix}CB_OBJECT_PERMISSIONS" + + "(OBJECT_ID,OBJECT_TYPE,GRANT_TIME,GRANTED_BY,SUBJECT_ID,PERMISSION) " + + "VALUES(?,?,?,?,?,?)")) + ) { + for (String objectId : objectIds) { + dbStat.setString(1, objectId); + dbStat.setString(2, objectType.name()); + dbStat.setTimestamp(3, new Timestamp(System.currentTimeMillis())); + dbStat.setString(4, grantor); + for (String subjectId : subjectIds) { + if (!filteredSubjects.contains(subjectId)) { + log.error("Subject '" + subjectId + "' is not found in database"); + continue; + } + dbStat.setString(5, subjectId); + for (String permission : permissions) { + dbStat.setString(6, permission); + dbStat.execute(); + } + } + txn.commit(); + } + addObjectPermissionsUpdateEvent(objectIds, objectType); + } catch (SQLException e) { + throw new DBCException("Error granting object permissions", e); + } + } + + @Override + public void deleteObjectPermissions( + @NotNull Set objectIds, + @NotNull SMObjectType objectType, + @NotNull Set subjectIds, + @NotNull Set permissions + ) throws DBException { + if (CommonUtils.isEmpty(objectIds) || CommonUtils.isEmpty(subjectIds) || CommonUtils.isEmpty(permissions)) { + return; + } + String sql = "DELETE FROM {table_prefix}CB_OBJECT_PERMISSIONS WHERE " + + "OBJECT_TYPE=?" + + " AND " + + "OBJECT_ID IN (" + SQLUtils.generateParamList(objectIds.size()) + ")" + + " AND " + + "SUBJECT_ID IN (" + SQLUtils.generateParamList(subjectIds.size()) + ")" + + " AND " + + "PERMISSION IN (" + SQLUtils.generateParamList(permissions.size()) + ")"; + + try ( + Connection dbCon = database.openConnection(); + PreparedStatement dbStat = dbCon.prepareStatement(database.normalizeTableNames(sql)) + ) { + int index = 1; + dbStat.setString(index++, objectType.name()); + for (String objectId : objectIds) { + dbStat.setString(index++, objectId); + } + for (String subjectId : subjectIds) { + dbStat.setString(index++, subjectId); + } + for (String permission : permissions) { + dbStat.setString(index++, permission); + } + dbStat.execute(); + } catch (SQLException e) { + throw new DBCException("Error granting object permissions", e); + } + } + private void addSubjectPermissionsUpdateEvent(@NotNull String subjectId, @Nullable SMSubjectType subjectType) { if (subjectType == null) {