blob: 22e91db76417b17d125adc9a289300a41e871f5f [file] [log] [blame]
/*
* Copyright (c) 2012, 2013, 2015, 2016, 2018, 2019 Eike Stepper (Loehne, 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
* Christian W. Damus (CEA LIST) - bug 399306
* Christian W. Damus (CEA LIST) - bug 418454
* Christian W. Damus (CEA LIST) - bug 399487
* Laurent Redor (Obeo) - bug 501607
*/
package org.eclipse.emf.cdo.server.internal.security;
import org.eclipse.emf.cdo.common.branch.CDOBranchPoint;
import org.eclipse.emf.cdo.common.commit.CDOCommitInfo;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.model.CDOModelUtil;
import org.eclipse.emf.cdo.common.model.CDOPackageUnit;
import org.eclipse.emf.cdo.common.protocol.CDOProtocol.CommitNotificationInfo;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionProvider;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.security.CDOPermission;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.eresource.EresourcePackage;
import org.eclipse.emf.cdo.internal.security.PermissionUtil;
import org.eclipse.emf.cdo.internal.security.ViewCreator;
import org.eclipse.emf.cdo.net4j.CDONet4jSession;
import org.eclipse.emf.cdo.net4j.CDONet4jSessionConfiguration;
import org.eclipse.emf.cdo.net4j.CDONet4jUtil;
import org.eclipse.emf.cdo.security.Access;
import org.eclipse.emf.cdo.security.Directory;
import org.eclipse.emf.cdo.security.Group;
import org.eclipse.emf.cdo.security.PatternStyle;
import org.eclipse.emf.cdo.security.Permission;
import org.eclipse.emf.cdo.security.Realm;
import org.eclipse.emf.cdo.security.Role;
import org.eclipse.emf.cdo.security.SecurityFactory;
import org.eclipse.emf.cdo.security.SecurityPackage;
import org.eclipse.emf.cdo.security.User;
import org.eclipse.emf.cdo.security.UserPassword;
import org.eclipse.emf.cdo.security.impl.PermissionImpl;
import org.eclipse.emf.cdo.security.impl.PermissionImpl.CommitImpactContext;
import org.eclipse.emf.cdo.server.CDOServerUtil;
import org.eclipse.emf.cdo.server.IPermissionManager;
import org.eclipse.emf.cdo.server.IRepository;
import org.eclipse.emf.cdo.server.ISession;
import org.eclipse.emf.cdo.server.IStoreAccessor;
import org.eclipse.emf.cdo.server.IStoreAccessor.CommitContext;
import org.eclipse.emf.cdo.server.ITransaction;
import org.eclipse.emf.cdo.server.StoreThreadLocal;
import org.eclipse.emf.cdo.server.internal.security.bundle.OM;
import org.eclipse.emf.cdo.server.spi.security.InternalSecurityManager;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageRegistry;
import org.eclipse.emf.cdo.spi.common.model.InternalCDOPackageUnit;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionManager;
import org.eclipse.emf.cdo.spi.common.revision.ManagedRevisionProvider;
import org.eclipse.emf.cdo.spi.server.InternalCommitContext;
import org.eclipse.emf.cdo.spi.server.InternalRepository;
import org.eclipse.emf.cdo.spi.server.InternalSessionManager;
import org.eclipse.emf.cdo.spi.server.ObjectWriteAccessHandler;
import org.eclipse.emf.cdo.transaction.CDOTransaction;
import org.eclipse.emf.cdo.util.CommitException;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.emf.cdo.view.CDOViewInvalidationEvent;
import org.eclipse.net4j.Net4jUtil;
import org.eclipse.net4j.acceptor.IAcceptor;
import org.eclipse.net4j.connector.IConnector;
import org.eclipse.net4j.util.ArrayUtil;
import org.eclipse.net4j.util.RunnableWithException;
import org.eclipse.net4j.util.StringUtil;
import org.eclipse.net4j.util.WrappedException;
import org.eclipse.net4j.util.collection.HashBag;
import org.eclipse.net4j.util.concurrent.TimeoutRuntimeException;
import org.eclipse.net4j.util.container.ContainerEventAdapter;
import org.eclipse.net4j.util.container.IContainer;
import org.eclipse.net4j.util.container.IManagedContainer;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.ILifecycle;
import org.eclipse.net4j.util.lifecycle.Lifecycle;
import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.om.OMPlatform;
import org.eclipse.net4j.util.om.monitor.Monitor;
import org.eclipse.net4j.util.om.monitor.OMMonitor;
import org.eclipse.net4j.util.security.IAuthenticator;
import org.eclipse.net4j.util.security.IAuthenticator2;
import org.eclipse.net4j.util.security.IPasswordCredentials;
import org.eclipse.net4j.util.security.SecurityUtil;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.spi.cdo.InternalCDOSessionInvalidationEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* @author Eike Stepper
*/
public class SecurityManager extends Lifecycle implements InternalSecurityManager
{
private static final Map<IRepository, InternalSecurityManager> SECURITY_MANAGERS = Collections.synchronizedMap(new HashMap<>());
private static final boolean DISABLE_DETACH_CHECKS = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.server.security.DISABLE_DETACH_CHECKS");
private static final boolean ALLOW_EMPTY_PASSWORDS = OMPlatform.INSTANCE.isProperty("org.eclipse.emf.cdo.server.security.ALLOW_EMPTY_PASSWORDS");
private static final Consumer<String> EMPTY_PASSWORD_PREVENTER = pw -> {
if (StringUtil.isEmpty(pw))
{
throw new SecurityException("Password is empty");
}
};
private static final SecurityFactory SF = SecurityFactory.eINSTANCE;
private final IListener repositoryListener = new LifecycleEventAdapter()
{
@Override
protected void onActivated(ILifecycle lifecycle)
{
init();
}
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
unregister(repository);
SecurityManager.this.deactivate();
}
};
private final IListener sessionManagerListener = new ContainerEventAdapter<ISession>()
{
@Override
protected void onAdded(IContainer<ISession> container, ISession session)
{
sessionAdded(session);
}
@Override
protected void onRemoved(IContainer<ISession> container, ISession session)
{
sessionRemoved(session);
}
};
private final IListener realmInvalidationListener = new IListener()
{
private boolean clearUserInfos;
@Override
public void notifyEvent(IEvent event)
{
if (event instanceof InternalCDOSessionInvalidationEvent)
{
InternalCDOSessionInvalidationEvent e = (InternalCDOSessionInvalidationEvent)event;
if (e.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
{
clearUserInfos = true;
}
}
else if (event instanceof CDOViewInvalidationEvent)
{
if (clearUserInfos)
{
clearUserInfos(true);
clearUserInfos = false;
}
}
}
};
private final IAuthenticator authenticator = new Authenticator();
private final IPermissionManager permissionManager = new PermissionManager();
private final IRepository.WriteAccessHandler writeAccessHandler = new WriteAccessHandler();
private final String realmPath;
private final IManagedContainer container;
private final ConcurrentMap<InternalRepository, SecondaryRepository> secondaryRepositories = new ConcurrentHashMap<>();
private final ConcurrentMap<String, UserInfo> userInfos = new ConcurrentHashMap<>();
private final HashBag<PermissionImpl> permissionBag = new HashBag<>();
private final Object commitHandlerLock = new Object();
private CommitHandler[] commitHandlers = {};
private CommitHandler2[] commitHandlers2 = {};
private PermissionImpl[] permissionArray = {};
private Consumer<String> passwordValidator = ALLOW_EMPTY_PASSWORDS ? null : EMPTY_PASSWORD_PREVENTER;
private InternalRepository repository;
private IAcceptor acceptor;
private IConnector connector;
private CDONet4jSession realmSession;
private CDOView realmView;
private Realm realm;
private CDOID realmID;
private volatile Long lastRealmModification;
private Object lastRealmModificationLock = new Object();
public SecurityManager(String realmPath, IManagedContainer container)
{
this.realmPath = realmPath;
this.container = container;
}
@Override
public final IManagedContainer getContainer()
{
return container;
}
@Override
public final String getRealmPath()
{
return realmPath;
}
@Override
public final InternalRepository getRepository()
{
return repository;
}
@Override
public void setRepository(InternalRepository repository)
{
this.repository = repository;
if (isActive())
{
init();
}
}
@Override
public InternalRepository[] getSecondaryRepositories()
{
return secondaryRepositories.keySet().toArray(new InternalRepository[0]);
}
@Override
public void addSecondaryRepository(InternalRepository repository)
{
secondaryRepositories.computeIfAbsent(repository, k -> {
return new SecondaryRepository(k);
});
}
@Override
public void removeSecondaryRepository(InternalRepository repository)
{
SecondaryRepository secondaryRepository = secondaryRepositories.remove(repository);
if (secondaryRepository != null)
{
secondaryRepository.dispose();
}
}
public Consumer<String> getPasswordValidator()
{
return passwordValidator;
}
public void setPasswordValidator(Consumer<String> passwordValidator)
{
checkInactive();
this.passwordValidator = passwordValidator;
}
@Override
public Realm getRealm()
{
return realm;
}
@Override
public Role getRole(String id)
{
Role item = realm.getRole(id);
if (item == null)
{
throw new SecurityException("Role " + id + " not found");
}
return item;
}
@Override
public Group getGroup(String id)
{
Group item = realm.getGroup(id);
if (item == null)
{
throw new SecurityException("Group " + id + " not found");
}
return item;
}
@Override
public User getUser(String id)
{
User item = realm.getUser(id);
if (item == null)
{
throw new SecurityException("User " + id + " not found");
}
return item;
}
@Override
public Role addRole(String id)
{
Role[] result = { null };
modify(realm -> result[0] = realm.addRole(id));
return result[0];
}
@Override
public Group addGroup(String id)
{
Group[] result = { null };
modify(realm -> result[0] = realm.addGroup(id));
return result[0];
}
@Override
public User addUser(String id)
{
User[] result = { null };
modify(realm -> result[0] = realm.addUser(id));
return result[0];
}
@Override
public User addUser(String id, String password)
{
User[] result = { null };
modify(realm -> {
result[0] = realm.addUser(id);
if (!StringUtil.isEmpty(password))
{
UserPassword userPassword = SF.createUserPassword();
userPassword.setEncrypted(new String(password));
result[0].setPassword(userPassword);
}
});
return result[0];
}
@Override
public User addUser(IPasswordCredentials credentials)
{
return addUser(credentials.getUserID(), SecurityUtil.toString(credentials.getPassword()));
}
@Override
public User setPassword(String id, String password)
{
if (passwordValidator != null)
{
passwordValidator.accept(password);
}
User[] result = { null };
modify(realm -> result[0] = realm.setPassword(id, password));
return result[0];
}
@Override
public Role removeRole(String id)
{
Role[] result = { null };
modify(realm -> result[0] = realm.removeRole(id));
return result[0];
}
@Override
public Group removeGroup(String id)
{
Group[] result = { null };
modify(realm -> result[0] = realm.removeGroup(id));
return result[0];
}
@Override
public User removeUser(String id)
{
User[] result = { null };
modify(realm -> result[0] = realm.removeUser(id));
return result[0];
}
@Override
public void read(RealmOperation operation)
{
checkReady();
operation.execute(realm);
}
@Override
public void modify(RealmOperation operation)
{
modify(operation, false);
}
@Override
public void modify(RealmOperation operation, boolean waitUntilReadable)
{
modifyWithInfo(operation, waitUntilReadable);
}
@Override
public CDOCommitInfo modifyWithInfo(RealmOperation operation, boolean waitUntilReadable)
{
checkReady();
CDOTransaction transaction = realmSession.openTransaction();
try
{
Realm transactionRealm = transaction.getObject(realm);
operation.execute(transactionRealm);
CDOCommitInfo info = transaction.commit();
if (waitUntilReadable)
{
if (!realmView.waitForUpdate(info.getTimeStamp(), 10000))
{
throw new TimeoutRuntimeException();
}
}
return info;
}
catch (CommitException ex)
{
throw WrappedException.wrap(ex);
}
finally
{
transaction.close();
}
}
@Override
public CommitHandler[] getCommitHandlers()
{
return commitHandlers;
}
@Override
public CommitHandler2[] getCommitHandlers2()
{
return commitHandlers2;
}
@Override
public void addCommitHandler(CommitHandler handler)
{
checkInactive();
synchronized (commitHandlerLock)
{
commitHandlers = ArrayUtil.add(commitHandlers, handler);
if (handler instanceof CommitHandler2)
{
commitHandlers2 = ArrayUtil.add(commitHandlers2, (CommitHandler2)handler);
}
}
}
@Override
public void removeCommitHandler(CommitHandler handler)
{
checkInactive();
synchronized (commitHandlerLock)
{
commitHandlers = ArrayUtil.remove(commitHandlers, handler);
if (handler instanceof CommitHandler2)
{
commitHandlers2 = ArrayUtil.remove(commitHandlers2, (CommitHandler2)handler);
}
}
}
protected void initCommitHandlers(boolean firstTime)
{
CommitHandler[] handlers = getCommitHandlers();
for (int i = 0; i < handlers.length; i++)
{
CommitHandler handler = handlers[i];
try
{
handler.init(this, firstTime);
OM.LOG.info("Security realm handled by " + handler);
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
protected void handleCommit(CommitContext commitContext, User user)
{
CommitHandler[] handlers = getCommitHandlers();
for (int i = 0; i < handlers.length; i++)
{
CommitHandler handler = handlers[i];
try
{
handler.handleCommit(this, commitContext, user);
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
protected void handleCommitted(CommitContext commitContext)
{
CommitHandler2[] handlers = getCommitHandlers2();
for (int i = 0; i < handlers.length; i++)
{
CommitHandler2 handler = handlers[i];
try
{
handler.handleCommitted(this, commitContext);
}
catch (Exception ex)
{
OM.LOG.error(ex);
}
}
}
/**
* Commit-handlers can call back into the security manager to read/modify the realm
* while the security manager is in the process of initializing, so cannot strictly
* check for active state to assert that we are ready.
*/
protected void checkReady()
{
if (realm == null || realmSession == null)
{
// If I have no realm or session, I am probably inactive, so this will throw
checkActive();
}
}
protected void init()
{
if (realm != null)
{
// Already initialized
return;
}
if (repository == null)
{
// Cannot initialize
return;
}
repository.addListener(repositoryListener);
if (!LifecycleUtil.isActive(repository))
{
// Cannot initialize now
return;
}
String repositoryName = repository.getName();
String acceptorName = repositoryName + "_security";
acceptor = Net4jUtil.getAcceptor(container, "jvm", acceptorName);
connector = Net4jUtil.getConnector(container, "jvm", acceptorName);
CDONet4jSessionConfiguration config = CDONet4jUtil.createNet4jSessionConfiguration();
config.setConnector(connector);
config.setRepositoryName(repositoryName);
config.setUserID(SYSTEM_USER_ID);
realmSession = config.openNet4jSession();
realmSession.options().setGeneratedPackageEmulationEnabled(true);
realmSession.addListener(realmInvalidationListener);
CDOTransaction initialTransaction = realmSession.openTransaction();
boolean firstTime = !initialTransaction.hasResource(realmPath);
if (firstTime)
{
realm = createRealm();
CDOResource resource = initialTransaction.createResource(realmPath);
resource.getContents().add(realm);
OM.LOG.info("Security realm created in " + realmPath);
}
else
{
CDOResource resource = initialTransaction.getResource(realmPath);
realm = (Realm)resource.getContents().get(0);
OM.LOG.info("Security realm loaded from " + realmPath);
}
try
{
initialTransaction.commit();
}
catch (Exception ex)
{
throw WrappedException.wrap(ex);
}
finally
{
initialTransaction.close();
}
realmView = realmSession.openView();
realmView.addListener(realmInvalidationListener);
realm = realmView.getObject(realm);
realmID = realm.cdoID();
InternalSessionManager sessionManager = repository.getSessionManager();
sessionManager.setAuthenticator(authenticator);
sessionManager.setPermissionManager(permissionManager);
sessionManager.addListener(sessionManagerListener);
repository.addHandler(writeAccessHandler);
register(repository);
initCommitHandlers(firstTime);
}
protected Realm createRealm()
{
Realm realm = SF.createRealm("Security Realm");
realm.setDefaultRoleDirectory(addDirectory(realm, Directory.ROLES));
realm.setDefaultGroupDirectory(addDirectory(realm, Directory.GROUPS));
realm.setDefaultUserDirectory(addDirectory(realm, Directory.USERS));
// Create roles
Role allReaderRole = realm.addRole(Role.ALL_OBJECTS_READER);
allReaderRole.getPermissions().add(SF.createFilterPermission(Access.READ, SF.createResourceFilter(".*", PatternStyle.REGEX)));
Role allWriterRole = realm.addRole(Role.ALL_OBJECTS_WRITER);
allWriterRole.getPermissions().add(SF.createFilterPermission(Access.WRITE, SF.createResourceFilter(".*", PatternStyle.REGEX)));
Role normalReaderRole = realm.addRole(Role.NORMAL_OBJECTS_READER);
normalReaderRole.getPermissions()
.add(SF.createFilterPermission(Access.READ, SF.createNotFilter(SF.createResourceFilter(realmPath, PatternStyle.EXACT, false))));
Role normalWriterRole = realm.addRole(Role.NORMAL_OBJECTS_WRITER);
normalWriterRole.getPermissions()
.add(SF.createFilterPermission(Access.WRITE, SF.createNotFilter(SF.createResourceFilter(realmPath, PatternStyle.EXACT, false))));
Role treeReaderRole = realm.addRole(Role.RESOURCE_TREE_READER);
treeReaderRole.getPermissions().add(SF.createFilterPermission(Access.READ, SF.createPackageFilter(EresourcePackage.eINSTANCE)));
Role treeWriterRole = realm.addRole(Role.RESOURCE_TREE_WRITER);
treeWriterRole.getPermissions().add(SF.createFilterPermission(Access.WRITE, SF.createPackageFilter(EresourcePackage.eINSTANCE)));
Role adminRole = realm.addRole(Role.ADMINISTRATION);
adminRole.getPermissions().add(SF.createFilterPermission(Access.WRITE, SF.createResourceFilter(realmPath, PatternStyle.EXACT, false)));
adminRole.getPermissions().add(SF.createFilterPermission(Access.READ, SF.createResourceFilter(realmPath, PatternStyle.EXACT, true)));
// Create groups
Group adminsGroup = realm.addGroup(Group.ADMINISTRATORS);
adminsGroup.getRoles().add(adminRole);
realm.addGroup(Directory.USERS);
// Create users
User adminUser = realm.addUser(User.ADMINISTRATOR, "0000");
adminUser.getGroups().add(adminsGroup);
return realm;
}
protected Directory addDirectory(Realm realm, String name)
{
Directory directory = SF.createDirectory(name);
realm.getItems().add(directory);
return directory;
}
protected CDOPermission convertPermission(Access access)
{
if (access != null)
{
switch (access)
{
case READ:
return CDOPermission.READ;
case WRITE:
return CDOPermission.WRITE;
}
}
return CDOPermission.NONE;
}
protected CDOPermission authorize(CDORevision revision, CDORevisionProvider revisionProvider, CDOBranchPoint securityContext, ISession session,
Access defaultAccess, Permission[] permissions)
{
waitForRealmUpdate(securityContext);
boolean setUser = defaultAccess == null;
if (setUser)
{
UserInfo userInfo = getUserInfo(session);
User user = userInfo.getUser();
defaultAccess = user.getDefaultAccess();
permissions = userInfo.getPermissions();
PermissionUtil.setUser(user.getId());
}
try
{
CDOPermission result = convertPermission(defaultAccess);
if (result == CDOPermission.WRITE)
{
return result;
}
for (int i = 0; i < permissions.length; i++)
{
Permission permission = permissions[i];
CDOPermission p = convertPermission(permission.getAccess());
if (p.ordinal() <= result.ordinal())
{
// Avoid expensive calls to Permission.isApplicable() if the permission wouldn't increase
continue;
}
if (permission.isApplicable(revision, revisionProvider, securityContext))
{
result = p;
if (result == CDOPermission.WRITE)
{
return result;
}
}
}
return result;
}
finally
{
if (setUser)
{
PermissionUtil.setUser(null);
}
}
}
protected void authorizeCommit(CommitContext commitContext, UserInfo userInfo)
{
PermissionUtil.setUser(userInfo.getUserId());
PermissionUtil.initViewCreation(xxx -> CDOServerUtil.openView(commitContext));
try
{
CDOBranchPoint securityContext = commitContext.getBranchPoint();
ITransaction transaction = commitContext.getTransaction();
ISession session = transaction.getSession();
Access userDefaultAccess = userInfo.getDefaultAccess();
Permission[] userPermissions = userInfo.getPermissions();
if (!DISABLE_DETACH_CHECKS)
{
CDOID[] detachedObjects = commitContext.getDetachedObjects();
if (detachedObjects != null && detachedObjects.length != 0)
{
for (int i = 0; i < detachedObjects.length; i++)
{
CDOID id = detachedObjects[i];
CDORevision revision = transaction.getRevision(id);
CDOPermission permission = authorize(revision, transaction, securityContext, session, userDefaultAccess, userPermissions);
if (permission != CDOPermission.WRITE)
{
throw new SecurityException("User " + commitContext.getUserID() + " is not allowed to detach " + revision);
}
}
}
}
InternalCDORevision[] revisions = commitContext.getDirtyObjects();
InternalCDORevisionDelta[] revisionDeltas = commitContext.getDirtyObjectDeltas();
// Check permissions on the commit changes and detect realm modifications
byte securityImpact = CommitNotificationInfo.IMPACT_NONE;
for (int i = 0; i < revisions.length; i++)
{
InternalCDORevision revision = revisions[i];
CDOPermission permission = authorize(revision, commitContext, securityContext, session, userDefaultAccess, userPermissions);
if (permission != CDOPermission.WRITE)
{
throw new SecurityException("User " + commitContext.getUserID() + " is not allowed to write to " + revision);
}
if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
{
InternalCDORevisionDelta revisionDelta = revisionDeltas[i];
if (CDORevisionUtil.isContained(revisionDelta.getID(), realmID, transaction)) // Use "before commit" state
{
securityImpact = CommitNotificationInfo.IMPACT_REALM;
}
}
}
// Determine permissions that are impacted by the commit changes
Set<Permission> impactedRules = null;
if (securityImpact != CommitNotificationInfo.IMPACT_REALM)
{
PermissionImpl[] assignedPermissions = permissionArray; // Thread-safe
if (assignedPermissions.length != 0)
{
CommitImpactContext commitImpactContext = new PermissionImpl.CommitImpactContext()
{
@Override
public CDORevision[] getNewObjects()
{
return commitContext.getNewObjects();
}
@Override
public CDORevision[] getDirtyObjects()
{
return revisions;
}
@Override
public CDORevisionDelta[] getDirtyObjectDeltas()
{
return revisionDeltas;
}
@Override
public CDOID[] getDetachedObjects()
{
return commitContext.getDetachedObjects();
}
};
for (int i = 0; i < assignedPermissions.length; i++)
{
PermissionImpl permission = assignedPermissions[i];
if (permission.isImpacted(commitImpactContext))
{
if (impactedRules == null)
{
impactedRules = new HashSet<>();
}
impactedRules.add(permission);
}
}
if (impactedRules != null)
{
securityImpact = CommitNotificationInfo.IMPACT_PERMISSIONS;
}
}
}
((InternalCommitContext)commitContext).setSecurityImpact(securityImpact, impactedRules);
}
finally
{
PermissionUtil.setUser(null);
PermissionUtil.doneViewCreation();
}
}
protected void sessionAdded(ISession session)
{
String userID = session.getUserID();
if (userID != null)
{
UserInfo result = userInfos.computeIfAbsent(userID, k -> {
User user = getUser(k);
UserInfo userInfo = new UserInfo(user);
updatePermissions(userInfo, true);
return userInfo;
});
result.addSessionRef();
}
}
protected void sessionRemoved(ISession session)
{
String userID = session.getUserID();
if (userID != null)
{
userInfos.computeIfPresent(userID, (k, v) -> {
if (v.removeSessionRef())
{
return null;
}
return v;
});
}
}
protected UserInfo getUserInfo(ISession session)
{
String userID = session.getUserID();
UserInfo userInfo = userInfos.get(userID);
if (userInfo == null)
{
throw new IllegalStateException("No user info for " + userID);
}
return userInfo;
}
protected void clearUserInfos(boolean rebuild)
{
synchronized (permissionBag)
{
permissionBag.clear();
permissionArray = null;
if (rebuild)
{
for (UserInfo userInfo : userInfos.values())
{
userInfo.rebuildPermissions();
updatePermissions(userInfo, false);
}
updatePermissions(null, true);
}
else
{
userInfos.clear();
}
}
}
protected void updatePermissions(UserInfo userInfo, boolean updateArray)
{
Permission[] permissions = userInfo == null ? null : userInfo.getPermissions();
synchronized (permissionBag)
{
if (permissions != null)
{
for (int i = 0; i < permissions.length; i++)
{
Permission permission = permissions[i];
permissionBag.add((PermissionImpl)permission);
}
}
if (updateArray)
{
// Atomic update
permissionArray = permissionBag.toArray(new PermissionImpl[permissionBag.size()]);
}
}
}
protected final boolean isAdministrator(User user)
{
// An administrator is one that has write permission on the realm resource
Realm realm = getRealm();
if (realm != null)
{
// Can't be an administrator if there isn't a realm
CDORevision revision = realm.cdoRevision();
CDORevisionProvider revisionProvider = realm.cdoView();
CDOBranchPoint securityContext = realm.cdoView();
for (Permission permission : user.getAllPermissions())
{
if (permission.getAccess() == Access.WRITE && permission.isApplicable(revision, revisionProvider, securityContext))
{
return true;
}
}
}
return false;
}
@Override
protected void doActivate() throws Exception
{
super.doActivate();
init();
}
@Override
protected void doDeactivate() throws Exception
{
clearUserInfos(false);
realm = null;
realmID = null;
realmSession.close();
realmSession = null;
realmView = null;
connector.close();
connector = null;
acceptor.close();
acceptor = null;
super.doDeactivate();
}
private void rememberRealmCommit(CDOBranchPoint commitBranchPoint)
{
synchronized (lastRealmModificationLock)
{
lastRealmModification = commitBranchPoint.getTimeStamp();
}
}
private void waitForRealmUpdate(CDOBranchPoint securityContext)
{
if (lastRealmModification != null)
{
long updateTime;
synchronized (lastRealmModificationLock)
{
if (lastRealmModification != null)
{
updateTime = lastRealmModification;
lastRealmModification = null;
}
else
{
updateTime = CDOBranchPoint.UNSPECIFIED_DATE;
}
}
if (updateTime != CDOBranchPoint.UNSPECIFIED_DATE)
{
long contextTime = securityContext.getTimeStamp();
if (contextTime == CDOBranchPoint.UNSPECIFIED_DATE || contextTime < updateTime)
{
if (!realmView.waitForUpdate(updateTime, 10000L))
{
throw new TimeoutRuntimeException();
}
}
}
}
}
private void register(InternalRepository repository)
{
if (SECURITY_MANAGERS.putIfAbsent(repository, this) != null)
{
throw new IllegalStateException("A security manager is already associated with repository " + repository);
}
}
private void unregister(InternalRepository repository)
{
SECURITY_MANAGERS.remove(repository);
}
public static InternalSecurityManager get(IRepository repository)
{
return SECURITY_MANAGERS.get(repository);
}
/**
* @author Eike Stepper
*/
private static final class UserInfo extends AtomicInteger
{
private static final long serialVersionUID = 1L;
private final User user;
private Permission[] permissions;
public UserInfo(User user)
{
this.user = user;
rebuildPermissions();
}
public User getUser()
{
return user;
}
public String getUserId()
{
return user.getId();
}
public Access getDefaultAccess()
{
return user.getDefaultAccess();
}
public Permission[] getPermissions()
{
return permissions;
}
public void rebuildPermissions()
{
EList<Permission> allPermissions = user.getAllPermissions();
permissions = allPermissions.toArray(new Permission[allPermissions.size()]);
}
public synchronized void addSessionRef()
{
incrementAndGet();
}
public synchronized boolean removeSessionRef()
{
return decrementAndGet() == 0;
}
@Override
public String toString()
{
return "UserInfo[user=" + getUserId() + ", refs=" + super.toString() + "]";
}
}
/**
* @author Eike Stepper
*/
private final class Authenticator implements IAuthenticator2
{
public Authenticator()
{
}
@Override
public void authenticate(String userID, char[] password) throws SecurityException
{
User user = getUser(userID);
if (!user.isLocked())
{
UserPassword userPassword = user.getPassword();
String encrypted = userPassword == null ? null : userPassword.getEncrypted();
if (Arrays.equals(password, SecurityUtil.toCharArray(encrypted)))
{
// Access granted.
return;
}
}
throw new SecurityException("Access denied"); //$NON-NLS-1$
}
@Override
public void updatePassword(String userID, char[] oldPassword, char[] newPassword)
{
authenticate(userID, oldPassword);
setPassword(userID, SecurityUtil.toString(newPassword));
}
@Override
public void resetPassword(String adminID, char[] adminPassword, String userID, char[] newPassword)
{
authenticate(adminID, adminPassword);
User admin = getUser(adminID);
if (!SecurityManager.this.isAdministrator(admin))
{
throw new SecurityException("Password reset requires administrator privilege"); //$NON-NLS-1$
}
setPassword(userID, SecurityUtil.toString(newPassword));
}
@Override
public boolean isAdministrator(String userID)
{
Realm realm = getRealm();
if (realm != null)
{
// Can't be an administrator if there isn't a realm
// (but then where did we get the user ID?)
User user = realm.getUser(userID);
return user != null && SecurityManager.this.isAdministrator(user);
}
return false;
}
}
/**
* @author Eike Stepper
*/
private final class PermissionManager implements IPermissionManager
{
public PermissionManager()
{
}
@Override
@Deprecated
public CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext, String userID)
{
throw new UnsupportedOperationException();
}
@Override
public CDOPermission getPermission(CDORevision revision, CDOBranchPoint securityContext, ISession session)
{
String userID = session.getUserID();
if (SYSTEM_USER_ID.equals(userID))
{
return CDOPermission.WRITE;
}
if (revision.getEClass() == SecurityPackage.Literals.USER_PASSWORD)
{
return CDOPermission.NONE;
}
InternalCDORevisionManager revisionManager = repository.getRevisionManager();
CDORevisionProvider revisionProvider = new ManagedRevisionProvider(revisionManager, securityContext);
PermissionUtil.initViewCreation(new ViewCreator()
{
@Override
public CDOView createView(CDORevisionProvider revisionProvider)
{
CDOView view = CDOServerUtil.openView(session, securityContext, revisionProvider);
view.getSession().options().setGeneratedPackageEmulationEnabled(true);
return view;
}
});
try
{
CDOPermission permission = authorize(revision, revisionProvider, securityContext, session, null, null);
// System.out.println("Loading from " + session + ": " + permission + " --> " + revision);
return permission;
}
finally
{
PermissionUtil.doneViewCreation();
}
}
@Override
public boolean hasAnyRule(ISession session, Set<? extends Object> rules)
{
String userID = session.getUserID();
if (SYSTEM_USER_ID.equals(userID))
{
return false;
}
UserInfo userInfo = getUserInfo(session);
Permission[] userPermissions = userInfo.getPermissions();
for (int i = 0; i < userPermissions.length; i++)
{
Permission userPermission = userPermissions[i];
if (rules.contains(userPermission))
{
return true;
}
}
return false;
}
}
/**
* @author Eike Stepper
*/
private final class WriteAccessHandler implements IRepository.WriteAccessHandler
{
private final IRepository.WriteAccessHandler realmValidationHandler = new RealmValidationHandler();
public WriteAccessHandler()
{
}
@Override
public void handleTransactionBeforeCommitting(ITransaction transaction, CommitContext commitContext, OMMonitor monitor) throws RuntimeException
{
if (transaction.getSessionID() == realmSession.getSessionID())
{
// Access through ISecurityManager.modify(RealmOperation)
handleCommit(commitContext, null);
((InternalCommitContext)commitContext).setSecurityImpact(CommitNotificationInfo.IMPACT_REALM, null);
return;
}
UserInfo userInfo = getUserInfo(transaction.getSession());
handleCommit(commitContext, userInfo.getUser());
authorizeCommit(commitContext, userInfo);
if (commitContext.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
{
// Validate changes to the realm
realmValidationHandler.handleTransactionBeforeCommitting(transaction, commitContext, monitor);
}
}
@Override
public void handleTransactionAfterCommitted(ITransaction transaction, CommitContext commitContext, OMMonitor monitor)
{
if (commitContext.getSecurityImpact() == CommitNotificationInfo.IMPACT_REALM)
{
CDOBranchPoint commitBranchPoint = commitContext.getBranchPoint();
rememberRealmCommit(commitBranchPoint);
}
handleCommitted(commitContext);
}
}
/**
* A write-access handler that checks changes about to be committed to the security realm
* against its well-formedness rules, and rejects the commit if there are any integrity
* errors.
*
* @author Christian W. Damus (CEA LIST)
*/
private final class RealmValidationHandler extends ObjectWriteAccessHandler
{
private final EValidator realmValidator = EValidator.Registry.INSTANCE.getEValidator(SecurityPackage.eINSTANCE);
public RealmValidationHandler()
{
}
@Override
protected void handleTransactionBeforeCommitting(OMMonitor monitor) throws RuntimeException
{
BasicDiagnostic diagnostic = new BasicDiagnostic();
Map<Object, Object> context = createValidationContext();
boolean realmChecked = false;
for (EObject object : getDirtyObjects())
{
if (object.eClass().getEPackage() == SecurityPackage.eINSTANCE)
{
validate(object, diagnostic, context);
realmChecked |= object instanceof Realm;
}
}
for (EObject object : getNewObjects())
{
if (object.eClass().getEPackage() == SecurityPackage.eINSTANCE)
{
validate(object, diagnostic, context);
// The realm cannot be new
}
}
if (!realmChecked)
{
// Check it, because it has some wide-ranging integrity constraints
validate(getView().getObject(realmID), diagnostic, context);
}
}
protected Map<Object, Object> createValidationContext()
{
Map<Object, Object> result = new java.util.HashMap<>();
CommitContext commitContext = getCommitContext();
// Supply the revision-provider and branch point required by realm validation
result.put(CDORevisionProvider.class, commitContext);
result.put(CDOBranchPoint.class, commitContext.getBranchPoint());
return result;
}
protected void validate(EObject object, DiagnosticChain diagnostics, Map<Object, Object> context)
{
realmValidator.validate(object, diagnostics, context);
Diagnostic error = getError(diagnostics);
if (error != null)
{
throw new TransactionValidationException("Security realm integrity violation: " + error.getMessage());
}
}
protected Diagnostic getError(DiagnosticChain diagnostics)
{
Diagnostic diagnostic = (Diagnostic)diagnostics;
if (diagnostic.getSeverity() >= Diagnostic.ERROR)
{
for (Diagnostic child : diagnostic.getChildren())
{
if (child.getSeverity() >= Diagnostic.ERROR)
{
return child;
}
}
}
return null;
}
}
/**
* @author Eike Stepper
*/
private final class SecondaryRepository extends LifecycleEventAdapter implements IRepository.WriteAccessHandler
{
private final InternalRepository delegate;
public SecondaryRepository(InternalRepository delegate)
{
this.delegate = delegate;
InternalSessionManager sessionManager = delegate.getSessionManager();
sessionManager.setAuthenticator(authenticator);
sessionManager.setPermissionManager(permissionManager);
sessionManager.addListener(sessionManagerListener);
delegate.addListener(this);
delegate.addHandler(this);
register(delegate);
}
@Override
public void handleTransactionBeforeCommitting(ITransaction transaction, CommitContext commitContext, OMMonitor monitor) throws RuntimeException
{
handleNewPackageUnits(commitContext);
UserInfo userInfo = getUserInfo(transaction.getSession());
authorizeCommit(commitContext, userInfo);
}
@Override
public void handleTransactionAfterCommitted(ITransaction transaction, CommitContext commitContext, OMMonitor monitor)
{
// Do nothing.
}
public void dispose()
{
unregister(delegate);
}
@Override
protected void onDeactivated(ILifecycle lifecycle)
{
removeSecondaryRepository(delegate);
}
private void handleNewPackageUnits(CommitContext commitContext)
{
InternalCDOPackageUnit[] newPackageUnits = commitContext.getNewPackageUnits();
if (newPackageUnits != null && newPackageUnits.length != 0)
{
InternalCDOPackageRegistry realmPackageRegistry = getRepository().getPackageRegistry();
List<InternalCDOPackageUnit> unknownPackageUnits = new ArrayList<>();
for (InternalCDOPackageUnit packageUnit : newPackageUnits)
{
String nsURI = packageUnit.getID();
if (!realmPackageRegistry.containsKey(nsURI))
{
unknownPackageUnits.add((InternalCDOPackageUnit)CDOModelUtil.copyPackageUnit(packageUnit));
}
}
if (!unknownPackageUnits.isEmpty())
{
InternalCDOPackageUnit[] unitArray = unknownPackageUnits.toArray(new InternalCDOPackageUnit[unknownPackageUnits.size()]);
try
{
RunnableWithException.forkAndWait(() -> {
synchronized (realmPackageRegistry)
{
realmPackageRegistry.putPackageUnits(unitArray, CDOPackageUnit.State.LOADED);
}
commitRealmPackageUnits(unitArray);
});
}
catch (Exception ex)
{
throw WrappedException.wrap(ex);
}
}
}
}
private void commitRealmPackageUnits(InternalCDOPackageUnit[] packageUnits)
{
IStoreAccessor writer = getRepository().getStore().getWriter(null);
StoreThreadLocal.setAccessor(writer);
try
{
writer.writePackageUnits(packageUnits, new Monitor());
writer.commit(new Monitor());
}
finally
{
StoreThreadLocal.release();
}
}
}
}