/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG 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:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.core.permit;
import java.security.Principal;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.model.Group;
import org.eclipse.skalli.model.Member;
import org.eclipse.skalli.model.Project;
import org.eclipse.skalli.model.User;
import org.eclipse.skalli.services.configuration.ConfigurationService;
import org.eclipse.skalli.services.configuration.EventConfigUpdate;
import org.eclipse.skalli.services.event.EventListener;
import org.eclipse.skalli.services.event.EventService;
import org.eclipse.skalli.services.group.GroupService;
import org.eclipse.skalli.services.permit.Permit;
import org.eclipse.skalli.services.permit.PermitService;
import org.eclipse.skalli.services.permit.PermitSet;
import org.eclipse.skalli.services.role.RoleProvider;
import org.eclipse.skalli.services.role.RoleService;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PermitComponent implements PermitService, EventListener<EventConfigUpdate> {
private static final Logger LOG = LoggerFactory.getLogger(PermitComponent.class);
private static final String PERMITS_ATTRIBUTE = "PERMITS"; //$NON-NLS-1$
private static final String PERMITS_TIMESTAMP_ATTRIBUTE = "PERMITS_TIMESTAMP"; //$NON-NLS-1$
private static final String PERMITS_PROJECT_ATTRIBUTE = "PERMITS_PROJECT"; //$NON-NLS-1$
private static final String PROPERTY_PROJECTID =
StringUtils.substringBetween(Permit.PROJECT_WILDCARD, "{", "}"); //$NON-NLS-1$ //$NON-NLS-2$
private static final String USER_WILDCARD =
StringUtils.substringBetween(Permit.USER_WILDCARD, "{", "}"); //$NON-NLS-1$ //$NON-NLS-2$
private static final PermitSet DEFAULT_PERMITS = new PermitSet(Permit.FORBID_ALL);
private static final PermitSet DEFAULT_ADMIN_PERMITS = new PermitSet(Permit.ALLOW_ALL);
private static final String PATH_PROJECTS = "projects"; //$NON-NLS-1$
private static ThreadLocal<String> threadUserId = new InheritableThreadLocal<String>();
private static ThreadLocal<Project> threadProject = new InheritableThreadLocal<Project>();
private static ThreadLocal<PermitSet> threadPermits = new InheritableThreadLocal<PermitSet>();
private ComponentContext context;
private ConfigurationService configService;
private Set<RoleProvider> roleProviders = new HashSet<RoleProvider>();
// ensure that threads are properly synchronized when accessing this instance variable
private volatile long permitsConfigChangedTimestamp;
protected void activate(ComponentContext context) {
this.context = context;
LOG.info(MessageFormat.format("[PermitService] {0} : activated", //$NON-NLS-1$
(String) context.getProperties().get(ComponentConstants.COMPONENT_NAME)));
}
protected void deactivate(ComponentContext context) {
this.context = null;
LOG.info(MessageFormat.format("[PermitService] {0} : deactivated", //$NON-NLS-1$
(String) context.getProperties().get(ComponentConstants.COMPONENT_NAME)));
}
protected void bindConfigurationService(ConfigurationService configService) {
LOG.info(MessageFormat.format("bindConfigurationService({0})", configService)); //$NON-NLS-1$
this.configService = configService;
logoutAll();
}
protected void unbindConfigurationService(ConfigurationService configService) {
LOG.info(MessageFormat.format("unbindConfigurationService({0})", configService)); //$NON-NLS-1$
this.configService = null;
logoutAll();
}
protected void bindEventService(EventService eventService) {
LOG.info(MessageFormat.format("bindEventService({0})", eventService)); //$NON-NLS-1$
eventService.registerListener(EventConfigUpdate.class, this);
}
protected void unbindEventService(EventService eventService) {
LOG.info(MessageFormat.format("unbindEventService({0})", eventService)); //$NON-NLS-1$
}
protected void bindRoleProvider(RoleProvider roleProvider) {
roleProviders.add(roleProvider);
logoutAll();
}
protected void unbindRoleProvider(RoleProvider roleProvider) {
roleProviders.remove(roleProvider);
logoutAll();
}
private GroupService getGroupService() {
if (context != null) {
return (GroupService)context.locateService("GroupService"); //$NON-NLS-1$
}
return null;
}
private RoleService getRoleService() {
if (context != null) {
return (RoleService)context.locateService("RoleService"); //$NON-NLS-1$
}
return null;
}
@Override
public boolean hasPermit(Permit permit) {
return hasPermit(permit.getLevel(), permit.getAction(), permit.getSegments());
}
@Override
public boolean hasPermit(int level, String action, String... segments) {
PermitSet permits = threadPermits.get();
if (permits == null) {
permits = DEFAULT_PERMITS;
threadPermits.set(permits);
}
return Permit.match(permits, level, action, segments);
}
@Override
public boolean hasPermit(int level, String action, Project project) {
String projectId = project.getProjectId();
return StringUtils.isNotBlank(projectId) && hasPermit(level, action, PATH_PROJECTS, projectId)
|| hasPermit(level, action, PATH_PROJECTS, project.getUuid().toString());
}
@Override
public boolean hasPermit(int level, String action, Project project, String... segments) {
if (segments == null || segments.length == 0) {
return hasPermit(level, action, project);
}
String[] amendedSegments = new String[segments.length + 2];
amendedSegments[0] = PATH_PROJECTS;
System.arraycopy(segments, 0, amendedSegments, 2, segments.length);
String projectId = project.getProjectId();
return StringUtils.isNotBlank(projectId)?
hasProjectPermit(level, action, projectId, amendedSegments) :
hasProjectPermit(level, action, project.getUuid().toString(), amendedSegments);
}
private boolean hasProjectPermit(int level, String action, String id, String... segments) {
segments[1] = id;
return StringUtils.isNotBlank(id) && hasPermit(level, action, segments);
}
@Override
public String login(HttpServletRequest request, Project project) {
String userId = null;
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null) {
userId = userPrincipal.getName();
if (StringUtils.isNotBlank(userId)) {
userId = userId.toLowerCase(Locale.ENGLISH);
}
}
HttpSession session = request.getSession();
PermitSet permits = getSessionPermits(session, userId, project);
if (permits == null) {
permits = getPermits(userId, project);
setSessionPermits(session, permits, project);
}
threadUserId.set(userId);
threadProject.set(project);
threadPermits.set(permits);
return userId;
}
@Override
public void switchProject(Project project) {
String userId = threadUserId.get();
PermitSet permits = getPermits(userId, project);
threadProject.set(project);
threadPermits.set(permits);
}
@Override
public void logout(HttpServletRequest request) {
removeSessionPermits(request.getSession());
threadUserId.set(null);
threadProject.set(null);
threadPermits.set(null);
}
@Override
public void logoutAll() {
permitsConfigChangedTimestamp = System.currentTimeMillis();
}
@Override
public String getLoggedInUser() {
return threadUserId.get();
}
@Override
public void onEvent(EventConfigUpdate event) {
// we do not know which configurations affect permissions,
// so throw away all login sessions just to be sure
logoutAll();
}
private PermitSet getSessionPermits(HttpSession session, String userId, Project project) {
if (session == null) {
return null;
}
if (StringUtils.isBlank(userId)) {
// anonymous users have never a session
return null;
}
if (project != null) {
// ensure that session permits apply to the same project
String projectId = (String)session.getAttribute(PERMITS_PROJECT_ATTRIBUTE);
if (!project.getProjectId().equals(projectId)) {
return null;
}
}
// ensure that session permits are still valid, i.e. that no configuration
// change has occurred in the meantime
Long timestamp = (Long)session.getAttribute(PERMITS_TIMESTAMP_ATTRIBUTE);
if (timestamp == null || timestamp <= permitsConfigChangedTimestamp) {
return null;
}
return (PermitSet)session.getAttribute(PERMITS_ATTRIBUTE);
}
private void setSessionPermits(HttpSession session, PermitSet permits, Project project) {
if (session != null) {
session.setAttribute(PERMITS_TIMESTAMP_ATTRIBUTE, System.currentTimeMillis());
session.setAttribute(PERMITS_ATTRIBUTE, permits);
if (project != null) {
session.setAttribute(PERMITS_PROJECT_ATTRIBUTE, project.getProjectId());
} else {
session.removeAttribute(PERMITS_PROJECT_ATTRIBUTE);
}
}
}
private void removeSessionPermits(HttpSession session) {
if (session != null) {
session.removeAttribute(PERMITS_TIMESTAMP_ATTRIBUTE);
session.removeAttribute(PERMITS_ATTRIBUTE);
session.removeAttribute(PERMITS_PROJECT_ATTRIBUTE);
}
}
@Override
public PermitSet getPermits(String userId, Project project) {
PermitSet permits = new PermitSet();
if (configService != null) {
userId = StringUtils.isNotBlank(userId)? userId : User.UNKNOWN;
String templateId = project != null? project.getProjectTemplateId() : null;
List<String> groups = getGroupNames(userId);
List<String> groupRoles = getRoles(groups);
List<String> roles = getRoles(userId);
List<String> projectRoles = getProjectRoles(userId, project);
PermitsConfig permitsConfig = configService.readConfiguration(PermitsConfig.class);
if (permitsConfig != null) {
Map<String,String> properties = new HashMap<String,String>();
properties.put(USER_WILDCARD, userId);
String projectId = "?"; //$NON-NLS-1$
if (project != null) {
projectId = project.getProjectId();
if (StringUtils.isBlank(projectId)) {
projectId = project.getUuid().toString();
}
}
properties.put(PROPERTY_PROJECTID, projectId);
Map<String, List<PermitConfig>> permitsByType = permitsConfig.getByType();
collectGlobalCommits(properties, permitsByType, permits);
if (templateId != null) {
collectTemplatePermits(templateId, properties, permitsByType, permits);
}
if (groupRoles.size() > 0) {
collectRolePermits(groupRoles, properties, permitsByType, permits);
}
if (roles.size() > 0) {
collectRolePermits(roles, properties, permitsByType, permits);
}
if (projectRoles.size() > 0) {
collectRolePermits(projectRoles, properties, permitsByType, permits);
}
if (groups.size() > 0) {
collectGroupPermits(groups, properties, permitsByType, permits);
}
collectUserPermits(userId, properties, permitsByType, permits);
} else {
// special handling for bootstrapping of a new instance or for
// instances where authorization is not relevant
permits.addAll(DEFAULT_ADMIN_PERMITS);
}
}
if (permits.isEmpty()) {
permits.addAll(DEFAULT_PERMITS);
}
return permits;
}
private void collectGlobalCommits(Map<String,String> properties,
Map<String, List<PermitConfig>> permitsConfig, PermitSet permits) {
List<PermitConfig> globalPermits = permitsConfig.get(PermitsConfig.GLOBAL_PERMIT);
if (globalPermits != null) {
for (PermitConfig globalPermit: globalPermits) {
permits.add(globalPermit.asPermit(properties));
}
}
}
private void collectUserPermits(String userId, Map<String,String> properties,
Map<String, List<PermitConfig>> permitsConfig, PermitSet permits) {
List<PermitConfig> userPermits = permitsConfig.get(PermitsConfig.USER_PERMIT);
if (userPermits != null) {
for (PermitConfig userPermit: userPermits) {
if (userId.equals(userPermit.getOwner())) {
permits.add(userPermit.asPermit(properties));
}
}
}
}
private void collectGroupPermits(List<String> groups, Map<String,String> properties,
Map<String, List<PermitConfig>> permitsConfig, PermitSet permits) {
List<PermitConfig> groupPermits = permitsConfig.get(PermitsConfig.GROUP_PERMIT);
if (groupPermits != null) {
for (PermitConfig groupPermit: groupPermits) {
if (groups.contains(groupPermit.getOwner())) {
permits.add(groupPermit.asPermit(properties));
}
}
}
}
private void collectRolePermits(List<String> roles, Map<String,String> properties,
Map<String, List<PermitConfig>> permitsConfig, PermitSet permits) {
List<PermitConfig> rolePermits = permitsConfig.get(PermitsConfig.ROLE_PERMIT);
if (rolePermits != null) {
for (PermitConfig rolePermit: rolePermits) {
if (roles.contains(rolePermit.getOwner())) {
permits.add(rolePermit.asPermit(properties));
}
}
}
}
private void collectTemplatePermits(String templateId, Map<String,String> properties,
Map<String, List<PermitConfig>> permitsConfig, PermitSet permits) {
List<PermitConfig> templatePermits = permitsConfig.get(PermitsConfig.TEMPLATE_PERMIT);
if (templatePermits != null) {
for (PermitConfig templatePermit: templatePermits) {
if (templateId.equals(templatePermit.getOwner())) {
permits.add(templatePermit.asPermit(properties));
}
}
}
}
private List<String> getRoles(List<String> groups) {
RoleService roleService = getRoleService();
if (roleService == null) {
return Collections.emptyList();
}
return roleService.getRoles(groups.toArray(new String[groups.size()]));
}
private List<String> getRoles(String userId) {
List<String> roles = new ArrayList<String>();
RoleService roleService = getRoleService();
if (roleService != null) {
roles.addAll(roleService.getRoles(userId));
}
return roles;
}
private List<String> getProjectRoles(String userId, Project project) {
List<String> roles = new ArrayList<String>();
if (project != null) {
Member member = new Member(userId);
for (RoleProvider roleProvider: roleProviders) {
Map<String, SortedSet<Member>> membersByRole = roleProvider.getMembersByRole(project);
for (String role: membersByRole.keySet()) {
if (membersByRole.get(role).contains(member)) {
roles.add(role);
}
}
}
}
return roles;
}
private List<String> getGroupNames(String userId) {
List<String> groupNames = new ArrayList<String>();
GroupService groupService = getGroupService();
if (groupService != null) {
for (Group group: groupService.getGroups(userId)) {
groupNames.add(group.getGroupId());
}
}
return groupNames;
}
}