001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstr. 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043 package org.deegree.security.owsrequestvalidator.wfs;
044
045 import java.io.IOException;
046 import java.util.HashMap;
047 import java.util.List;
048 import java.util.Map;
049
050 import org.deegree.datatypes.QualifiedName;
051 import org.deegree.datatypes.Types;
052 import org.deegree.framework.log.ILogger;
053 import org.deegree.framework.log.LoggerFactory;
054 import org.deegree.framework.xml.XMLParsingException;
055 import org.deegree.i18n.Messages;
056 import org.deegree.model.feature.Feature;
057 import org.deegree.model.feature.FeatureFactory;
058 import org.deegree.model.feature.FeatureProperty;
059 import org.deegree.model.feature.schema.FeatureType;
060 import org.deegree.model.feature.schema.PropertyType;
061 import org.deegree.model.filterencoding.ComplexFilter;
062 import org.deegree.model.filterencoding.FeatureFilter;
063 import org.deegree.model.filterencoding.Filter;
064 import org.deegree.model.filterencoding.FilterConstructionException;
065 import org.deegree.model.filterencoding.OperationDefines;
066 import org.deegree.ogcwebservices.InvalidParameterValueException;
067 import org.deegree.ogcwebservices.OGCWebServiceRequest;
068 import org.deegree.ogcwebservices.wfs.XMLFactory;
069 import org.deegree.ogcwebservices.wfs.operation.GetFeature;
070 import org.deegree.ogcwebservices.wfs.operation.Query;
071 import org.deegree.portal.standard.security.control.ClientHelper;
072 import org.deegree.security.GeneralSecurityException;
073 import org.deegree.security.UnauthorizedException;
074 import org.deegree.security.drm.SecurityAccess;
075 import org.deegree.security.drm.SecurityAccessManager;
076 import org.deegree.security.drm.model.Right;
077 import org.deegree.security.drm.model.RightSet;
078 import org.deegree.security.drm.model.RightType;
079 import org.deegree.security.drm.model.SecuredObject;
080 import org.deegree.security.drm.model.User;
081 import org.deegree.security.owsproxy.Condition;
082 import org.deegree.security.owsproxy.OperationParameter;
083 import org.deegree.security.owsproxy.Request;
084 import org.deegree.security.owsrequestvalidator.Policy;
085 import org.w3c.dom.Element;
086 import org.xml.sax.SAXException;
087
088 /**
089 *
090 *
091 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
092 * @author last edited by: $Author: apoth $
093 *
094 * @version 1.1, $Revision: 9683 $, $Date: 2008-01-23 10:37:26 +0100 (Mi, 23 Jan 2008) $
095 *
096 * @since 1.1
097 */
098 class GetFeatureRequestValidator extends AbstractWFSRequestValidator {
099
100 private static final ILogger LOG = LoggerFactory.getLogger( GetFeatureRequestValidator.class );
101
102 // known condition parameter
103 private static final String FORMAT = "format";
104
105 private static final String MAXFEATURES = "maxFeatures";
106
107 private static Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>();
108
109 private static FeatureType gfFT = null;
110
111 static {
112 if ( gfFT == null ) {
113 gfFT = GetFeatureRequestValidator.createFeatureType();
114 }
115 }
116
117 /**
118 * @param policy
119 */
120 public GetFeatureRequestValidator( Policy policy ) {
121 super( policy );
122 }
123
124 /**
125 * validates if the passed request is valid against the policy assigned to the validator. If the
126 * passed user is not <tt>null</tt> user coupled parameters will be validated against a users
127 * and rights management system.
128 */
129 public void validateRequest( OGCWebServiceRequest request, User user )
130 throws InvalidParameterValueException, UnauthorizedException {
131
132 userCoupled = false;
133 Request req = policy.getRequest( "WFS", "GetFeature" );
134 // request is valid because no restrictions are made
135 if ( req.isAny() )
136 return;
137 Condition condition = req.getPreConditions();
138
139 GetFeature wfsreq = (GetFeature) request;
140
141 validateVersion( condition, wfsreq.getVersion() );
142
143 Query[] queries = wfsreq.getQuery();
144 String[] ft = new String[queries.length];
145 for ( int i = 0; i < ft.length; i++ ) {
146 ft[i] = queries[i].getTypeNames()[0].getFormattedString();
147 }
148
149 validateFeatureTypes( condition, ft );
150 validateFormat( condition, wfsreq.getOutputFormat() );
151 validateMaxFeatures( condition, wfsreq.getMaxFeatures() );
152
153 if ( userCoupled ) {
154 validateAgainstRightsDB( wfsreq, user );
155 }
156
157 if ( req.getPostConditions() != null ) {
158 addFilter( wfsreq, req.getPostConditions(), user );
159 }
160
161 }
162
163 /**
164 * adds an additional Filter read from parameter 'instanceFilter'to the Filter of the passed
165 * GetFeature request. If parameter 'instanceFilter' is userCoupled the filter will be read from
166 * DRM, if it is not the filter defined within the responsible policy document will be used.
167 *
168 * @param wfsreq
169 * @param postConditions
170 * @param user
171 * @throws InvalidParameterValueException
172 * @throws UnauthorizedException
173 */
174 private void addFilter( GetFeature wfsreq, Condition postConditions, User user )
175 throws InvalidParameterValueException, UnauthorizedException {
176 if ( postConditions.getOperationParameter( "instanceFilter" ) != null
177 && !postConditions.getOperationParameter( "instanceFilter" ).isAny() ) {
178 Map<QualifiedName, Filter> localFilterMap;
179 if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) {
180 // read filterMap from constraints defined in deegree DRM
181 localFilterMap = readFilterFromDRM( wfsreq, user );
182 } else {
183 fillFilterMap( postConditions );
184 // use filterMap read from policy document
185 localFilterMap = filterMap;
186 }
187 Query[] queries = wfsreq.getQuery();
188 for ( int i = 0; i < queries.length; i++ ) {
189 Filter filter = null;
190 if ( queries[i].getFilter() == null ) {
191 // if query does not define a filter just use the matching
192 // one from the post conditions
193 filter = localFilterMap.get( queries[i].getTypeNames()[0] );
194 } else if ( queries[i].getFilter() instanceof ComplexFilter ) {
195 // create a new Filter that is a combination of the
196 // original filter and the one defined in the GetFeatures
197 // PostConditions coupled by a logical 'And'
198 ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter();
199 filter = localFilterMap.get( queries[i].getTypeNames()[0] );
200 if ( filter == null ) {
201 filter = qFilter;
202 } else {
203 filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND );
204 }
205 } else if ( queries[i].getFilter() instanceof FeatureFilter ) {
206 // just take original filter if it is as feature filter
207 // because feature filter and complex filters can not
208 // be combined
209 filter = queries[i].getFilter();
210 }
211 // substitue query by a new one using the re-created filter
212 queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(),
213 queries[i].getSortProperties(), queries[i].getHandle(),
214 queries[i].getFeatureVersion(), queries[i].getTypeNames(),
215 queries[i].getAliases(), queries[i].getSrsName(), filter,
216 queries[i].getMaxFeatures(), queries[i].getStartPosition(),
217 queries[i].getResultType() );
218 }
219 wfsreq.setQueries( queries );
220 }
221 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
222 try {
223 XMLFactory.export( wfsreq ).prettyPrint( System.out );
224 } catch ( Exception e ) {
225 }
226 }
227 }
228
229 /**
230 *
231 * @param wfsreq
232 * @param user
233 * @return
234 * @throws UnauthorizedException
235 * @throws InvalidParameterValueException
236 */
237 private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user )
238 throws UnauthorizedException, InvalidParameterValueException {
239
240 Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>();
241 try {
242 SecurityAccessManager sam = SecurityAccessManager.getInstance();
243 SecurityAccess access = sam.acquireAccess( user );
244 Query[] queries = wfsreq.getQuery();
245 for ( int i = 0; i < queries.length; i++ ) {
246 QualifiedName qn = queries[i].getTypeNames()[0];
247 SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(),
248 ClientHelper.TYPE_FEATURETYPE );
249
250 RightSet rs = user.getRights( access, secObj );
251 Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE );
252 // a constraint - if available - is constructed as a OGC Filter
253 // one of the filter operations may is 'PropertyIsEqualTo' and
254 // defines a ProperyName == 'instanceFilter'. The Literal of this
255 // operation itself is a complete and valid Filter expression.
256
257 if ( right != null ) {
258 ComplexFilter filter = (ComplexFilter) right.getConstraints();
259 if ( filter != null ) {
260 // extract filter expression to be used as additional
261 // filter for a GetFeature request
262 filter = extractInstanceFilter( filter.getOperation() );
263 if ( filter != null ) {
264 map.put( qn, filter );
265 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
266 LOG.logDebug( "instance filter for right GETFEATURE_RESPONSE", filter.toXML() );
267 }
268 } else {
269 LOG.logDebug( "no instance filter defined for right GETFEATURE_RESPONSE and feature type: "
270 + qn );
271 }
272 } else {
273 LOG.logDebug( "no constraint defined for right GETFEATURE_RESPONSE and feature type: " + qn );
274 }
275 } else {
276 LOG.logDebug( "right GETFEATURE_RESPONSE not defined for current user and feature type: " + qn );
277 }
278 }
279 } catch ( GeneralSecurityException e ) {
280 LOG.logError( e.getMessage(), e );
281 throw new UnauthorizedException( e.getMessage(), e );
282 } catch ( FilterConstructionException e ) {
283 LOG.logError( e.getMessage(), e );
284 throw new InvalidParameterValueException( e.getMessage(), e );
285 } catch ( SAXException e ) {
286 LOG.logError( e.getMessage(), e );
287 throw new InvalidParameterValueException( e.getMessage(), e );
288 } catch ( IOException e ) {
289 LOG.logError( e.getMessage(), e );
290 throw new InvalidParameterValueException( e.getMessage(), e );
291 }
292
293 return map;
294 }
295
296 private void fillFilterMap( Condition postConditions )
297 throws InvalidParameterValueException {
298 List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues();
299 try {
300 if ( filterMap.size() == 0 ) {
301 for ( int i = 0; i < complexValues.size(); i++ ) {
302 Query q = Query.create( complexValues.get( 0 ) );
303 Filter f = q.getFilter();
304 QualifiedName qn = q.getTypeNames()[0];
305 filterMap.put( qn, f );
306 }
307 }
308 } catch ( XMLParsingException e ) {
309 LOG.logError( e.getMessage(), e );
310 throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() );
311 }
312 }
313
314 /**
315 * valides if the format you in a GetFeature request is valid against the policy assigned to
316 * Validator. If the passed user is not <tt>null</tt> and the format parameter is user coupled
317 * the format will be validated against a users and rights management system.
318 *
319 * @param condition
320 * @param format
321 * @throws InvalidParameterValueException
322 */
323 private void validateFormat( Condition condition, String format )
324 throws InvalidParameterValueException {
325 OperationParameter op = condition.getOperationParameter( FORMAT );
326
327 // version is valid because no restrictions are made
328 if ( op.isAny() )
329 return;
330
331 List validLayers = op.getValues();
332 if ( op.isUserCoupled() ) {
333 userCoupled = true;
334 } else {
335 if ( !validLayers.contains( format ) ) {
336 String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format );
337 throw new InvalidParameterValueException( s );
338 }
339 }
340
341 }
342
343 /**
344 * valides if the format you in a GetFeature request is valid against the policy assigned to
345 * Validator. If the passed user is not <tt>null</tt> and the maxFeatures parameter is user
346 * coupled the maxFeatures will be validated against a users and rights management system.
347 *
348 * @param condition
349 * @param maxFeatures
350 * @throws InvalidParameterValueException
351 */
352 private void validateMaxFeatures( Condition condition, int maxFeatures )
353 throws InvalidParameterValueException {
354 OperationParameter op = condition.getOperationParameter( MAXFEATURES );
355
356 // version is valid because no restrictions are made
357 if ( op.isAny() )
358 return;
359
360 int maxF = Integer.parseInt( op.getValues().get( 0 ) );
361
362 if ( op.isUserCoupled() ) {
363 userCoupled = true;
364 } else {
365 if ( maxFeatures > maxF || maxFeatures < 0 ) {
366 String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures );
367 throw new InvalidParameterValueException( s );
368 }
369 }
370
371 }
372
373 /**
374 * validates the passed WMS GetMap request against a User- and Rights-Management DB.
375 *
376 * @param wmsreq
377 * @param user
378 * @throws InvalidParameterValueException
379 */
380 private void validateAgainstRightsDB( GetFeature wfsreq, User user )
381 throws InvalidParameterValueException, UnauthorizedException {
382
383 if ( user == null ) {
384 throw new UnauthorizedException( "no access to anonymous user" );
385 }
386
387 // create feature that describes the map request
388 FeatureProperty[] fps = new FeatureProperty[3];
389 fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() );
390 Integer mxf = new Integer( wfsreq.getMaxFeatures() );
391 // The database can handle "features as a key", this feature is build from the request's
392 // features
393 fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf );
394 fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() );
395
396 Feature feature = FeatureFactory.createFeature( "id", gfFT, fps );
397 Query[] queries = wfsreq.getQuery();
398 for ( int i = 0; i < queries.length; i++ ) {
399 StringBuffer sb = new StringBuffer( 200 );
400 sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() );
401 sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() );
402 handleUserCoupledRules( user, // the user who posted the request
403 feature, // This is the Database feature
404 sb.toString(), // the Qualified name of the users
405 // Featurerequest
406 ClientHelper.TYPE_FEATURETYPE, // a primary key in the db.
407 RightType.GETFEATURE );// We're requesting a featuretype.
408 }
409
410 }
411
412 /**
413 * creates a feature type that matches the parameters of a GetLagendGraphic request
414 *
415 * @return created <tt>FeatureType</tt>
416 */
417 private static FeatureType createFeatureType() {
418 PropertyType[] ftps = new PropertyType[3];
419 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false );
420 ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false );
421 ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false );
422
423 return FeatureFactory.createFeatureType( "GetFeature", false, ftps );
424 }
425 }