/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstr. 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.security.owsrequestvalidator.wms; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.util.ColorUtils; import org.deegree.framework.util.MapUtils; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.XMLParsingException; import org.deegree.graphics.sld.AbstractStyle; import org.deegree.graphics.sld.NamedLayer; import org.deegree.graphics.sld.NamedStyle; import org.deegree.graphics.sld.SLDFactory; import org.deegree.graphics.sld.StyledLayerDescriptor; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.CoordinateSystem; import org.deegree.model.crs.GeoTransformer; import org.deegree.model.crs.IGeoTransformer; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.GeometryFactory; import org.deegree.ogcwebservices.InvalidParameterValueException; import org.deegree.ogcwebservices.OGCWebServiceRequest; import org.deegree.ogcwebservices.wms.operation.GetMap; import org.deegree.security.UnauthorizedException; import org.deegree.security.drm.model.RightType; import org.deegree.security.drm.model.User; import org.deegree.security.owsproxy.Condition; import org.deegree.security.owsproxy.OperationParameter; import org.deegree.security.owsproxy.Request; import org.deegree.security.owsrequestvalidator.Messages; import org.deegree.security.owsrequestvalidator.Policy; /** * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a> * @author last edited by: $Author: poth $ * * @version 1.1, $Revision: 1.9 $, $Date: 2006/11/27 09:07:52 $ * * @since 1.1 */ public class GetMapRequestValidator extends AbstractWMSRequestValidator { private static double DEFAULT_PIXEL_SIZE = 0.00028; // known condition parameter private static final String BBOX = "bbox"; private static final String LAYERS = "layers"; private static final String BGCOLOR = "bgcolor"; private static final String TRANSPARENCY = "transparency"; private static final String RESOLUTION = "resolution"; private static final String SLD = "sld"; private static final String SLD_BODY = "sld_body"; private static final String INVALIDBBOX = Messages .getString("GetMapRequestValidator.INVALIDBBOX"); private static final String INVALIDLAYER = Messages .getString("GetMapRequestValidator.INVALIDLAYER"); private static final String INVALIDSTYLE = Messages .getString("GetMapRequestValidator.INVALIDSTYLE"); private static final String INVALIDBGCOLOR = Messages .getString("GetMapRequestValidator.INVALIDBGCOLOR"); private static final String INVALIDTRANSPARENCY = Messages .getString("GetMapRequestValidator.INVALIDTRANSPARENCY"); private static final String INVALIDRESOLUTION = Messages .getString("GetMapRequestValidator.INVALIDRESOLUTION"); private static final String INVALIDSLD = Messages .getString("GetMapRequestValidator.INVALIDSLD"); private static final String INVALIDSLD_BODY = Messages .getString("GetMapRequestValidator.INVALIDSLD_BODY"); private static final String MISSINGCRS = Messages .getString("GetMapRequestValidator.MISSINGCRS"); private List<String> accessdRes = new ArrayList<String>(); private static FeatureType mapFT = null; private GeoTransformer wgs84Transformer = null; static { if (mapFT == null) { mapFT = GetMapRequestValidator.createFeatureType(); } } /** * @param policy */ public GetMapRequestValidator(Policy policy) { super(policy); try { wgs84Transformer = new GeoTransformer("EPSG:4326"); } catch (Exception e) { e.printStackTrace(); } } /** * validates the incomming GetMap request against the policy assigend to a * validator * * @param request * request to validate * @param user * name of the user who likes to perform the request (can be * null) */ public void validateRequest(OGCWebServiceRequest request, User user) throws InvalidParameterValueException, UnauthorizedException { accessdRes.clear(); userCoupled = false; Request req = policy.getRequest("WMS", "GetMap"); // request is valid because no restrictions are made if (req.isAny()) return; Condition condition = req.getPreConditions(); GetMap wmsreq = (GetMap) request; validateVersion(condition, wmsreq.getVersion()); Envelope env = wmsreq.getBoundingBox(); validateBBOX(condition, env, wmsreq.getSrs()); validateLayers(condition, wmsreq.getLayers()); validateBGColor(condition, ColorUtils.toHexCode("0x", wmsreq .getBGColor())); validateTransparency(condition, wmsreq.getTransparency()); validateExceptions(condition, wmsreq.getExceptions()); validateFormat(condition, wmsreq.getFormat()); validateMaxWidth(condition, wmsreq.getWidth()); validateMaxHeight(condition, wmsreq.getHeight()); validateResolution(condition, wmsreq); validateSLD(condition, wmsreq.getSLD_URL()); validateSLD_Body(condition, wmsreq.getStyledLayerDescriptor()); if (userCoupled) { validateAgainstRightsDB(wmsreq, user); } } /** * checks if the passed envelope is valid against the maximum bounding box * defined in the policy. If <tt>user</ff> != <tt>null</tt> the * maximu valid BBOX will be read from the user/rights repository * @param condition condition containing the definition of the valid BBOX * @param envelope * @param requestSRS * @throws InvalidParameterValueException */ private void validateBBOX(Condition condition, Envelope envelope, String requestSRS) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(BBOX); // version is valid because no restrictions are made if (op.isAny()) return; String v = op.getFirstAsString(); // Look at the deny policy String POLICY_DENY_COMPLETELY_OUTSIDE = "@deny_if_completely_outside"; boolean denyCompletelyOutside = false; if (v.indexOf(POLICY_DENY_COMPLETELY_OUTSIDE) != -1) { denyCompletelyOutside = true; v = v.replaceAll(POLICY_DENY_COMPLETELY_OUTSIDE, ""); } String[] d = StringTools.toArray(v, ",", false); Envelope env = GeometryFactory.createEnvelope(Double.parseDouble(d[0]), Double.parseDouble(d[1]), Double.parseDouble(d[2]), Double .parseDouble(d[3]), null); String conditionSRS = "EPSG:4326"; if (d.length > 4) { conditionSRS = d[4]; } GeoTransformer gt; try { gt = new GeoTransformer(conditionSRS); }catch (Throwable e) { e.printStackTrace(); gt=this.wgs84Transformer; } Envelope tranformedEnvelope = envelope; if (requestSRS != null) { try { CoordinateSystem crs = CRSFactory.create(requestSRS); try { tranformedEnvelope = gt.transform(envelope, crs); } catch (Throwable e) { throw new InvalidParameterValueException( "Unable to transform request to a the condition SRS. ", e); } } catch (Throwable e) { // SRS is either bad or this server doesn't support it. // TODO change getCaps so that it doesn't provide the unsupported SRSs // or improve CS Factory e.printStackTrace(); } } boolean denied = denyCompletelyOutside ? !env.intersects(tranformedEnvelope) : !env.contains(tranformedEnvelope); if (denied) { if (!op.isUserCoupled()) { // if not user coupled the validation has failed throw new InvalidParameterValueException(INVALIDBBOX + op.getFirstAsString()); } userCoupled = true; accessdRes.add("BBOX: " + v); } } /** * checks if the passed layres/styles are valid against the layers/styles * list defined in the policy. If <tt>user</ff> != <tt>null</tt> the * valid layers/styles will be read from the user/rights repository * @param condition condition containing the definition of the valid layers/styles * @param layers * @throws InvalidParameterValueException */ private void validateLayers(Condition condition, GetMap.Layer[] layers) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(LAYERS); // version is valid because no restrictions are made if (op.isAny()) { return; } List<String> v = op.getValues(); // seperate layers from assigned styles Map map = new HashMap(); for (int i = 0; i < v.size(); i++) { String[] tmp = StringTools.toArray(v.get(i), "|", false); // XXXsyp: allow lack of "|$any" in layer name String value = "$any$"; if (tmp.length == 2) value = tmp[1]; map.put(tmp[0], value); } for (int i = 0; i < layers.length; i++) { String style = layers[i].getStyleName(); String vs = (String) map.get(layers[i].getName()); if (vs == null) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDLAYER + layers[i].getName()); } accessdRes.add("Layers: " + layers[i].getName()); userCoupled = true; } else if (!style.equalsIgnoreCase("default") && vs.indexOf("$any$") < 0 && vs.indexOf(style) < 0) { // a style is valid for a layer if it's the default style // or the layer accepts any style or a style is explicit defined // to be valid if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDSTYLE + layers[i].getName() + ':' + style); } userCoupled = true; accessdRes.add("Styles: " + style); } } } /** * checks if the passed bgcolor is valid against the bgcolor(s) defined in * the policy. If * <tt>user</ff> != <tt>null</tt> the valid bgcolors will be read from * the user/rights repository * @param condition condition containing the definition of the valid bgcolors * @param bgcolor * @throws InvalidParameterValueException */ private void validateBGColor(Condition condition, String bgcolor) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(BGCOLOR); // version is valid because no restrictions are made if (op.isAny()) return; List list = op.getValues(); if (!list.contains(bgcolor)) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDBGCOLOR + bgcolor); } accessdRes.add("BGCOLOR" + bgcolor); userCoupled = true; } } /** * checks if the passed transparency is valid against the transparency * defined in the policy. If * <tt>user</ff> != <tt>null</tt> the valid transparency will be * read from the user/rights repository * @param condition condition containing the definition of the valid transparency * @param transparency * @throws InvalidParameterValueException */ private void validateTransparency(Condition condition, boolean transparency) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(TRANSPARENCY); // version is valid because no restrictions are made if (op.isAny()) return; List<String> v = op.getValues(); String s = "" + transparency; if (!v.get(0).equals(s) && !v.get(v.size() - 1).equals(s)) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDTRANSPARENCY + transparency); } userCoupled = true; accessdRes.add("Transparency: " + transparency); } } /** * checks if the requested map area/size is valid against the minimum * resolution defined in the policy. If * <tt>user</ff> != <tt>null</tt> the valid * resolution will be read from the user/rights repository * @param condition condition containing the definition of the valid resolution * @param resolution * @throws InvalidParameterValueException */ private void validateResolution(Condition condition, GetMap gmr) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(RESOLUTION); // version is valid because no restrictions are made if (op.isAny()) return; double scale = 0; try { scale = calcScale(gmr); } catch (Exception e) { throw new InvalidParameterValueException(StringTools .stackTraceToString(e)); } double compareRes = 0; compareRes = op.getFirstAsDouble(); if (scale < compareRes) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDRESOLUTION + scale); } userCoupled = true; accessdRes.add("resolution: " + scale); } } /** * checks if the passed reference to a SLD document is valid against the * defined in the policy. If <tt>user</ff> != <tt>null</tt> the valid * sld reference addresses will be read from the user/rights repository * @param condition condition containing the definition of the valid sldRef * @param sldRef * @throws InvalidParameterValueException */ private void validateSLD(Condition condition, URL sldRef) throws InvalidParameterValueException { OperationParameter op = condition.getOperationParameter(SLD); OperationParameter gmop = condition.getOperationParameter(LAYERS); // version is valid because no restrictions are made if (sldRef == null || op.isAny()) { return; } // validate reference base of the SLD List<String> list = op.getValues(); String port = null; if (sldRef.getPort() != -1) { port = ":" + sldRef.getPort(); } else { port = ":80"; } String addr = sldRef.getProtocol() + "://" + sldRef.getHost() + port; if (!list.contains(addr)) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDSLD + sldRef); } userCoupled = true; } // validate referenced dacument to be a valid SLD StyledLayerDescriptor sld = null; try { sld = SLDFactory.createSLD(sldRef); } catch (XMLParsingException e) { String s = org.deegree.i18n.Messages.getMessage( "WMS_SLD_IS_NOT_VALID", sldRef); throw new InvalidParameterValueException(s); } // validate NamedLayers referenced by the SLD NamedLayer[] nl = sld.getNamedLayers(); List<String> v = gmop.getValues(); // seperate layers from assigned styles Map map = new HashMap(); for (int i = 0; i < v.size(); i++) { String[] tmp = StringTools.toArray(v.get(i), "|", false); map.put(tmp[0], tmp[1]); } if (!userCoupled) { for (int i = 0; i < nl.length; i++) { AbstractStyle st = nl[i].getStyles()[0]; String style = null; if (st instanceof NamedStyle) { style = ((NamedStyle) st).getName(); } else { // use default as name if a UserStyle is defined // to ensure that the style will be accepted by // the validator style = "default"; } String vs = (String) map.get(nl[i].getName()); if (vs == null) { if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDLAYER + nl[i].getName()); } accessdRes.add("Layers: " + nl[i].getName()); userCoupled = true; } else if (!style.equalsIgnoreCase("default") && vs.indexOf("$any$") < 0 && vs.indexOf(style) < 0) { // a style is valid for a layer if it's the default style // or the layer accepts any style or a style is explicit // defined // to be valid if (!op.isUserCoupled()) { throw new InvalidParameterValueException(INVALIDSTYLE + nl[i].getName() + ':' + style); } userCoupled = true; accessdRes.add("Styles: " + style); } } } } /** * checks if the passed user is allowed to perform a GetMap request * containing a SLD_BODY parameter. * * @param condition * condition containing when SLD_BODY is valid or nots * @param sld_body * @throws InvalidParameterValueException */ private void validateSLD_Body(Condition condition, StyledLayerDescriptor sld_body) throws InvalidParameterValueException { /* * * the problem is that sld_body never is null because it always will * contain the requested layers and styles * * OperationParameter op = condition.getOperationParameter( SLD_BODY ); // * version is valid because no restrictions are made if ( sld_body == * null ||op.isAny() ) return; // at the moment it is just evaluated if * the user is allowed // to perform a SLD request or not. no content * validation will // be made boolean isAllowed = false; if ( * op.isUserCoupled() ) { //TODO // get comparator list from security * registry } if (!isAllowed ) { throw new * InvalidParameterValueException( INVALIDSLD_BODY ); } */ } /** * validates the passed WMS GetMap request against a User- and * Rights-Management DB. * * @param wmsreq * @param user * @throws InvalidParameterValueException */ private void validateAgainstRightsDB(GetMap wmsreq, User user) throws InvalidParameterValueException, UnauthorizedException { if (user == null) { StringBuffer sb = new StringBuffer(1000); sb.append(' '); for (int i = 0; i < accessdRes.size(); i++) { sb.append(accessdRes.get(i)).append("; "); } throw new UnauthorizedException(Messages.format( "RequestValidator.NOACCESS", sb)); } Double scale = null; try { scale = new Double(calcScale(wmsreq)); } catch (Exception e) { throw new InvalidParameterValueException(e); } // create feature that describes the map request FeatureProperty[] fps = new FeatureProperty[11]; fps[0] = FeatureFactory.createFeatureProperty("version", wmsreq .getVersion()); fps[1] = FeatureFactory.createFeatureProperty("width", new Integer( wmsreq.getWidth())); fps[2] = FeatureFactory.createFeatureProperty("height", new Integer( wmsreq.getHeight())); Envelope env = wmsreq.getBoundingBox(); try { env = wgs84Transformer.transform(env, wmsreq.getSrs()); } catch (Exception e) { throw new InvalidParameterValueException( "A:condition envelope isn't in " + "the right CRS ", e); } Object geom = null; try { geom = GeometryFactory.createSurface(env, null); } catch (Exception e1) { e1.printStackTrace(); } fps[3] = FeatureFactory.createFeatureProperty("GEOM", geom); fps[4] = FeatureFactory.createFeatureProperty("format", wmsreq .getFormat()); fps[5] = FeatureFactory.createFeatureProperty("bgcolor", ColorUtils .toHexCode("0x", wmsreq.getBGColor())); fps[6] = FeatureFactory.createFeatureProperty("transparent", "" + wmsreq.getTransparency()); fps[7] = FeatureFactory.createFeatureProperty("exceptions", wmsreq .getExceptions()); fps[8] = FeatureFactory.createFeatureProperty("resolution", scale); fps[9] = FeatureFactory.createFeatureProperty("sld", wmsreq .getSLD_URL()); GetMap.Layer[] layers = wmsreq.getLayers(); for (int i = 0; i < layers.length; i++) { fps[10] = FeatureFactory.createFeatureProperty("style", layers[i] .getStyleName()); Feature feature = FeatureFactory.createFeature("id", mapFT, fps); handleUserCoupledRules(user, feature, layers[i].getName(), "Layer", RightType.GETMAP); } } /** * calculates the map scale as defined in the OGC WMS 1.1.1 specifications * * @return scale of the map */ private double calcScale(GetMap request) throws Exception { Envelope bbox = request.getBoundingBox(); CoordinateSystem crs = CRSFactory.create(request.getSrs()); return MapUtils.calcScale(request.getWidth(), request.getHeight(), bbox, crs, DEFAULT_PIXEL_SIZE); /* * if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) { // * transform the bounding box of the request to EPSG:4326 bbox = * gt.transformEnvelope( bbox, request.getSrs()); } * * double dx = bbox.getWidth() / request.getWidth(); double dy = * bbox.getHeight() / request.getHeight(); * // create a box on the central map pixel to determine its size in * meter Position min = GeometryFactory.createPosition( * bbox.getMin().getX() + dx * ( request.getWidth() / 2d - 1d ), * bbox.getMin().getY() + dy * ( request.getHeight() / 2d - 1d ) ); * Position max = GeometryFactory.createPosition( bbox.getMin().getX() + * dx * ( request.getWidth() / 2d ), bbox.getMin().getY() + dy * ( * request.getHeight() / 2d ) ); * * double sc = calcDistance( min.getY(), min.getX(), max.getY(), * max.getX() ); * * return sc; */ } private static FeatureType createFeatureType() { PropertyType[] ftps = new PropertyType[11]; ftps[0] = FeatureFactory.createSimplePropertyType(new QualifiedName( "version"), Types.VARCHAR, false); ftps[1] = FeatureFactory.createSimplePropertyType(new QualifiedName( "width"), Types.INTEGER, false); ftps[2] = FeatureFactory.createSimplePropertyType(new QualifiedName( "height"), Types.INTEGER, false); ftps[3] = FeatureFactory.createSimplePropertyType(new QualifiedName( "GEOM"), Types.GEOMETRY, false); ftps[4] = FeatureFactory.createSimplePropertyType(new QualifiedName( "format"), Types.VARCHAR, false); ftps[5] = FeatureFactory.createSimplePropertyType(new QualifiedName( "bgcolor"), Types.VARCHAR, false); ftps[6] = FeatureFactory.createSimplePropertyType(new QualifiedName( "transparent"), Types.VARCHAR, false); ftps[7] = FeatureFactory.createSimplePropertyType(new QualifiedName( "exceptions"), Types.VARCHAR, false); ftps[8] = FeatureFactory.createSimplePropertyType(new QualifiedName( "resolution"), Types.DOUBLE, false); ftps[9] = FeatureFactory.createSimplePropertyType(new QualifiedName( "sld"), Types.VARCHAR, false); ftps[10] = FeatureFactory.createSimplePropertyType(new QualifiedName( "style"), Types.VARCHAR, false); return FeatureFactory.createFeatureType("GetMap", false, ftps); } } /******************************************************************************* * Changes to this class. What the people have been up to: $Log: * GetMapRequestValidator.java,v $ Revision 1.9 2006/11/27 09:07:52 poth JNI * integration of proj4 has been removed. The CRS functionality now will be done * by native deegree code. * * Revision 1.8 2006/11/07 09:56:11 poth support for GetMap SLD parameter added * * Revision 1.7 2006/10/17 20:31:19 poth ** empty log message *** * * Revision 1.6 2006/09/27 16:46:41 poth transformation method signature changed * * Revision 1.5 2006/09/25 12:47:00 poth bug fixes - map scale calculation * * Revision 1.4 2006/08/10 07:17:52 poth bug fix - removing Arrays.asList calls * for transforming op.geValues because accoring to refactoring this method it * already returns a list * * Revision 1.3 2006/08/02 14:16:16 poth message for throwing an * UnauthrizedException corrected * * Revision 1.2 2006/08/02 09:45:09 poth changes required as consequence of * changing OperationParameter * * Revision 1.1 2006/07/23 08:44:53 poth refactoring - moved validators assigned * to OWS into specialized packages * * Revision 1.27 2006/06/07 12:19:38 poth ** empty log message *** * * Revision 1.26 2006/06/05 09:59:25 poth method for conversation from * java.awt.Color to its Hex code representation centralized in * framework.ColorUtil * * Revision 1.25 2006/05/25 09:53:31 poth adapated to changed/simplified policy * xml-schema * * ******************************************************************************/