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    }