diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/VirtualProjectImpl.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/VirtualProjectImpl.java index 12c5538a77..b3f2622609 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/VirtualProjectImpl.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/VirtualProjectImpl.java @@ -79,4 +79,8 @@ public class VirtualProjectImpl extends BaseProjectImpl { public void ensureOpen() { } + @NotNull + public RMProject getRmProject() { + return this.project; + } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java index 64bdac3ac4..b3e16ec16a 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/BaseWebApplication.java @@ -32,6 +32,7 @@ import org.jkiss.dbeaver.model.auth.SMSessionContext; import org.jkiss.dbeaver.model.data.json.JSONUtils; import org.jkiss.dbeaver.model.rm.RMController; import org.jkiss.dbeaver.model.rm.RMProject; +import org.jkiss.dbeaver.model.security.SMController; import org.jkiss.dbeaver.registry.BaseApplicationImpl; import org.jkiss.dbeaver.registry.EclipseWorkspaceImpl; import io.cloudbeaver.VirtualProjectImpl; @@ -123,12 +124,9 @@ public abstract class BaseWebApplication extends BaseApplicationImpl implements } @Override - public RMController getResourceController(@NotNull SMCredentialsProvider credentialsProvider) { - return createResourceController(credentialsProvider); - } + public RMController getResourceController(@NotNull SMCredentialsProvider credentialsProvider, @NotNull SMController smController) { + return LocalResourceController.builder(credentialsProvider, smController).build(); - protected @NotNull RMController createResourceController(@NotNull SMCredentialsProvider credentialsProvider) { - return LocalResourceController.builder(credentialsProvider).build(); } protected Map getServerConfigProps(Map configProps) { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java index 951b434cbf..f2ceebeab3 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/app/WebApplication.java @@ -51,7 +51,10 @@ public interface WebApplication extends DBPApplication { SMAdminController getAdminSecurityController(@NotNull SMCredentialsProvider credentialsProvider); - RMController getResourceController(@NotNull SMCredentialsProvider credentialsProvider); + RMController getResourceController( + @NotNull SMCredentialsProvider credentialsProvider, + @NotNull SMController smController + ); String getServerURL(); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerRoot.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerRoot.java index b780e96236..c944723c49 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerRoot.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/DBNResourceManagerRoot.java @@ -74,7 +74,7 @@ public class DBNResourceManagerRoot extends DBNNode implements DBPHiddenObject, @Override public DBNResourceManagerProject[] getChildren(DBRProgressMonitor monitor) throws DBException { if (projects == null) { - List projectList = getParentNode().getModel().getModelProjects(); + List projectList = getParentNode().getModel().getModelProjects(); SMSession session = null; for (DBPProject project : projectList) { session = getParentNode().getModel().getModelAuthContext().getSpaceSession(monitor, project, false); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java index 6f5cfe8bcc..8e88f4eeca 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/RMUtils.java @@ -4,9 +4,13 @@ import io.cloudbeaver.DBWConstants; import io.cloudbeaver.model.app.BaseWebApplication; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.model.rm.RMProject; +import org.jkiss.dbeaver.model.rm.RMProjectPermission; import org.jkiss.dbeaver.runtime.DBWorkbench; import java.nio.file.Path; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; public class RMUtils { @@ -33,4 +37,19 @@ public class RMUtils { return getUserProjectsPath().resolve(project.getName()); } } + + public static Set parseProjectPermissions(Set permissions) { + return permissions.stream() + .map(RMProjectPermission::fromPermission).filter(Objects::nonNull) + .flatMap(permission -> permission.getAllPermissions().stream()) + .collect(Collectors.toSet()); + } + + public static RMProject createAnonymousProject() { + RMProject project = new RMProject("anonymous"); + project.setId("anonymous"); + project.setType(RMProject.Type.USER); + project.setProjectPermissions(RMProjectPermission.CONNECTIONS_EDIT.getAllPermissions()); + return project; + } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java index 81dee23bf2..3e459d5cf6 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/rm/local/LocalResourceController.java @@ -16,6 +16,7 @@ */ package io.cloudbeaver.model.rm.local; +import io.cloudbeaver.DBWConstants; import io.cloudbeaver.VirtualProjectImpl; import io.cloudbeaver.model.rm.RMUtils; import io.cloudbeaver.service.sql.WebSQLConstants; @@ -32,6 +33,8 @@ import org.jkiss.dbeaver.model.auth.SMCredentialsProvider; import org.jkiss.dbeaver.model.impl.auth.SessionContextImpl; import org.jkiss.dbeaver.model.rm.*; import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor; +import org.jkiss.dbeaver.model.security.SMController; +import org.jkiss.dbeaver.model.security.SMObjects; import org.jkiss.dbeaver.model.sql.DBQuotaException; import org.jkiss.dbeaver.registry.*; import org.jkiss.dbeaver.runtime.DBWorkbench; @@ -63,6 +66,7 @@ public class LocalResourceController implements RMController { private final Path userProjectsPath; private final Path sharedProjectsPath; private final String globalProjectName; + private final SMController smController; private final Map projectRegistries = new LinkedHashMap<>(); @@ -70,11 +74,14 @@ public class LocalResourceController implements RMController { SMCredentialsProvider credentialsProvider, Path rootPath, Path userProjectsPath, - Path sharedProjectsPath) { + Path sharedProjectsPath, + SMController smController + ) { this.credentialsProvider = credentialsProvider; this.rootPath = rootPath; this.userProjectsPath = userProjectsPath; this.sharedProjectsPath = sharedProjectsPath; + this.smController = smController; this.globalProjectName = DBWorkbench.getPlatform().getApplication().getDefaultProjectName(); } @@ -94,7 +101,7 @@ public class LocalResourceController implements RMController { VirtualProjectImpl project = projectRegistries.get(projectId); if (project == null) { SessionContextImpl sessionContext = new SessionContextImpl(null); - RMProject rmProject = makeProjectFromId(projectId); + RMProject rmProject = makeProjectFromId(projectId, false); project = new VirtualProjectImpl( rmProject, sessionContext); @@ -107,39 +114,80 @@ public class LocalResourceController implements RMController { @NotNull @Override public RMProject[] listAccessibleProjects() throws DBException { - try { - List projects; - if (Files.exists(sharedProjectsPath)) { - projects = Files.list(sharedProjectsPath) - .map((Path path) -> makeProjectFromPath(path, RMProject.Type.SHARED, true)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - } else { - projects = new ArrayList<>(); - } - RMProject globalProject = makeProjectFromPath(getGlobalProjectPath(), RMProject.Type.GLOBAL, true); - if (globalProject != null) { - projects.add(globalProject); - } - RMProject userProject = makeProjectFromPath(getPrivateProjectPath(), RMProject.Type.USER, false); - if (userProject != null) { - projects.add(0, userProject); - } - return projects.toArray(new RMProject[0]); - } catch (IOException e) { - throw new DBException("Error reading projects", e); + List projects; + //TODO refactor after implement current user api in sm + var activeUserCreds = credentialsProvider.getActiveUserCredentials(); + if (Files.exists(sharedProjectsPath) || activeUserCreds != null && activeUserCreds.getUserId() != null) { + var accessibleSharedProjects = smController.getAllAvailableObjectsPermissions( + activeUserCreds.getUserId(), + SMObjects.PROJECT + ); + + projects = accessibleSharedProjects + .stream() + .map(projectPermission -> makeProjectFromPath( + sharedProjectsPath.resolve(projectPermission.getObjectId()), + projectPermission.getPermissions().stream().map(RMProjectPermission::fromPermission).collect(Collectors.toSet()), + RMProject.Type.SHARED, true) + ) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } else { + projects = new ArrayList<>(); + } + + //FIXME: remove legacy global project support + //admin has all edit access + //user has only read access + var globalProjectPermissions = getProjectPermissions(globalProjectName, RMProject.Type.GLOBAL); + + RMProject globalProject = makeProjectFromPath(getGlobalProjectPath(), globalProjectPermissions, RMProject.Type.GLOBAL, true); + if (globalProject != null) { + projects.add(globalProject); + } + + //user has full access to his private project + var userProjectPermission = getProjectPermissions(null, RMProject.Type.USER); + RMProject userProject = makeProjectFromPath(getPrivateProjectPath(), userProjectPermission, RMProject.Type.USER, false); + if (userProject != null) { + projects.add(0, userProject); + } + return projects.toArray(new RMProject[0]); + } + + private Set getProjectPermissions(@Nullable String projectId, RMProject.Type projectType) throws DBException { + var activeUserCreds = credentialsProvider.getActiveUserCredentials(); + + switch (projectType) { + case GLOBAL: + return credentialsProvider.hasPermission(DBWConstants.PERMISSION_ADMIN) + ? Set.of(RMProjectPermission.RESOURCE_EDIT, RMProjectPermission.CONNECTIONS_EDIT) + : Set.of(RMProjectPermission.RESOURCE_VIEW, RMProjectPermission.CONNECTIONS_VIEW); + case SHARED: + if(projectId == null) { + throw new DBException("Project id required"); + } + return smController.getObjectPermissions(activeUserCreds.getUserId(), projectId, SMObjects.PROJECT) + .getPermissions() + .stream() + .map(RMProjectPermission::fromPermission) + .collect(Collectors.toSet()); + case USER: + return Set.of(RMProjectPermission.RESOURCE_EDIT, RMProjectPermission.CONNECTIONS_EDIT); + default: + throw new DBException("Unknown project type:" + projectType); } } @NotNull @Override - public RMProject[] listSharedProjects() throws DBException { + public RMProject[] listAllSharedProjects() throws DBException { try { if (!Files.exists(sharedProjectsPath)) { return new RMProject[0]; } return Files.list(sharedProjectsPath) - .map((Path path) -> makeProjectFromPath(path, RMProject.Type.SHARED, false)) + .map((Path path) -> makeProjectFromPath(path, Set.of(), RMProject.Type.SHARED, false)) .filter(Objects::nonNull) .toArray(RMProject[]::new); } catch (IOException e) { @@ -158,8 +206,7 @@ public class LocalResourceController implements RMController { } validateResourcePath(name); RMProject project; - project = makeProjectFromPath(sharedProjectsPath.resolve(name), - RMProject.Type.SHARED, false); + project = makeProjectFromPath(sharedProjectsPath.resolve(name), Set.of(), RMProject.Type.SHARED, false); if (project == null) { throw new DBException("Project '" + name + "' already exists"); } @@ -173,7 +220,7 @@ public class LocalResourceController implements RMController { @Override public void deleteProject(@NotNull String projectId) throws DBException { - RMProject project = makeProjectFromId(projectId); + RMProject project = makeProjectFromId(projectId, false); Path targetPath = getProjectPath(projectId); if (!Files.exists(targetPath)) { throw new DBException("Project '" + project.getName() + "' doesn't exists"); @@ -187,7 +234,7 @@ public class LocalResourceController implements RMController { @Override public RMProject getProject(@NotNull String projectId, boolean readResources) throws DBException { - RMProject project = makeProjectFromId(projectId); + RMProject project = makeProjectFromId(projectId, true); if (readResources) { project.setChildren( listResources(projectId, null, null, true, false, true) @@ -341,7 +388,7 @@ public class LocalResourceController implements RMController { } RMEventManager.fireEvent( new RMEvent(RMEvent.Action.RESOURCE_DELETE, - makeProjectFromId(projectId), + makeProjectFromId(projectId, false), rmResourcePath ) ); @@ -431,13 +478,17 @@ public class LocalResourceController implements RMController { } - private RMProject makeProjectFromId(String projectId) throws DBException { + private RMProject makeProjectFromId(String projectId, boolean loadPermissions) throws DBException { var projectName = parseProjectName(projectId); var projectPath = getProjectPath(projectId); - return makeProjectFromPath(projectPath, projectName.getType(), false); + Set permissions = Set.of(); + if(loadPermissions && credentialsProvider.getActiveUserCredentials() != null) { + permissions = getProjectPermissions(projectId, projectName.getType()); + } + return makeProjectFromPath(projectPath, permissions, projectName.getType(), false); } - private RMProject makeProjectFromPath(Path path, RMProject.Type type, boolean checkExistence) { + private RMProject makeProjectFromPath(Path path, Set permissions, RMProject.Type type, boolean checkExistence) { if (path == null) { return null; } @@ -450,11 +501,16 @@ public class LocalResourceController implements RMController { return null; } + Set allProjectPermissions = permissions.stream() + .flatMap(rmProjectPermission -> rmProjectPermission.getAllPermissions().stream()) + .collect(Collectors.toSet()); + RMProject project = new RMProject(); String projectName = path.getFileName().toString(); project.setName(projectName); project.setId(type.getPrefix() + "_" + projectName); project.setType(type); + project.setProjectPermissions(allProjectPermissions); if (Files.exists(path)) { try { project.setCreateTime( @@ -550,19 +606,21 @@ public class LocalResourceController implements RMController { return resource; } - public static Builder builder(SMCredentialsProvider credentialsProvider) { - return new Builder(credentialsProvider); + public static Builder builder(SMCredentialsProvider credentialsProvider, SMController smController) { + return new Builder(credentialsProvider, smController); } public static final class Builder { private final SMCredentialsProvider credentialsProvider; + private final SMController smController; private Path rootPath; private Path userProjectsPath; private Path sharedProjectsPath; - private Builder(SMCredentialsProvider credentialsProvider) { + private Builder(SMCredentialsProvider credentialsProvider, SMController smController) { this.credentialsProvider = credentialsProvider; + this.smController = smController; this.rootPath = RMUtils.getRootPath(); this.userProjectsPath = RMUtils.getUserProjectsPath(); this.sharedProjectsPath = RMUtils.getSharedProjectsPath(); @@ -584,7 +642,7 @@ public class LocalResourceController implements RMController { } public LocalResourceController build() { - return new LocalResourceController(credentialsProvider, rootPath, userProjectsPath, sharedProjectsPath); + return new LocalResourceController(credentialsProvider, rootPath, userProjectsPath, sharedProjectsPath, smController); } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java index cbc413734b..154076d386 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebSession.java @@ -22,6 +22,7 @@ import io.cloudbeaver.model.WebAsyncTaskInfo; import io.cloudbeaver.model.WebConnectionInfo; import io.cloudbeaver.model.WebServerMessage; import io.cloudbeaver.model.app.WebApplication; +import io.cloudbeaver.model.rm.RMUtils; import io.cloudbeaver.model.user.WebUser; import io.cloudbeaver.service.DBWSessionHandler; import io.cloudbeaver.service.sql.WebSQLConstants; @@ -109,13 +110,13 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, private final Map asyncTasks = new HashMap<>(); private final Map> attributeDisposers = new HashMap<>(); - // Map of auth tokens. Key is authentication provdier + // Map of auth tokens. Key is authentication provider private final List authTokens = new ArrayList<>(); private DBNModel navigatorModel; private final DBRProgressMonitor progressMonitor = new SessionProgressMonitor(); private VirtualProjectImpl defaultProject; - private final List sessionProjects = new ArrayList<>(); + private final List accessibleProjects = new ArrayList<>(); private final SessionContextImpl sessionAuthContext; private final WebApplication application; private final Map sessionHandlers; @@ -295,20 +296,20 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, this.navigatorModel = null; } - if (!this.sessionProjects.isEmpty()) { - for (DBPProject project : sessionProjects) { + if (!this.accessibleProjects.isEmpty()) { + for (VirtualProjectImpl project : accessibleProjects) { if (project.equals(DBWorkbench.getPlatform().getWorkspace().getActiveProject())) { continue; } - ((BaseProjectImpl) project).dispose(); + project.dispose(); } this.defaultProject = null; - this.sessionProjects.clear(); + this.accessibleProjects.clear(); } loadProjects(); - this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), this.sessionProjects); + this.navigatorModel = new DBNModel(DBWorkbench.getPlatform(), this.accessibleProjects); this.navigatorModel.setModelAuthContext(sessionAuthContext); this.navigatorModel.initialize(); @@ -325,19 +326,14 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, private void loadProjects() { WebUser user = userContext.getUser(); try { - RMController controller = application.getResourceController(this); + RMController controller = application.getResourceController(this, getSecurityController()); RMProject[] rmProjects = controller.listAccessibleProjects(); for (RMProject project : rmProjects) { - VirtualProjectImpl sessionProject = application.createProjectImpl(project, getSessionAuthContext(), this); - if (!project.isShared() || (user == null && project.getType().equals(RMProject.Type.GLOBAL))) { - this.defaultProject = sessionProject; - } - DBPDataSourceRegistry dataSourceRegistry = sessionProject.getDataSourceRegistry(); - ((DataSourceRegistry) dataSourceRegistry).setAuthCredentialsProvider(this); - addSessionProject(sessionProject); - if (user == null && sessionProject.equals(defaultProject)) { - sessionProject.setInMemory(true); - } + createVirtualProject(project); + } + if (user == null) { + createVirtualProject(RMUtils.createAnonymousProject()); + this.defaultProject.setInMemory(true); } } catch (DBException e) { addSessionError(e); @@ -345,11 +341,21 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, } } + private void createVirtualProject(RMProject project) { + VirtualProjectImpl sessionProject = application.createProjectImpl(project, getSessionAuthContext(), this); + DBPDataSourceRegistry dataSourceRegistry = sessionProject.getDataSourceRegistry(); + ((DataSourceRegistry) dataSourceRegistry).setAuthCredentialsProvider(this); + addSessionProject(sessionProject); + if (!project.isShared()) { + this.defaultProject = sessionProject; + } + } + public void refreshConnections() { // Add all provided datasources to the session List connList = new ArrayList<>(); - for (DBPProject project : sessionProjects) { + for (DBPProject project : accessibleProjects) { DBPDataSourceRegistry registry = project.getDataSourceRegistry(); for (DBPDataSourceContainer ds : registry.getDataSources()) { @@ -930,11 +936,11 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, return userContext.getActiveUserCredentials(); } - public DBPProject getProjectById(@Nullable String projectId) { + public VirtualProjectImpl getProjectById(@Nullable String projectId) { if (projectId == null) { return defaultProject; } - for (DBPProject project : sessionProjects) { + for (VirtualProjectImpl project : accessibleProjects) { if (project.getId().equals(projectId)) { return project; } @@ -942,13 +948,13 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, return null; } - public List getSessionProjects() { - return sessionProjects; + public List getAccessibleProjects() { + return accessibleProjects; } - public void addSessionProject(DBPProject project) { - synchronized (sessionProjects) { - sessionProjects.add(project); + public void addSessionProject(VirtualProjectImpl project) { + synchronized (accessibleProjects) { + accessibleProjects.add(project); } if (navigatorModel != null) { navigatorModel.getRoot().addProject(project, false); @@ -956,8 +962,8 @@ public class WebSession extends AbstractSessionPersistent implements SMSession, } public void deleteSessionProject(DBPProject project) { - synchronized (sessionProjects) { - sessionProjects.remove(project); + synchronized (accessibleProjects) { + accessibleProjects.remove(project); } if (navigatorModel != null) { navigatorModel.getRoot().removeProject(project); diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java index 2142fba75c..7585495414 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/session/WebUserContext.java @@ -77,11 +77,11 @@ public class WebUserContext implements SMCredentialsProvider { if (isNonAnonymousUserAuthorized && isSessionChanged && !Objects.equals(getUserId(), authPermissions.getUserId())) { throw new DBCException("Another user is already logged in"); } - this.smCredentials = new SMCredentials(smAuthInfo.getSmAuthToken(), authPermissions.getUserId()); + this.smCredentials = new SMCredentials(smAuthInfo.getSmAuthToken(), authPermissions.getUserId(), authPermissions.getPermissions()); this.userPermissions = authPermissions.getPermissions(); this.securityController = application.getSecurityController(this); this.adminSecurityController = application.getAdminSecurityController(this); - this.rmController = application.getResourceController(this); + this.rmController = application.getResourceController(this, this.securityController); if (isSessionChanged) { this.smSessionId = smAuthInfo.getAuthPermissions().getSessionId(); setUser(authPermissions.getUserId() == null ? null : new WebUser(securityController.getUserById(authPermissions.getUserId()))); @@ -98,7 +98,7 @@ public class WebUserContext implements SMCredentialsProvider { this.user = null; this.securityController = application.getSecurityController(this); this.adminSecurityController = null; - this.rmController = application.getResourceController(this); + this.rmController = application.getResourceController(this, this.securityController); } @NotNull diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebObjectId.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebObjectId.java new file mode 100644 index 0000000000..98838400e6 --- /dev/null +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebObjectId.java @@ -0,0 +1,11 @@ +package io.cloudbeaver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(value = {ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface WebObjectId { +} diff --git a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/RMConstants.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebProjectAction.java similarity index 63% rename from server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/RMConstants.java rename to server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebProjectAction.java index 36f7c36189..3276623d29 100644 --- a/server/bundles/io.cloudbeaver.service.rm/src/io/cloudbeaver/service/rm/RMConstants.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/WebProjectAction.java @@ -14,18 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.cloudbeaver.service.rm; + +package io.cloudbeaver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * RM constants + * Object association annotation */ -public interface RMConstants { - - String PERMISSION_PROJECT_VIEW = "project-view"; - String PERMISSION_PROJECT_EDIT = "project-edit"; - String PERMISSION_PROJECT_ADMIN = "project-admin"; - - // RM admin can create/delete projects. It also can assign project permissions. - String PERMISSION_RM_ADMIN = "rm-admin"; - +@Target(value = {ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface WebProjectAction { + String[] requireProjectPermissions() default {}; } diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java index afce78950a..33ad78014b 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/WebServiceBindingBase.java @@ -29,6 +29,7 @@ import io.cloudbeaver.server.graphql.GraphQLEndpoint; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.model.rm.RMProject; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; @@ -38,10 +39,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; +import java.lang.reflect.*; import java.util.Set; /** @@ -153,6 +151,10 @@ public abstract class WebServiceBindingBase impleme if (webAction != null) { checkActionPermissions(method, webAction); } + WebProjectAction projectAction = method.getAnnotation(WebProjectAction.class); + if(projectAction != null) { + checkObjectActionPermissions(method, projectAction, args); + } beforeWebActionCall(webAction, method, args); try { return method.invoke(impl, args); @@ -173,6 +175,42 @@ public abstract class WebServiceBindingBase impleme } } + private void checkObjectActionPermissions(Method method, WebProjectAction objectAction, Object[] args) throws DBException { + WebSession webSession = findWebSession(env); + + String[] requireProjectPermissions = objectAction.requireProjectPermissions(); + if (requireProjectPermissions.length > 0) { + int objectIdArgumentIndex = -1; + for (int i = 0; i < method.getParameters().length; i++) { + Parameter parameter = method.getParameters()[i]; + if (parameter.isAnnotationPresent(WebObjectId.class)) { + if (String.class != parameter.getAnnotatedType().getType()) { + throw new DBWebExceptionAccessDenied("Invalid object id type"); + } + objectIdArgumentIndex = i; + break; + } + } + + if (objectIdArgumentIndex < 0) { + throw new DBWebExceptionAccessDenied("Project id argument not found"); + } + + String projectId = args[objectIdArgumentIndex] == null ? null : String.valueOf(args[objectIdArgumentIndex]); + VirtualProjectImpl project = webSession.getProjectById(projectId); + if(project == null) { + throw new DBException("Project not found:" + projectId); + } + RMProject rmProject = project.getRmProject(); + + for (String reqProjectPermission : requireProjectPermissions) { + if (!rmProject.getProjectPermissions().contains(reqProjectPermission)) { + throw new DBWebExceptionAccessDenied("Access denied"); + } + } + } + } + private void checkServicePermissions(Method method, WebActionSet actionSet) throws DBWebException { String[] features = actionSet.requireFeatures(); if (features.length > 0) { diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java index db8739065b..1994a80d37 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/DBWServiceCore.java @@ -18,12 +18,15 @@ package io.cloudbeaver.service.core; import io.cloudbeaver.DBWebException; import io.cloudbeaver.WebAction; +import io.cloudbeaver.WebObjectId; +import io.cloudbeaver.WebProjectAction; import io.cloudbeaver.model.*; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.service.DBWService; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; +import org.jkiss.dbeaver.model.rm.RMConstants; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -105,35 +108,35 @@ public interface DBWServiceCore extends DBWService { @Nullable List networkCredentials, @Nullable Boolean saveCredentials) throws DBWebException; - @WebAction + @WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_CONNECTIONS_EDIT}) WebConnectionInfo createConnection( @NotNull WebSession webSession, - @Nullable String projectId, + @Nullable @WebObjectId String projectId, @NotNull WebConnectionConfig connectionConfig) throws DBWebException; - @WebAction + @WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_CONNECTIONS_EDIT}) WebConnectionInfo updateConnection( @NotNull WebSession webSession, - @Nullable String projectId, + @Nullable @WebObjectId String projectId, @NotNull WebConnectionConfig connectionConfig) throws DBWebException; - @WebAction + @WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_CONNECTIONS_EDIT}) boolean deleteConnection( @NotNull WebSession webSession, - @Nullable String projectId, + @Nullable @WebObjectId String projectId, @NotNull String connectionId) throws DBWebException; - @WebAction + @WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_CONNECTIONS_EDIT}) WebConnectionInfo createConnectionFromTemplate( @NotNull WebSession webSession, - @Nullable String projectId, + @Nullable @WebObjectId String projectId, @NotNull String templateId, @Nullable String connectionName) throws DBWebException; - @WebAction() + @WebProjectAction(requireProjectPermissions = {RMConstants.PERMISSION_PROJECT_CONNECTIONS_EDIT}) WebConnectionInfo copyConnectionFromNode( @NotNull WebSession webSession, - @Nullable String projectId, + @Nullable @WebObjectId String projectId, @NotNull String nodePath, @NotNull WebConnectionConfig config) throws DBWebException; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java index 479282354d..322b0ea91c 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/core/impl/WebServiceCore.java @@ -567,7 +567,7 @@ public class WebServiceCore implements DBWServiceCore { // Projects @Override public List getProjects(@NotNull WebSession session) { - return session.getSessionProjects().stream() + return session.getAccessibleProjects().stream() .map(pr -> new WebProjectInfo(session, pr)).collect(Collectors.toList()); } diff --git a/server/bundles/io.cloudbeaver.service.rm/plugin.xml b/server/bundles/io.cloudbeaver.service.rm/plugin.xml index 7590bcee04..ca3ff4442b 100644 --- a/server/bundles/io.cloudbeaver.service.rm/plugin.xml +++ b/server/bundles/io.cloudbeaver.service.rm/plugin.xml @@ -4,8 +4,22 @@ - + + + + + + + + + + + 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 1bd94d32b9..60ab97190d 100644 --- a/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls +++ b/server/bundles/io.cloudbeaver.service.rm/schema/service.rm.graphqls @@ -1,4 +1,7 @@ # Metadata queries +enum RMProjectPermission { + resource_edit +} type RMProject { id: String! @@ -8,6 +11,8 @@ type RMProject { createTime: DateTime! creator: String! + canEditResource: Boolean! + canEditConnection: Boolean! } type RMResource { 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 69a09c942f..66ef312d19 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 @@ -16,14 +16,14 @@ */ package io.cloudbeaver.service.rm; -import io.cloudbeaver.DBWebException; -import io.cloudbeaver.WebAction; +import io.cloudbeaver.*; import io.cloudbeaver.model.WebProjectInfo; import io.cloudbeaver.model.session.WebSession; import io.cloudbeaver.service.DBWService; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; +import org.jkiss.dbeaver.model.rm.RMConstants; import org.jkiss.dbeaver.model.rm.RMProject; import org.jkiss.dbeaver.model.rm.RMResource; @@ -36,43 +36,60 @@ public interface DBWServiceRM extends DBWService { RMProject[] listProjects(@NotNull WebSession webSession) throws DBWebException; @NotNull + @WebProjectAction( + requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_VIEW + ) RMResource[] listResources( @NotNull WebSession webSession, - @NotNull String projectId, + @NotNull @WebObjectId String projectId, @Nullable String folder, @Nullable String nameMask, boolean readProperties, boolean readHistory) throws DBException; + @WebProjectAction( + requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_VIEW + ) String readResourceAsString( @NotNull WebSession webSession, - @NotNull String projectId, + @NotNull @WebObjectId String projectId, @NotNull String resourcePath) throws DBException; + @WebProjectAction( + requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_EDIT + ) String createResource( @NotNull WebSession webSession, - @NotNull String projectId, + @NotNull @WebObjectId String projectId, @NotNull String resourcePath, boolean isFolder) throws DBException; + @WebProjectAction( + requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_EDIT + ) boolean deleteResource( @NotNull WebSession webSession, - @NotNull String projectId, + @NotNull @WebObjectId String projectId, @NotNull String resourcePath) throws DBException; @NotNull + @WebProjectAction( + requireProjectPermissions = RMConstants.PERMISSION_PROJECT_RESOURCE_EDIT + ) String writeResourceStringContent( @NotNull WebSession webSession, - @NotNull String projectId, + @NotNull @WebObjectId String projectId, @NotNull String resourcePath, @NotNull String data) throws DBException; + @WebAction(requirePermissions = {DBWConstants.PERMISSION_ADMIN, RMConstants.PERMISSION_RM_ADMIN}) WebProjectInfo createProject( @NotNull WebSession session, @NotNull String name, @Nullable String description) throws DBWebException; + @WebProjectAction(requireProjectPermissions = RMConstants.PERMISSION_RM_ADMIN) boolean deleteProject( @NotNull WebSession session, - @NotNull String projectId) throws DBWebException; + @NotNull @WebObjectId String projectId) throws DBWebException; } 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 6018b37989..80a21c8b88 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 @@ -105,7 +105,7 @@ public class WebServiceRM implements DBWServiceRM { ) throws DBWebException { try { RMProject rmProject = getResourceController(session).createProject(name, description); - DBPProject project = session.getApplication().createProjectImpl(rmProject, session.getSessionAuthContext(), session); + var project = session.getApplication().createProjectImpl(rmProject, session.getSessionAuthContext(), session); session.addSessionProject(project); return new WebProjectInfo(session, project); } catch (DBException e) {