]> source.dussan.org Git - archiva.git/blob
efef673c0fb1d554c48f88d9a42f1cffa15b54a6
[archiva.git] /
1 package org.apache.maven.archiva.webdav;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import com.opensymphony.xwork.ActionContext;
23 import org.apache.jackrabbit.webdav.*;
24 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
25 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
26 import org.apache.maven.archiva.repository.RepositoryException;
27 import org.apache.maven.archiva.repository.RepositoryContentFactory;
28 import org.apache.maven.archiva.repository.layout.LayoutException;
29 import org.apache.maven.archiva.repository.content.RepositoryRequest;
30 import org.apache.maven.archiva.repository.audit.AuditListener;
31 import org.apache.maven.archiva.repository.audit.Auditable;
32 import org.apache.maven.archiva.repository.audit.AuditEvent;
33 import org.apache.maven.archiva.repository.metadata.MetadataTools;
34 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
35 import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
36 import org.apache.maven.archiva.webdav.util.MimeTypes;
37 import org.apache.maven.archiva.webdav.util.RepositoryPathUtil;
38 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
39 import org.apache.maven.archiva.common.utils.PathUtil;
40 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
41 import org.apache.maven.archiva.configuration.RepositoryGroupConfiguration;
42 import org.apache.maven.archiva.model.ArtifactReference;
43 import org.apache.maven.archiva.model.ProjectReference;
44 import org.apache.maven.archiva.model.VersionedReference;
45 import org.apache.maven.archiva.policies.ProxyDownloadException;
46 import org.apache.maven.archiva.security.ArchivaXworkUser;
47 import org.apache.maven.archiva.security.ServletAuthenticator;
48 import org.apache.maven.model.DistributionManagement;
49 import org.apache.maven.model.Model;
50 import org.apache.maven.model.Relocation;
51 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
52 import org.codehaus.plexus.redback.authentication.AuthenticationException;
53 import org.codehaus.plexus.redback.authentication.AuthenticationResult;
54 import org.codehaus.plexus.redback.authorization.AuthorizationException;
55 import org.codehaus.plexus.redback.authorization.UnauthorizedException;
56 import org.codehaus.plexus.redback.policy.AccountLockedException;
57 import org.codehaus.plexus.redback.policy.MustChangePasswordException;
58 import org.codehaus.plexus.redback.system.SecuritySession;
59 import org.codehaus.plexus.redback.xwork.filter.authentication.HttpAuthenticator;
60 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import javax.servlet.http.HttpServletResponse;
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.io.*;
68
69 /**
70  * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
71  * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
72  */
73 public class ArchivaDavResourceFactory
74     implements DavResourceFactory, Auditable
75 {
76     private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
77
78     /**
79      * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
80      */
81     private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
82
83     /**
84      * @plexus.requirement
85      */
86     private RepositoryContentFactory repositoryFactory;
87
88     /**
89      * @plexus.requirement
90      */
91     private RepositoryRequest repositoryRequest;
92
93     /**
94      * @plexus.requirement role-hint="default"
95      */
96     private RepositoryProxyConnectors connectors;
97
98     /**
99      * @plexus.requirement
100      */
101     private MetadataTools metadataTools;
102
103     /**
104      * @plexus.requirement
105      */
106     private MimeTypes mimeTypes;
107
108     /**
109      * @plexus.requirement
110      */
111     private ArchivaConfiguration archivaConfiguration;
112     
113     /**
114      * @plexus.requirement
115      */
116     private ServletAuthenticator servletAuth;
117
118     /**
119      * @plexus.requirement role-hint="basic"
120      */
121     private HttpAuthenticator httpAuth;
122     
123     public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
124                                        final DavServletResponse response )
125         throws DavException
126     {
127         checkLocatorIsInstanceOfRepositoryLocator( locator );
128         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
129         
130         RepositoryGroupConfiguration repoGroupConfig =
131             archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get(
132                                                                                     ( (RepositoryLocator) locator ).getRepositoryId() );
133         List<String> repositories = new ArrayList<String>();
134
135         boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
136         boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
137         
138         if ( repoGroupConfig != null )
139         {
140             if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).equals( "/" ) ||
141                 WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
142             {
143                 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Bad request to repository group <" +
144                     repoGroupConfig.getId() + ">" );
145             }
146             repositories.addAll( repoGroupConfig.getRepositories() );
147             
148             // do not allow write request for repo groups
149             if( isPut )
150             {
151                 throw new DavException( HttpServletResponse.SC_FORBIDDEN, "Write request is not allowed for <" +
152                     repoGroupConfig.getId() + ">" );
153             }
154         }
155         else
156         {
157             repositories.add( ( (RepositoryLocator) locator ).getRepositoryId() );
158         }
159
160         DavResource resource = null;
161         DavException e = null;
162
163         for ( String repositoryId : repositories )
164         {
165             ManagedRepositoryContent managedRepository = null;
166
167             try
168             {
169                 managedRepository = getManagedRepository( repositoryId );
170             }
171             catch ( DavException de )
172             {
173                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
174                     repositoryId + ">" );
175             }
176
177             if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
178             {
179                 if ( managedRepository != null )
180                 {
181                     try
182                     {
183                         if( isAuthorized( request, repositoryId ) )
184                         {                        
185                             LogicalResource logicalResource =
186                                 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
187                                                 
188                             if ( isGet )
189                             {
190                                 resource = doGet( managedRepository, request, archivaLocator, logicalResource );
191                             }
192         
193                             if ( isPut )
194                             {
195                                 resource = doPut( managedRepository, request, archivaLocator, logicalResource );
196                             }
197                         }
198                     }
199                     catch ( DavException de )
200                     {   
201                         e = de;
202                         continue;
203                     }
204                                         
205                     if( resource == null )
206                     {
207                         e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
208                     }
209                     else
210                     {
211                         setHeaders( locator, response );
212
213                         // compatibility with MRM-440 to ensure browsing the repository works ok
214                         if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
215                         {
216                             throw new BrowserRedirectException( resource.getHref() );
217                         }
218
219                         return resource;
220                     }
221                 }
222                 else
223                 {
224                     e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
225                 }
226             }
227         }
228
229         throw e;
230     }
231     
232     public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
233         throws DavException
234     {
235         checkLocatorIsInstanceOfRepositoryLocator( locator );
236         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
237
238         DavResource resource = null;
239         if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
240         {
241             ManagedRepositoryContent managedRepository = getManagedRepository( archivaLocator.getRepositoryId() );
242             String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
243             File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource );
244             resource =
245                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, mimeTypes, archivaLocator,
246                                         this );
247         }
248         return resource;
249     }
250
251     private DavResource doGet( ManagedRepositoryContent managedRepository, DavServletRequest request,
252                                ArchivaDavResourceLocator locator, LogicalResource logicalResource )
253         throws DavException
254     {
255         File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
256         ArchivaDavResource resource =
257             new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this );
258
259         if ( !resource.isCollection() )
260         {
261             // At this point the incoming request can either be in default or
262             // legacy layout format.
263             boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource );
264
265             boolean previouslyExisted = resourceFile.exists();
266
267             try
268             {
269                 // Perform an adjustment of the resource to the managed
270                 // repository expected path.
271                 String localResourcePath =
272                     repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
273                 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
274             }
275             catch ( LayoutException e )
276             {
277                 if ( previouslyExisted )
278                 {
279                     return resource;
280                 }
281                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
282             }
283
284             // Attempt to fetch the resource from any defined proxy.
285             if ( fromProxy )
286             {
287                 processAuditEvents( request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted,
288                                     resourceFile, " (proxied)" );
289             }
290             resource =
291                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
292                                         this );
293
294             if ( !resourceFile.exists() )
295             {
296                 resource = null;
297             }
298         }
299         return resource;
300     }
301
302     private DavResource doPut( ManagedRepositoryContent managedRepository, DavServletRequest request,
303                                ArchivaDavResourceLocator locator, LogicalResource logicalResource )
304         throws DavException
305     {
306         /*
307          * Create parent directories that don't exist when writing a file This actually makes this implementation not
308          * compliant to the WebDAV RFC - but we have enough knowledge about how the collection is being used to do this
309          * reasonably and some versions of Maven's WebDAV don't correctly create the collections themselves.
310          */
311
312         File rootDirectory = new File( managedRepository.getRepoRoot() );
313         File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
314         if ( !destDir.exists() )
315         {
316             destDir.mkdirs();
317             String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
318             triggerAuditEvent( request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
319         }
320
321         File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
322
323         boolean previouslyExisted = resourceFile.exists();
324
325         processAuditEvents( request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted,
326                             resourceFile, null );
327
328         return new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
329                                        this );
330     }
331
332     private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
333                                              LogicalResource resource )
334         throws DavException
335     {
336         if ( repositoryRequest.isSupportFile( resource.getPath() ) )
337         {
338             // Checksums are fetched with artifact / metadata.
339
340             // Need to adjust the path for the checksum resource.
341             return false;
342         }
343
344         // Is it a Metadata resource?
345         if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
346         {
347             return fetchMetadataFromProxies( managedRepository, request, resource );
348         }
349
350         // Not any of the above? Then it's gotta be an artifact reference.
351         try
352         {
353             // Get the artifact reference in a layout neutral way.
354             ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
355
356             if ( artifact != null )
357             {
358                 applyServerSideRelocation( managedRepository, artifact );
359
360                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
361
362                 resource.setPath( managedRepository.toPath( artifact ) );
363
364                 return ( proxiedFile != null );
365             }
366         }
367         catch ( LayoutException e )
368         {
369             /* eat it */
370         }
371         catch ( ProxyDownloadException e )
372         {
373             log.error( e.getMessage(), e );
374             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource." );
375         }
376         return false;
377     }
378
379     private boolean fetchMetadataFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
380                                               LogicalResource resource )
381         throws DavException
382     {
383         ProjectReference project;
384         VersionedReference versioned;
385
386         try
387         {
388
389             versioned = metadataTools.toVersionedReference( resource.getPath() );
390             if ( versioned != null )
391             {
392                 connectors.fetchFromProxies( managedRepository, versioned );
393                 return true;
394             }
395         }
396         catch ( RepositoryMetadataException e )
397         {
398             /* eat it */
399         }
400
401         try
402         {
403             project = metadataTools.toProjectReference( resource.getPath() );
404             if ( project != null )
405             {
406                 connectors.fetchFromProxies( managedRepository, project );
407                 return true;
408             }
409         }
410         catch ( RepositoryMetadataException e )
411         {
412             /* eat it */
413         }
414
415         return false;
416     }
417
418     /**
419      * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do
420      * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the
421      * metadatas.
422      * <p>
423      * For such clients, archiva does server-side relocation by reading itself the &lt;relocation&gt; element in
424      * metadatas and serving the expected artifact.
425      */
426     protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
427         throws ProxyDownloadException
428     {
429         if ( "pom".equals( artifact.getType() ) )
430         {
431             return;
432         }
433
434         // Build the artifact POM reference
435         ArtifactReference pomReference = new ArtifactReference();
436         pomReference.setGroupId( artifact.getGroupId() );
437         pomReference.setArtifactId( artifact.getArtifactId() );
438         pomReference.setVersion( artifact.getVersion() );
439         pomReference.setType( "pom" );
440
441         // Get the artifact POM from proxied repositories if needed
442         connectors.fetchFromProxies( managedRepository, pomReference );
443
444         // Open and read the POM from the managed repo
445         File pom = managedRepository.toFile( pomReference );
446
447         if ( !pom.exists() )
448         {
449             return;
450         }
451
452         try
453         {
454             Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
455             DistributionManagement dist = model.getDistributionManagement();
456             if ( dist != null )
457             {
458                 Relocation relocation = dist.getRelocation();
459                 if ( relocation != null )
460                 {
461                     // artifact is relocated : update the repositoryPath
462                     if ( relocation.getGroupId() != null )
463                     {
464                         artifact.setGroupId( relocation.getGroupId() );
465                     }
466                     if ( relocation.getArtifactId() != null )
467                     {
468                         artifact.setArtifactId( relocation.getArtifactId() );
469                     }
470                     if ( relocation.getVersion() != null )
471                     {
472                         artifact.setVersion( relocation.getVersion() );
473                     }
474                 }
475             }
476         }
477         catch ( FileNotFoundException e )
478         {
479             // Artifact has no POM in repo : ignore
480         }
481         catch ( IOException e )
482         {
483             // Unable to read POM : ignore.
484         }
485         catch ( XmlPullParserException e )
486         {
487             // Invalid POM : ignore
488         }
489     }
490
491     private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
492                                      boolean previouslyExisted, File resourceFile, String suffix )
493     {
494         if ( suffix == null )
495         {
496             suffix = "";
497         }
498
499         // Process Create Audit Events.
500         if ( !previouslyExisted && resourceFile.exists() )
501         {
502             if ( resourceFile.isFile() )
503             {
504                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
505             }
506             else if ( resourceFile.isDirectory() )
507             {
508                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
509             }
510         }
511         // Process Remove Audit Events.
512         else if ( previouslyExisted && !resourceFile.exists() )
513         {
514             if ( resourceFile.isFile() )
515             {
516                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
517             }
518             else if ( resourceFile.isDirectory() )
519             {
520                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
521             }
522         }
523         // Process modify events.
524         else
525         {
526             if ( resourceFile.isFile() )
527             {
528                 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
529             }
530         }
531     }
532
533     private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
534     {
535         AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
536         event.setRemoteIP( remoteIP );
537
538         for ( AuditListener listener : auditListeners )
539         {
540             listener.auditEvent( event );
541         }
542     }
543
544     private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
545     {
546         triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ),
547                            getRemoteIP( request ), repositoryId, resource, action );
548     }
549
550     private String getRemoteIP( DavServletRequest request )
551     {
552         return request.getRemoteAddr();
553     }
554
555     public void addAuditListener( AuditListener listener )
556     {
557         this.auditListeners.add( listener );
558     }
559
560     public void clearAuditListeners()
561     {
562         this.auditListeners.clear();
563     }
564
565     public void removeAuditListener( AuditListener listener )
566     {
567         this.auditListeners.remove( listener );
568     }
569
570     private void setHeaders( DavResourceLocator locator, DavServletResponse response )
571     {
572         // [MRM-503] - Metadata file need Pragma:no-cache response
573         // header.
574         if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
575         {
576             response.addHeader( "Pragma", "no-cache" );
577             response.addHeader( "Cache-Control", "no-cache" );
578         }
579
580         // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
581     }
582
583     private ManagedRepositoryContent getManagedRepository( String respositoryId )
584         throws DavException
585     {
586         if ( respositoryId != null )
587         {
588             try
589             {
590                 return repositoryFactory.getManagedRepositoryContent( respositoryId );
591             }
592             catch ( RepositoryNotFoundException e )
593             {
594                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
595             }
596             catch ( RepositoryException e )
597             {
598                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
599             }
600         }
601         return null;
602     }
603
604     private void checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
605         throws DavException
606     {
607         if ( !( locator instanceof RepositoryLocator ) )
608         {
609             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
610                                     "Locator does not implement RepositoryLocator" );
611         }
612     }
613
614     class LogicalResource
615     {
616         private String path;
617
618         public LogicalResource( String path )
619         {
620             this.path = path;
621         }
622
623         public String getPath()
624         {
625             return path;
626         }
627
628         public void setPath( String path )
629         {
630             this.path = path;
631         }
632     }
633     
634     protected boolean isAuthorized( DavServletRequest request, String repositoryId )
635         throws DavException
636     {
637         try
638         {
639             AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );            
640             SecuritySession securitySession = httpAuth.getSecuritySession();
641             
642             return servletAuth.isAuthenticated( request, result ) &&
643                 servletAuth.isAuthorized( request, securitySession, repositoryId,
644                                           WebdavMethodUtil.isWriteMethod( request.getMethod() ) );
645         }
646         catch ( AuthenticationException e )
647         {            
648             throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
649         }
650         catch ( MustChangePasswordException e )
651         {         
652             throw new UnauthorizedDavException( repositoryId, "You must change your password." );
653         }
654         catch ( AccountLockedException e )
655         {            
656             throw new UnauthorizedDavException( repositoryId, "User account is locked." );
657         }
658         catch ( AuthorizationException e )
659         {         
660             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
661                                     "Fatal Authorization Subsystem Error." );
662         }
663         catch ( UnauthorizedException e )
664         {         
665             throw new UnauthorizedDavException( repositoryId, e.getMessage() );
666         }
667     }
668     
669 }