[417483] [Security] Issues in invalidation when missing write Permission
https://bugs.eclipse.org/bugs/show_bug.cgi?id=417483
- Modify CDORevisionImpl to avoid throwing NoPermissionExceptions when
copying a new revision out of an existing one
- Modify CDOSessionImpl to avoid throwing NoPermissionExceptions when
revising revisisons
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionImpl.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionImpl.java
index 2475856..2b7c718 100644
--- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionImpl.java
+++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/internal/common/revision/CDORevisionImpl.java
@@ -31,6 +31,11 @@
{
private Object[] values;
+ /**
+ * Indicates if the revision is being copied from another revision. In that case isWritable() will allways return true.
+ */
+ private boolean isCopying;
+
public CDORevisionImpl(EClass eClass)
{
super(eClass);
@@ -42,23 +47,31 @@
EStructuralFeature[] features = clearValues();
int length = features.length;
- for (int i = 0; i < length; i++)
+ isCopying = true;
+ try
{
- EStructuralFeature feature = features[i];
- if (feature.isMany())
+ for (int i = 0; i < length; i++)
{
- InternalCDOList sourceList = (InternalCDOList)source.values[i];
- if (sourceList != null)
+ EStructuralFeature feature = features[i];
+ if (feature.isMany())
{
- EClassifier classifier = feature.getEType();
- setValue(i, sourceList.clone(classifier));
+ InternalCDOList sourceList = (InternalCDOList)source.values[i];
+ if (sourceList != null)
+ {
+ EClassifier classifier = feature.getEType();
+ setValue(i, sourceList.clone(classifier));
+ }
+ }
+ else
+ {
+ CDOType type = CDOModelUtil.getType(feature);
+ setValue(i, type.copyValue(source.values[i]));
}
}
- else
- {
- CDOType type = CDOModelUtil.getType(feature);
- setValue(i, type.copyValue(source.values[i]));
- }
+ }
+ finally
+ {
+ isCopying = false;
}
}
@@ -84,4 +97,22 @@
{
values[featureIndex] = value;
}
+
+ /**
+ *
+ * {@inheritDoc}
+ *
+ * @see org.eclipse.emf.cdo.spi.common.revision.AbstractCDORevision#isWritable()
+ */
+ @Override
+ public boolean isWritable()
+ {
+ // If we are copying another revision (
+ if (isCopying)
+ {
+ return true;
+ }
+ return super.isWritable();
+ }
+
}
diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_417483_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_417483_Test.java
new file mode 100644
index 0000000..b75a8a3
--- /dev/null
+++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_417483_Test.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2004-2013 Eike Stepper (Berlin, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Eike Stepper - initial API and implementation
+ */
+package org.eclipse.emf.cdo.tests.bugzilla;
+
+import org.eclipse.emf.cdo.common.CDOCommonSession.Options.PassiveUpdateMode;
+import org.eclipse.emf.cdo.eresource.CDOResource;
+import org.eclipse.emf.cdo.security.Access;
+import org.eclipse.emf.cdo.security.Group;
+import org.eclipse.emf.cdo.security.Realm;
+import org.eclipse.emf.cdo.security.ResourcePermission;
+import org.eclipse.emf.cdo.security.Role;
+import org.eclipse.emf.cdo.security.SecurityFactory;
+import org.eclipse.emf.cdo.security.User;
+import org.eclipse.emf.cdo.server.security.ISecurityManager;
+import org.eclipse.emf.cdo.server.security.SecurityManagerUtil;
+import org.eclipse.emf.cdo.session.CDOSession;
+import org.eclipse.emf.cdo.session.CDOSessionInvalidationEvent;
+import org.eclipse.emf.cdo.tests.AbstractCDOTest;
+import org.eclipse.emf.cdo.tests.config.impl.ConfigTest.CleanRepositoriesAfter;
+import org.eclipse.emf.cdo.tests.config.impl.ConfigTest.CleanRepositoriesBefore;
+import org.eclipse.emf.cdo.tests.config.impl.RepositoryConfig;
+import org.eclipse.emf.cdo.tests.config.impl.SessionConfig;
+import org.eclipse.emf.cdo.tests.model1.Category;
+import org.eclipse.emf.cdo.transaction.CDOTransaction;
+import org.eclipse.emf.cdo.util.CommitException;
+import org.eclipse.emf.cdo.util.ConcurrentAccessException;
+
+import org.eclipse.net4j.util.event.IEvent;
+import org.eclipse.net4j.util.event.IListener;
+import org.eclipse.net4j.util.security.IPasswordCredentials;
+import org.eclipse.net4j.util.security.IPasswordCredentialsProvider;
+import org.eclipse.net4j.util.security.PasswordCredentials;
+import org.eclipse.net4j.util.security.PasswordCredentialsProvider;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests ensuring that the permissions are correctly computed, no matter what passive update mode is chosen.
+ * @author Alex Lagarde <alex.lagarde@obeo.fr>
+ */
+@CleanRepositoriesBefore
+@CleanRepositoriesAfter
+public class Bugzilla_417483_Test extends AbstractCDOTest
+{
+
+ /**
+ * Login for use with write rights.
+ */
+ private static final String USER_WITH_WRITE_RIGHTS_ID = "Stepper";
+
+ /**
+ * Password for use with write rights.
+ */
+ private static final String USER_WITH_WRITE_RIGHTS_PASSWORD = "12345";
+
+ /**
+ * Login for use without write rights.
+ */
+ private static final String USER_WITHOUT_WRITE_RIGHTS_ID = "Lagarde";
+
+ /**
+ * Password for use without write rights.
+ */
+ private static final String USER_WITHOUT_WRITE_RIGHTS_PASSWORD = "54321";
+
+ /**
+ * Boolean used by the
+ */
+ private boolean loginWithUserWithoutRights;
+
+ @Override
+ protected void doSetUp() throws Exception
+ {
+ super.doSetUp();
+
+ // Define 2 users:
+ // - one with all rights
+ // - one without write rights (but all read rights)
+ ISecurityManager securityManager = startRepository();
+ securityManager.modify(new ISecurityManager.RealmOperation()
+ {
+ public void execute(Realm realm)
+ {
+ // User with write rights
+ User userwithWriteRights = realm.addUser(USER_WITH_WRITE_RIGHTS_ID, USER_WITH_WRITE_RIGHTS_PASSWORD);
+ userwithWriteRights.getGroups().add(realm.getGroup("Users"));
+ userwithWriteRights.getRoles().add(realm.getRole("All Objects Writer"));
+
+ // User with only read rights
+ ResourcePermission readlAllPermision = SecurityFactory.eINSTANCE.createResourcePermission();
+ readlAllPermision.setPattern(".*");
+ readlAllPermision.setAccess(Access.READ);
+ Role roleWithoutWriteRights = realm.addRole("User without Write - Role");
+ roleWithoutWriteRights.getPermissions().add(readlAllPermision);
+ Group groupWithoutWriteRights = realm.addGroup("User without Write - Group");
+ groupWithoutWriteRights.getRoles().add(roleWithoutWriteRights);
+ User userwithoutWriteRights = realm.addUser(USER_WITHOUT_WRITE_RIGHTS_ID, USER_WITHOUT_WRITE_RIGHTS_PASSWORD);
+ userwithoutWriteRights.getGroups().add(groupWithoutWriteRights);
+ }
+ });
+ }
+
+ /**
+ * Ensures that the permissions are correctly computed when integrating changes with the {@link PassiveUpdateMode#CHANGES} update mode.
+ */
+ public void testPermissionWithChangesPassiveUpdateMode() throws Exception
+ {
+ doTestPermissionWithPassiveUpdates(PassiveUpdateMode.CHANGES);
+ }
+
+ /**
+ * Ensures that the permissions are correctly computed when integrating changes with the {@link PassiveUpdateMode#ADDITIONS} update mode.
+ */
+ public void testPermissionWithAdditionPassiveUpdateMode() throws Exception
+ {
+ doTestPermissionWithPassiveUpdates(PassiveUpdateMode.ADDITIONS);
+ }
+
+ /**
+ * Ensures that the permissions are correctly computed when integrating changes with the {@link PassiveUpdateMode#INVALIDATIONS} update mode.
+ */
+ public void testPermissionWithInvalidationPassiveUpdateMode() throws Exception
+ {
+ doTestPermissionWithPassiveUpdates(PassiveUpdateMode.INVALIDATIONS);
+ }
+
+ /**
+ * Ensures that the permissions are correctly computed, with the given {@link PassiveUpdateMode}.
+ */
+ protected void doTestPermissionWithPassiveUpdates(PassiveUpdateMode passiveUpdateMode)
+ throws ConcurrentAccessException, CommitException, InterruptedException
+ {
+ // Step 1: Both users open transaction on the repository
+ CDOSession userWithWriteRightSession = openSession();
+ CDOTransaction userWithWriteRightTransaction = userWithWriteRightSession.openTransaction();
+ loginWithUserWithoutRights = true;
+ CDOSession userWithoutWriteRightSession = openSession();
+ userWithoutWriteRightSession.options().setPassiveUpdateMode(passiveUpdateMode);
+
+ CDOTransaction userWithoutWriteRightTransaction = userWithoutWriteRightSession.openTransaction();
+ assertEquals(USER_WITH_WRITE_RIGHTS_ID, userWithWriteRightSession.getUserID());
+ assertEquals(USER_WITHOUT_WRITE_RIGHTS_ID, userWithoutWriteRightSession.getUserID());
+
+ // Step 2: User with write rights creates a resource and commits
+ CDOResource resource = userWithWriteRightTransaction.createResource(getResourcePath("/res"));
+ assertEquals(true, resource.cdoRevision().isWritable());
+ Category resourceRoot = getModel1Factory().createCategory();
+ resource.getContents().add(resourceRoot);
+ userWithWriteRightTransaction.commit();
+
+ // => User without rights should be able to integrate changes without permission issues
+ CDOResource resourceWithoutWrite = userWithoutWriteRightTransaction.getResource(getResourcePath("/res"));
+ assertEquals(1, resourceWithoutWrite.getContents().size());
+ // => Trigger loading of resource root so that invalidation are sent
+ resourceWithoutWrite.getContents().iterator().next();
+
+ // Step 3: User with write rights modifies the resource root and commits
+ resourceRoot.setName("RENAMMED");
+ final CountDownLatch latch = new CountDownLatch(1);
+ userWithoutWriteRightSession.addListener(new IListener()
+ {
+ public void notifyEvent(IEvent event)
+ {
+ if (event instanceof CDOSessionInvalidationEvent)
+ {
+ latch.countDown();
+ }
+ }
+ });
+ userWithWriteRightTransaction.commit();
+
+ // => User without rights should be able to integrate changes without permission issues
+ boolean notified = latch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertEquals("Timeout: user without Write Rights should have been notified of changes", true, notified);
+ }
+
+ /**
+ * Starts a repository with security enabled.
+ * @return the {@link ISecurityManager} associated to the started repository
+ */
+ private ISecurityManager startRepository()
+ {
+ // Create & register security manager
+ ISecurityManager securityManager = SecurityManagerUtil.createSecurityManager("/security", getServerContainer());
+ getTestProperties().put(RepositoryConfig.PROP_TEST_SECURITY_MANAGER, securityManager);
+
+ // Create & register a credential provider allowing to logging with one user or another
+ IPasswordCredentialsProvider credentialsProvider = new PasswordCredentialsProvider(USER_WITH_WRITE_RIGHTS_ID,
+ USER_WITH_WRITE_RIGHTS_PASSWORD)
+ {
+ @Override
+ public IPasswordCredentials getCredentials()
+ {
+ if (loginWithUserWithoutRights)
+ {
+ return new PasswordCredentials(USER_WITHOUT_WRITE_RIGHTS_ID, USER_WITHOUT_WRITE_RIGHTS_PASSWORD.toCharArray());
+ }
+ return super.getCredentials();
+ }
+ };
+ getTestProperties().put(SessionConfig.PROP_TEST_CREDENTIALS_PROVIDER, credentialsProvider);
+
+ // Start repository
+ getRepository();
+
+ return securityManager;
+ }
+
+}
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
index 1385fab..35dbb60 100644
--- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
+++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java
@@ -47,6 +47,7 @@
import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
+import org.eclipse.emf.cdo.common.security.CDOPermission;
import org.eclipse.emf.cdo.common.util.CDOCommonUtil;
import org.eclipse.emf.cdo.common.util.CDOException;
import org.eclipse.emf.cdo.common.util.RepositoryStateChangedEvent;
@@ -938,6 +939,12 @@
addOldValuesToDelta(oldRevision, revisionDelta);
InternalCDORevision newRevision = oldRevision.copy();
+ // If revision as READ permission, temporarily provide WRITE permission to be able to integrate remote changes
+ CDOPermission oldPermission = newRevision.getPermission();
+ if (CDOPermission.READ.equals(oldPermission))
+ {
+ newRevision.setPermission(CDOPermission.WRITE);
+ }
newRevision.adjustForCommit(commitInfo.getBranch(), commitInfo.getTimeStamp());
CDORevisable target = revisionDelta.getTarget();
@@ -948,7 +955,7 @@
revisionDelta.apply(newRevision);
newRevision.freeze();
-
+ newRevision.setPermission(oldPermission);
revisionManager.addRevision(newRevision);
if (oldRevisions == null)
{