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