    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    }