blob: df2e7b788c7221e55bf868782c063a2cfbd21358 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2015 EclipseSource Muenchen GmbH 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:
* Edgar Mueller - initial API and implementation
******************************************************************************/
package org.eclipse.emf.emfstore.internal.server.accesscontrol;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.emfstore.internal.common.APIUtil;
import org.eclipse.emf.emfstore.internal.server.ServerConfiguration;
import org.eclipse.emf.emfstore.internal.server.core.MonitorProvider;
import org.eclipse.emf.emfstore.internal.server.exceptions.AccessControlException;
import org.eclipse.emf.emfstore.internal.server.exceptions.SessionTimedOutException;
import org.eclipse.emf.emfstore.internal.server.model.ProjectHistory;
import org.eclipse.emf.emfstore.internal.server.model.ProjectId;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.ACGroup;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.ACOrgUnit;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.ACOrgUnitId;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.ACUser;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.roles.ProjectAdminRole;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.roles.Role;
import org.eclipse.emf.emfstore.internal.server.model.accesscontrol.roles.ServerAdmin;
import org.eclipse.emf.emfstore.internal.server.model.impl.api.ESGroupImpl;
import org.eclipse.emf.emfstore.internal.server.model.impl.api.ESProjectHistoryImpl;
import org.eclipse.emf.emfstore.internal.server.model.impl.api.ESUserImpl;
import org.eclipse.emf.emfstore.server.auth.ESAuthorizationService;
import org.eclipse.emf.emfstore.server.auth.ESMethod.MethodId;
import org.eclipse.emf.emfstore.server.auth.ESMethodInvocation;
import org.eclipse.emf.emfstore.server.auth.ESOrgUnitResolver;
import org.eclipse.emf.emfstore.server.auth.ESProjectAdminPrivileges;
import org.eclipse.emf.emfstore.server.auth.ESSessions;
import org.eclipse.emf.emfstore.server.model.ESGlobalProjectId;
import org.eclipse.emf.emfstore.server.model.ESGroup;
import org.eclipse.emf.emfstore.server.model.ESOrgUnit;
import org.eclipse.emf.emfstore.server.model.ESOrgUnitId;
import org.eclipse.emf.emfstore.server.model.ESOrgUnitProvider;
import org.eclipse.emf.emfstore.server.model.ESProjectHistory;
import org.eclipse.emf.emfstore.server.model.ESSessionId;
import org.eclipse.emf.emfstore.server.model.ESUser;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
/**
* Default implementation of the {@link ESAuthorizationService}.
*
* @author emueller
*
*/
public class DefaultESAuthorizationService implements ESAuthorizationService {
/**
* Contains possible access levels.
*/
private enum AccessLevel {
PROJECT_READ, PROJECT_WRITE, PROJECT_ADMIN, SERVER_ADMIN, NONE
}
private EnumMap<MethodId, AccessLevel> accessMap;
private ESSessions sessions;
private ESOrgUnitResolver orgUnitResolver;
private ESOrgUnitProvider orgUnitProvider;
private final Predicate<Role> isServerAdminPredicate = new HasRolePredicate(ServerAdmin.class);
private final Predicate<Role> isProjectAdminPredicate = new HasRolePredicate(ProjectAdminRole.class);
private void initAccessMap() {
if (accessMap != null) {
return;
}
accessMap = new EnumMap<MethodId, AccessLevel>(MethodId.class);
addAccessMapping(AccessLevel.NONE,
MethodId.GETVERSION);
addAccessMapping(AccessLevel.PROJECT_READ,
MethodId.GETPROJECT,
MethodId.GETEMFPROPERTIES,
MethodId.GETHISTORYINFO,
MethodId.GETCHANGES,
MethodId.RESOLVEVERSIONSPEC,
MethodId.DOWNLOADFILECHUNK,
MethodId.DOWNLOADCHANGEPACKAGEFRAGMENT);
addAccessMapping(AccessLevel.PROJECT_WRITE,
MethodId.SETEMFPROPERTIES,
MethodId.TRANSMITPROPERTY,
MethodId.UPLOADFILECHUNK,
MethodId.CREATEVERSION,
MethodId.UPLOADCHANGEPACKAGEFRAGMENT,
MethodId.GETBRANCHES);
addAccessMapping(AccessLevel.PROJECT_ADMIN,
MethodId.DELETEPROJECT,
MethodId.REMOVETAG,
MethodId.ADDTAG);
addAccessMapping(AccessLevel.SERVER_ADMIN,
MethodId.IMPORTPROJECTHISTORYTOSERVER,
MethodId.EXPORTPROJECTHISTORYFROMSERVER,
MethodId.REGISTEREPACKAGE);
// TODO: extract
if (ServerConfiguration.isProjectAdminPrivileg(ESProjectAdminPrivileges.ShareProject)) {
addAccessMapping(AccessLevel.PROJECT_ADMIN,
MethodId.CREATEPROJECT,
MethodId.CREATEEMPTYPROJECT);
} else {
addAccessMapping(AccessLevel.SERVER_ADMIN,
MethodId.CREATEPROJECT,
MethodId.CREATEEMPTYPROJECT);
}
addAccessMapping(AccessLevel.NONE, MethodId.GETPROJECTLIST, MethodId.RESOLVEUSER);
}
private void addAccessMapping(AccessLevel type, MethodId... operationTypes) {
for (final MethodId opType : operationTypes) {
accessMap.put(opType, type);
}
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkProjectAdminAccess(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESGlobalProjectId)
*/
public boolean checkProjectAdminAccess(ESSessionId sessionId, ESGlobalProjectId globalProjectId)
throws AccessControlException {
checkSession(sessionId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final Iterable<Role> roles = getAllRoles(user.getId().toAPI());
if (Iterables.any(roles, isServerAdminPredicate)) {
return true;
}
final ProjectId projectId = APIUtil.toInternal(ProjectId.class, globalProjectId);
for (final Role role : roles) {
if (projectId == null && ProjectAdminRole.class.isInstance(role)) {
return false;
}
if (role.canAdministrate(projectId)) {
return false;
}
}
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkServerAdminAccess(org.eclipse.emf.emfstore.server.model.ESSessionId)
*/
public void checkServerAdminAccess(ESSessionId sessionId) throws AccessControlException {
checkSession(sessionId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final List<Role> rolesFromGroups = APIUtil.toInternal(orgUnitResolver.getRolesFromGroups(user.toAPI()));
final Iterable<Role> roles = Iterables.concat(user.getRoles(), rolesFromGroups);
if (Iterables.any(roles, isServerAdminPredicate)) {
return;
}
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkProjectAdminAccessForOrgUnit(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESOrgUnitId)
*/
public boolean checkProjectAdminAccessForOrgUnit(ESSessionId sessionId, ESOrgUnitId orgUnitId)
throws AccessControlException {
checkSession(sessionId);
cleanupPARole(orgUnitId);
final List<Role> allRoles = getAllRoles(orgUnitId);
final Set<ProjectId> involvedProjects = new LinkedHashSet<ProjectId>();
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final boolean hasServerAdminRole = Iterables.any(user.getRoles(), isServerAdminPredicate);
for (final Role role : allRoles) {
if ((isServerAdminPredicate.apply(role) || isProjectAdminPredicate.apply(role))
&& !hasServerAdminRole) {
throw new AccessControlException(Messages.AccessControlImpl_Not_Allowed_To_Remove_Other_Admin);
}
involvedProjects.addAll(role.getProjects());
}
final Set<ESGlobalProjectId> globalIds = new LinkedHashSet<ESGlobalProjectId>();
for (final ProjectId projectId : involvedProjects) {
globalIds.add(projectId.toAPI());
}
return checkProjectAdminAccessForOrgUnit(
sessionId,
orgUnitId,
globalIds);
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkProjectAdminAccessForOrgUnit(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESOrgUnitId, java.util.Set)
*/
public boolean checkProjectAdminAccessForOrgUnit(ESSessionId sessionId, ESOrgUnitId orgUnitId,
Set<ESGlobalProjectId> projectIds)
throws AccessControlException {
checkSession(sessionId);
cleanupPARole(orgUnitId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final List<Role> allRoles = getAllRoles(user.getId().toAPI());
if (Iterables.any(allRoles, isServerAdminPredicate)) {
return true;
}
try {
final ProjectAdminRole projectAdminRole = (ProjectAdminRole) Iterables.find(
user.getRoles(),
isProjectAdminPredicate);
final Set<ProjectId> ids = new LinkedHashSet<ProjectId>();
for (final ESGlobalProjectId projectId : projectIds) {
ids.add(APIUtil.toInternal(ProjectId.class, projectId));
}
// projectAdminRole can not be null
if (projectAdminRole.getProjects().containsAll(ids)) {
return false;
}
} catch (final NoSuchElementException e) {
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkProjectAdminAccess(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESGlobalProjectId,
* org.eclipse.emf.emfstore.server.auth.ESProjectAdminPrivileges)
*/
// TODO: second parameter is optional
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkProjectAdminAccess(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESGlobalProjectId,
* org.eclipse.emf.emfstore.server.auth.ESProjectAdminPrivileges)
*/
public boolean checkProjectAdminAccess(ESSessionId sessionId, ESGlobalProjectId globalProjectId,
ESProjectAdminPrivileges privileg) throws AccessControlException {
checkSession(sessionId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final Iterable<Role> roles = getAllRoles(user.getId().toAPI());
if (Iterables.any(roles, isServerAdminPredicate)) {
return true;
}
final Iterable<ProjectAdminRole> projectAdminRoles = Iterables.filter(roles, ProjectAdminRole.class);
final Iterator<ProjectAdminRole> iterator = projectAdminRoles.iterator();
if (iterator.hasNext()) {
/* if at least one project admin role, perform validity checks */
if (!isProjectAdminPrivileg(privileg)) {
throw new AccessControlException(Messages.AccessControlImpl_PARole_Missing_Privilege);
}
if (globalProjectId == null) {
return false;
}
}
final ProjectId projectId = APIUtil.toInternal(ProjectId.class, globalProjectId);
while (iterator.hasNext()) {
final ProjectAdminRole projectAdminRole = iterator.next();
if (projectAdminRole.canAdministrate(projectId)) {
/* return false, because no server admin. no exception because still valid */
return false;
}
if (!iterator.hasNext()) {
/* no project admin role allows this operation -> throw exception */
throw new AccessControlException(Messages.AccessControlImpl_PARole_Missing_Privilege);
}
}
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
/**
* @param privileg the {@link ESProjectAdminPrivileges}
* @return <code>true</code> if a project admin has the required privileges, <code>false</code> otherwise
*/
protected boolean isProjectAdminPrivileg(ESProjectAdminPrivileges privileg) {
return ServerConfiguration.isProjectAdminPrivileg(privileg);
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkReadAccess(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESGlobalProjectId, java.util.Set)
*/
public void checkReadAccess(ESSessionId sessionId, ESGlobalProjectId globalProjectId, Set<EObject> modelElements)
throws AccessControlException {
checkSession(sessionId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final List<Role> internalRoles = APIUtil.toInternal(orgUnitResolver.getRolesFromGroups(user.toAPI()));
final Iterable<Role> roles = Iterables.concat(user.getRoles(), internalRoles);
final ProjectId internalAPI = APIUtil.toInternal(ProjectId.class, globalProjectId);
// MK: remove access control simplification
if (!canRead(roles, internalAPI, null)) {
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
// for (ModelElement modelElement : modelElements) {
// if (!canRead(roles, projectId, modelElement)) {
// throw new AccessControlException();
// }
}
}
/**
* Check if the given list of roles can write to the model element in the
* project.
*
* @param roles
* a list of roles
* @param projectId
* a project id
* @param modelElement
* a model element
* @return true if one of the roles can write
*/
private boolean canWrite(Iterable<Role> roles, ProjectId projectId, EObject modelElement) {
for (final Role role : roles) {
if (role.canModify(projectId, modelElement)
|| role.canCreate(projectId, modelElement)
|| role.canDelete(projectId, modelElement)) {
return true;
}
}
return false;
}
/**
* Check if the given list of roles can read the model element in the
* project.
*
* @param roles
* a list of roles
* @param projectId
* a project id
* @param modelElement
* a model element
* @return true if one of the roles can read
* @throws AccessControlException
*/
private boolean canRead(Iterable<Role> roles, ProjectId projectId, EObject modelElement)
throws AccessControlException {
for (final Role role : roles) {
if (role.canRead(projectId, modelElement)) {
return true;
}
}
return false;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkWriteAccess(org.eclipse.emf.emfstore.server.model.ESSessionId,
* org.eclipse.emf.emfstore.server.model.ESGlobalProjectId, java.util.Set)
*/
public void checkWriteAccess(ESSessionId sessionId, ESGlobalProjectId globalProjectId, Set<EObject> modelElements)
throws AccessControlException {
checkSession(sessionId);
final ACUser user = (ACUser) getOrgUnit(sessions.resolveToOrgUnitId(sessionId));
final List<Role> internalRoles = APIUtil.toInternal(orgUnitResolver.getRolesFromGroups(user.toAPI()));
final Iterable<Role> roles = Iterables.concat(user.getRoles(), internalRoles);
// MK: remove access control simplification
final ProjectId projectId = APIUtil.toInternal(ProjectId.class, globalProjectId);
if (!canWrite(roles, projectId, null)) {
throw new AccessControlException(Messages.AccessControlImpl_Insufficient_Rights);
}
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#checkAccess(org.eclipse.emf.emfstore.server.auth.ESMethodInvocation)
*/
public void checkAccess(ESMethodInvocation op) throws AccessControlException {
initAccessMap();
final AccessLevel accessType = accessMap.get(op.getType());
if (accessType == null) {
// no access type means "no access"
throw new AccessControlException(Messages.AccessControlImpl_No_Access);
}
switch (accessType) {
case PROJECT_READ:
ProjectId projectId = getProjectIdFromParameters(op);
checkReadAccess(
op.getSessionId(),
projectId == null ? null : projectId.toAPI(),
null);
break;
case PROJECT_WRITE:
projectId = getProjectIdFromParameters(op);
checkWriteAccess(
op.getSessionId(),
projectId == null ? null : projectId.toAPI(),
null);
break;
case PROJECT_ADMIN:
projectId = getProjectIdFromParameters(op);
checkProjectAdminAccess(
op.getSessionId(),
projectId == null ? null : projectId.toAPI());
break;
case SERVER_ADMIN:
checkServerAdminAccess(op.getSessionId());
break;
case NONE:
break;
default:
throw new AccessControlException(Messages.AccessControlImpl_Unknown_Access_Type);
}
}
private ProjectId getProjectIdFromParameters(ESMethodInvocation op) {
for (final Object obj : op.getParameters()) {
if (obj instanceof ProjectId) {
return (ProjectId) obj;
}
}
return null;
// throw new IllegalArgumentException("the operation MUST have a project id");
}
/**
* Removes any orphan {@link ProjectId}s from the given {@link ProjectAdminRole}, i.e.
* all invalid {@link ProjectId}s will be removed.
*
* @param orgUnitId
*
* @param paRole the {@link ProjectAdminRole} to be cleaned up
*
* @throws AccessControlException in case the requested orgUnit does not exist
*/
private void cleanupPARole(ESOrgUnitId esOrgUnitId) throws AccessControlException {
ProjectAdminRole paRole = null;
// final ACOrgUnitId orgUnitId = ESOrgUnitIdImpl.class.cast(esOrgUnitId).toInternalAPI();
final List<Role> roles = getAllRoles(esOrgUnitId);
for (final Role role : roles) {
if (ProjectAdminRole.class.isInstance(role)) {
paRole = (ProjectAdminRole) role;
break;
}
}
if (paRole == null) {
return;
}
final List<ESProjectHistory> externalProjects = orgUnitProvider.getProjects();
final List<ProjectHistory> projects = new ArrayList<ProjectHistory>();
for (final ESProjectHistory projectHistory : externalProjects) {
projects.add(
ESProjectHistoryImpl.class.cast(projectHistory).toInternalAPI());
}
final Set<ProjectId> validProjectIds = new LinkedHashSet<ProjectId>();
final Set<ProjectId> invalidProjectIdsOfRole = new LinkedHashSet<ProjectId>();
for (final ProjectHistory projectHistory : projects) {
validProjectIds.add(projectHistory.getProjectId());
}
for (final ProjectId projectId : paRole.getProjects()) {
if (!validProjectIds.contains(projectId)) {
invalidProjectIdsOfRole.add(projectId);
}
}
paRole.getProjects().removeAll(invalidProjectIdsOfRole);
if (paRole.getProjects().size() == 0) {
getOrgUnit(esOrgUnitId).getRoles().remove(paRole);
}
}
private List<Role> getAllRoles(ESOrgUnitId orgUnitId) throws AccessControlException {
final ACOrgUnit<?> internalOrgUnit = getOrgUnit(orgUnitId);
final ESOrgUnit orgUnit = internalOrgUnit.toAPI();
final List<ACGroup> groups = APIUtil.toInternal(orgUnitResolver.getGroups(orgUnit));
final ArrayList<Role> roles = new ArrayList<Role>();
for (final ACGroup group : groups) {
roles.addAll(group.getRoles());
}
roles.addAll(internalOrgUnit.getRoles());
return roles;
}
private ACOrgUnit<?> getOrgUnit(ESOrgUnitId orgUnitId) throws AccessControlException {
Preconditions.checkNotNull(orgUnitId, "orgUnitId must not be null"); //$NON-NLS-1$
final ACOrgUnitId internalId = APIUtil.toInternal(ACOrgUnitId.class, orgUnitId);
synchronized (MonitorProvider.getInstance().getMonitor()) {
for (final ESUser user : orgUnitProvider.getUsers()) {
final ACUser internalAPI = (ACUser) ESUserImpl.class.cast(user).toInternalAPI();
if (internalAPI.getId().equals(internalId)) {
return internalAPI;
}
}
for (final ESGroup group : orgUnitProvider.getGroups()) {
final ACGroup internalAPI = (ACGroup) ESGroupImpl.class.cast(group).toInternalAPI();
if (internalAPI.getId().equals(internalId)) {
return internalAPI;
}
}
throw new AccessControlException(Messages.AccessControlImpl_Given_OrgUnit_Does_Not_Exist);
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.server.auth.ESAuthorizationService#init(org.eclipse.emf.emfstore.server.auth.ESSessions,
* org.eclipse.emf.emfstore.server.auth.ESOrgUnitResolver,
* org.eclipse.emf.emfstore.server.model.ESOrgUnitProvider)
*/
public void init(ESSessions sessions, ESOrgUnitResolver orgUnitResolverServive, ESOrgUnitProvider orgUnitProvider) {
this.sessions = sessions;
orgUnitResolver = orgUnitResolverServive;
this.orgUnitProvider = orgUnitProvider;
}
private void checkSession(ESSessionId sessionId) throws SessionTimedOutException {
sessions.isValid(sessionId);
}
}