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 }