001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/security/owsrequestvalidator/wfs/GetFeatureRequestValidator.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2006 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: mschneider $
093 *
094 * @version 1.1, $Revision: 6625 $, $Date: 2007-04-17 18:05:27 +0200 (Di, 17 Apr 2007) $
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 Map<QualifiedName, Filter> localFilterMap;
178 if ( postConditions.getOperationParameter( "instanceFilter" ).isUserCoupled() ) {
179 // read filterMap from constraints defined in deegree DRM
180 localFilterMap = readFilterFromDRM( wfsreq, user );
181 } else {
182 fillFilterMap( postConditions );
183 // use filterMap read from policy document
184 localFilterMap = filterMap;
185 }
186 Query[] queries = wfsreq.getQuery();
187 for ( int i = 0; i < queries.length; i++ ) {
188 Filter filter = null;
189 if ( queries[i].getFilter() == null ) {
190 // if query does not define a filter just use the matching
191 // one from the post conditions
192 filter = localFilterMap.get( queries[i].getTypeNames()[0] );
193 } else if ( queries[i].getFilter() instanceof ComplexFilter ) {
194 // create a new Filter that is a combination of the
195 // original filter and the one defined in the GetFeatures
196 // PostConditions coupled by a logical 'And'
197 ComplexFilter qFilter = (ComplexFilter) queries[i].getFilter();
198 filter = localFilterMap.get( queries[i].getTypeNames()[0] );
199 if ( filter == null ) {
200 filter = qFilter;
201 } else {
202 filter = new ComplexFilter( qFilter, (ComplexFilter) filter, OperationDefines.AND );
203 }
204 } else if ( queries[i].getFilter() instanceof FeatureFilter ) {
205 // just take original filter if it is as feature filter
206 // because feature filter and complex filters can not
207 // be combined
208 filter = queries[i].getFilter();
209 }
210 // substitue query by a new one using the re-created filter
211 queries[i] = Query.create( queries[i].getPropertyNames(), queries[i].getFunctions(),
212 queries[i].getSortProperties(), queries[i].getHandle(),
213 queries[i].getFeatureVersion(), queries[i].getTypeNames(),
214 queries[i].getAliases(), queries[i].getSrsName(), filter,
215 queries[i].getMaxFeatures(), queries[i].getStartPosition(),
216 queries[i].getResultType() );
217 }
218 wfsreq.setQueries( queries );
219 }
220 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
221 try {
222 XMLFactory.export( wfsreq ).prettyPrint( System.out );
223 } catch ( Exception e ) {
224 }
225 }
226 }
227
228 /**
229 *
230 * @param wfsreq
231 * @param user
232 * @return
233 * @throws UnauthorizedException
234 * @throws InvalidParameterValueException
235 */
236 private Map<QualifiedName, Filter> readFilterFromDRM( GetFeature wfsreq, User user )
237 throws UnauthorizedException, InvalidParameterValueException {
238
239 Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>();
240 try {
241 SecurityAccessManager sam = SecurityAccessManager.getInstance();
242 SecurityAccess access = sam.acquireAccess( user );
243 Query[] queries = wfsreq.getQuery();
244 for ( int i = 0; i < queries.length; i++ ) {
245 QualifiedName qn = queries[i].getTypeNames()[0];
246 SecuredObject secObj = access.getSecuredObjectByName( qn.getFormattedString(),
247 ClientHelper.TYPE_FEATURETYPE );
248
249 RightSet rs = user.getRights( access, secObj );
250 Right right = rs.getRight( secObj, RightType.GETFEATURE_RESPONSE );
251 // a constraint - if available - is constructed as a OGC Filter
252 // one of the filter operations may is 'PropertyIsEqualTo' and
253 // defines a ProperyName == 'instanceFilter'. The Literal of this
254 // operation itself is a complete and valid Filter expression.
255 if ( right != null ) {
256 ComplexFilter filter = (ComplexFilter) right.getConstraints();
257 if ( filter != null ) {
258 // extract filter expression to be used as additional
259 // filter for a GetFeature request
260 filter = extractInstanceFilter( filter.getOperation() );
261 if ( filter != null ) {
262 map.put( qn, filter );
263 }
264 }
265 }
266 }
267 } catch ( GeneralSecurityException e ) {
268 LOG.logError( e.getMessage(), e );
269 throw new UnauthorizedException( e.getMessage(), e );
270 } catch ( FilterConstructionException e ) {
271 LOG.logError( e.getMessage(), e );
272 throw new InvalidParameterValueException( e.getMessage(), e );
273 } catch ( SAXException e ) {
274 LOG.logError( e.getMessage(), e );
275 throw new InvalidParameterValueException( e.getMessage(), e );
276 } catch ( IOException e ) {
277 LOG.logError( e.getMessage(), e );
278 throw new InvalidParameterValueException( e.getMessage(), e );
279 }
280
281 return map;
282 }
283
284 private void fillFilterMap( Condition postConditions )
285 throws InvalidParameterValueException {
286 List<Element> complexValues = postConditions.getOperationParameter( "instanceFilter" ).getComplexValues();
287 try {
288 if ( filterMap.size() == 0 ) {
289 for ( int i = 0; i < complexValues.size(); i++ ) {
290 Query q = Query.create( complexValues.get( 0 ) );
291 Filter f = q.getFilter();
292 QualifiedName qn = q.getTypeNames()[0];
293 filterMap.put( qn, f );
294 }
295 }
296 } catch ( XMLParsingException e ) {
297 LOG.logError( e.getMessage(), e );
298 throw new InvalidParameterValueException( this.getClass().getName(), e.getMessage() );
299 }
300 }
301
302 /**
303 * valides if the format you in a GetFeature request is valid against the policy assigned to
304 * Validator. If the passed user is not <tt>null</tt> and the format parameter is user coupled
305 * the format will be validated against a users and rights management system.
306 *
307 * @param condition
308 * @param format
309 * @throws InvalidParameterValueException
310 */
311 private void validateFormat( Condition condition, String format )
312 throws InvalidParameterValueException {
313 OperationParameter op = condition.getOperationParameter( FORMAT );
314
315 // version is valid because no restrictions are made
316 if ( op.isAny() )
317 return;
318
319 List validLayers = op.getValues();
320 if ( op.isUserCoupled() ) {
321 userCoupled = true;
322 } else {
323 if ( !validLayers.contains( format ) ) {
324 String s = Messages.getMessage( "OWSPROXY_DESCRIBEFEATURETYPE_FORMAT", format );
325 throw new InvalidParameterValueException( s );
326 }
327 }
328
329 }
330
331 /**
332 * valides if the format you in a GetFeature request is valid against the policy assigned to
333 * Validator. If the passed user is not <tt>null</tt> and the maxFeatures parameter is user
334 * coupled the maxFeatures will be validated against a users and rights management system.
335 *
336 * @param condition
337 * @param maxFeatures
338 * @throws InvalidParameterValueException
339 */
340 private void validateMaxFeatures( Condition condition, int maxFeatures )
341 throws InvalidParameterValueException {
342 OperationParameter op = condition.getOperationParameter( MAXFEATURES );
343
344 // version is valid because no restrictions are made
345 if ( op.isAny() )
346 return;
347
348 int maxF = Integer.parseInt( op.getValues().get( 0 ) );
349
350 if ( op.isUserCoupled() ) {
351 userCoupled = true;
352 } else {
353 if ( maxFeatures > maxF || maxFeatures < 0 ) {
354 String s = Messages.getMessage( "OWSPROXY_GETFEATURE_MAXFEATURE", maxFeatures );
355 throw new InvalidParameterValueException( s );
356 }
357 }
358
359 }
360
361 /**
362 * validates the passed WMS GetMap request against a User- and Rights-Management DB.
363 *
364 * @param wmsreq
365 * @param user
366 * @throws InvalidParameterValueException
367 */
368 private void validateAgainstRightsDB( GetFeature wfsreq, User user )
369 throws InvalidParameterValueException, UnauthorizedException {
370
371 if ( user == null ) {
372 throw new UnauthorizedException( "no access to anonymous user" );
373 }
374
375 // create feature that describes the map request
376 FeatureProperty[] fps = new FeatureProperty[3];
377 fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wfsreq.getVersion() );
378 Integer mxf = new Integer( wfsreq.getMaxFeatures() );
379 // The database can handle "features as a key", this feature is build from the request's
380 // features
381 fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "maxfeatures" ), mxf );
382 fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "outputformat" ), wfsreq.getOutputFormat() );
383
384 Feature feature = FeatureFactory.createFeature( "id", gfFT, fps );
385 Query[] queries = wfsreq.getQuery();
386 for ( int i = 0; i < queries.length; i++ ) {
387 StringBuffer sb = new StringBuffer( 200 );
388 sb.append( '{' ).append( queries[i].getTypeNames()[0].getNamespace().toASCIIString() );
389 sb.append( "}:" ).append( queries[i].getTypeNames()[0].getLocalName() );
390 handleUserCoupledRules( user, // the user who posted the request
391 feature, // This is the Database feature
392 sb.toString(), // the Qualified name of the users
393 // Featurerequest
394 ClientHelper.TYPE_FEATURETYPE, // a primary key in the db.
395 RightType.GETFEATURE );// We're requesting a featuretype.
396 }
397
398 }
399
400 /**
401 * creates a feature type that matches the parameters of a GetLagendGraphic request
402 *
403 * @return created <tt>FeatureType</tt>
404 */
405 private static FeatureType createFeatureType() {
406 PropertyType[] ftps = new PropertyType[3];
407 ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false );
408 ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "maxfeatures" ), Types.INTEGER, false );
409 ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "outputformat" ), Types.VARCHAR, false );
410
411 return FeatureFactory.createFeatureType( "GetFeature", false, ftps );
412 }
413 }