]> source.dussan.org Git - archiva.git/blob
f57f64b80a9ec879e86557a8764ab902f55ba0fa
[archiva.git] /
1 package org.apache.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 org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.audit.AuditEvent;
24 import org.apache.archiva.audit.AuditListener;
25 import org.apache.archiva.audit.Auditable;
26 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
27 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
28 import org.apache.archiva.common.utils.PathUtil;
29 import org.apache.archiva.common.utils.VersionUtil;
30 import org.apache.archiva.configuration.ArchivaConfiguration;
31 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
32 import org.apache.archiva.indexer.merger.IndexMerger;
33 import org.apache.archiva.indexer.merger.IndexMergerException;
34 import org.apache.archiva.indexer.search.RepositorySearch;
35 import org.apache.archiva.model.ArchivaRepositoryMetadata;
36 import org.apache.archiva.model.ArtifactReference;
37 import org.apache.archiva.policies.ProxyDownloadException;
38 import org.apache.archiva.proxy.RepositoryProxyConnectors;
39 import org.apache.archiva.repository.ManagedRepositoryContent;
40 import org.apache.archiva.repository.RepositoryContentFactory;
41 import org.apache.archiva.repository.RepositoryException;
42 import org.apache.archiva.repository.RepositoryNotFoundException;
43 import org.apache.archiva.repository.content.LegacyPathParser;
44 import org.apache.archiva.repository.content.RepositoryRequest;
45 import org.apache.archiva.repository.layout.LayoutException;
46 import org.apache.archiva.repository.metadata.MetadataTools;
47 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
48 import org.apache.archiva.repository.metadata.RepositoryMetadataMerge;
49 import org.apache.archiva.repository.metadata.RepositoryMetadataReader;
50 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
51 import org.apache.archiva.scheduler.repository.RepositoryArchivaTaskScheduler;
52 import org.apache.archiva.security.ServletAuthenticator;
53 import org.apache.archiva.webdav.util.MimeTypes;
54 import org.apache.archiva.webdav.util.RepositoryPathUtil;
55 import org.apache.archiva.webdav.util.WebdavMethodUtil;
56 import org.apache.commons.io.FileUtils;
57 import org.apache.commons.io.FilenameUtils;
58 import org.apache.commons.lang.StringUtils;
59 import org.apache.jackrabbit.webdav.DavException;
60 import org.apache.jackrabbit.webdav.DavResource;
61 import org.apache.jackrabbit.webdav.DavResourceFactory;
62 import org.apache.jackrabbit.webdav.DavResourceLocator;
63 import org.apache.jackrabbit.webdav.DavServletRequest;
64 import org.apache.jackrabbit.webdav.DavServletResponse;
65 import org.apache.jackrabbit.webdav.DavSession;
66 import org.apache.jackrabbit.webdav.lock.LockManager;
67 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
68 import org.apache.maven.model.DistributionManagement;
69 import org.apache.maven.model.Model;
70 import org.apache.maven.model.Relocation;
71 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
72 import org.codehaus.plexus.digest.ChecksumFile;
73 import org.codehaus.plexus.digest.Digester;
74 import org.codehaus.plexus.digest.DigesterException;
75 import org.codehaus.plexus.redback.authentication.AuthenticationException;
76 import org.codehaus.plexus.redback.authentication.AuthenticationResult;
77 import org.codehaus.plexus.redback.authorization.AuthorizationException;
78 import org.codehaus.plexus.redback.authorization.UnauthorizedException;
79 import org.codehaus.plexus.redback.policy.AccountLockedException;
80 import org.codehaus.plexus.redback.policy.MustChangePasswordException;
81 import org.codehaus.plexus.redback.system.SecuritySession;
82 import org.codehaus.plexus.redback.users.User;
83 import org.codehaus.plexus.redback.users.UserManager;
84 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
85 import org.codehaus.redback.integration.filter.authentication.HttpAuthenticator;
86 import org.slf4j.Logger;
87 import org.slf4j.LoggerFactory;
88 import org.springframework.context.ApplicationContext;
89 import org.springframework.stereotype.Service;
90
91 import javax.annotation.PostConstruct;
92 import javax.inject.Inject;
93 import javax.inject.Named;
94 import javax.servlet.http.HttpServletResponse;
95 import javax.servlet.http.HttpSession;
96 import java.io.File;
97 import java.io.FileNotFoundException;
98 import java.io.FileReader;
99 import java.io.IOException;
100 import java.util.ArrayList;
101 import java.util.HashMap;
102 import java.util.HashSet;
103 import java.util.List;
104 import java.util.Map;
105 import java.util.Set;
106
107 /**
108  *
109  */
110 @Service( "davResourceFactory#archiva" )
111 public class ArchivaDavResourceFactory
112     implements DavResourceFactory, Auditable
113 {
114     private static final String PROXIED_SUFFIX = " (proxied)";
115
116     private static final String HTTP_PUT_METHOD = "PUT";
117
118     private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
119
120     /**
121      *
122      */
123     @Inject
124     private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
125
126     /**
127      *
128      */
129     @Inject
130     private RepositoryContentFactory repositoryFactory;
131
132     /**
133      *
134      */
135     private RepositoryRequest repositoryRequest;
136
137     /**
138      *
139      */
140     @Inject
141     @Named( value = "repositoryProxyConnectors#default" )
142     private RepositoryProxyConnectors connectors;
143
144     /**
145      *
146      */
147     @Inject
148     private MetadataTools metadataTools;
149
150     /**
151      *
152      */
153     @Inject
154     private MimeTypes mimeTypes;
155
156     /**
157      *
158      */
159     private ArchivaConfiguration archivaConfiguration;
160
161     /**
162      *
163      */
164     @Inject
165     private ServletAuthenticator servletAuth;
166
167     /**
168      *
169      */
170     @Inject
171     @Named( value = "httpAuthenticator#basic" )
172     private HttpAuthenticator httpAuth;
173
174     @Inject
175     private IndexMerger indexMerger;
176
177     @Inject
178     private RepositorySearch repositorySearch;
179
180     /**
181      * Lock Manager - use simple implementation from JackRabbit
182      */
183     private final LockManager lockManager = new SimpleLockManager();
184
185     /**
186      *
187      */
188     private ChecksumFile checksum;
189
190     /**
191      *
192      */
193     private Digester digestSha1;
194
195     /**
196      *
197      */
198     private Digester digestMd5;
199
200     /**
201      *
202      */
203     @Inject
204     @Named( value = "archivaTaskScheduler#repository" )
205     private RepositoryArchivaTaskScheduler scheduler;
206
207     private ApplicationContext applicationContext;
208
209     @Inject
210     public ArchivaDavResourceFactory( ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge,
211                                       ArchivaConfiguration archivaConfiguration )
212         throws PlexusSisuBridgeException
213     {
214         this.archivaConfiguration = archivaConfiguration;
215         this.applicationContext = applicationContext;
216         this.checksum = plexusSisuBridge.lookup( ChecksumFile.class );
217
218         this.digestMd5 = plexusSisuBridge.lookup( Digester.class, "md5" );
219         this.digestSha1 = plexusSisuBridge.lookup( Digester.class, "sha1" );
220
221         repositoryRequest = new RepositoryRequest( new LegacyPathParser( archivaConfiguration ) );
222     }
223
224     @PostConstruct
225     public void initialize()
226     {
227
228     }
229
230     public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
231                                        final DavServletResponse response )
232         throws DavException
233     {
234         ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
235
236         RepositoryGroupConfiguration repoGroupConfig =
237             archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get( archivaLocator.getRepositoryId() );
238
239         String activePrincipal = getActivePrincipal( request );
240
241         List<String> resourcesInAbsolutePath = new ArrayList<String>();
242
243         boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
244         DavResource resource;
245         if ( repoGroupConfig != null )
246         {
247             if ( !readMethod )
248             {
249                 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
250                                         "Write method not allowed for repository groups." );
251             }
252
253             log.debug( "Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal );
254
255             // handle browse requests for virtual repos
256             if ( RepositoryPathUtil.getLogicalResource( archivaLocator.getOrigResourcePath() ).endsWith( "/" ) )
257             {
258                 return getResource( request, repoGroupConfig.getRepositories(), archivaLocator,
259                                     archivaLocator.getRepositoryId() );
260             }
261             else
262             {
263                 // make a copy to avoid potential concurrent modifications (eg. by configuration)
264                 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are
265                 //  infrequent
266                 List<String> repositories = new ArrayList<String>( repoGroupConfig.getRepositories() );
267                 resource = processRepositoryGroup( request, archivaLocator, repositories, activePrincipal,
268                                                    resourcesInAbsolutePath, archivaLocator.getRepositoryId() );
269             }
270         }
271         else
272         {
273             ManagedRepositoryContent managedRepository = null;
274
275             try
276             {
277                 managedRepository = repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
278             }
279             catch ( RepositoryNotFoundException e )
280             {
281                 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
282                                         "Invalid repository: " + archivaLocator.getRepositoryId() );
283             }
284             catch ( RepositoryException e )
285             {
286                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
287             }
288
289             log.debug( "Managed repository '{}' accessed by '{}'", managedRepository.getId(), activePrincipal );
290
291             resource = processRepository( request, archivaLocator, activePrincipal, managedRepository );
292
293             String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
294             resourcesInAbsolutePath.add(
295                 new File( managedRepository.getRepoRoot(), logicalResource ).getAbsolutePath() );
296         }
297
298         String requestedResource = request.getRequestURI();
299
300         // MRM-872 : merge all available metadata
301         // merge metadata only when requested via the repo group
302         if ( ( repositoryRequest.isMetadata( requestedResource ) || repositoryRequest.isMetadataSupportFile(
303             requestedResource ) ) && repoGroupConfig != null )
304         {
305             // this should only be at the project level not version level!
306             if ( isProjectReference( requestedResource ) )
307             {
308                 String artifactId = StringUtils.substringBeforeLast( requestedResource.replace( '\\', '/' ), "/" );
309                 artifactId = StringUtils.substringAfterLast( artifactId, "/" );
310
311                 ArchivaDavResource res = (ArchivaDavResource) resource;
312                 String filePath =
313                     StringUtils.substringBeforeLast( res.getLocalResource().getAbsolutePath().replace( '\\', '/' ),
314                                                      "/" );
315                 filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml";
316
317                 // for MRM-872 handle checksums of the merged metadata files
318                 if ( repositoryRequest.isSupportFile( requestedResource ) )
319                 {
320                     File metadataChecksum =
321                         new File( filePath + "." + StringUtils.substringAfterLast( requestedResource, "." ) );
322                     if ( metadataChecksum.exists() )
323                     {
324                         LogicalResource logicalResource =
325                             new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
326
327                         resource =
328                             new ArchivaDavResource( metadataChecksum.getAbsolutePath(), logicalResource.getPath(), null,
329                                                     request.getRemoteAddr(), activePrincipal, request.getDavSession(),
330                                                     archivaLocator, this, mimeTypes, auditListeners, scheduler );
331                     }
332                 }
333                 else
334                 {
335                     if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 )
336                     {
337                         // merge the metadata of all repos under group
338                         ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata();
339                         for ( String resourceAbsPath : resourcesInAbsolutePath )
340                         {
341                             try
342                             {
343                                 File metadataFile = new File( resourceAbsPath );
344                                 ArchivaRepositoryMetadata repoMetadata = RepositoryMetadataReader.read( metadataFile );
345                                 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata );
346                             }
347                             catch ( RepositoryMetadataException r )
348                             {
349                                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
350                                                         "Error occurred while reading metadata file." );
351                             }
352                         }
353
354                         try
355                         {
356                             File resourceFile = writeMergedMetadataToFile( mergedMetadata, filePath );
357
358                             LogicalResource logicalResource = new LogicalResource(
359                                 RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
360
361                             resource =
362                                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), null,
363                                                         request.getRemoteAddr(), activePrincipal,
364                                                         request.getDavSession(), archivaLocator, this, mimeTypes,
365                                                         auditListeners, scheduler );
366                         }
367                         catch ( RepositoryMetadataException r )
368                         {
369                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
370                                                     "Error occurred while writing metadata file." );
371                         }
372                         catch ( IOException ie )
373                         {
374                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
375                                                     "Error occurred while generating checksum files." );
376                         }
377                         catch ( DigesterException de )
378                         {
379                             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
380                                                     "Error occurred while generating checksum files." );
381                         }
382                     }
383                 }
384             }
385         }
386
387         setHeaders( response, locator, resource );
388
389         // compatibility with MRM-440 to ensure browsing the repository works ok
390         if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
391         {
392             throw new BrowserRedirectException( resource.getHref() );
393         }
394         resource.addLockManager( lockManager );
395         return resource;
396     }
397
398     private DavResource processRepositoryGroup( final DavServletRequest request,
399                                                 ArchivaDavResourceLocator archivaLocator, List<String> repositories,
400                                                 String activePrincipal, List<String> resourcesInAbsolutePath,
401                                                 String repositoryGroupId )
402         throws DavException
403     {
404         DavResource resource = null;
405         List<DavException> storedExceptions = new ArrayList<DavException>();
406
407         String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
408
409         String rootPath = StringUtils.substringBeforeLast( pathInfo, "/" );
410
411         if ( StringUtils.endsWith( rootPath, "/.indexer" ) )
412         {
413             // we are in the case of index file request
414             String requestedFileName = StringUtils.substringAfterLast( pathInfo, "/" );
415             File temporaryIndexDirectory =
416                 buildMergedIndexDirectory( repositories, activePrincipal, request, repositoryGroupId );
417
418             File resourceFile = new File( temporaryIndexDirectory, requestedFileName );
419             resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), requestedFileName, null,
420                                                request.getRemoteAddr(), activePrincipal, request.getDavSession(),
421                                                archivaLocator, this, mimeTypes, auditListeners, scheduler );
422
423         }
424         else
425         {
426             for ( String repositoryId : repositories )
427             {
428                 ManagedRepositoryContent managedRepository;
429                 try
430                 {
431                     managedRepository = repositoryFactory.getManagedRepositoryContent( repositoryId );
432                 }
433                 catch ( RepositoryNotFoundException e )
434                 {
435                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
436                 }
437                 catch ( RepositoryException e )
438                 {
439                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
440                 }
441
442                 try
443                 {
444                     DavResource updatedResource =
445                         processRepository( request, archivaLocator, activePrincipal, managedRepository );
446                     if ( resource == null )
447                     {
448                         resource = updatedResource;
449                     }
450
451                     String logicalResource = RepositoryPathUtil.getLogicalResource( archivaLocator.getResourcePath() );
452                     if ( logicalResource.endsWith( "/" ) )
453                     {
454                         logicalResource = logicalResource.substring( 1 );
455                     }
456                     resourcesInAbsolutePath.add(
457                         new File( managedRepository.getRepoRoot(), logicalResource ).getAbsolutePath() );
458                 }
459                 catch ( DavException e )
460                 {
461                     storedExceptions.add( e );
462                 }
463             }
464         }
465         if ( resource == null )
466         {
467             if ( !storedExceptions.isEmpty() )
468             {
469                 // MRM-1232
470                 for ( DavException e : storedExceptions )
471                 {
472                     if ( 401 == e.getErrorCode() )
473                     {
474                         throw e;
475                     }
476                 }
477
478                 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
479             }
480             else
481             {
482                 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
483             }
484         }
485         return resource;
486     }
487
488     private DavResource processRepository( final DavServletRequest request, ArchivaDavResourceLocator archivaLocator,
489                                            String activePrincipal, ManagedRepositoryContent managedRepository )
490         throws DavException
491     {
492         DavResource resource = null;
493         if ( isAuthorized( request, managedRepository.getId() ) )
494         {
495             String path = RepositoryPathUtil.getLogicalResource( archivaLocator.getResourcePath() );
496             if ( path.startsWith( "/" ) )
497             {
498                 path = path.substring( 1 );
499             }
500             LogicalResource logicalResource = new LogicalResource( path );
501             File resourceFile = new File( managedRepository.getRepoRoot(), path );
502             resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), path, managedRepository.getRepository(),
503                                                request.getRemoteAddr(), activePrincipal, request.getDavSession(),
504                                                archivaLocator, this, mimeTypes, auditListeners, scheduler );
505
506             if ( WebdavMethodUtil.isReadMethod( request.getMethod() ) )
507             {
508                 if ( archivaLocator.getHref( false ).endsWith( "/" ) && !resourceFile.isDirectory() )
509                 {
510                     // force a resource not found
511                     throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
512                 }
513                 else
514                 {
515                     if ( !resource.isCollection() )
516                     {
517                         boolean previouslyExisted = resourceFile.exists();
518
519                         // Attempt to fetch the resource from any defined proxy.
520                         boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource );
521
522                         // At this point the incoming request can either be in default or
523                         // legacy layout format.
524                         try
525                         {
526                             // Perform an adjustment of the resource to the managed
527                             // repository expected path.
528                             String localResourcePath =
529                                 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
530                             resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
531                             resource =
532                                 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(),
533                                                         managedRepository.getRepository(), request.getRemoteAddr(),
534                                                         activePrincipal, request.getDavSession(), archivaLocator, this,
535                                                         mimeTypes, auditListeners, scheduler );
536                         }
537                         catch ( LayoutException e )
538                         {
539                             if ( !resourceFile.exists() )
540                             {
541                                 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
542                             }
543                         }
544
545                         if ( fromProxy )
546                         {
547                             String event = ( previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE )
548                                 + PROXIED_SUFFIX;
549
550                             if ( log.isDebugEnabled() )
551                             {
552                                 log.debug( "Proxied artifact '" + resourceFile.getName() + "' in repository '"
553                                                + managedRepository.getId() + "' (current user '" + activePrincipal
554                                                + "')" );
555                             }
556                             triggerAuditEvent( request.getRemoteAddr(), archivaLocator.getRepositoryId(),
557                                                logicalResource.getPath(), event, activePrincipal );
558                         }
559
560                         if ( !resourceFile.exists() )
561                         {
562                             throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
563                         }
564                     }
565                 }
566             }
567
568             if ( request.getMethod().equals( HTTP_PUT_METHOD ) )
569             {
570                 String resourcePath = logicalResource.getPath();
571
572                 // check if target repo is enabled for releases
573                 // we suppose that release-artifacts can be deployed only to repos enabled for releases
574                 if ( managedRepository.getRepository().isReleases() && !repositoryRequest.isMetadata( resourcePath )
575                     && !repositoryRequest.isSupportFile( resourcePath ) )
576                 {
577                     ArtifactReference artifact = null;
578                     try
579                     {
580                         artifact = managedRepository.toArtifactReference( resourcePath );
581
582                         if ( !VersionUtil.isSnapshot( artifact.getVersion() ) )
583                         {
584                             // check if artifact already exists and if artifact re-deployment to the repository is allowed
585                             if ( managedRepository.hasContent( artifact )
586                                 && managedRepository.getRepository().isBlockRedeployments() )
587                             {
588                                 log.warn( "Overwriting released artifacts in repository '" + managedRepository.getId()
589                                               + "' is not allowed." );
590                                 throw new DavException( HttpServletResponse.SC_CONFLICT,
591                                                         "Overwriting released artifacts is not allowed." );
592                             }
593                         }
594                     }
595                     catch ( LayoutException e )
596                     {
597                         log.warn( "Artifact path '" + resourcePath + "' is invalid." );
598                     }
599                 }
600
601                 /*
602                  * Create parent directories that don't exist when writing a file This actually makes this
603                  * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the
604                  * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly
605                  * create the collections themselves.
606                  */
607
608                 File rootDirectory = new File( managedRepository.getRepoRoot() );
609                 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
610
611                 if ( !destDir.exists() )
612                 {
613                     destDir.mkdirs();
614                     String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
615
616                     log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getName(),
617                                activePrincipal );
618
619                     triggerAuditEvent( request.getRemoteAddr(), managedRepository.getId(), relPath,
620                                        AuditEvent.CREATE_DIR, activePrincipal );
621                 }
622             }
623         }
624         return resource;
625     }
626
627     public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
628         throws DavException
629     {
630         ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
631
632         ManagedRepositoryContent managedRepository;
633         try
634         {
635             managedRepository = repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
636         }
637         catch ( RepositoryNotFoundException e )
638         {
639             throw new DavException( HttpServletResponse.SC_NOT_FOUND,
640                                     "Invalid repository: " + archivaLocator.getRepositoryId() );
641         }
642         catch ( RepositoryException e )
643         {
644             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
645         }
646
647         String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
648         if ( logicalResource.startsWith( "/" ) )
649         {
650             logicalResource = logicalResource.substring( 1 );
651         }
652         File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource );
653         DavResource resource =
654             new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, managedRepository.getRepository(),
655                                     davSession, archivaLocator, this, mimeTypes, auditListeners, scheduler );
656
657         resource.addLockManager( lockManager );
658         return resource;
659     }
660
661     private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
662                                              LogicalResource resource )
663         throws DavException
664     {
665         String path = resource.getPath();
666         if ( repositoryRequest.isSupportFile( path ) )
667         {
668             File proxiedFile = connectors.fetchFromProxies( managedRepository, path );
669
670             return ( proxiedFile != null );
671         }
672
673         // Is it a Metadata resource?
674         if ( repositoryRequest.isDefault( path ) && repositoryRequest.isMetadata( path ) )
675         {
676             return connectors.fetchMetatadaFromProxies( managedRepository, path ) != null;
677         }
678
679         // Not any of the above? Then it's gotta be an artifact reference.
680         try
681         {
682             // Get the artifact reference in a layout neutral way.
683             ArtifactReference artifact = repositoryRequest.toArtifactReference( path );
684
685             if ( artifact != null )
686             {
687                 applyServerSideRelocation( managedRepository, artifact );
688
689                 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
690
691                 resource.setPath( managedRepository.toPath( artifact ) );
692                 if ( log.isDebugEnabled() )
693                 {
694                     log.debug( "Proxied artifact '" + artifact.getGroupId() + ":" + artifact.getArtifactId() + ":"
695                                    + artifact.getVersion() + "'" );
696                 }
697                 return ( proxiedFile != null );
698             }
699         }
700         catch ( LayoutException e )
701         {
702             /* eat it */
703         }
704         catch ( ProxyDownloadException e )
705         {
706             log.error( e.getMessage(), e );
707             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
708                                     "Unable to fetch artifact resource." );
709         }
710         return false;
711     }
712
713     /**
714      * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do
715      * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the
716      * metadatas.
717      * <p/>
718      * For such clients, archiva does server-side relocation by reading itself the &lt;relocation&gt; element in
719      * metadatas and serving the expected artifact.
720      */
721     protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
722         throws ProxyDownloadException
723     {
724         if ( "pom".equals( artifact.getType() ) )
725         {
726             return;
727         }
728
729         // Build the artifact POM reference
730         ArtifactReference pomReference = new ArtifactReference();
731         pomReference.setGroupId( artifact.getGroupId() );
732         pomReference.setArtifactId( artifact.getArtifactId() );
733         pomReference.setVersion( artifact.getVersion() );
734         pomReference.setType( "pom" );
735
736         // Get the artifact POM from proxied repositories if needed
737         connectors.fetchFromProxies( managedRepository, pomReference );
738
739         // Open and read the POM from the managed repo
740         File pom = managedRepository.toFile( pomReference );
741
742         if ( !pom.exists() )
743         {
744             return;
745         }
746
747         try
748         {
749             // MavenXpp3Reader leaves the file open, so we need to close it ourselves.
750             FileReader reader = new FileReader( pom );
751             Model model = null;
752             try
753             {
754                 model = new MavenXpp3Reader().read( reader );
755             }
756             finally
757             {
758                 if ( reader != null )
759                 {
760                     reader.close();
761                 }
762             }
763
764             DistributionManagement dist = model.getDistributionManagement();
765             if ( dist != null )
766             {
767                 Relocation relocation = dist.getRelocation();
768                 if ( relocation != null )
769                 {
770                     // artifact is relocated : update the repositoryPath
771                     if ( relocation.getGroupId() != null )
772                     {
773                         artifact.setGroupId( relocation.getGroupId() );
774                     }
775                     if ( relocation.getArtifactId() != null )
776                     {
777                         artifact.setArtifactId( relocation.getArtifactId() );
778                     }
779                     if ( relocation.getVersion() != null )
780                     {
781                         artifact.setVersion( relocation.getVersion() );
782                     }
783                 }
784             }
785         }
786         catch ( FileNotFoundException e )
787         {
788             // Artifact has no POM in repo : ignore
789         }
790         catch ( IOException e )
791         {
792             // Unable to read POM : ignore.
793         }
794         catch ( XmlPullParserException e )
795         {
796             // Invalid POM : ignore
797         }
798     }
799
800     // TODO: remove?
801
802     private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action,
803                                     String principal )
804     {
805         AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
806         event.setRemoteIP( remoteIP );
807
808         for ( AuditListener listener : auditListeners )
809         {
810             listener.auditEvent( event );
811         }
812     }
813
814     public void addAuditListener( AuditListener listener )
815     {
816         this.auditListeners.add( listener );
817     }
818
819     public void clearAuditListeners()
820     {
821         this.auditListeners.clear();
822     }
823
824     public void removeAuditListener( AuditListener listener )
825     {
826         this.auditListeners.remove( listener );
827     }
828
829     private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource )
830     {
831         // [MRM-503] - Metadata file need Pragma:no-cache response
832         // header.
833         if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
834         {
835             response.addHeader( "Pragma", "no-cache" );
836             response.addHeader( "Cache-Control", "no-cache" );
837         }
838
839         // We need to specify this so connecting wagons can work correctly
840         response.addDateHeader( "last-modified", resource.getModificationTime() );
841
842         // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
843     }
844
845     private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
846         throws DavException
847     {
848         if ( !( locator instanceof ArchivaDavResourceLocator ) )
849         {
850             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
851                                     "Locator does not implement RepositoryLocator" );
852         }
853
854         // Hidden paths
855         if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
856         {
857             throw new DavException( HttpServletResponse.SC_NOT_FOUND );
858         }
859
860         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
861
862         // MRM-419 - Windows Webdav support. Should not 404 if there is no content.
863         if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) )
864         {
865             throw new DavException( HttpServletResponse.SC_NO_CONTENT );
866         }
867         return archivaLocator;
868     }
869
870     private static class LogicalResource
871     {
872         private String path;
873
874         public LogicalResource( String path )
875         {
876             this.path = path;
877         }
878
879         public String getPath()
880         {
881             return path;
882         }
883
884         public void setPath( String path )
885         {
886             this.path = path;
887         }
888     }
889
890     protected boolean isAuthorized( DavServletRequest request, String repositoryId )
891         throws DavException
892     {
893         try
894         {
895             AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
896             SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) );
897
898             return servletAuth.isAuthenticated( request, result ) && servletAuth.isAuthorized( request, securitySession,
899                                                                                                repositoryId,
900                                                                                                WebdavMethodUtil.getMethodPermission(
901                                                                                                    request.getMethod() ) );
902         }
903         catch ( AuthenticationException e )
904         {
905             // safety check for MRM-911
906             String guest = UserManager.GUEST_USERNAME;
907             try
908             {
909                 if ( servletAuth.isAuthorized( guest,
910                                                ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(),
911                                                WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
912                 {
913                     return true;
914                 }
915             }
916             catch ( UnauthorizedException ae )
917             {
918                 throw new UnauthorizedDavException( repositoryId,
919                                                     "You are not authenticated and authorized to access any repository." );
920             }
921
922             throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
923         }
924         catch ( MustChangePasswordException e )
925         {
926             throw new UnauthorizedDavException( repositoryId, "You must change your password." );
927         }
928         catch ( AccountLockedException e )
929         {
930             throw new UnauthorizedDavException( repositoryId, "User account is locked." );
931         }
932         catch ( AuthorizationException e )
933         {
934             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
935                                     "Fatal Authorization Subsystem Error." );
936         }
937         catch ( UnauthorizedException e )
938         {
939             throw new UnauthorizedDavException( repositoryId, e.getMessage() );
940         }
941     }
942
943     private DavResource getResource( DavServletRequest request, List<String> repositories,
944                                      ArchivaDavResourceLocator locator, String groupId )
945         throws DavException
946     {
947         List<File> mergedRepositoryContents = new ArrayList<File>();
948         String path = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
949         if ( path.startsWith( "/" ) )
950         {
951             path = path.substring( 1 );
952         }
953         LogicalResource logicalResource = new LogicalResource( path );
954
955         // flow:
956         // if the current user logged in has permission to any of the repositories, allow user to
957         // browse the repo group but displaying only the repositories which the user has permission to access.
958         // otherwise, prompt for authentication.
959
960         String activePrincipal = getActivePrincipal( request );
961
962         boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
963
964         if ( allow )
965         {
966
967             // remove last /
968             String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
969             if ( StringUtils.endsWith( pathInfo, "/.indexer" ) )
970             {
971                 File mergedRepoDir = buildMergedIndexDirectory( repositories, activePrincipal, request, groupId );
972                 mergedRepositoryContents.add( mergedRepoDir );
973             }
974             else
975             {
976                 for ( String repository : repositories )
977                 {
978                     ManagedRepositoryContent managedRepository = null;
979
980                     try
981                     {
982                         managedRepository = repositoryFactory.getManagedRepositoryContent( repository );
983                     }
984                     catch ( RepositoryNotFoundException e )
985                     {
986                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
987                                                 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
988                     }
989                     catch ( RepositoryException e )
990                     {
991                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
992                                                 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
993                     }
994
995                     File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
996                     if ( resourceFile.exists() )
997                     {
998                         // in case of group displaying index directory doesn't have sense !!
999                         String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory();
1000                         if ( StringUtils.isNotEmpty( repoIndexDirectory ) )
1001                         {
1002                             if ( !new File( repoIndexDirectory ).isAbsolute() )
1003                             {
1004                                 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1005                                                                StringUtils.isEmpty( repoIndexDirectory )
1006                                                                    ? ".indexer"
1007                                                                    : repoIndexDirectory ).getAbsolutePath();
1008                             }
1009                         }
1010                         if ( StringUtils.isEmpty( repoIndexDirectory ) )
1011                         {
1012                             repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1013                                                            ".indexer" ).getAbsolutePath();
1014                         }
1015
1016                         if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory ),
1017                                                   FilenameUtils.normalize( resourceFile.getAbsolutePath() ) ) )
1018                         {
1019                             // for prompted authentication
1020                             if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null )
1021                             {
1022                                 try
1023                                 {
1024                                     if ( isAuthorized( request, repository ) )
1025                                     {
1026                                         mergedRepositoryContents.add( resourceFile );
1027                                         log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1028                                     }
1029                                 }
1030                                 catch ( DavException e )
1031                                 {
1032                                     // TODO: review exception handling
1033                                     if ( log.isDebugEnabled() )
1034                                     {
1035                                         log.debug( "Skipping repository '" + managedRepository + "' for user '"
1036                                                        + activePrincipal + "': " + e.getMessage() );
1037                                     }
1038                                 }
1039
1040                             }
1041                             else
1042                             {
1043                                 // for the current user logged in
1044                                 try
1045                                 {
1046                                     if ( servletAuth.isAuthorized( activePrincipal, repository,
1047                                                                    WebdavMethodUtil.getMethodPermission(
1048                                                                        request.getMethod() ) ) )
1049                                     {
1050                                         mergedRepositoryContents.add( resourceFile );
1051                                         log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1052                                     }
1053                                 }
1054                                 catch ( UnauthorizedException e )
1055                                 {
1056                                     // TODO: review exception handling
1057                                     if ( log.isDebugEnabled() )
1058                                     {
1059                                         log.debug( "Skipping repository '" + managedRepository + "' for user '"
1060                                                        + activePrincipal + "': " + e.getMessage() );
1061                                     }
1062                                 }
1063                             }
1064                         }
1065                     }
1066                 }
1067             }
1068         }
1069         else
1070         {
1071             throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
1072         }
1073
1074         ArchivaVirtualDavResource resource =
1075             new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator,
1076                                            this );
1077
1078         // compatibility with MRM-440 to ensure browsing the repository group works ok
1079         if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
1080         {
1081             throw new BrowserRedirectException( resource.getHref() );
1082         }
1083
1084         return resource;
1085     }
1086
1087     protected String getActivePrincipal( DavServletRequest request )
1088     {
1089         User sessionUser = httpAuth.getSessionUser( request.getSession() );
1090         return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME;
1091     }
1092
1093     /**
1094      * Check if the current user is authorized to access any of the repos
1095      *
1096      * @param request
1097      * @param repositories
1098      * @param activePrincipal
1099      * @return
1100      */
1101     private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
1102     {
1103         boolean allow = false;
1104
1105         // if securitySession != null, it means that the user was prompted for authentication
1106         if ( httpAuth.getSecuritySession( request.getSession() ) != null )
1107         {
1108             for ( String repository : repositories )
1109             {
1110                 try
1111                 {
1112                     if ( isAuthorized( request, repository ) )
1113                     {
1114                         allow = true;
1115                         break;
1116                     }
1117                 }
1118                 catch ( DavException e )
1119                 {
1120                     continue;
1121                 }
1122             }
1123         }
1124         else
1125         {
1126             for ( String repository : repositories )
1127             {
1128                 try
1129                 {
1130                     if ( servletAuth.isAuthorized( activePrincipal, repository,
1131                                                    WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1132                     {
1133                         allow = true;
1134                         break;
1135                     }
1136                 }
1137                 catch ( UnauthorizedException e )
1138                 {
1139                     continue;
1140                 }
1141             }
1142         }
1143
1144         return allow;
1145     }
1146
1147     private File writeMergedMetadataToFile( ArchivaRepositoryMetadata mergedMetadata, String outputFilename )
1148         throws RepositoryMetadataException, DigesterException, IOException
1149     {
1150         File outputFile = new File( outputFilename );
1151         if ( outputFile.exists() )
1152         {
1153             FileUtils.deleteQuietly( outputFile );
1154         }
1155
1156         outputFile.getParentFile().mkdirs();
1157         RepositoryMetadataWriter.write( mergedMetadata, outputFile );
1158
1159         createChecksumFile( outputFilename, digestSha1 );
1160         createChecksumFile( outputFilename, digestMd5 );
1161
1162         return outputFile;
1163     }
1164
1165     private void createChecksumFile( String path, Digester digester )
1166         throws DigesterException, IOException
1167     {
1168         File checksumFile = new File( path + digester.getFilenameExtension() );
1169         if ( !checksumFile.exists() )
1170         {
1171             FileUtils.deleteQuietly( checksumFile );
1172             checksum.createChecksum( new File( path ), digester );
1173         }
1174         else if ( !checksumFile.isFile() )
1175         {
1176             log.error( "Checksum file is not a file." );
1177         }
1178     }
1179
1180     private boolean isProjectReference( String requestedResource )
1181     {
1182         try
1183         {
1184             metadataTools.toVersionedReference( requestedResource );
1185             return false;
1186         }
1187         catch ( RepositoryMetadataException re )
1188         {
1189             return true;
1190         }
1191     }
1192
1193     protected File buildMergedIndexDirectory( List<String> repositories, String activePrincipal,
1194                                               DavServletRequest request, String groupId )
1195         throws DavException
1196     {
1197
1198         try
1199         {
1200             HttpSession session = request.getSession();
1201             Map<String, File> testValue = (Map<String, File>) session.getAttribute( "TMP_GROUP_INDEXES" );
1202             if ( testValue == null )
1203             {
1204                 testValue = new HashMap<String, File>();
1205             }
1206
1207             File tmp = testValue.get( groupId );
1208             if ( tmp != null )
1209             {
1210                 return tmp;
1211             }
1212
1213             Set<String> authzRepos = new HashSet<String>();
1214             for ( String repository : repositories )
1215             {
1216                 try
1217                 {
1218                     if ( servletAuth.isAuthorized( activePrincipal, repository,
1219                                                    WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1220                     {
1221                         authzRepos.add( repository );
1222                         authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository ) );
1223                     }
1224                 }
1225                 catch ( UnauthorizedException e )
1226                 {
1227                     // TODO: review exception handling
1228                     if ( log.isDebugEnabled() )
1229                     {
1230                         log.debug( "Skipping repository '" + repository + "' for user '" + activePrincipal + "': "
1231                                        + e.getMessage() );
1232                     }
1233                 }
1234             }
1235
1236             File mergedRepoDir = indexMerger.buildMergedIndex( authzRepos, true );
1237             testValue.put( groupId, mergedRepoDir );
1238             session.setAttribute( "TMP_GROUP_INDEXES", testValue );
1239             return mergedRepoDir;
1240         }
1241         catch ( RepositoryAdminException e )
1242         {
1243             throw new DavException( 500, e );
1244         }
1245         catch ( IndexMergerException e )
1246         {
1247             throw new DavException( 500, e );
1248         }
1249     }
1250
1251
1252     public void setServletAuth( ServletAuthenticator servletAuth )
1253     {
1254         this.servletAuth = servletAuth;
1255     }
1256
1257     public void setHttpAuth( HttpAuthenticator httpAuth )
1258     {
1259         this.httpAuth = httpAuth;
1260     }
1261
1262     public void setScheduler( RepositoryArchivaTaskScheduler scheduler )
1263     {
1264         this.scheduler = scheduler;
1265     }
1266
1267     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1268     {
1269         this.archivaConfiguration = archivaConfiguration;
1270     }
1271
1272     public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1273     {
1274         this.repositoryFactory = repositoryFactory;
1275     }
1276
1277     public void setRepositoryRequest( RepositoryRequest repositoryRequest )
1278     {
1279         this.repositoryRequest = repositoryRequest;
1280     }
1281
1282     public void setConnectors( RepositoryProxyConnectors connectors )
1283     {
1284         this.connectors = connectors;
1285     }
1286 }