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 }