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