001 //$HeadURL: svn+ssh://rbezema@wald.intevation.org/deegree/base/trunk/src/org/deegree/security/owsrequestvalidator/csw/GetRecordsRequestValidator.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.csw;
044
045 import java.io.IOException;
046 import java.net.URI;
047 import java.net.URISyntaxException;
048 import java.net.URL;
049 import java.util.ArrayList;
050 import java.util.HashMap;
051 import java.util.List;
052 import java.util.Map;
053
054 import org.deegree.datatypes.QualifiedName;
055 import org.deegree.framework.log.ILogger;
056 import org.deegree.framework.log.LoggerFactory;
057 import org.deegree.framework.xml.NamespaceContext;
058 import org.deegree.framework.xml.XMLFragment;
059 import org.deegree.framework.xml.XMLParsingException;
060 import org.deegree.framework.xml.XMLTools;
061 import org.deegree.i18n.Messages;
062 import org.deegree.model.filterencoding.ComplexFilter;
063 import org.deegree.model.filterencoding.Filter;
064 import org.deegree.model.filterencoding.FilterConstructionException;
065 import org.deegree.model.filterencoding.Literal;
066 import org.deegree.model.filterencoding.LogicalOperation;
067 import org.deegree.model.filterencoding.Operation;
068 import org.deegree.model.filterencoding.OperationDefines;
069 import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
070 import org.deegree.model.filterencoding.PropertyName;
071 import org.deegree.ogcbase.CommonNamespaces;
072 import org.deegree.ogcwebservices.InvalidParameterValueException;
073 import org.deegree.ogcwebservices.OGCWebServiceRequest;
074 import org.deegree.ogcwebservices.csw.discovery.GetRepositoryItem;
075 import org.deegree.portal.standard.security.control.ClientHelper;
076 import org.deegree.security.GeneralSecurityException;
077 import org.deegree.security.UnauthorizedException;
078 import org.deegree.security.drm.SecurityAccess;
079 import org.deegree.security.drm.SecurityAccessManager;
080 import org.deegree.security.drm.model.Right;
081 import org.deegree.security.drm.model.RightSet;
082 import org.deegree.security.drm.model.RightType;
083 import org.deegree.security.drm.model.SecuredObject;
084 import org.deegree.security.drm.model.User;
085 import org.deegree.security.owsproxy.Condition;
086 import org.deegree.security.owsproxy.OperationParameter;
087 import org.deegree.security.owsproxy.Request;
088 import org.deegree.security.owsrequestvalidator.Policy;
089 import org.w3c.dom.Element;
090 import org.w3c.dom.Node;
091 import org.xml.sax.SAXException;
092
093 /**
094 * The <code>GetRepositoryItemRequestValidator</code> class can be used to check if a user has
095 * enough rights to request a repositoryItem.
096 *
097 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
098 *
099 * @author last edited by: $Author:$
100 *
101 * @version $Revision:$, $Date:$
102 *
103 */
104 public class GetRepositoryItemRequestValidator extends AbstractCSWRequestValidator {
105
106 private static ILogger LOG = LoggerFactory.getLogger( GetRepositoryItemRequestValidator.class );
107
108 private static Map<QualifiedName, Filter> filterMap = new HashMap<QualifiedName, Filter>();
109
110 private static String CSW_ADDRESS = "cswAddress";
111
112 /**
113 * @param policy
114 */
115 public GetRepositoryItemRequestValidator( Policy policy ) {
116 super( policy );
117 }
118
119 /**
120 * @param request
121 * @param user
122 */
123 @Override
124 public void validateRequest( OGCWebServiceRequest request, User user )
125 throws InvalidParameterValueException, UnauthorizedException {
126
127 userCoupled = false;
128 Request req = policy.getRequest( "CSW", "GetRepositoryItem" );
129
130 if ( req == null ) {
131 String message = org.deegree.i18n.Messages.getMessage( "OWSPROXY_GETREPITEM_NOT_DEFINED" );
132 LOG.logDebug( message );
133 throw new UnauthorizedException( message );
134 }
135 // request is valid because no restrictions are made
136 if ( req.isAny() )
137 return;
138 Condition condition = req.getPreConditions();
139 if ( condition == null ) {
140 String message = org.deegree.i18n.Messages.getMessage( "OWSPROXY_GETREPITEM_PRE_NOT_DEFINED" );
141 LOG.logDebug( message );
142 throw new UnauthorizedException( message );
143 }
144
145 GetRepositoryItem casreq = (GetRepositoryItem) request;
146
147 validateVersion( condition, casreq.getVersion() );
148
149 try {
150 // validate if the currect user is allowed to read the RegistryObject assigned
151 // to the requested repository item
152 validateReqistryObject( user, casreq, condition );
153 } catch ( XMLParsingException e ) {
154 throw new InvalidParameterValueException( e.getMessage(), e );
155 }
156
157 }
158
159 /**
160 * validate if the currect user is allowed to read the RegistryObject assigned to the requested
161 * repository item
162 *
163 * @param user
164 * @param casreq
165 * @param op
166 * @throws XMLParsingException
167 * @throws UnauthorizedException
168 * @throws InvalidParameterValueException
169 */
170 private void validateReqistryObject( User user, GetRepositoryItem casreq, Condition preConditions )
171 throws XMLParsingException, UnauthorizedException, InvalidParameterValueException {
172
173 OperationParameter oparam = preConditions.getOperationParameter( "extrinsicObject" );
174 if ( oparam.isAny() ) {
175 return;
176 }
177
178 QualifiedName elementName = null;
179 try {
180 // this can be hard coded because just ExtrinsicObjects can be have an assigend
181 // resource
182 elementName = new QualifiedName( null, "ExtrinsicObject",
183 new URI( "urn:oasis:names:tc:ebxml-regrep:xsd:rim:3.0" ) );
184 } catch ( URISyntaxException e ) {
185 // never happens
186 }
187
188 // reading ExtrinsicObjects that describes the requested resource
189 XMLFragment xml;
190 try {
191 xml = readExtrinsicObject( user, elementName, casreq.getRepositoryItemID() );
192 Element hit = XMLTools.getElement( xml.getRootElement(), "*", CommonNamespaces.getNamespaceContext() );
193 if( hit == null ){
194 return;
195 }
196 } catch ( Exception e ) {
197 LOG.logError( e.getMessage(), e );
198 throw new InvalidParameterValueException( e.getMessage(), e );
199 }
200
201 NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
202
203 Map<QualifiedName, Filter> localFilterMap = null;
204 if ( oparam.isUserCoupled() ) {
205 localFilterMap = readFilterFromDRM( user, elementName );
206 } else {
207 fillFilterMap( preConditions );
208 // use filterMap read from policy document
209 localFilterMap = filterMap;
210 }
211
212 ComplexFilter filter = (ComplexFilter) localFilterMap.get( elementName );
213 if ( filter == null ) {
214 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_GETREPITEM_PRE_NOT_ALLOWED" ) );
215 }
216
217 // check if returned dataset is valid against the instance filter. If not,
218 // thrown an UnauthorizedException
219 Operation op = filter.getOperation();
220 if ( op instanceof LogicalOperation ) {
221 LogicalOperation lo = (LogicalOperation) op;
222 if ( lo.getOperatorId() == OperationDefines.AND ) {
223 handleAnd( xml, lo );
224 } else if ( lo.getOperatorId() == OperationDefines.OR ) {
225 handleOr( xml, lo );
226 } else {
227 String msg = Messages.getMessage( "OWSPROXY_GETREPITEM_PRE_INVALID_LOGICAL_OPERATOR" );
228 throw new InvalidParameterValueException( msg );
229 }
230 } else {
231 Literal literal = (Literal) ( (PropertyIsCOMPOperation) op ).getSecondExpression();
232 String xpath = literal.getValue();
233 LOG.logDebug( "evaluated xpath expression: " + xpath );
234 List<Node> list = XMLTools.getNodes( xml.getRootElement(), xpath, nsc );
235 if ( list == null || list.size() == 0 ) {
236 // if the XPath do not return a result the user is not authorized
237 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_GETREPITEM_PRE_NOT_ALLOWED" ) );
238 }
239 }
240 }
241
242 private void fillFilterMap( Condition preConditions ) {
243 // TODO Auto-generated method stub
244
245 }
246
247 /**
248 * checks if a passed XML matches the XPath conditions contained in the passed OR operation. If
249 * at least one XPath matches the user is authoried to see the XML
250 *
251 * @param xml
252 * @param lo
253 * @throws UnauthorizedException
254 * @throws XMLParsingException
255 */
256 private void handleOr( XMLFragment xml, LogicalOperation lo )
257 throws UnauthorizedException, XMLParsingException {
258
259 NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
260 // It is assumed that an XPath is contained within the operations literal (second
261 // expression).
262 List<Operation> ops = lo.getArguments();
263 for ( Operation operation : ops ) {
264 Literal literal = (Literal) ( (PropertyIsCOMPOperation) operation ).getSecondExpression();
265 String xpath = literal.getValue();
266 LOG.logDebug( "evaluated xpath expression: " + xpath );
267 // check if the XML document matches the XPath. If none of xpath returns a result
268 // the user is not authorized to see this dataset
269 List<Node> list = XMLTools.getNodes( xml.getRootElement(), xpath, nsc );
270 if ( list != null && list.size() > 0 ) {
271 // at least one xpath returned more than nothing so the user is authorized
272 return;
273 }
274 }
275 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_GETRECBYID_NOT_ALLOWED" ) );
276 }
277
278 /**
279 * checks if a passed XML matches the XPath conditions contained in the passed AND operation.
280 * Just If all XPaths matches the user is authoried to see the XML
281 *
282 * @param xml
283 * @param lo
284 * @throws XMLParsingException
285 * @throws UnauthorizedException
286 */
287 private void handleAnd( XMLFragment xml, LogicalOperation lo )
288 throws XMLParsingException, UnauthorizedException {
289
290 NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
291 // It is assumed that an XPath is contained within the operations literal (second
292 // expression).
293 List<Operation> ops = lo.getArguments();
294 for ( Operation operation : ops ) {
295 Literal literal = (Literal) ( (PropertyIsCOMPOperation) operation ).getSecondExpression();
296 String xpath = literal.getValue();
297 LOG.logDebug( "evaluated xpath expression: " + xpath );
298 // check if the XML document matches the XPath. If at least one of xpath returns no
299 // result
300 // the user is not authorized to see this dataset
301 List<Node> list = XMLTools.getNodes( xml.getRootElement(), xpath, nsc );
302 if ( list == null || list.size() == 0 ) {
303 // if at least one XPath do not return a result the user is not authorized
304 throw new UnauthorizedException( Messages.getMessage( "OWSPROXY_CSW_GETRECBYID_NOT_ALLOWED" ) );
305 }
306 }
307
308 }
309
310 /**
311 *
312 * @param casreq
313 * @param user
314 * @return
315 * @throws UnauthorizedException
316 * @throws InvalidParameterValueException
317 */
318 private Map<QualifiedName, Filter> readFilterFromDRM( User user, QualifiedName elementName )
319 throws UnauthorizedException, InvalidParameterValueException {
320
321 Map<QualifiedName, Filter> map = new HashMap<QualifiedName, Filter>();
322 try {
323 Right right = getRight( user, elementName );
324 // a constraint - if available - is constructed as a OGC Filter
325 // one of the filter operations may is 'PropertyIsEqualTo' and
326 // defines a ProperyName == 'instanceFilter'. The Literal of this
327 // operation itself is a complete and valid Filter expression.
328 if ( right != null ) {
329 ComplexFilter filter = (ComplexFilter) right.getConstraints();
330 if ( filter != null ) {
331 List<ComplexFilter> foundFilters = new ArrayList<ComplexFilter>();
332 // extract filter expression to be used as additional
333 // filter for a GetFeature request
334 extractInstanceFilter( filter.getOperation(), foundFilters );
335 if ( foundFilters.size() == 1 ) {
336 filter = foundFilters.get( 0 );
337 } else if ( foundFilters.size() > 1 ) {
338 List<org.deegree.model.filterencoding.Operation> list = new ArrayList<org.deegree.model.filterencoding.Operation>();
339 for ( ComplexFilter cf : foundFilters ) {
340 list.add( cf.getOperation() );
341 }
342 LogicalOperation lo = new LogicalOperation( OperationDefines.OR, list );
343 filter = new ComplexFilter( lo );
344 }
345 map.put( elementName, filter );
346 }
347 }
348 } catch ( GeneralSecurityException e ) {
349 LOG.logError( e.getMessage(), e );
350 throw new UnauthorizedException( e.getMessage(), e );
351 } catch ( FilterConstructionException e ) {
352 LOG.logError( e.getMessage(), e );
353 throw new InvalidParameterValueException( e.getMessage(), e );
354 } catch ( SAXException e ) {
355 LOG.logError( e.getMessage(), e );
356 throw new InvalidParameterValueException( e.getMessage(), e );
357 } catch ( IOException e ) {
358 LOG.logError( e.getMessage(), e );
359 throw new InvalidParameterValueException( e.getMessage(), e );
360 }
361
362 return map;
363 }
364
365 /**
366 *
367 * @param user
368 * @param elementName
369 * @param repositoryItemID
370 * @return
371 * @throws InvalidParameterValueException
372 * @throws UnauthorizedException
373 * @throws SAXException
374 * @throws IOException
375 */
376 private XMLFragment readExtrinsicObject( User user, QualifiedName elementName, URI repositoryItemID )
377 throws InvalidParameterValueException, UnauthorizedException, IOException, SAXException {
378
379 String baseURL = null;
380 try {
381 Right right = getRight( user, elementName );
382 // a constraint - if available - is constructed as a OGC Filter
383 // one of the filter operations may is 'PropertyIsEqualTo' and
384 // defines a ProperyName == 'instanceFilter'. The Literal of this
385 // operation itself is a complete and valid Filter expression.
386 if ( right != null ) {
387 ComplexFilter filter = (ComplexFilter) right.getConstraints();
388 if ( filter != null ) {
389 // extract CSW address
390 baseURL = extractCSWAddress( filter.getOperation() );
391 }
392 }
393 } catch ( GeneralSecurityException e ) {
394 LOG.logError( e.getMessage(), e );
395 throw new UnauthorizedException( e.getMessage(), e );
396 }
397
398 URL url = new URL( baseURL + repositoryItemID.toASCIIString() );
399
400 return new XMLFragment( url );
401 }
402
403 private Right getRight( User user, QualifiedName elementName )
404 throws GeneralSecurityException, UnauthorizedException {
405 SecurityAccessManager sam = SecurityAccessManager.getInstance();
406 SecurityAccess access = sam.acquireAccess( user );
407 String entity = elementName.getFormattedString();
408 SecuredObject secObj = access.getSecuredObjectByName( entity, ClientHelper.TYPE_METADATASCHEMA );
409
410 RightSet rs = user.getRights( access, secObj );
411 return rs.getRight( secObj, RightType.GETREPOSITORYITEM );
412
413 }
414
415 /**
416 * reads address of the CSW to be used to request the RegistryObject describing the requested
417 * resource
418 *
419 * @param operation
420 * @return
421 * @throws InvalidParameterValueException
422 * @throws IOException
423 */
424 private String extractCSWAddress( Operation operation )
425 throws InvalidParameterValueException {
426
427 if ( operation.getOperatorId() == OperationDefines.AND ) {
428 List<Operation> arguments = ( (LogicalOperation) operation ).getArguments();
429 for ( int i = 0; i < arguments.size(); i++ ) {
430 Operation op = arguments.get( i );
431 if ( op.getOperatorId() == OperationDefines.PROPERTYISEQUALTO ) {
432 PropertyName pn = (PropertyName) ( (PropertyIsCOMPOperation) op ).getFirstExpression();
433 if ( CSW_ADDRESS.equals( pn.getValue().getAsString() ) ) {
434 Literal literal = (Literal) ( (PropertyIsCOMPOperation) op ).getSecondExpression();
435 return literal.getValue();
436 }
437 }
438 }
439 } else {
440 if ( operation.getOperatorId() == OperationDefines.PROPERTYISEQUALTO ) {
441 PropertyName pn = (PropertyName) ( (PropertyIsCOMPOperation) operation ).getFirstExpression();
442 if ( CSW_ADDRESS.equals( pn.getValue().getAsString() ) ) {
443 Literal literal = (Literal) ( (PropertyIsCOMPOperation) operation ).getSecondExpression();
444 return literal.getValue();
445 }
446 }
447 }
448 String msg = Messages.getMessage( "OWSPROXY_GETREPITEM_PRE_MISSING_CSWADDRESS" );
449 throw new InvalidParameterValueException( msg );
450 }
451
452 }