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    }