package org.sigmah.server.security.impl;
/*
* #%L
* Sigmah
* %%
* Copyright (C) 2010 - 2016 URD
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.sigmah.client.page.Page;
import org.sigmah.server.domain.User;
import org.sigmah.server.handler.util.Handlers;
import org.sigmah.server.mapper.Mapper;
import org.sigmah.server.servlet.base.ServletExecutionContext;
import org.sigmah.shared.command.*;
import org.sigmah.shared.command.base.Command;
import org.sigmah.shared.dto.profile.ProfileDTO;
import org.sigmah.shared.dto.referential.GlobalPermissionEnum;
import org.sigmah.shared.servlet.ServletConstants.Servlet;
import org.sigmah.shared.servlet.ServletConstants.ServletMethod;
import org.sigmah.shared.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Access rights configuration.
*
* @author Denis Colliot (dcolliot@ideia.fr)
*/
final class AccessRights {
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(AccessRights.class);
/**
* Permissions map linking a secured token to a set of {@link GlobalPermissionEnum}.
*/
private static final Map<String, Pair<GrantType, Set<GlobalPermissionEnum>>> permissions = new HashMap<>();
/**
* Unchecked resources tokens (they are always granted).
*/
private static final Set<String> grantedTokens = new HashSet<>();
/**
* Token representing <em>missing tokens</em>.
* If a token is not declared among security permissions, this token is used.
*/
private static final String MISSING_TOKEN = "*";
/**
* Permissions configuration.
*/
// TODO Complete permissions.
static {
// FIXME For the time being, all missing tokens are considered NOT secured. This line should be deleted in
// production.
sperm(MISSING_TOKEN, GrantType.BOTH);
// Pages.
sperm(pageToken(Page.LOGIN), GrantType.ANONYMOUS_ONLY);
sperm(pageToken(Page.RESET_PASSWORD), GrantType.ANONYMOUS_ONLY);
sperm(pageToken(Page.LOST_PASSWORD), GrantType.ANONYMOUS_ONLY);
sperm(pageToken(Page.CHANGE_OWN_PASSWORD), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.CHANGE_PASSWORD);
sperm(pageToken(Page.MOCKUP), GrantType.BOTH);
sperm(pageToken(Page.CREDITS), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.HELP), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.DASHBOARD), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.PROJECT_DASHBOARD), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS);
sperm(pageToken(Page.PROJECT_DETAILS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS);
sperm(pageToken(Page.PROJECT_CALENDAR), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_PROJECT_AGENDA);
sperm(pageToken(Page.PROJECT_INDICATORS_ENTRIES), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_INDICATOR);
sperm(pageToken(Page.PROJECT_INDICATORS_MANAGEMENT), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_INDICATOR);
sperm(pageToken(Page.PROJECT_INDICATORS_MAP), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_INDICATOR);
sperm(pageToken(Page.PROJECT_LOGFRAME), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_LOGFRAME);
sperm(pageToken(Page.PROJECT_REPORTS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS);
sperm(pageToken(Page.PROJECT_TEAM_MEMBERS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS, GlobalPermissionEnum.VIEW_PROJECT_TEAM_MEMBERS);
sperm(pageToken(Page.INDICATOR_EDIT), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_INDICATOR);
sperm(pageToken(Page.SITE_EDIT), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_INDICATOR);
sperm(pageToken(Page.CREATE_PROJECT), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.CREATE_PROJECT);
sperm(pageToken(Page.ORGUNIT_DASHBOARD), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.ORGUNIT_CALENDAR), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_PROJECT_AGENDA);
sperm(pageToken(Page.ORGUNIT_DETAILS), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.ORGUNIT_REPORTS), GrantType.AUTHENTICATED_ONLY);
sperm(pageToken(Page.CONTACT_DASHBOARD), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(pageToken(Page.ADMIN_PARAMETERS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_SETTINGS);
sperm(pageToken(Page.ADMIN_CATEGORIES), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_CATEGORIES);
sperm(pageToken(Page.ADMIN_ORG_UNITS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_ORG_UNITS);
sperm(pageToken(Page.ADMIN_USERS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_USERS);
sperm(pageToken(Page.ADMIN_PROJECTS_MODELS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_PROJECT_MODELS);
sperm(pageToken(Page.ADMIN_REPORTS_MODELS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_REPORT_MODELS);
sperm(pageToken(Page.ADMIN_ORG_UNITS_MODELS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_ORG_UNIT_MODELS);
sperm(pageToken(Page.ADMIN_CONTACT_MODELS), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_CONTACT_MODELS);
sperm(pageToken(Page.ADMIN_IMPORTATION_SCHEME), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_IMPORTATION_SCHEMES);
sperm(pageToken(Page.ADMIN_ADD_IMPORTATION_SCHEME), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_IMPORTATION_SCHEMES);
sperm(pageToken(Page.ADMIN_ADD_VARIABLE_IMPORTATION_SCHEME), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_ADMIN, GlobalPermissionEnum.MANAGE_IMPORTATION_SCHEMES);
// Commands.
sperm(commandToken(AddOrgUnit.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(AmendmentActionCommand.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(BackupArchiveManagementCommand.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(BatchCommand.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(ChangePasswordCommand.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.CHANGE_PASSWORD);
sperm(commandToken(ChangePhase.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.CHANGE_PHASE);
sperm(commandToken(CheckContactDuplication.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(CheckModelUsage.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(CreateEntity.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(CopyLogFrame.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.EDIT_LOGFRAME);
sperm(commandToken(DeactivateUsers.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_USERS);
sperm(commandToken(DedupeContact.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(DisableFlexibleElements.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeleteCategories.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeleteFlexibleElements.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(Delete.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeleteImportationSchemeModels.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeleteImportationSchemes.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeletePrivacyGroups.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(DeleteProfiles.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_USERS);
sperm(commandToken(DeleteReportModels.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_REPORT_MODELS);
sperm(commandToken(DownloadSlice.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GenerateElement.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetAdminEntities.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetAvailableFrameworks.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_PROJECT_MODELS);
sperm(commandToken(GetAvailableStatusForModel.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetBaseMaps.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetCalendar.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetCategories.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetContactDuplicatedProperties.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(GetContactHistory.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(GetContactModel.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetContactModelCopy.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetContactModels.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetContactRelationships.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(GetCountries.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetCountry.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetFilesFromFavoriteProjects.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetFrameworkFulfillmentsByProjectModelId.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.MANAGE_PROJECT_MODELS);
sperm(commandToken(GetGlobalContactExportSettings.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetGlobalExportSettings.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetGlobalContactExports.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetGlobalExports.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetHistory.class), GrantType.AUTHENTICATED_ONLY);
// TODO: Add the missing commands
sperm(commandToken(GetLayoutGroupIterations.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetLinkedProjects.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_MY_PROJECTS);
sperm(commandToken(GetMonitoredPoints.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetOrganization.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetOrgUnit.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetOrgUnitModel.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetUserUnitsByUser.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProfiles.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProject.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectDocuments.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectModel.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectModels.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectReport.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectReports.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjects.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectsByModel.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectsFromId.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetProjectTeamMembers.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_PROJECT_TEAM_MEMBERS);
sperm(commandToken(GetReminders.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetUsersByOrganization.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetUsersWithProfiles.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetUsersByOrgUnit.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetValue.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(GetValueFromLinkedProjects.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(Synchronize.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(UpdateContact.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.VIEW_VISIBLE_CONTACTS);
sperm(commandToken(UpdateLayoutGroupIterations.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(UpdateProject.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(UpdateProjectFavorite.class), GrantType.AUTHENTICATED_ONLY);
sperm(commandToken(UpdateProjectTeamMembers.class), GrantType.AUTHENTICATED_ONLY, GlobalPermissionEnum.EDIT_PROJECT_TEAM_MEMBERS);
sperm(commandToken(UploadSlice.class), GrantType.AUTHENTICATED_ONLY);
// Servlet methods.
sperm(servletToken(Servlet.FILE, ServletMethod.DOWNLOAD_LOGO), GrantType.AUTHENTICATED_ONLY);
sperm(servletToken(Servlet.FILE, ServletMethod.DOWNLOAD_FILE), GrantType.AUTHENTICATED_ONLY);
sperm(servletToken(Servlet.FILE, ServletMethod.DOWNLOAD_ARCHIVE), GrantType.AUTHENTICATED_ONLY);
}
/**
* Granted tokens that are always granted in order to optimize application processes.
*/
static {
grantedTokens.add(commandToken(SecureNavigationCommand.class));
}
/**
* Grants or refuse {@code user} access to the given {@code token}.
*
* @param user
* The user (authenticated or anonymous).
* @param token
* The resource token (page, command, servlet method, etc.).
* @param originPageToken
* The origin page token <em>(TODO Not used yet)</em>.
* @param mapper
* The mapper service.
* @return {@code true} if the user is granted, {@code false} otherwise.
*/
static boolean isGranted(final User user, final String token, final String originPageToken, final Mapper mapper) {
if (grantedTokens.contains(token)) {
// Granted tokens ; avoids profile aggregation if user is authenticated.
return true;
}
if (!permissions.containsKey(token)) {
if (LOG.isWarnEnabled()) {
LOG.warn("No security permission can be found for token '{}'. Did you forget to declare corresponding 'sperm'?", token);
}
return isGranted(user, MISSING_TOKEN, originPageToken, mapper);
}
final Pair<GrantType, Set<GlobalPermissionEnum>> grantData = permissions.get(token);
final GrantType grantType = grantData.left;
if (user == null || ServletExecutionContext.ANONYMOUS_USER.equals(user)) {
// Anonymous user.
return grantType != null && grantType != GrantType.AUTHENTICATED_ONLY;
} else {
// Authenticated user.
if (grantType != null && grantType == GrantType.ANONYMOUS_ONLY) {
return false;
} else {
for (Map.Entry<Integer, ProfileDTO> aggregatedProfileEntry : Handlers.aggregateProfiles(user, mapper).entrySet()) {
if (CollectionUtils.containsAll(aggregatedProfileEntry.getValue().getGlobalPermissions(), grantData.right)) {
return true;
}
}
}
}
return false;
}
// -------------------------------------------------------------------------------------
//
// TOKEN METHODS.
//
// -------------------------------------------------------------------------------------
/**
* Return the <em>resource</em> token for the given servlet arguments.
*
* @param servlet
* The {@link Servlet} name.
* @param method
* The {@link Servlet} method.
* @return the <em>resource</em> token for the given servlet arguments, or {@code null}.
*/
static String servletToken(final Servlet servlet, final ServletMethod method) {
if (servlet == null || method == null) {
return null;
}
return servlet.name() + '#' + method.name();
}
/**
* Return the <em>resource</em> token for the given {@code commandClass}.
*
* @param commandClass
* The {@link Command} class.
* @return the <em>resource</em> token for the given {@code commandClass}, or {@code null}.
*/
@SuppressWarnings("rawtypes")
static String commandToken(final Class<? extends Command> commandClass) {
if (commandClass == null) {
return null;
}
return commandClass.getName();
}
/**
* Return the <em>resource</em> token for the given {@code page}.
*
* @param page
* The {@link Page} instance.
* @return the <em>resource</em> token for the given {@code page}, or {@code null}.
*/
static String pageToken(final Page page) {
if (page == null) {
return null;
}
return page.getToken();
}
// -------------------------------------------------------------------------------------
//
// UTILITY METHODS.
//
// -------------------------------------------------------------------------------------
private static enum GrantType {
/**
* Access granted to <em>anonymous</em> user <b>only</b>.
*/
ANONYMOUS_ONLY,
/**
* Access granted to <em>authenticated</em> users <b>only</b>.
*/
AUTHENTICATED_ONLY,
/**
* Access granted to <em>anonymous</em> <b>and</b> <em>authenticated</em> users.
*/
BOTH;
}
/**
* <p>
* Registers a new <u>S</u>ecurity <u>PERM</u>ission for the given {@code token}.
* </p>
* <p>
* ;-)
* </p>
*
* @param token
* The resource token.
* @param grantType
* The grant type, see {@link GrantType}.
* @param gpes
* The {@link GlobalPermissionEnum} that the user needs to possess in order to be granted for the
* {@code token}.
*/
private static void sperm(final String token, final GrantType grantType, final GlobalPermissionEnum... gpes) {
permissions.put(token, new Pair<>(grantType, toSet(gpes)));
}
/**
* Transforms the given {@code gpes} array into a {@link Set}.
* Ignores {@code null} values in the process.
*
* @param gpes
* The {@link GlobalPermissionEnum} array.
* @return the given {@code gpes} array transformed into a {@link Set} with no {@code null} values.
*/
private static Set<GlobalPermissionEnum> toSet(final GlobalPermissionEnum... gpes) {
final Set<GlobalPermissionEnum> set = new HashSet<GlobalPermissionEnum>();
if (ArrayUtils.isEmpty(gpes)) {
return set;
}
for (final GlobalPermissionEnum gpe : gpes) {
if (gpe == null) {
continue;
}
set.add(gpe);
}
return set;
}
/**
* Utility class constructor.
*/
private AccessRights() {
// Only provides static constants.
}
}