diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index 6f6e820f5fe..496d7a050c9 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -291,10 +291,6 @@ org.eclipse.che.multiuser che-multiuser-machine-authentication - - org.eclipse.che.multiuser - che-multiuser-permission-devfile - org.eclipse.che.multiuser che-multiuser-permission-logger @@ -311,6 +307,10 @@ org.eclipse.che.multiuser che-multiuser-permission-user + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + org.eclipse.che.multiuser che-multiuser-permission-workspace diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 58c982fe893..d7b0af919ca 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -77,7 +77,6 @@ import org.eclipse.che.multiuser.api.permission.server.AdminPermissionInitializer; import org.eclipse.che.multiuser.api.permission.server.PermissionChecker; import org.eclipse.che.multiuser.api.permission.server.PermissionCheckerImpl; -import org.eclipse.che.multiuser.api.workspace.activity.MultiUserWorkspaceActivityModule; import org.eclipse.che.multiuser.keycloak.server.deploy.KeycloakModule; import org.eclipse.che.multiuser.machine.authentication.server.MachineAuthModule; import org.eclipse.che.multiuser.organization.api.OrganizationApiModule; @@ -345,8 +344,13 @@ private void configureMultiUserMode( install( new org.eclipse.che.multiuser.permission.workspace.server.jpa .MultiuserWorkspaceJpaModule()); - install(new org.eclipse.che.api.devfile.server.jpa.UserDevfileJpaModule()); - install(new MultiUserWorkspaceActivityModule()); + install( + new org.eclipse.che.multiuser.permission.devfile.server.jpa + .MultiuserUserDevfileJpaModule()); + install( + new org.eclipse.che.multiuser.permission.devfile.server.UserDevfileApiPermissionsModule()); + install( + new org.eclipse.che.multiuser.api.workspace.activity.MultiUserWorkspaceActivityModule()); // Permission filters bind(org.eclipse.che.multiuser.permission.system.SystemServicePermissionsFilter.class); @@ -361,7 +365,6 @@ private void configureMultiUserMode( bind(org.eclipse.che.multiuser.permission.user.UserServicePermissionsFilter.class); bind(org.eclipse.che.multiuser.permission.logger.LoggerServicePermissionsFilter.class); - bind(org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter.class); bind(org.eclipse.che.multiuser.permission.workspace.activity.ActivityPermissionsFilter.class); bind(AdminPermissionInitializer.class).asEagerSingleton(); bind( diff --git a/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml b/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml index ad62ad0180d..f4ae661ee33 100644 --- a/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml +++ b/assembly/assembly-wsmaster-war/src/main/resources/META-INF/persistence.xml @@ -58,12 +58,14 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl org.eclipse.che.api.ssh.server.model.impl.SshPairImpl org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml b/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml index 1a8ba41587c..37bd382118f 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/pom.xml @@ -83,6 +83,11 @@ che-core-api-core test + + org.eclipse.che.core + che-core-api-devfile + test + org.eclipse.che.core che-core-api-factory @@ -178,6 +183,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java index ec557918e92..7d24bd4bf83 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/JpaEntitiesCascadeRemovalTest.java @@ -24,6 +24,9 @@ import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createUser; import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createWorker; import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createWorkspace; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; import static org.eclipse.che.multiuser.resource.spi.jpa.JpaFreeResourcesLimitDao.RemoveFreeResourcesLimitSubscriber; import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; @@ -33,6 +36,7 @@ import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +import com.google.common.collect.ImmutableList; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; @@ -54,6 +58,8 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.factory.server.jpa.FactoryJpaModule; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.factory.server.spi.FactoryDao; @@ -106,6 +112,10 @@ import org.eclipse.che.multiuser.organization.spi.MemberDao; import org.eclipse.che.multiuser.organization.spi.impl.MemberImpl; import org.eclipse.che.multiuser.organization.spi.impl.OrganizationImpl; +import org.eclipse.che.multiuser.permission.devfile.server.jpa.MultiuserUserDevfileJpaModule; +import org.eclipse.che.multiuser.permission.devfile.server.listener.RemoveUserDevfileOnLastUserRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.workspace.server.jpa.MultiuserWorkspaceJpaModule; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.resource.api.AvailableResourcesProvider; @@ -143,6 +153,8 @@ public class JpaEntitiesCascadeRemovalTest { private SshDao sshDao; private FactoryDao factoryDao; private WorkerDao workerDao; + private UserDevfilePermissionDao userDevfilePermissionDao; + private UserDevfileDao userDevfileDao; private SignatureKeyDao signatureKeyDao; private FreeResourcesLimitDao freeResourcesLimitDao; private OrganizationManager organizationManager; @@ -195,6 +207,9 @@ public class JpaEntitiesCascadeRemovalTest { private FreeResourcesLimitImpl freeResourcesLimit2; + private UserDevfileImpl devfile; + private UserDevfilePermissionImpl devfilePermission; + private H2JpaCleaner h2JpaCleaner; @BeforeMethod @@ -221,6 +236,8 @@ protected void configure() { install(new MultiuserWorkspaceJpaModule()); install(new MachineAuthModule()); install(new DevfileModule()); + install(new MultiuserUserDevfileJpaModule()); + bind(ExecutorServiceWrapper.class).to(NoopExecutorServiceWrapper.class); bind(FreeResourcesLimitDao.class).to(JpaFreeResourcesLimitDao.class); @@ -305,6 +322,8 @@ protected void configure() { workspaceDao = injector.getInstance(WorkspaceDao.class); factoryDao = injector.getInstance(FactoryDao.class); workerDao = injector.getInstance(WorkerDao.class); + userDevfileDao = injector.getInstance(UserDevfileDao.class); + userDevfilePermissionDao = injector.getInstance(UserDevfilePermissionDao.class); signatureKeyDao = injector.getInstance(SignatureKeyDao.class); freeResourcesLimitDao = injector.getInstance(FreeResourcesLimitDao.class); organizationManager = injector.getInstance(OrganizationManager.class); @@ -338,6 +357,12 @@ public void shouldDeleteAllTheEntitiesWhenUserIsDeleted() throws Exception { // Check workers and parent entity is removed assertTrue(workspaceDao.getByNamespace(user2.getId(), 30, 0).isEmpty()); assertEquals(workerDao.getWorkers(workspace3.getId(), 1, 0).getTotalItemsCount(), 0); + assertNull( + notFoundToNull( + () -> + userDevfilePermissionDao.getUserDevfilePermission(devfile.getId(), user2.getId()))); + assertFalse(userDevfileDao.getById(devfile.getId()).isPresent()); + // Permissions are removed // Non-removed user permissions and stack are present // Check existence of organizations @@ -391,6 +416,11 @@ public void shouldRollbackTransactionWhenFailedToRemoveAnyOfEntries( assertNotNull(notFoundToNull(() -> organizationManager.getById(childOrganization.getId()))); assertNotNull(notFoundToNull(() -> organizationManager.getById(organization2.getId()))); assertNotNull(notFoundToNull(() -> signatureKeyDao.get(workspace2.getId()))); + assertTrue(userDevfileDao.getById(devfile.getId()).isPresent()); + assertNotNull( + notFoundToNull( + () -> + userDevfilePermissionDao.getUserDevfilePermission(devfile.getId(), user2.getId()))); assertFalse( organizationResourcesDistributor.getResourcesCaps(childOrganization.getId()).isEmpty()); wipeTestData(); @@ -399,7 +429,8 @@ public void shouldRollbackTransactionWhenFailedToRemoveAnyOfEntries( @DataProvider(name = "beforeRemoveRollbackActions") public Object[][] beforeRemoveActions() { return new Class[][] { - {RemoveOrganizationOnLastUserRemovedEventSubscriber.class, BeforeUserRemovedEvent.class} + {RemoveOrganizationOnLastUserRemovedEventSubscriber.class, BeforeUserRemovedEvent.class}, + {RemoveUserDevfileOnLastUserRemovedEventSubscriber.class, BeforeUserRemovedEvent.class} }; } @@ -453,6 +484,12 @@ private void createTestData() organizationResourcesDistributor.capResources( childOrganization.getId(), singletonList(new ResourceImpl(RamResourceType.ID, 1024, RamResourceType.UNIT))); + + userDevfileDao.create(devfile = TestObjectsFactory.createUserDevfile("id-dev1", "devfile1")); + userDevfilePermissionDao.store( + devfilePermission = + new UserDevfilePermissionImpl( + devfile.getId(), user2.getId(), ImmutableList.of(READ, DELETE, UPDATE))); } private void prepareCreator(String userId) { @@ -477,6 +514,9 @@ private void wipeTestData() throws ConflictException, ServerException, NotFoundE workerDao.removeWorker(workspace3.getId(), user2.getId()); + userDevfilePermissionDao.removeUserDevfilePermission(devfile.getId(), user2.getId()); + userDevfileDao.remove(devfile.getId()); + factoryDao.remove(factory1.getId()); factoryDao.remove(factory2.getId()); diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java index c2a2e6bb7a2..a9ad9743ac0 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/java/org/eclipse/che/multiuser/integration/jpa/cascaderemoval/TestObjectsFactory.java @@ -12,6 +12,8 @@ package org.eclipse.che.multiuser.integration.jpa.cascaderemoval; import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; import com.google.common.collect.ImmutableMap; import java.security.KeyPair; @@ -22,6 +24,7 @@ import java.util.Map; import org.eclipse.che.account.shared.model.Account; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; import org.eclipse.che.api.factory.server.model.impl.FactoryImpl; import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl; @@ -29,6 +32,16 @@ import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl; @@ -112,5 +125,139 @@ public static SignatureKeyPairImpl createSignatureKeyPair(String workspaceId) return new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate()); } + public static UserDevfileImpl createUserDevfile(String id, String name) { + return new UserDevfileImpl(id, createDevfile(name)); + } + + public static DevfileImpl createDevfile(String name) { + + SourceImpl source1 = + new SourceImpl( + "type1", + "http://location", + "branch1", + "point1", + "tag1", + "commit1", + "sparseCheckoutDir1"); + ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); + + SourceImpl source2 = + new SourceImpl( + "type2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"); + ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); + + ActionImpl action1 = + new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); + ActionImpl action2 = + new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); + + CommandImpl command1 = + new CommandImpl(name + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); + CommandImpl command2 = + new CommandImpl(name + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); + + EntrypointImpl entrypoint1 = + new EntrypointImpl( + "parentName1", + singletonMap("parent1", "selector1"), + "containerName1", + asList("command1", "command2"), + asList("arg1", "arg2")); + + EntrypointImpl entrypoint2 = + new EntrypointImpl( + "parentName2", + singletonMap("parent2", "selector2"), + "containerName2", + asList("command3", "command4"), + asList("arg3", "arg4")); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); + + EnvImpl env1 = new EnvImpl("name1", "value1"); + EnvImpl env2 = new EnvImpl("name2", "value2"); + + EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); + EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); + + ComponentImpl component1 = + new ComponentImpl( + "kubernetes", + "component1", + "eclipse/che-theia/0.0.1", + ImmutableMap.of("java.home", "/home/user/jdk11"), + "https://mysite.com/registry/somepath1", + "/dev.yaml", + "refcontent1", + ImmutableMap.of("app.kubernetes.io/component", "db"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "128M", + "2", + "130m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component1.setSelector(singletonMap("key1", "value1")); + + ComponentImpl component2 = + new ComponentImpl( + "kubernetes", + "component2", + "eclipse/che-theia/0.0.1", + ImmutableMap.of( + "java.home", + "/home/user/jdk11aertwertert", + "java.boolean", + true, + "java.long", + 123444L), + "https://mysite.com/registry/somepath2", + "/dev.yaml", + "refcontent2", + ImmutableMap.of("app.kubernetes.io/component", "webapp"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "256M", + "3", + "180m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component2.setSelector(singletonMap("key2", "value2")); + + DevfileImpl devfile = + new DevfileImpl( + "0.0.1", + asList(project1, project2), + asList(component1, component2), + asList(command1, command2), + singletonMap("attribute1", "value1"), + new MetadataImpl(name)); + + return devfile; + } + private TestObjectsFactory() {} } diff --git a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml index d76a333e66c..7ab69dc1434 100644 --- a/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-cascade-removal/src/test/resources/META-INF/persistence.xml @@ -74,6 +74,11 @@ org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl + + + true diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml b/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml index 3df4a9d5d71..17fd074c8a6 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/pom.xml @@ -47,6 +47,11 @@ che-multiuser-api-resource tests + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + tests + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -112,6 +117,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -165,12 +175,6 @@ org.apache.maven.plugins maven-dependency-plugin - - analyze - - true - - unpack-dependencies @@ -178,9 +182,10 @@ ${project.build.testOutputDirectory} che-multiuser-api-resource, - che-multiuser-api-organization, - che-multiuser-api-permission, - che-multiuser-permission-workspace + che-multiuser-api-organization, + che-multiuser-api-permission, + che-multiuser-permission-userdevfile, + che-multiuser-permission-workspace test @@ -192,7 +197,7 @@ che-core-sql-schema, - che-multiuser-sql-schema + che-multiuser-sql-schema che-schema/ ${project.build.directory} diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java index b9ed9f510d8..d4f8d46d54f 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/java/MultiuserMySqlTckModule.java @@ -38,6 +38,7 @@ import javax.persistence.NoResultException; import javax.persistence.spi.PersistenceUnitTransactionType; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.commons.test.tck.JpaCleaner; @@ -68,6 +69,11 @@ import org.eclipse.che.multiuser.organization.spi.jpa.JpaMemberDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDistributedResourcesDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaWorkerDao; @@ -159,6 +165,9 @@ protected void configure() { bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + // dao bind(OrganizationDao.class).to(JpaOrganizationDao.class); bind(OrganizationDistributedResourcesDao.class) @@ -171,6 +180,12 @@ protected void configure() { bind(new TypeLiteral>() {}).to(JpaMemberDao.class); bind(new TypeLiteral>() {}).to(OrganizationDomain.class); + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); diff --git a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml index b2867c60161..9ccbdb08e04 100644 --- a/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-mysql-tck/src/test/resources/META-INF/persistence.xml @@ -43,6 +43,8 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.api.workspace.server.model.impl.CommandImpl org.eclipse.che.workspace.infrastructure.docker.snapshot.MachineSourceImpl diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml b/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml index 556fd76a459..60efee4ac53 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/pom.xml @@ -47,6 +47,11 @@ che-multiuser-api-resource tests + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + tests + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -107,6 +112,11 @@ che-multiuser-machine-authentication test + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + test + org.eclipse.che.multiuser che-multiuser-permission-workspace @@ -174,6 +184,7 @@ che-multiuser-api-resource, che-multiuser-api-organization, che-multiuser-api-permission, + che-multiuser-permission-userdevfile, che-multiuser-permission-workspace test diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java index 4f52b65f885..9e1437037c0 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/java/MultiuserPostgresqlTckModule.java @@ -38,6 +38,7 @@ import javax.persistence.NoResultException; import javax.persistence.spi.PersistenceUnitTransactionType; import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.user.server.model.impl.UserImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; import org.eclipse.che.commons.test.tck.JpaCleaner; @@ -68,6 +69,11 @@ import org.eclipse.che.multiuser.organization.spi.jpa.JpaMemberDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDao; import org.eclipse.che.multiuser.organization.spi.jpa.JpaOrganizationDistributedResourcesDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl; import org.eclipse.che.multiuser.permission.workspace.server.spi.WorkerDao; import org.eclipse.che.multiuser.permission.workspace.server.spi.jpa.JpaWorkerDao; @@ -158,6 +164,9 @@ protected void configure() { bind(new TypeLiteral>() {}) .toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + // dao bind(OrganizationDao.class).to(JpaOrganizationDao.class); bind(OrganizationDistributedResourcesDao.class) @@ -170,6 +179,12 @@ protected void configure() { bind(new TypeLiteral>() {}).to(JpaMemberDao.class); bind(new TypeLiteral>() {}).to(OrganizationDomain.class); + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + // SHA-512 ecnryptor is faster than PBKDF2 so it is better for testing bind(PasswordEncryptor.class).to(SHA512PasswordEncryptor.class).in(Singleton.class); diff --git a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml index 12e02437e12..3df010aecc0 100644 --- a/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml +++ b/multiuser/integration-tests/che-multiuser-postgresql-tck/src/test/resources/META-INF/persistence.xml @@ -43,6 +43,8 @@ org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl org.eclipse.che.api.workspace.server.devfile.SerializableConverter + org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl + org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl org.eclipse.che.api.workspace.server.model.impl.CommandImpl org.eclipse.che.workspace.infrastructure.docker.snapshot.MachineSourceImpl diff --git a/multiuser/permission/che-multiuser-permission-devfile/pom.xml b/multiuser/permission/che-multiuser-permission-devfile/pom.xml deleted file mode 100644 index 015e21465bf..00000000000 --- a/multiuser/permission/che-multiuser-permission-devfile/pom.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - 4.0.0 - - che-multiuser-permission - org.eclipse.che.multiuser - 7.19.0-SNAPSHOT - - che-multiuser-permission-devfile - Che Multiuser :: Devfile Permissions - - - javax.ws.rs - javax.ws.rs-api - - - org.eclipse.che.core - che-core-api-core - - - org.eclipse.che.core - che-core-api-devfile - - - org.eclipse.che.core - che-core-commons-test - - - org.eclipse.che.multiuser - che-multiuser-api-permission - - - org.everrest - everrest-core - - - ch.qos.logback - logback-classic - test - - - com.jayway.restassured - rest-assured - test - - - org.eclipse.che.core - che-core-api-dto - test - - - org.eclipse.che.core - che-core-api-factory-shared - test - - - org.everrest - everrest-assured - test - - - org.mockito - mockito-core - test - - - org.mockito - mockito-testng - test - - - org.testng - testng - test - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - - analyze - - - org.eclipse.che.multiuser:che-multiuser-api-permission - - - - - - - - diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java b/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java deleted file mode 100644 index 788395565e4..00000000000 --- a/multiuser/permission/che-multiuser-permission-devfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/DevfilePermissionsFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.multiuser.permission.devfile; - -import javax.ws.rs.Path; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.devfile.server.DevfileService; -import org.eclipse.che.everrest.CheMethodInvokerFilter; -import org.everrest.core.Filter; -import org.everrest.core.resource.GenericResourceMethod; - -/** Restricts access to methods of {@link DevfileService} by user's permissions. */ -@Filter -@Path("/devfile{path:(/.*)?}") -public class DevfilePermissionsFilter extends CheMethodInvokerFilter { - - public static final String GET_SCHEMA_METHOD = "getSchema"; - - @Override - protected void filter(GenericResourceMethod genericResourceMethod, Object[] arguments) - throws ForbiddenException { - final String methodName = genericResourceMethod.getMethod().getName(); - switch (methodName) { - // public methods - case GET_SCHEMA_METHOD: - return; - default: - throw new ForbiddenException("The user does not have permission to perform this operation"); - } - } -} diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java deleted file mode 100644 index 267ad9ae09f..00000000000 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/java/org/eclipse/che/multiuser/permissions/devfile/DevfilePermissionsFilterTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.multiuser.permissions.devfile; - -import static com.jayway.restassured.RestAssured.given; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; -import static org.everrest.assured.JettyHttpServer.SECURE_PATH; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - -import com.jayway.restassured.response.Response; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.eclipse.che.api.devfile.server.DevfileService; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.multiuser.permission.devfile.DevfilePermissionsFilter; -import org.everrest.assured.EverrestJetty; -import org.everrest.core.Filter; -import org.everrest.core.GenericContainerRequest; -import org.everrest.core.RequestFilter; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) -public class DevfilePermissionsFilterTest { - - @SuppressWarnings("unused") - private static final EnvironmentFilter FILTER = new EnvironmentFilter(); - - @Mock private static Subject subject; - - @Mock private DevfileService service; - - @SuppressWarnings("unused") - @InjectMocks - private DevfilePermissionsFilter permissionsFilter; - - @Test - public void shouldTestThatAllPublicMethodsAreCoveredByPermissionsFilter() throws Exception { - // given - final List collect = - Stream.of(DevfileService.class.getDeclaredMethods()) - .filter(method -> Modifier.isPublic(method.getModifiers())) - .map(Method::getName) - .collect(Collectors.toList()); - - // then - assertEquals(collect.size(), 6); - assertTrue(collect.contains(DevfilePermissionsFilter.GET_SCHEMA_METHOD)); - } - - @Test - public void shouldNotCheckPermissionsOnExportingSchema() throws Exception { - final Response response = - given() - .auth() - .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) - .when() - .get(SECURE_PATH + "/devfile"); - - assertEquals(response.getStatusCode(), 204); - } - - @Filter - public static class EnvironmentFilter implements RequestFilter { - public void doFilter(GenericContainerRequest request) { - EnvironmentContext.getCurrent().setSubject(subject); - } - } -} diff --git a/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java index dbf64736e06..3c04ff38cad 100644 --- a/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java +++ b/multiuser/permission/che-multiuser-permission-factory/src/test/java/org/eclipse/che/multiuser/permission/factory/FactoryPermissionsFilterTest.java @@ -11,36 +11,15 @@ */ package org.eclipse.che.multiuser.permission.factory; -import static com.jayway.restassured.RestAssured.given; -import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.DOMAIN_ID; -import static org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain.READ; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; -import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; -import static org.everrest.assured.JettyHttpServer.SECURE_PATH; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; -import com.jayway.restassured.response.Response; import java.util.Map; -import javax.ws.rs.core.UriInfo; import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.factory.server.FactoryManager; import org.eclipse.che.api.factory.server.FactoryService; -import org.eclipse.che.api.factory.server.model.impl.AuthorImpl; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.dto.server.DtoFactory; import org.everrest.assured.EverrestJetty; import org.everrest.core.Filter; import org.everrest.core.GenericContainerRequest; @@ -85,7 +64,6 @@ public void shouldDoNothingWhenPublicMethodMethodIsCalled(String name, Class[] p @DataProvider(name = "publicMethods") public Object[][] publicMethods() { return new Object[][] { - {"resolveFactory", new Class[] {Map.class, Boolean.class}}, }; } diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/pom.xml b/multiuser/permission/che-multiuser-permission-userdevfile/pom.xml new file mode 100644 index 00000000000..5ae699ba1d2 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/pom.xml @@ -0,0 +1,226 @@ + + + + 4.0.0 + + che-multiuser-permission + org.eclipse.che.multiuser + 7.19.0-SNAPSHOT + + che-multiuser-permission-userdevfile + Che Multiuser :: UserDevfile Permissions + + + com.google.guava + guava + + + com.google.inject + guice + + + javax.annotation + javax.annotation-api + + + javax.inject + javax.inject + + + javax.ws.rs + javax.ws.rs-api + + + org.eclipse.che.core + che-core-api-account + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-devfile + + + org.eclipse.che.core + che-core-api-devfile-shared + + + org.eclipse.che.core + che-core-api-model + + + org.eclipse.che.core + che-core-api-user + + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-lang + + + org.eclipse.che.core + che-core-commons-test + + + org.everrest + everrest-core + + + org.slf4j + slf4j-api + + + com.google.inject.extensions + guice-persist + provided + + + org.eclipse.che.core + che-core-db + provided + + + org.eclipse.che.multiuser + che-multiuser-api-permission + provided + + + org.eclipse.che.multiuser + che-multiuser-api-permission-shared + provided + + + org.eclipse.persistence + javax.persistence + provided + + + aopalliance + aopalliance + test + + + ch.qos.logback + logback-classic + test + + + com.h2database + h2 + test + + + com.jayway.restassured + rest-assured + test + + + org.eclipse.che.core + che-core-api-dto + test + + + org.eclipse.che.core + che-core-commons-inject + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.eclipse.che.core + che-core-db-vendor-h2 + test + + + org.eclipse.che.core + che-core-sql-schema + test + + + org.eclipse.che.multiuser + che-multiuser-sql-schema + test + + + org.eclipse.persistence + org.eclipse.persistence.core + test + + + org.eclipse.persistence + org.eclipse.persistence.jpa + test + + + org.everrest + everrest-assured + test + + + org.flywaydb + flyway-core + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + test + + + org.testng + testng + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/spi/tck/*.* + **/TestObjectGenerator.* + + + + + + + + diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java new file mode 100644 index 00000000000..a6d7f8ddf36 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileApiPermissionsModule.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import com.google.inject.AbstractModule; +import com.google.inject.multibindings.Multibinder; +import com.google.inject.name.Names; +import org.eclipse.che.multiuser.api.permission.server.SuperPrivilegesChecker; +import org.eclipse.che.multiuser.api.permission.shared.model.PermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.filters.UserDevfilePermissionsFilter; + +public class UserDevfileApiPermissionsModule extends AbstractModule { + + @Override + protected void configure() { + bind(UserDevfilePermissionsFilter.class); + bind(UserDevfileCreatorPermissionsProvider.class).asEagerSingleton(); + + Multibinder.newSetBinder( + binder(), + PermissionsDomain.class, + Names.named(SuperPrivilegesChecker.SUPER_PRIVILEGED_DOMAINS)) + .addBinding() + .to(UserDevfileDomain.class); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java new file mode 100644 index 00000000000..a9eea681b53 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileCreatorPermissionsProvider.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import java.util.ArrayList; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.core.notification.EventSubscriber; +import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Adds permissions for creator after user devfile creation */ +@Singleton +public class UserDevfileCreatorPermissionsProvider implements EventSubscriber { + private static final Logger LOG = + LoggerFactory.getLogger(UserDevfileCreatorPermissionsProvider.class); + + private final UserDevfilePermissionDao userDevfilePermissionDao; + private final EventService eventService; + + @Inject + public UserDevfileCreatorPermissionsProvider( + EventService eventService, UserDevfilePermissionDao userDevfilePermissionDao) { + this.userDevfilePermissionDao = userDevfilePermissionDao; + this.eventService = eventService; + } + + @PostConstruct + void subscribe() { + eventService.subscribe(this); + } + + @PreDestroy + void unsubscribe() { + eventService.subscribe(this); + } + + @Override + public void onEvent(DevfileCreatedEvent event) { + try { + userDevfilePermissionDao.store( + new UserDevfilePermissionImpl( + event.getUserDevfile().getId(), + EnvironmentContext.getCurrent().getSubject().getUserId(), + new ArrayList<>(new UserDevfileDomain().getAllowedActions()))); + } catch (ServerException e) { + LOG.error( + "Can't add creator's permissions for user devfile with id '" + + event.getUserDevfile().getId() + + "'", + e); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java new file mode 100644 index 00000000000..5276ead6a21 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/UserDevfileDomain.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; + +/** Domain for storing devfile's permissions */ +public class UserDevfileDomain extends AbstractPermissionsDomain { + public static final String READ = "read"; + public static final String DELETE = "delete"; + public static final String UPDATE = "update"; + public static final String DOMAIN_ID = "userDevfile"; + + public UserDevfileDomain() { + super(DOMAIN_ID, ImmutableList.of(READ, DELETE, UPDATE)); + } + + @Override + public UserDevfilePermissionImpl doCreateInstance( + String userId, String instanceId, List allowedActions) { + return new UserDevfilePermissionImpl(instanceId, userId, allowedActions); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java new file mode 100644 index 00000000000..4eed25619e5 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilter.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.filters; + +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DOMAIN_ID; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; + +import com.google.common.annotations.VisibleForTesting; +import javax.inject.Inject; +import javax.ws.rs.Path; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.devfile.server.DevfileService; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.everrest.CheMethodInvokerFilter; +import org.everrest.core.Filter; +import org.everrest.core.resource.GenericResourceMethod; + +/** + * Restricts access to methods of {@link DevfileService} by users' permissions. + * + *

Filter contains rules for protecting of all methods of {@link DevfileService}.
+ * In case when requested method is unknown filter throws {@link ForbiddenException} + */ +@Filter +@Path("/devfile{path:(/.*)?}") +public class UserDevfilePermissionsFilter extends CheMethodInvokerFilter { + private final UserDevfileManager userDevfileManager; + + @Inject + public UserDevfilePermissionsFilter(UserDevfileManager userDevfileManager) { + this.userDevfileManager = userDevfileManager; + } + + @Override + public void filter(GenericResourceMethod genericResourceMethod, Object[] arguments) + throws ForbiddenException { + final String methodName = genericResourceMethod.getMethod().getName(); + switch (methodName) { + case "getById": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), READ); + break; + case "update": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), UPDATE); + break; + case "delete": + doCheckPermission(DOMAIN_ID, ((String) arguments[0]), DELETE); + break; + case "create": + case "getUserDevfiles": + case "getSchema": + return; + default: + throw new ForbiddenException("The user does not have permission to perform this operation"); + } + } + + @VisibleForTesting + void doCheckPermission(String domain, String instance, String action) throws ForbiddenException { + EnvironmentContext.getCurrent().getSubject().checkPermission(domain, instance, action); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java new file mode 100644 index 00000000000..a7e882d662f --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserUserDevfileJpaModule.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import com.google.inject.AbstractModule; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; +import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.listener.RemoveUserDevfileOnLastUserRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.MultiuserJpaUserDevfileDao; + +public class MultiuserUserDevfileJpaModule extends AbstractModule { + + @Override + protected void configure() { + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(UserDevfileDao.class).to(MultiuserJpaUserDevfileDao.class); + bind(JpaUserDevfilePermissionDao + .RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class) + .asEagerSingleton(); + + bind(RemoveUserDevfileOnLastUserRemovedEventSubscriber.class).asEagerSingleton(); + + bind(new TypeLiteral>() {}) + .to(UserDevfileDomain.class); + + Multibinder> daos = + Multibinder.newSetBinder( + binder(), new TypeLiteral>() {}); + daos.addBinding().to(JpaUserDevfilePermissionDao.class); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/listener/RemoveUserDevfileOnLastUserRemovedEventSubscriber.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/listener/RemoveUserDevfileOnLastUserRemovedEventSubscriber.java new file mode 100644 index 00000000000..81fa0e841ef --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/listener/RemoveUserDevfileOnLastUserRemovedEventSubscriber.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.listener; + +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.multiuser.api.permission.server.jpa.listener.RemovePermissionsOnLastUserRemovedEventSubscriber; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; + +/** + * Listens for {@link UserImpl} removal events, and checks if the removing user is the last who has + * "setPermissions" permission to particular user devfile, and if it is, then removes devfile + * itself. + */ +@Singleton +public class RemoveUserDevfileOnLastUserRemovedEventSubscriber + extends RemovePermissionsOnLastUserRemovedEventSubscriber { + + @Inject private UserDevfileManager userDevfileManager; + + @Override + public void remove(String instanceId) throws ServerException { + userDevfileManager.removeUserDevfile(instanceId); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java new file mode 100644 index 00000000000..97fdf87cb16 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/UserDevfilePermission.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.model; + +import java.util.List; + +public interface UserDevfilePermission { + /** Returns user id */ + String getUserId(); + + /** Returns user devfile id */ + String getUserDevfileId(); + + /** Returns list of user devfile actions which can be performed by current user */ + List getActions(); +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java new file mode 100644 index 00000000000..f5c7166602e --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/model/impl/UserDevfilePermissionImpl.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.model.impl; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; +import javax.persistence.Table; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; + +/** Data object for {@link UserDevfilePermission} */ +@Entity(name = "UserDevfilePermission") +@NamedQueries({ + @NamedQuery( + name = "UserDevfilePermission.getByUserDevfileId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userDevfileId = :userDevfileId "), + @NamedQuery( + name = "UserDevfilePermission.getCountByUserDevfileId", + query = + "SELECT COUNT(permission) " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userDevfileId = :userDevfileId "), + @NamedQuery( + name = "UserDevfilePermission.getByUserId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userId = :userId "), + @NamedQuery( + name = "UserDevfilePermission.getByUserAndUserDevfileId", + query = + "SELECT permission " + + "FROM UserDevfilePermission permission " + + "WHERE permission.userId = :userId " + + "AND permission.userDevfileId = :userDevfileId ", + hints = {@QueryHint(name = "eclipselink.query-results-cache", value = "true")}) +}) +@Table(name = "che_userdevfile_permissions") +public class UserDevfilePermissionImpl extends AbstractPermissions + implements UserDevfilePermission { + + @Column(name = "userdevfile_id") + private String userDevfileId; + + @ManyToOne + @JoinColumn(name = "userdevfile_id", insertable = false, updatable = false) + private UserDevfileImpl userDevfile; + + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "actions") + @CollectionTable( + name = "che_userdevfile_permissions_actions", + joinColumns = @JoinColumn(name = "userdevfile_permissions_id")) + protected List actions; + + public UserDevfilePermissionImpl() {} + + public UserDevfilePermissionImpl(String userDevfileId, String userId, List actions) { + super(userId); + this.userDevfileId = userDevfileId; + if (actions != null) { + this.actions = new ArrayList<>(actions); + } + } + + public UserDevfilePermissionImpl(UserDevfilePermission userDevfilePermission) { + this( + userDevfilePermission.getUserDevfileId(), + userDevfilePermission.getUserId(), + userDevfilePermission.getActions()); + } + + @Override + public String getInstanceId() { + return userDevfileId; + } + + @Override + public String getDomainId() { + return UserDevfileDomain.DOMAIN_ID; + } + + @Override + public List getActions() { + return actions; + } + + @Override + public String getUserDevfileId() { + return userDevfileId; + } + + @Override + public String toString() { + return "UserDevfilePermissionImpl{" + + "userDevfileId='" + + userDevfileId + + '\'' + + ", userDevfile=" + + userDevfile + + ", actions=" + + actions + + "} " + + super.toString(); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java new file mode 100644 index 00000000000..402eae79e55 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/UserDevfilePermissionDao.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi; + +import java.util.List; +import java.util.Optional; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; + +/** Defines data access object contract for {@link UserDevfilePermissionImpl}. */ +public interface UserDevfilePermissionDao { + + /** + * Stores (adds or updates) UserDevfilePermissions. + * + * @param userDevfilePermissions userDevfilePermissions to store + * @return optional with updated userDevfilePermissions, other way empty optional must be returned + * @throws NullPointerException when {@code userDevfilePermissions} is null + * @throws ServerException when any other error occurs during userDevfilePermissions storing + */ + Optional store(UserDevfilePermissionImpl userDevfilePermissions) + throws ServerException; + + /** + * Gets userDevfilePermissions by user and userDevfileId + * + * @param userDevfileId user devfile identifier + * @param userId user identifier + * @return userDevfilePermissions instance, never null + * @throws NullPointerException when {@code workspace} or {@code user} is null + * @throws NotFoundException when worker with given {@code workspace} and {@code user} was not + * found + * @throws ServerException when any other error occurs during worker fetching + */ + UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String userId) + throws ServerException, NotFoundException; + + /** + * Removes userDevfilePermissions + * + *

Doesn't throw an exception when userDevfilePermissions with given {@code UserDevfile} and + * {@code user} does not exist + * + * @param userDevfileId workspace identifier + * @param userId user identifier + * @throws NullPointerException when {@code UserDevfile} or {@code user} is null + * @throws ServerException when any other error occurs during userDevfilePermissions removing + */ + void removeUserDevfilePermission(String userDevfileId, String userId) throws ServerException; + + /** + * Gets userDevfilePermissions by user devfile id. + * + * @param userDevfileId user devfile identifier + * @param maxItems the maximum number of userDevfilePermissions to return + * @param skipCount the number of userDevfilePermissions to skip + * @return list of userDevfilePermissions instance + * @throws NullPointerException when {@code userDevfile} is null + * @throws ServerException when any other error occurs during userDevfilePermissions fetching + */ + Page getUserDevfilePermission( + String userDevfileId, int maxItems, long skipCount) throws ServerException; + + /** + * Gets UserDevfilePermissions by user + * + * @param userId user identifier + * @return list of UserDevfilePermissions instance + * @throws NullPointerException when {@code user} is null + * @throws ServerException when any other error occurs during UserDevfilePermissions fetching + */ + List getUserDevfilePermissionByUser(String userId) + throws ServerException; +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java new file mode 100644 index 00000000000..67668c550bb --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDao.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; + +import com.google.common.annotations.VisibleForTesting; +import com.google.inject.persist.Transactional; +import java.util.List; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.core.db.cascade.CascadeEventSubscriber; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.api.permission.server.jpa.AbstractJpaPermissionsDao; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; + +public class JpaUserDevfilePermissionDao + extends AbstractJpaPermissionsDao + implements UserDevfilePermissionDao { + + @Inject + public JpaUserDevfilePermissionDao( + AbstractPermissionsDomain supportedDomain) { + super(supportedDomain); + } + + @Override + public UserDevfilePermissionImpl get(String userId, String instanceId) + throws ServerException, NotFoundException { + + requireNonNull(instanceId, "User devfile identifier required"); + requireNonNull(userId, "User identifier required"); + try { + return new UserDevfilePermissionImpl(getEntity(wildcardToNull(userId), instanceId)); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + + @Override + public List getByUser(String userId) throws ServerException { + requireNonNull(userId, "User identifier required"); + return doGetByUser(wildcardToNull(userId)) + .stream() + .map(UserDevfilePermissionImpl::new) + .collect(toList()); + } + + @Override + @Transactional + public Page getByInstance( + String instanceId, int maxItems, long skipCount) throws ServerException { + requireNonNull(instanceId, "User devfile identifier required"); + checkArgument( + skipCount <= Integer.MAX_VALUE, + "The number of items to skip can't be greater than " + Integer.MAX_VALUE); + + try { + final EntityManager entityManager = managerProvider.get(); + final List permissions = + entityManager + .createNamedQuery( + "UserDevfilePermission.getByUserDevfileId", UserDevfilePermissionImpl.class) + .setParameter("userDevfileId", instanceId) + .setMaxResults(maxItems) + .setFirstResult((int) skipCount) + .getResultList() + .stream() + .map(UserDevfilePermissionImpl::new) + .collect(toList()); + final Long workersCount = + entityManager + .createNamedQuery("UserDevfilePermission.getCountByUserDevfileId", Long.class) + .setParameter("userDevfileId", instanceId) + .getSingleResult(); + return new Page<>(permissions, skipCount, maxItems, workersCount); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Override + protected UserDevfilePermissionImpl getEntity(String userId, String instanceId) + throws NotFoundException, ServerException { + try { + return doGet(userId, instanceId); + } catch (NoResultException e) { + throw new NotFoundException( + format( + "User devfile permission for devfile '%s' with id '%s' was not found.", + instanceId, userId)); + } catch (RuntimeException e) { + throw new ServerException(e.getMessage(), e); + } + } + + @Override + public UserDevfilePermissionImpl getUserDevfilePermission(String userDevfileId, String userId) + throws ServerException, NotFoundException { + return new UserDevfilePermissionImpl(get(userId, userDevfileId)); + } + + @Override + public void removeUserDevfilePermission(String userDevfileId, String userId) + throws ServerException { + try { + super.remove(userId, userDevfileId); + } catch (NotFoundException e) { + throw new ServerException(e); + } + } + + @Override + public Page getUserDevfilePermission( + String userDevfileId, int maxItems, long skipCount) throws ServerException { + return getByInstance(userDevfileId, maxItems, skipCount); + } + + @Override + public List getUserDevfilePermissionByUser(String userId) + throws ServerException { + return getByUser(userId); + } + + @Transactional + protected UserDevfilePermissionImpl doGet(String userId, String instanceId) { + return managerProvider + .get() + .createNamedQuery( + "UserDevfilePermission.getByUserAndUserDevfileId", UserDevfilePermissionImpl.class) + .setParameter("userDevfileId", instanceId) + .setParameter("userId", userId) + .getSingleResult(); + } + + @Transactional + protected List doGetByUser(@Nullable String userId) + throws ServerException { + try { + return managerProvider + .get() + .createNamedQuery("UserDevfilePermission.getByUserId", UserDevfilePermissionImpl.class) + .setParameter("userId", userId) + .getResultList(); + } catch (RuntimeException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + + @Singleton + public static class RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber + extends CascadeEventSubscriber { + private static final int PAGE_SIZE = 100; + + @Inject private EventService eventService; + @Inject private UserDevfilePermissionDao userDevfilePermissionDao; + + @PostConstruct + public void subscribe() { + eventService.subscribe(this, BeforeDevfileRemovedEvent.class); + } + + @PreDestroy + public void unsubscribe() { + eventService.unsubscribe(this, BeforeDevfileRemovedEvent.class); + } + + @Override + public void onCascadeEvent(BeforeDevfileRemovedEvent event) throws Exception { + removeUserDevfilePermissions(event.getUserDevfile().getId(), PAGE_SIZE); + } + + @VisibleForTesting + void removeUserDevfilePermissions(String userDevfileId, int pageSize) throws ServerException { + Page permissionsPage; + do { + // skip count always equals to 0 because elements will be shifted after removing previous + // items + permissionsPage = + userDevfilePermissionDao.getUserDevfilePermission(userDevfileId, pageSize, 0); + for (UserDevfilePermissionImpl permission : permissionsPage.getItems()) { + userDevfilePermissionDao.removeUserDevfilePermission( + permission.getInstanceId(), permission.getUserId()); + } + } while (permissionsPage.hasNextPage()); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java new file mode 100644 index 00000000000..7c113ee0a5c --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/main/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/MultiuserJpaUserDevfileDao.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.List; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.subject.Subject; + +/** JPA based implementation of {@link UserDevfileDao}. */ +@Singleton +public class MultiuserJpaUserDevfileDao extends JpaUserDevfileDao { + + @Override + public Page getDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order) + throws ServerException { + checkArgument(maxItems > 0, "The number of items has to be positive."); + checkArgument( + skipCount >= 0, + "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); + + final Subject subject = EnvironmentContext.getCurrent().getSubject(); + if (subject.isAnonymous()) { + throw new ServerException("Unexpected state. Current user is not set."); + } + + return doGetDevfiles( + maxItems, + skipCount, + filter, + order, + () -> + MultiuserUserDevfileSearchQueryBuilder.newBuilder(managerProvider.get()) + .withUserId(subject.getUserId())); + } + + public static class MultiuserUserDevfileSearchQueryBuilder + extends JpaUserDevfileDao.UserDevfileSearchQueryBuilder { + + MultiuserUserDevfileSearchQueryBuilder(EntityManager entityManager) { + super(entityManager); + } + + public MultiuserUserDevfileSearchQueryBuilder withUserId(String userId) { + params.put("userId", userId); + return this; + } + + public static MultiuserUserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { + return new MultiuserUserDevfileSearchQueryBuilder(entityManager); + } + + @Override + public JpaUserDevfileDao.UserDevfileSearchQueryBuilder withFilter( + List> filter) { + super.withFilter(filter); + if (this.filter.isEmpty()) { + this.filter = "WHERE permission.userId = :userId AND 'read' MEMBER OF permission.actions"; + } else { + this.filter += " AND permission.userId = :userId AND 'read' MEMBER OF permission.actions"; + } + return this; + } + + @Override + public TypedQuery buildCountQuery() { + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" COUNT(userdevfile) ") + .append("FROM UserDevfilePermission permission ") + .append("LEFT JOIN permission.userDevfile userdevfile ") + .append(filter); + TypedQuery typedQuery = entityManager.createQuery(query.toString(), Long.class); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + + @Override + public TypedQuery buildSelectItemsQuery() { + StringBuilder query = + new StringBuilder() + .append("SELECT ") + .append(" userdevfile ") + .append("FROM UserDevfilePermission permission ") + .append("LEFT JOIN permission.userDevfile userdevfile ") + .append(filter) + .append(order); + TypedQuery typedQuery = + entityManager + .createQuery(query.toString(), UserDevfileImpl.class) + .setFirstResult(skipCount) + .setMaxResults(maxItems); + params.forEach((k, v) -> typedQuery.setParameter(k, v)); + return typedQuery; + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java new file mode 100644 index 00000000000..4b21dd84913 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/TestObjectGenerator.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; + +import com.google.common.collect.ImmutableMap; +import org.eclipse.che.api.devfile.server.DtoConverter; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.lang.NameGenerator; + +public class TestObjectGenerator { + + public static UserDevfileDto createUserDevfileDto() { + return DtoConverter.asDto(createUserDevfile(NameGenerator.generate("name", 6))); + } + + public static UserDevfileImpl createUserDevfile() { + return createUserDevfile(NameGenerator.generate("name", 6)); + } + + public static UserDevfileImpl createUserDevfile(String name) { + return createUserDevfile(NameGenerator.generate("id", 6), name); + } + + public static UserDevfileImpl createUserDevfile(String id, String name) { + return new UserDevfileImpl(id, createDevfile(name)); + } + + public static DevfileImpl createDevfile(String name) { + + SourceImpl source1 = + new SourceImpl( + "type1", + "http://location", + "branch1", + "point1", + "tag1", + "commit1", + "sparseCheckoutDir1"); + ProjectImpl project1 = new ProjectImpl("project1", source1, "path1"); + + SourceImpl source2 = + new SourceImpl( + "type2", + "http://location", + "branch2", + "point2", + "tag2", + "commit2", + "sparseCheckoutDir2"); + ProjectImpl project2 = new ProjectImpl("project2", source2, "path2"); + + ActionImpl action1 = + new ActionImpl("exec1", "component1", "run.sh", "/home/user/1", null, null); + ActionImpl action2 = + new ActionImpl("exec2", "component2", "run.sh", "/home/user/2", null, null); + + CommandImpl command1 = + new CommandImpl(name + "-1", singletonList(action1), singletonMap("attr1", "value1"), null); + CommandImpl command2 = + new CommandImpl(name + "-2", singletonList(action2), singletonMap("attr2", "value2"), null); + + EntrypointImpl entrypoint1 = + new EntrypointImpl( + "parentName1", + singletonMap("parent1", "selector1"), + "containerName1", + asList("command1", "command2"), + asList("arg1", "arg2")); + + EntrypointImpl entrypoint2 = + new EntrypointImpl( + "parentName2", + singletonMap("parent2", "selector2"), + "containerName2", + asList("command3", "command4"), + asList("arg3", "arg4")); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume1 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name1", "path1"); + + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl volume2 = + new org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl("name2", "path2"); + + EnvImpl env1 = new EnvImpl("name1", "value1"); + EnvImpl env2 = new EnvImpl("name2", "value2"); + + EndpointImpl endpoint1 = new EndpointImpl("name1", 1111, singletonMap("key1", "value1")); + EndpointImpl endpoint2 = new EndpointImpl("name2", 2222, singletonMap("key2", "value2")); + + ComponentImpl component1 = + new ComponentImpl( + "kubernetes", + "component1", + "eclipse/che-theia/0.0.1", + ImmutableMap.of("java.home", "/home/user/jdk11"), + "https://mysite.com/registry/somepath1", + "/dev.yaml", + "refcontent1", + ImmutableMap.of("app.kubernetes.io/component", "db"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "128M", + "2", + "130m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component1.setSelector(singletonMap("key1", "value1")); + + ComponentImpl component2 = + new ComponentImpl( + "kubernetes", + "component2", + "eclipse/che-theia/0.0.1", + ImmutableMap.of( + "java.home", + "/home/user/jdk11aertwertert", + "java.boolean", + true, + "java.long", + 123444L), + "https://mysite.com/registry/somepath2", + "/dev.yaml", + "refcontent2", + ImmutableMap.of("app.kubernetes.io/component", "webapp"), + asList(entrypoint1, entrypoint2), + "image", + "256G", + "256M", + "3", + "180m", + false, + false, + singletonList("command"), + singletonList("arg"), + asList(volume1, volume2), + asList(env1, env2), + asList(endpoint1, endpoint2)); + component2.setSelector(singletonMap("key2", "value2")); + + DevfileImpl devfile = + new DevfileImpl( + "0.0.1", + asList(project1, project2), + asList(component1, component2), + asList(command1, command2), + singletonMap("attribute1", "value1"), + new MetadataImpl(name)); + + return devfile; + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java new file mode 100644 index 00000000000..bb6a0b2b636 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/filters/UserDevfilePermissionsFilterTest.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.filters; + +import static com.jayway.restassured.RestAssured.given; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; +import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; +import static org.everrest.assured.JettyHttpServer.SECURE_PATH; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.testng.Assert.assertEquals; + +import com.jayway.restassured.response.Response; +import java.util.Collections; +import java.util.HashSet; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.rest.ApiExceptionMapper; +import org.eclipse.che.api.core.rest.CheJsonProvider; +import org.eclipse.che.api.devfile.server.DevfileService; +import org.eclipse.che.api.devfile.server.UserDevfileManager; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.devfile.shared.dto.UserDevfileDto; +import org.eclipse.che.api.workspace.server.devfile.DevfileEntityProvider; +import org.eclipse.che.api.workspace.server.devfile.DevfileParser; +import org.eclipse.che.api.workspace.server.devfile.schema.DevfileSchemaProvider; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; +import org.eclipse.che.api.workspace.server.devfile.validator.DevfileSchemaValidator; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.everrest.assured.EverrestJetty; +import org.everrest.core.Filter; +import org.everrest.core.GenericContainerRequest; +import org.everrest.core.RequestFilter; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** Tests for {@link UserDevfilePermissionsFilter}. */ +@Listeners(value = {EverrestJetty.class, MockitoTestNGListener.class}) +public class UserDevfilePermissionsFilterTest { + private static final String USERNAME = "userok"; + + ApiExceptionMapper mapper; + + CheJsonProvider jsonProvider = new CheJsonProvider(new HashSet<>()); + private DevfileEntityProvider devfileEntityProvider = + new DevfileEntityProvider( + new DevfileParser( + new DevfileSchemaValidator(new DevfileSchemaProvider()), + new DevfileIntegrityValidator(Collections.emptyMap()))); + + @SuppressWarnings("unused") + private static final EnvironmentFilter FILTER = new EnvironmentFilter(); + + @Mock private static Subject subject; + + @Mock private UserDevfileManager userDevfileManager; + + private UserDevfilePermissionsFilter permissionsFilter; + + @Mock private DevfileService devfileService; + private UserDevfileDto userDevfileDto = TestObjectGenerator.createUserDevfileDto(); + private UserDevfileImpl userDevfile = new UserDevfileImpl(userDevfileDto); + // + @BeforeMethod + public void setUp() throws Exception { + lenient().when(subject.getUserName()).thenReturn(USERNAME); + lenient().when(userDevfileManager.getById(any())).thenReturn(userDevfile); + + permissionsFilter = spy(new UserDevfilePermissionsFilter(userDevfileManager)); + + lenient() + .doThrow(new ForbiddenException("")) + .when(subject) + .checkPermission(anyString(), anyString(), anyString()); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileCreate() { + // given + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .post(SECURE_PATH + "/devfile/"); + // then + assertEquals(response.getStatusCode(), 204); + verifyZeroInteractions(subject); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileSearch() + throws BadRequestException, ForbiddenException, NotFoundException, ServerException { + // given + Mockito.when(devfileService.getUserDevfiles(any(), any(), any())) + .thenReturn(javax.ws.rs.core.Response.ok().build()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile/list"); + // then + assertEquals(response.getStatusCode(), 200); + verifyZeroInteractions(subject); + } + + @Test + public void shouldNotCheckAnyPermissionOnDevfileSchema() + throws BadRequestException, ForbiddenException, NotFoundException, ServerException { + // given + Mockito.when(devfileService.getSchema()).thenReturn(javax.ws.rs.core.Response.ok().build()); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile"); + // then + assertEquals(response.getStatusCode(), 200); + verifyZeroInteractions(subject); + } + + @Test + public void shouldCheckReadPermissionsOnFetchingUserDevfileById() throws Exception { + // given + Mockito.when(devfileService.getById(eq(userDevfileDto.getId()))).thenReturn(userDevfileDto); + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .when() + .get(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 200); + verify(devfileService).getById(eq(userDevfileDto.getId())); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileReadByID() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .get(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.READ)); + } + + @Test + public void shouldChecksPermissionDevfileUpdate() throws ForbiddenException { + // given + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 204); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileUpdate() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .contentType("application/json") + .body(userDevfileDto) + .when() + .put(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.UPDATE)); + } + + @Test + public void shouldChecksPermissionDevfileDelete() throws ForbiddenException { + // given + doNothing() + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + // then + assertEquals(response.getStatusCode(), 204); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + } + + @Test + public void shouldBeAbleToFailOnCheckPermissionDevfileDelete() throws ForbiddenException { + // given + doThrow(new ForbiddenException("forbidden")) + .when(subject) + .checkPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + // when + final Response response = + given() + .auth() + .basic(ADMIN_USER_NAME, ADMIN_USER_PASSWORD) + .when() + .delete(SECURE_PATH + "/devfile/" + userDevfileDto.getId()); + + // then + assertEquals(response.getStatusCode(), 403); + verify(permissionsFilter) + .doCheckPermission( + eq(UserDevfileDomain.DOMAIN_ID), + eq(userDevfileDto.getId()), + eq(UserDevfileDomain.DELETE)); + } + + @Filter + public static class EnvironmentFilter implements RequestFilter { + public void doFilter(GenericContainerRequest request) { + EnvironmentContext.getCurrent().setSubject(subject); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java new file mode 100644 index 00000000000..79c32d646e0 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/MultiuserJpaUserDevfileDaoTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createDevfile; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.persistence.EntityManager; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.env.EnvironmentContext; +import org.eclipse.che.commons.subject.SubjectImpl; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.MultiuserJpaUserDevfileDao; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class MultiuserJpaUserDevfileDaoTest { + private TckResourcesCleaner tckResourcesCleaner; + private EntityManager manager; + private MultiuserJpaUserDevfileDao dao; + + private List permissions; + private List users; + private List userDevfiles; + + @BeforeClass + public void setupEntities() throws Exception { + permissions = + ImmutableList.of( + new UserDevfilePermissionImpl( + "devfile_id1", "user1", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id2", "user1", Arrays.asList(READ, UPDATE)), + new UserDevfilePermissionImpl("devfile_id3", "user1", Arrays.asList(DELETE, UPDATE)), + new UserDevfilePermissionImpl( + "devfile_id1", "user2", Arrays.asList(READ, DELETE, UPDATE))); + + users = + ImmutableList.of( + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2")); + + userDevfiles = + ImmutableList.of( + new UserDevfileImpl("devfile_id1", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id2", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id3", createDevfile(generate("name", 6)))); + Injector injector = Guice.createInjector(new UserDevfileTckModule()); + manager = injector.getInstance(EntityManager.class); + dao = injector.getInstance(MultiuserJpaUserDevfileDao.class); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @BeforeMethod + public void setUp() throws Exception { + manager.getTransaction().begin(); + + users.stream().map(UserImpl::new).forEach(manager::persist); + userDevfiles.stream().map(UserDevfileImpl::new).forEach(manager::persist); + permissions.stream().map(UserDevfilePermissionImpl::new).forEach(manager::persist); + + manager.getTransaction().commit(); + manager.clear(); + } + + @AfterMethod + public void cleanup() { + manager.getTransaction().begin(); + + manager + .createQuery("SELECT e FROM UserDevfilePermission e", UserDevfilePermissionImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT w FROM UserDevfile w", UserDevfileImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT u FROM Usr u", UserImpl.class) + .getResultList() + .forEach(manager::remove); + + manager.getTransaction().commit(); + } + + @Test + public void shouldGetTotalWorkspaceCount() throws ServerException { + assertEquals(dao.getTotalCount(), 3); + } + + @AfterClass + public void shutdown() throws Exception { + tckResourcesCleaner.clean(); + EnvironmentContext.reset(); + } + + @Test + public void shouldFindDevfilesByByPermissions() throws Exception { + EnvironmentContext expected = EnvironmentContext.getCurrent(); + expected.setSubject(new SubjectImpl("user", users.get(0).getId(), "token", false)); + List results = + dao.getDevfiles(30, 0, Collections.emptyList(), Collections.emptyList()).getItems(); + assertEquals(results.size(), 2); + assertTrue(results.contains(userDevfiles.get(0))); + assertTrue(results.contains(userDevfiles.get(1))); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java new file mode 100644 index 00000000000..3690d730c38 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/jpa/UserDevfileTckModule.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.jpa; + +import com.google.inject.TypeLiteral; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; +import org.eclipse.che.api.workspace.server.jpa.JpaWorkspaceDao; +import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; +import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; +import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.api.workspace.server.spi.WorkspaceDao; +import org.eclipse.che.commons.test.db.H2DBTestServer; +import org.eclipse.che.commons.test.db.H2JpaCleaner; +import org.eclipse.che.commons.test.db.PersistTestModuleBuilder; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.core.db.DBInitializer; +import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler; +import org.eclipse.che.core.db.schema.SchemaInitializer; +import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.tck.UserDevfilePermissionDaoTest; +import org.h2.Driver; + +public class UserDevfileTckModule extends TckModule { + + @Override + protected void configure() { + H2DBTestServer server = H2DBTestServer.startDefault(); + install( + new PersistTestModuleBuilder() + .setDriver(Driver.class) + .runningOn(server) + .addEntityClasses( + AccountImpl.class, + UserImpl.class, + WorkspaceImpl.class, + WorkspaceConfigImpl.class, + ProjectConfigImpl.class, + EnvironmentImpl.class, + UserDevfilePermission.class, + MachineConfigImpl.class, + SourceStorageImpl.class, + ServerConfigImpl.class, + CommandImpl.class, + RecipeImpl.class, + VolumeImpl.class, + // devfile + UserDevfileImpl.class, + UserDevfilePermissionImpl.class, + ActionImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, + ComponentImpl.class, + DevfileImpl.class, + EndpointImpl.class, + EntrypointImpl.class, + EnvImpl.class, + ProjectImpl.class, + SourceImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl.class) + .addEntityClass( + "org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute") + .addClass(SerializableConverter.class) + .setExceptionHandler(H2ExceptionHandler.class) + .build()); + bind(DBInitializer.class).asEagerSingleton(); + bind(SchemaInitializer.class) + .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema")); + bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server)); + + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermissionImpl.class)); + + bind(new TypeLiteral>() {}) + .to(UserDevfilePermissionDaoTest.TestDomain.class); + + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(WorkspaceDao.class).to(JpaWorkspaceDao.class); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java new file mode 100644 index 00000000000..1d239b804cf --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/EntityManagerExceptionInterceptor.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import javax.persistence.EntityManager; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class EntityManagerExceptionInterceptor implements MethodInterceptor { + @Inject Provider emf; + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + emf.get().getTransaction().setRollbackOnly(); + throw new RuntimeException("Database exception"); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java new file mode 100644 index 00000000000..c4e620ae9c5 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaTckModule.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import com.google.inject.TypeLiteral; +import org.eclipse.che.account.spi.AccountImpl; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.api.workspace.server.devfile.SerializableConverter; +import org.eclipse.che.api.workspace.server.model.impl.CommandImpl; +import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; +import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl; +import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; +import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ActionImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ComponentImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EndpointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EntrypointImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.EnvImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.ProjectImpl; +import org.eclipse.che.api.workspace.server.model.impl.devfile.SourceImpl; +import org.eclipse.che.commons.test.db.H2DBTestServer; +import org.eclipse.che.commons.test.db.H2JpaCleaner; +import org.eclipse.che.commons.test.db.PersistTestModuleBuilder; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.commons.test.tck.repository.JpaTckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.core.db.DBInitializer; +import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler; +import org.eclipse.che.core.db.schema.SchemaInitializer; +import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.eclipse.che.multiuser.permission.devfile.server.spi.tck.UserDevfilePermissionDaoTest; +import org.h2.Driver; + +public class JpaTckModule extends TckModule { + + @Override + protected void configure() { + H2DBTestServer server = H2DBTestServer.startDefault(); + install( + new PersistTestModuleBuilder() + .setDriver(Driver.class) + .runningOn(server) + .addEntityClasses( + AccountImpl.class, + UserImpl.class, + WorkspaceImpl.class, + WorkspaceConfigImpl.class, + ProjectConfigImpl.class, + EnvironmentImpl.class, + UserDevfilePermissionImpl.class, + MachineConfigImpl.class, + SourceStorageImpl.class, + ServerConfigImpl.class, + CommandImpl.class, + RecipeImpl.class, + VolumeImpl.class, + // devfile + ActionImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.CommandImpl.class, + ComponentImpl.class, + DevfileImpl.class, + EndpointImpl.class, + EntrypointImpl.class, + EnvImpl.class, + ProjectImpl.class, + SourceImpl.class, + UserDevfileImpl.class, + org.eclipse.che.api.workspace.server.model.impl.devfile.VolumeImpl.class) + .addEntityClass( + "org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute") + .addClass(SerializableConverter.class) + .setExceptionHandler(H2ExceptionHandler.class) + .build()); + + bind(new TypeLiteral>() {}) + .to(UserDevfilePermissionDaoTest.TestDomain.class); + + bind(UserDevfilePermissionDao.class).to(JpaUserDevfilePermissionDao.class); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfilePermission.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserImpl.class)); + bind(new TypeLiteral>() {}) + .toInstance(new JpaTckRepository<>(UserDevfileImpl.class)); + + bind(SchemaInitializer.class) + .toInstance(new FlywaySchemaInitializer(server.getDataSource(), "che-schema")); + bind(DBInitializer.class).asEagerSingleton(); + bind(TckResourcesCleaner.class).toInstance(new H2JpaCleaner(server)); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java new file mode 100644 index 00000000000..cef84bb3ce0 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/JpaUserDevfilePermissionDaoTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.inject.Matchers.names; +import static org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain.SET_PERMISSIONS; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createDevfile; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.matcher.Matchers; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.persistence.EntityManager; +import org.aopalliance.intercept.MethodInterceptor; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckModule; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class JpaUserDevfilePermissionDaoTest { + + private JpaUserDevfilePermissionDao userDevfilePermissionsDao; + private EntityManager manager; + private TckResourcesCleaner tckResourcesCleaner; + + @BeforeMethod + private void setUpManager() { + final Injector injector = + Guice.createInjector(new JpaTckModule(), new ExceptionEntityManagerModule()); + manager = injector.getInstance(EntityManager.class); + userDevfilePermissionsDao = injector.getInstance(JpaUserDevfilePermissionDao.class); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @AfterMethod + private void cleanup() { + manager.getTransaction().begin(); + final List entities = new ArrayList<>(); + entities.addAll(manager.createQuery("SELECT p FROM UserDevfilePermission p").getResultList()); + entities.addAll(manager.createQuery("SELECT d FROM UserDevfile d").getResultList()); + entities.addAll(manager.createQuery("SELECT u FROM Usr u").getResultList()); + for (Object entity : entities) { + manager.remove(entity); + } + manager.getTransaction().commit(); + tckResourcesCleaner.clean(); + } + + @Test( + expectedExceptions = ServerException.class, + expectedExceptionsMessageRegExp = "Database exception") + public void shouldThrowServerExceptionOnExistsWhenRuntimeExceptionOccursInDoGetMethod() + throws Exception { + + final UserDevfileImpl userDevfile = + new UserDevfileImpl(generate("id", 6), createDevfile(generate("name", 6))); + // Persist the userdevfule + manager.getTransaction().begin(); + manager.persist(userDevfile); + manager.getTransaction().commit(); + manager.clear(); + + final UserImpl user = new UserImpl(generate("user", 6), "user0@com.com", "usr0"); + // Persist the user + manager.getTransaction().begin(); + manager.persist(user); + manager.getTransaction().commit(); + manager.clear(); + + // Persist the worker + UserDevfilePermissionImpl worker = + new UserDevfilePermissionImpl( + userDevfile.getId(), user.getId(), Collections.singletonList(SET_PERMISSIONS)); + manager.getTransaction().begin(); + manager.persist(worker); + manager.getTransaction().commit(); + manager.clear(); + + userDevfilePermissionsDao.exists(user.getId(), userDevfile.getId(), SET_PERMISSIONS); + } + + public class ExceptionEntityManagerModule extends TckModule { + + @Override + protected void configure() { + MethodInterceptor interceptor = new EntityManagerExceptionInterceptor(); + requestInjection(interceptor); + bindInterceptor( + Matchers.subclassesOf(JpaUserDevfilePermissionDao.class), names("doGet"), interceptor); + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java new file mode 100644 index 00000000000..9b932c6b83f --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/jpa/RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.jpa; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createDevfile; +import static org.testng.AssertJUnit.assertEquals; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import java.util.Arrays; +import java.util.stream.Stream; +import javax.persistence.EntityManager; +import org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckResourcesCleaner; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaUserDevfilePermissionDao.RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber; +import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** Tests for {@link RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber} */ +public class RemoveUserDevfilePermissionsBeforeUserDevfileRemovedEventSubscriberTest { + private TckResourcesCleaner tckResourcesCleaner; + private EntityManager manager; + private JpaUserDevfilePermissionDao userDevfilePermissionsDao; + private JpaUserDevfileDao userDevfileDao; + + private RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber subscriber; + + private UserDevfileImpl userDevfile; + private UserDevfilePermissionImpl[] userDevfilePermissions; + private UserImpl[] users; + + @BeforeClass + public void setupEntities() throws Exception { + + users = + new UserImpl[] { + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2") + }; + + userDevfile = new UserDevfileImpl("devfile_id1", createDevfile(generate("name", 6))); + + userDevfilePermissions = + new UserDevfilePermissionImpl[] { + new UserDevfilePermissionImpl( + userDevfile.getId(), "user1", Arrays.asList("read", "use", "run")), + new UserDevfilePermissionImpl(userDevfile.getId(), "user2", Arrays.asList("read", "use")) + }; + + Injector injector = Guice.createInjector(new JpaTckModule()); + + manager = injector.getInstance(EntityManager.class); + userDevfilePermissionsDao = injector.getInstance(JpaUserDevfilePermissionDao.class); + userDevfileDao = injector.getInstance(JpaUserDevfileDao.class); + subscriber = + injector.getInstance( + RemoveUserDevfilePermissionsBeforeUserDevfuleRemovedEventSubscriber.class); + subscriber.subscribe(); + tckResourcesCleaner = injector.getInstance(TckResourcesCleaner.class); + } + + @BeforeMethod + public void setUp() throws Exception { + manager.getTransaction().begin(); + manager.persist(userDevfile); + Stream.of(users).forEach(manager::persist); + Stream.of(userDevfilePermissions).forEach(manager::persist); + manager.getTransaction().commit(); + manager.clear(); + } + + @AfterMethod + public void cleanup() { + manager.getTransaction().begin(); + + manager + .createQuery("SELECT e FROM UserDevfilePermission e", UserDevfilePermissionImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT w FROM UserDevfile w", UserDevfileImpl.class) + .getResultList() + .forEach(manager::remove); + + manager + .createQuery("SELECT u FROM Usr u", UserImpl.class) + .getResultList() + .forEach(manager::remove); + + manager.getTransaction().commit(); + } + + @AfterClass + public void shutdown() throws Exception { + subscriber.unsubscribe(); + tckResourcesCleaner.clean(); + } + + @Test + public void shouldRemoveAllPermissionsWhenUserDevfileIsRemoved() throws Exception { + userDevfileDao.remove(userDevfile.getId()); + + assertEquals( + userDevfilePermissionsDao + .getUserDevfilePermission(userDevfile.getId(), 1, 0) + .getTotalItemsCount(), + 0); + } + + @Test + public void shouldRemoveAllPermissionsWhenPageSizeEqualsToOne() throws Exception { + subscriber.removeUserDevfilePermissions(userDevfile.getId(), 1); + + assertEquals( + userDevfilePermissionsDao + .getUserDevfilePermission(userDevfile.getId(), 1, 0) + .getTotalItemsCount(), + 0); + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java new file mode 100644 index 00000000000..a722c52d814 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/java/org/eclipse/che/multiuser/permission/devfile/server/spi/tck/UserDevfilePermissionDaoTest.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.multiuser.permission.devfile.server.spi.tck; + +import static org.eclipse.che.commons.lang.NameGenerator.generate; +import static org.eclipse.che.multiuser.permission.devfile.server.TestObjectGenerator.createDevfile; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.DELETE; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.READ; +import static org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain.UPDATE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.inject.Inject; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.Page; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.api.user.server.model.impl.UserImpl; +import org.eclipse.che.commons.test.tck.TckListener; +import org.eclipse.che.commons.test.tck.repository.TckRepository; +import org.eclipse.che.commons.test.tck.repository.TckRepositoryException; +import org.eclipse.che.multiuser.api.permission.server.AbstractPermissionsDomain; +import org.eclipse.che.multiuser.permission.devfile.server.UserDevfileDomain; +import org.eclipse.che.multiuser.permission.devfile.server.model.UserDevfilePermission; +import org.eclipse.che.multiuser.permission.devfile.server.model.impl.UserDevfilePermissionImpl; +import org.eclipse.che.multiuser.permission.devfile.server.spi.UserDevfilePermissionDao; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** Compatibility test for {@link UserDevfilePermissionDao} */ +@Listeners(TckListener.class) +@Test(suiteName = "UserDevfilePermissionTck") +public class UserDevfilePermissionDaoTest { + + @Inject private UserDevfilePermissionDao permissionDao; + + @Inject private TckRepository permissionTckRepository; + + @Inject private TckRepository userRepository; + + @Inject private TckRepository devfileRepository; + + UserDevfilePermissionImpl[] permissions; + + @BeforeMethod + public void setUp() throws TckRepositoryException { + permissions = + new UserDevfilePermissionImpl[] { + new UserDevfilePermissionImpl( + "devfile_id1", "user1", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id1", "user2", Arrays.asList(READ, DELETE)), + new UserDevfilePermissionImpl("devfile_id2", "user1", Arrays.asList(READ, UPDATE)), + new UserDevfilePermissionImpl( + "devfile_id2", "user2", Arrays.asList(READ, DELETE, UPDATE)), + new UserDevfilePermissionImpl("devfile_id2", "user0", Arrays.asList(READ, DELETE, UPDATE)) + }; + + final UserImpl[] users = + new UserImpl[] { + new UserImpl("user0", "user0@com.com", "usr0"), + new UserImpl("user1", "user1@com.com", "usr1"), + new UserImpl("user2", "user2@com.com", "usr2") + }; + userRepository.createAll(Arrays.asList(users)); + + devfileRepository.createAll( + ImmutableList.of( + new UserDevfileImpl("devfile_id1", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id2", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id3", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id4", createDevfile(generate("name", 6))), + new UserDevfileImpl("devfile_id5", createDevfile(generate("name", 6))))); + + permissionTckRepository.createAll( + Stream.of(permissions).map(UserDevfilePermissionImpl::new).collect(Collectors.toList())); + } + + @AfterMethod + public void cleanUp() throws TckRepositoryException { + permissionTckRepository.removeAll(); + devfileRepository.removeAll(); + userRepository.removeAll(); + } + + @Test + public void shouldStorePermissions() throws Exception { + UserDevfilePermissionImpl permission = + new UserDevfilePermissionImpl("devfile_id1", "user0", Arrays.asList(READ, DELETE, UPDATE)); + permissionDao.store(permission); + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id1", "user0"), + new UserDevfilePermissionImpl(permission)); + } + + @Test + public void shouldReplaceExistingPermissionOnStoring() throws Exception { + UserDevfilePermissionImpl replace = + new UserDevfilePermissionImpl("devfile_id1", "user1", Collections.singletonList("READ")); + permissionDao.store(replace); + Assert.assertEquals(permissionDao.getUserDevfilePermission("devfile_id1", "user1"), replace); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenStoringArgumentIsNull() throws Exception { + permissionDao.store(null); + } + + @Test + public void shouldGetPermissionByWorkspaceIdAndUserId() throws Exception { + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id1", "user1"), permissions[0]); + Assert.assertEquals( + permissionDao.getUserDevfilePermission("devfile_id2", "user2"), permissions[3]); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionDevfileIdArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission(null, "user1"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionUserIdArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission("devfile_id1", null); + } + + @Test(expectedExceptions = NotFoundException.class) + public void shouldThrowNotFoundExceptionOnGetIfPermissionWithSuchDevfileIdOrUserIdDoesNotExist() + throws Exception { + permissionDao.getUserDevfilePermission("devfile_id9", "user1"); + } + + @Test + public void shouldGetPermissionsByDevfileId() throws Exception { + Page permissionsPage = + permissionDao.getUserDevfilePermission("devfile_id2", 1, 1); + + final List fetchedPermissions = permissionsPage.getItems(); + assertEquals(permissionsPage.getTotalItemsCount(), 3); + assertEquals(permissionsPage.getItemsCount(), 1); + assertTrue( + fetchedPermissions.contains(permissions[2]) + ^ fetchedPermissions.contains(permissions[3]) + ^ fetchedPermissions.contains(permissions[4])); + } + + @Test + public void shouldGetPermissionsByUserId() throws Exception { + List actual = permissionDao.getUserDevfilePermissionByUser("user1"); + List expected = Arrays.asList(permissions[0], permissions[2]); + assertEquals(actual.size(), expected.size()); + assertTrue(new HashSet<>(actual).equals(new HashSet<>(expected))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionsByDevfileArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermission(null, 1, 0); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenGetPermissionsByUserArgumentIsNull() throws Exception { + permissionDao.getUserDevfilePermissionByUser(null); + } + + @Test + public void shouldReturnEmptyListIfPermissionsWithSuchDevfileIdDoesNotFound() throws Exception { + assertEquals( + 0, permissionDao.getUserDevfilePermission("unexisted_devfile_id", 1, 0).getItemsCount()); + } + + @Test + public void shouldReturnEmptyListIfPermissionsWithSuchUserIdDoesNotFound() throws Exception { + assertEquals(0, permissionDao.getUserDevfilePermissionByUser("unexisted_user").size()); + } + + @Test + public void shouldRemovePermission() throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", "user1"); + assertEquals(1, permissionDao.getUserDevfilePermissionByUser("user1").size()); + assertNull( + notFoundToNull(() -> permissionDao.getUserDevfilePermission("devfile_id1", "user1"))); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenRemovePermissionDevfileIdArgumentIsNull() throws Exception { + permissionDao.removeUserDevfilePermission(null, "user1"); + } + + @Test(expectedExceptions = NullPointerException.class) + public void shouldThrowExceptionWhenRemovePermissionUserIdArgumentIsNull() throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", null); + } + + @Test(expectedExceptions = ServerException.class) + public void shouldThrowNotFoundExceptionOnRemoveIfPermissionWithSuchDevfileIdDoesNotExist() + throws Exception { + permissionDao.removeUserDevfilePermission("unexisted_ws", "user1"); + } + + @Test(expectedExceptions = ServerException.class) + public void shouldThrowNotFoundExceptionOnRemoveIfPermissionWithSuchUserIdDoesNotExist() + throws Exception { + permissionDao.removeUserDevfilePermission("devfile_id1", "unexisted_user"); + } + + public static class TestDomain extends AbstractPermissionsDomain { + public TestDomain() { + super(UserDevfileDomain.DOMAIN_ID, Arrays.asList(READ, DELETE, UPDATE)); + } + + @Override + protected UserDevfilePermissionImpl doCreateInstance( + String userId, String instanceId, List allowedActions) { + return new UserDevfilePermissionImpl(userId, instanceId, allowedActions); + } + } + + private static T notFoundToNull(Callable action) throws Exception { + try { + return action.call(); + } catch (NotFoundException x) { + return null; + } + } +} diff --git a/multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule new file mode 100644 index 00000000000..edd289622d5 --- /dev/null +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/META-INF/services/org.eclipse.che.commons.test.tck.TckModule @@ -0,0 +1 @@ +org.eclipse.che.multiuser.permission.devfile.server.spi.jpa.JpaTckModule diff --git a/multiuser/permission/che-multiuser-permission-devfile/src/test/resources/logback-test.xml b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/logback-test.xml similarity index 70% rename from multiuser/permission/che-multiuser-permission-devfile/src/test/resources/logback-test.xml rename to multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/logback-test.xml index e7bf50602e3..99fef4a8dbc 100644 --- a/multiuser/permission/che-multiuser-permission-devfile/src/test/resources/logback-test.xml +++ b/multiuser/permission/che-multiuser-permission-userdevfile/src/test/resources/logback-test.xml @@ -13,14 +13,22 @@ --> - + + %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n%nopex + + + + target/log/test.log %-41(%date[%.15thread]) %-45([%-5level] [%.30logger{30} %L]) - %msg%n + + + diff --git a/multiuser/permission/pom.xml b/multiuser/permission/pom.xml index 08cd55e0baa..9b23040aa09 100644 --- a/multiuser/permission/pom.xml +++ b/multiuser/permission/pom.xml @@ -25,9 +25,10 @@ Che Multiuser :: Permissions Parent che-multiuser-permission-user - che-multiuser-permission-devfile che-multiuser-permission-workspace che-multiuser-permission-workspace-activity + che-multiuser-permission-userdevfile + che-multiuser-permission-factory che-multiuser-permission-system che-multiuser-permission-resource che-multiuser-permission-logger diff --git a/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql b/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql new file mode 100644 index 00000000000..4889a564038 --- /dev/null +++ b/multiuser/sql-schema/src/main/resources/che-schema/7.19.0/1.1__add_userdevfile_permissions.sql @@ -0,0 +1,40 @@ +-- +-- Copyright (c) 2012-2020 Red Hat, Inc. +-- This program and the accompanying materials are made +-- available under the terms of the Eclipse Public License 2.0 +-- which is available at https://www.eclipse.org/legal/epl-2.0/ +-- +-- SPDX-License-Identifier: EPL-2.0 +-- +-- Contributors: +-- Red Hat, Inc. - initial API and implementation +-- + +-- User devfile permissions ----------------------------------------------------------- +CREATE TABLE che_userdevfile_permissions ( + id VARCHAR(255) NOT NULL, + user_id VARCHAR(255), + userdevfile_id VARCHAR(255), + + PRIMARY KEY (id) +); +-- indexes +CREATE UNIQUE INDEX che_index_userdevfile_permissions_user_id_userdevfile_id ON che_userdevfile_permissions (user_id, userdevfile_id); +CREATE INDEX che_index_userdevfile_permissions_userdevfile_id ON che_userdevfile_permissions (userdevfile_id); +-- constraints +ALTER TABLE che_userdevfile_permissions ADD CONSTRAINT che_fk_userdevfile_permissions_user_id FOREIGN KEY (user_id) REFERENCES usr (id); +ALTER TABLE che_userdevfile_permissions ADD CONSTRAINT che_fk_userdevfile_permissions_workspace_id FOREIGN KEY (userdevfile_id) REFERENCES userdevfile (id); +-------------------------------------------------------------------------------- + + +-- Worker actions -------------------------------------------------------------- +CREATE TABLE che_userdevfile_permissions_actions ( + userdevfile_permissions_id VARCHAR(255), + actions VARCHAR(255) +); +-- indexes +CREATE INDEX che_index_userdevfile_permissions_actions_actions ON che_userdevfile_permissions_actions (actions); +CREATE INDEX che_index_userdevfile_permissions_actions_userdevfile_id ON che_userdevfile_permissions_actions (userdevfile_permissions_id); +-- constraints +ALTER TABLE che_userdevfile_permissions_actions ADD CONSTRAINT che_fk_userdevfile_permissions_actions_id FOREIGN KEY (userdevfile_permissions_id) REFERENCES che_userdevfile_permissions(id); +-------------------------------------------------------------------------------- \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8bce2ef2b38..b2d23a82427 100644 --- a/pom.xml +++ b/pom.xml @@ -1124,11 +1124,6 @@ che-multiuser-machine-authentication-shared ${che.version} - - org.eclipse.che.multiuser - che-multiuser-permission-devfile - ${che.version} - org.eclipse.che.multiuser che-multiuser-permission-logger @@ -1149,6 +1144,17 @@ che-multiuser-permission-user ${che.version} + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + ${che.version} + + + org.eclipse.che.multiuser + che-multiuser-permission-userdevfile + ${che.version} + tests + org.eclipse.che.multiuser che-multiuser-permission-workspace diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java index 16da05dc4fb..c325f36a422 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/UserDevfileManager.java @@ -29,7 +29,6 @@ import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.devfile.shared.event.DevfileCreatedEvent; -import org.eclipse.che.api.devfile.shared.event.DevfileDeletedEvent; import org.eclipse.che.api.devfile.shared.event.DevfileUpdatedEvent; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.NameGenerator; @@ -136,7 +135,6 @@ public void removeUserDevfile(String id) throws ServerException { "UserDevfile with id '{}' removed by user '{}'", id, EnvironmentContext.getCurrent().getSubject().getUserName()); - eventService.publish(new DevfileDeletedEvent(id)); } /** diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java new file mode 100644 index 00000000000..47fa4b108cd --- /dev/null +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/event/BeforeDevfileRemovedEvent.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.devfile.server.event; + +import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventOrigin; +import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; +import org.eclipse.che.core.db.cascade.event.RemoveEvent; + +/** Published before {@link UserDevfile user devfile} removed. */ +@EventOrigin("user") +public class BeforeDevfileRemovedEvent extends RemoveEvent { + + private final UserDevfileImpl userDevfile; + + public BeforeDevfileRemovedEvent(UserDevfileImpl userDevfile) { + this.userDevfile = userDevfile; + } + + /** Returns user which is going to be removed. */ + public UserDevfileImpl getUserDevfile() { + return userDevfile; + } +} diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java index 5ae3af8c022..5a249c841d5 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/jpa/JpaUserDevfileDao.java @@ -19,9 +19,9 @@ import static org.eclipse.che.api.devfile.server.jpa.JpaUserDevfileDao.UserDevfileSearchQueryBuilder.newBuilder; import com.google.common.annotations.Beta; +import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.inject.persist.Transactional; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -37,18 +37,25 @@ import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.core.db.jpa.DuplicateKeyException; import org.eclipse.che.core.db.jpa.IntegrityConstraintViolationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** JPA based implementation of {@link UserDevfileDao}. */ @Singleton @Beta public class JpaUserDevfileDao implements UserDevfileDao { + private static final Logger LOG = LoggerFactory.getLogger(JpaUserDevfileDao.class); + + @Inject protected Provider managerProvider; + @Inject protected EventService eventService; - @Inject private Provider managerProvider; public static final List> DEFAULT_ORDER = ImmutableList.of(new Pair<>("id", "ASC")); @@ -109,7 +116,7 @@ public Optional getById(String id) throws ServerException { } @Override - @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) + @Transactional(rollbackOn = {ServerException.class}) public Page getDevfiles( int maxItems, int skipCount, @@ -122,6 +129,18 @@ public Page getDevfiles( skipCount >= 0, "The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE); + return doGetDevfiles( + maxItems, skipCount, filter, order, () -> newBuilder(managerProvider.get())); + } + + @Transactional(rollbackOn = {ServerException.class}) + protected Page doGetDevfiles( + int maxItems, + int skipCount, + List> filter, + List> order, + Supplier queryBuilderSupplier) + throws ServerException { if (filter != null && !filter.isEmpty()) { List> invalidFilter = filter @@ -149,13 +168,14 @@ public Page getDevfiles( } try { final long count = - newBuilder(managerProvider.get()).withFilter(filter).buildCountQuery().getSingleResult(); + queryBuilderSupplier.get().withFilter(filter).buildCountQuery().getSingleResult(); if (count == 0) { return new Page<>(emptyList(), skipCount, maxItems, count); } List result = - newBuilder(managerProvider.get()) + queryBuilderSupplier + .get() .withFilter(filter) .withOrder(effectiveOrder) .withMaxItems(maxItems) @@ -172,6 +192,19 @@ public Page getDevfiles( } } + @Override + @Transactional + public long getTotalCount() throws ServerException { + try { + return managerProvider + .get() + .createNamedQuery("UserDevfile.getTotalCount", Long.class) + .getSingleResult(); + } catch (RuntimeException x) { + throw new ServerException(x.getLocalizedMessage(), x); + } + } + @Transactional protected void doCreate(UserDevfileImpl devfile) { final EntityManager manager = managerProvider.get(); @@ -190,49 +223,51 @@ protected Optional doUpdate(UserDevfileImpl update) { return Optional.of(merged); } - @Transactional - protected void doRemove(String id) { + @Transactional(rollbackOn = {RuntimeException.class, ServerException.class}) + protected void doRemove(String id) throws ServerException { final EntityManager manager = managerProvider.get(); final UserDevfileImpl devfile = manager.find(UserDevfileImpl.class, id); if (devfile != null) { + eventService + .publish(new BeforeDevfileRemovedEvent(new UserDevfileImpl(devfile))) + .propagateException(); manager.remove(devfile); manager.flush(); } } - protected static class UserDevfileSearchQueryBuilder { - private EntityManager entityManager; - private int maxItems; - private int skipCount; - private String filter; - private Map params; - private String order; + public static class UserDevfileSearchQueryBuilder { + protected EntityManager entityManager; + protected int maxItems; + protected int skipCount; + protected String filter; + protected Map params; + protected String order; - private UserDevfileSearchQueryBuilder(EntityManager entityManager) { + public UserDevfileSearchQueryBuilder(EntityManager entityManager) { this.entityManager = entityManager; - this.params = Collections.emptyMap(); + this.params = new HashMap<>(); } - static UserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { + public static UserDevfileSearchQueryBuilder newBuilder(EntityManager entityManager) { return new JpaUserDevfileDao.UserDevfileSearchQueryBuilder(entityManager); } - protected UserDevfileSearchQueryBuilder withMaxItems(int maxItems) { + public UserDevfileSearchQueryBuilder withMaxItems(int maxItems) { this.maxItems = maxItems; return this; } - protected UserDevfileSearchQueryBuilder withSkipCount(int skipCount) { + public UserDevfileSearchQueryBuilder withSkipCount(int skipCount) { this.skipCount = skipCount; return this; } - protected UserDevfileSearchQueryBuilder withFilter(List> filter) { + public UserDevfileSearchQueryBuilder withFilter(List> filter) { if (filter == null || filter.isEmpty()) { this.filter = ""; return this; } - this.params = new HashMap<>(); final StringJoiner matcher = new StringJoiner(" AND ", " WHERE ", " "); int i = 0; for (Pair attribute : filter) { @@ -250,7 +285,7 @@ protected UserDevfileSearchQueryBuilder withFilter(List> fi return this; } - protected UserDevfileSearchQueryBuilder withOrder(List> order) { + public UserDevfileSearchQueryBuilder withOrder(List> order) { if (order == null || order.isEmpty()) { this.order = ""; return this; @@ -262,7 +297,7 @@ protected UserDevfileSearchQueryBuilder withOrder(List> ord return this; } - protected TypedQuery buildCountQuery() { + public TypedQuery buildCountQuery() { StringBuilder query = new StringBuilder() .append("SELECT ") @@ -274,7 +309,7 @@ protected TypedQuery buildCountQuery() { return typedQuery; } - protected TypedQuery buildSelectItemsQuery() { + public TypedQuery buildSelectItemsQuery() { StringBuilder query = new StringBuilder() diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java index 91aefb2ba58..24ac4a70c4d 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/model/impl/UserDevfileImpl.java @@ -43,7 +43,7 @@ @Table(name = "userdevfile") @NamedQueries({ @NamedQuery(name = "UserDevfile.getAll", query = "SELECT d FROM UserDevfile d ORDER BY d.id"), - @NamedQuery(name = "UserDevfile.getAllCount", query = "SELECT COUNT(d) FROM UserDevfile d"), + @NamedQuery(name = "UserDevfile.getTotalCount", query = "SELECT COUNT(d) FROM UserDevfile d"), }) @Beta public class UserDevfileImpl implements UserDevfile { diff --git a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java index 1d8dac13661..c0d5bf20bfa 100644 --- a/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java +++ b/wsmaster/che-core-api-devfile/src/main/java/org/eclipse/che/api/devfile/server/spi/UserDevfileDao.java @@ -85,4 +85,12 @@ Page getDevfiles( List> filter, List> order) throws ServerException; + + /** + * Get the count of all user devfiles from the persistent layer. + * + * @return workspace count + * @throws ServerException when any error occurs + */ + long getTotalCount() throws ServerException; } diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java index d3644059719..e34c89e3da0 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/UserDevfileManagerTest.java @@ -150,17 +150,6 @@ public void shouldRemoveUserDevfile() throws Exception { verify(userDevfileDao).remove(userDevfile.getId()); } - @Test - public void shouldSendDevfileDeletedEventOnRemoveUserDevfile() throws Exception { - // given - final UserDevfileImpl userDevfile = createUserDevfile(); - // when - userDevfileManager.removeUserDevfile(userDevfile.getId()); - // then - verify(eventService).publish(devfileDeletedEventCaptor.capture()); - assertEquals(userDevfile.getId(), devfileDeletedEventCaptor.getValue().getId()); - } - @Test public void shouldSendDevfileUpdatedEventOnUpdateDevfile() throws Exception { // given diff --git a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java index ccacc342f65..81e6b826846 100644 --- a/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java +++ b/wsmaster/che-core-api-devfile/src/test/java/org/eclipse/che/api/devfile/server/spi/tck/UserDevfileDaoTest.java @@ -19,6 +19,7 @@ import static org.eclipse.che.api.devfile.server.TestObjectGenerator.createUserDevfile; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -35,6 +36,8 @@ import org.eclipse.che.api.core.Page; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.model.workspace.devfile.UserDevfile; +import org.eclipse.che.api.core.notification.EventService; +import org.eclipse.che.api.devfile.server.event.BeforeDevfileRemovedEvent; import org.eclipse.che.api.devfile.server.model.impl.UserDevfileImpl; import org.eclipse.che.api.devfile.server.spi.UserDevfileDao; import org.eclipse.che.api.user.server.model.impl.UserImpl; @@ -65,6 +68,8 @@ public class UserDevfileDaoTest { private UserDevfileImpl[] devfiles; + @Inject private EventService eventService; + @Inject private UserDevfileDao userDevfileDaoDao; @Inject private TckRepository devfileTckRepository; @@ -333,6 +338,18 @@ public void shouldBeAbleToGetDevfilesSortedByName() assertEquals(result.getItems().stream().toArray(UserDevfileImpl[]::new), expected); } + @Test + public void shouldSendDevfileDeletedEventOnRemoveUserDevfile() throws Exception { + // given + final String userDevfileId = devfiles[0].getId(); + final boolean[] isNotified = new boolean[] {false}; + eventService.subscribe(event -> isNotified[0] = true, BeforeDevfileRemovedEvent.class); + // when + userDevfileDaoDao.remove(userDevfileId); + // then + assertTrue(isNotified[0], "Event subscriber notified"); + } + @Test(dataProvider = "boundsdataprovider") public void shouldBeAbleToGetDevfilesSortedByNameWithSpecificMaxItemsAndSkipCount( int maxitems, int skipCount) throws ServerException, NotFoundException, ConflictException { diff --git a/wsmaster/integration-tests/mysql-tck/pom.xml b/wsmaster/integration-tests/mysql-tck/pom.xml index 1150d1285ef..480b00b80cd 100644 --- a/wsmaster/integration-tests/mysql-tck/pom.xml +++ b/wsmaster/integration-tests/mysql-tck/pom.xml @@ -241,7 +241,6 @@ che-core-api-user, che-core-api-ssh, che-core-api-workspace, - che-core-api-devfile, che-multiuser-machine-authentication, infrastructure-kubernetes test diff --git a/wsmaster/integration-tests/postgresql-tck/pom.xml b/wsmaster/integration-tests/postgresql-tck/pom.xml index 739d3d91c8b..9b528eaa0ac 100644 --- a/wsmaster/integration-tests/postgresql-tck/pom.xml +++ b/wsmaster/integration-tests/postgresql-tck/pom.xml @@ -231,7 +231,6 @@ che-core-api-user, che-core-api-ssh, che-core-api-workspace, - che-core-api-devfile, infrastructure-kubernetes test tests