package com.temenos.interaction.authorization.command;
/*
* Authorization inteceptor bean. This is passed parameters corresponding to the options of an OData request and a child
* InteractionCommand. It checks T24 Authorization and adds OData parameters, implementing the required additional filtering, as
* defined in the OData protocol specification:
*
* http://www.odata.org/documentation/odata-version-3-0/odata-version-3-0-core-protocol
*
* It then passes the modified parameters to the child command.
*
* The child command will make a 'best effort' to implement the requested filtering. This should limit the amount of data
* returned but cannot be guaranteed to be 100% complete for all databases. On exit this module must check and make any
* final adjustments to the returned data set.
*/
/*
* #%L
* interaction-commands-authorization
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response.Status;
import org.odata4j.expression.EntitySimpleProperty;
import org.odata4j.producer.EntityQueryInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.temenos.interaction.authorization.IAuthorizationProvider;
import com.temenos.interaction.authorization.exceptions.AuthorizationException;
import com.temenos.interaction.core.command.InteractionCommand;
import com.temenos.interaction.core.command.InteractionContext;
import com.temenos.interaction.core.command.InteractionException;
import com.temenos.interaction.odataext.odataparser.ODataParser;
import com.temenos.interaction.odataext.odataparser.ODataParser.UnsupportedQueryOperationException;
import com.temenos.interaction.odataext.odataparser.data.AccessProfile;
import com.temenos.interaction.odataext.odataparser.data.FieldName;
import com.temenos.interaction.odataext.odataparser.data.RowFilters;
public class AuthorizationCommand extends AbstractAuthorizationCommand implements InteractionCommand {
private final static Logger LOGGER = LoggerFactory.getLogger(AuthorizationCommand.class);
// Normal constructor
public AuthorizationCommand(IAuthorizationProvider authorizationBean) {
this.authorizationBean = authorizationBean;
}
/*
* Execute the command.
*
* If there is any form of internal error during authorization this will throw and nothing should be returned to the use.
*/
public Result execute(InteractionContext ctx) throws InteractionException {
// TODO Remove before production
// Dump query parameters
Iterator<String> it = ctx.getQueryParameters().keySet().iterator();
while (it.hasNext()) {
String theKey = (String) it.next();
LOGGER.info(" Key " + theKey + " = Value " + ctx.getQueryParameters().getFirst(theKey));
}
EntityQueryInfo queryInfo = ODataParser.getEntityQueryInfo(ctx);
// Add authorization to context
applyAuthorization(ctx, new RowFilters(queryInfo.filter), queryInfo.select);
// Set attributes indicating that authorization has not yet been done.
ctx.setAttribute(AuthorizationAttributes.FILTER_DONE_ATTRIBUTE, Boolean.FALSE);
ctx.setAttribute(AuthorizationAttributes.SELECT_DONE_ATTRIBUTE, Boolean.FALSE);
return (Result.SUCCESS);
}
/**
* This method will apply Authorization on InteractionContext for filtering
* data
*
* @param ctx
* @param oldFilter
* @param oldSelect
* @throws UnsupportedQueryOperationException
*/
private void applyAuthorization(InteractionContext ctx, RowFilters oldFilter,
List<EntitySimpleProperty> oldSelect) throws InteractionException {
// TODO When IRIS supports it the following line will become a call to an Authorization resource.
AccessProfile accessProfile = authorizationBean.getAccessProfile(ctx);
RowFilters newList = accessProfile.getNewRowFilters();
try {
addRowFilter(ctx, newList, oldFilter);
} catch (UnsupportedQueryOperationException e) {
LOGGER.warn("Attempted to do unauthorized action", e);
throw new AuthorizationException(Status.UNAUTHORIZED, e);
}
Set<FieldName> authSet = accessProfile.getFieldNames();
addColFilter(ctx, authSet, oldSelect);
}
private boolean addRowFilter(InteractionContext ctx, RowFilters newFilter, RowFilters oldFilter)
throws UnsupportedQueryOperationException {
MultivaluedMap<String, String> queryParams = ctx.getQueryParameters();
// Final list contains both sets of filters
if (null != oldFilter) {
// TODO Some additional work may be required to combine filters on
// the same column. What if "a > b" and
// "a = c"? For now include both and let the database decide how it
// handles tests conditions.
oldFilter.addFilters(newFilter);
}
// By the time we get here the target 'and' terms will be in newList.
if (oldFilter.isEmpty()) {
// No filtering, i.e. return everything. Delete any existing filter.
queryParams.remove(ODataParser.FILTER_KEY);
} else {
queryParams.putSingle(ODataParser.FILTER_KEY, ODataParser.toFilters(oldFilter));
}
// Return the entries specified by the filter.
return true;
}
private void addColFilter(InteractionContext ctx, Set<FieldName> authSet, List<EntitySimpleProperty> oldSelect) {
MultivaluedMap<String, String> queryParams = ctx.getQueryParameters();
// Get any existing select
Set<FieldName> oldSet = ODataParser.parseSelect(oldSelect);
if (authSet.isEmpty()) {
// Empty authorization list means 'return all requested' i.e.
// don't modify existing $select parameter.
return;
} else {
if (oldSet.isEmpty()) {
// Empty oldlist means just return authorization list
queryParams.putSingle(ODataParser.SELECT_KEY, ODataParser.toSelect(authSet));
} else {
// If we get here both sets contain entries. Final list is
// a union of the other two.
Iterator<FieldName> it = oldSet.iterator();
while (it.hasNext()) {
FieldName oldName = it.next();
if (!authSet.contains(oldName)) {
it.remove();
}
}
// By the time we get here the target select list will be
// in oldSet. Write the target list ... which may be empty
queryParams.putSingle(ODataParser.SELECT_KEY, ODataParser.toSelect(oldSet));
}
}
}
}