1 package org.apache.archiva.webdav;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
22 import org.apache.archiva.admin.model.RepositoryAdminException;
23 import org.apache.archiva.admin.model.beans.ManagedRepository;
24 import org.apache.archiva.admin.model.beans.RemoteRepository;
25 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
26 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
27 import org.apache.archiva.audit.AuditEvent;
28 import org.apache.archiva.audit.AuditListener;
29 import org.apache.archiva.audit.Auditable;
30 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
31 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
32 import org.apache.archiva.common.utils.PathUtil;
33 import org.apache.archiva.common.utils.VersionUtil;
34 import org.apache.archiva.configuration.ArchivaConfiguration;
35 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
36 import org.apache.archiva.indexer.merger.IndexMerger;
37 import org.apache.archiva.indexer.merger.IndexMergerException;
38 import org.apache.archiva.indexer.merger.IndexMergerRequest;
39 import org.apache.archiva.indexer.merger.TemporaryGroupIndex;
40 import org.apache.archiva.indexer.search.RepositorySearch;
41 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
42 import org.apache.archiva.metadata.repository.storage.RelocationException;
43 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
44 import org.apache.archiva.model.ArchivaRepositoryMetadata;
45 import org.apache.archiva.model.ArtifactReference;
46 import org.apache.archiva.policies.ProxyDownloadException;
47 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
48 import org.apache.archiva.redback.authentication.AuthenticationException;
49 import org.apache.archiva.redback.authentication.AuthenticationResult;
50 import org.apache.archiva.redback.authorization.AuthorizationException;
51 import org.apache.archiva.redback.authorization.UnauthorizedException;
52 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
53 import org.apache.archiva.redback.policy.AccountLockedException;
54 import org.apache.archiva.redback.policy.MustChangePasswordException;
55 import org.apache.archiva.redback.system.SecuritySession;
56 import org.apache.archiva.redback.users.User;
57 import org.apache.archiva.redback.users.UserManager;
58 import org.apache.archiva.repository.ManagedRepositoryContent;
59 import org.apache.archiva.repository.RepositoryContentFactory;
60 import org.apache.archiva.repository.RepositoryException;
61 import org.apache.archiva.repository.RepositoryNotFoundException;
62 import org.apache.archiva.repository.content.legacy.LegacyPathParser;
63 import org.apache.archiva.repository.content.maven2.RepositoryRequest;
64 import org.apache.archiva.repository.layout.LayoutException;
65 import org.apache.archiva.repository.metadata.MetadataTools;
66 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
67 import org.apache.archiva.repository.metadata.RepositoryMetadataMerge;
68 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
69 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
70 import org.apache.archiva.security.ServletAuthenticator;
71 import org.apache.archiva.webdav.util.MimeTypes;
72 import org.apache.archiva.webdav.util.RepositoryPathUtil;
73 import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner;
74 import org.apache.archiva.webdav.util.WebdavMethodUtil;
75 import org.apache.archiva.xml.XMLException;
76 import org.apache.commons.io.FileUtils;
77 import org.apache.commons.io.FilenameUtils;
78 import org.apache.commons.lang.StringUtils;
79 import org.apache.commons.lang.SystemUtils;
80 import org.apache.jackrabbit.webdav.DavException;
81 import org.apache.jackrabbit.webdav.DavResource;
82 import org.apache.jackrabbit.webdav.DavResourceFactory;
83 import org.apache.jackrabbit.webdav.DavResourceLocator;
84 import org.apache.jackrabbit.webdav.DavServletRequest;
85 import org.apache.jackrabbit.webdav.DavServletResponse;
86 import org.apache.jackrabbit.webdav.DavSession;
87 import org.apache.jackrabbit.webdav.lock.LockManager;
88 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
89 import org.apache.maven.index.context.IndexingContext;
90 import org.codehaus.plexus.digest.ChecksumFile;
91 import org.codehaus.plexus.digest.Digester;
92 import org.codehaus.plexus.digest.DigesterException;
93 import org.slf4j.Logger;
94 import org.slf4j.LoggerFactory;
95 import org.slf4j.MarkerFactory;
96 import org.springframework.context.ApplicationContext;
97 import org.springframework.stereotype.Service;
99 import javax.annotation.PostConstruct;
100 import javax.inject.Inject;
101 import javax.inject.Named;
102 import javax.servlet.http.HttpServletResponse;
103 import javax.servlet.http.HttpSession;
105 import java.io.IOException;
106 import java.util.ArrayList;
107 import java.util.Date;
108 import java.util.HashMap;
109 import java.util.HashSet;
110 import java.util.List;
111 import java.util.Map;
112 import java.util.Set;
117 @Service( "davResourceFactory#archiva" )
118 public class ArchivaDavResourceFactory
119 implements DavResourceFactory, Auditable
121 private static final String PROXIED_SUFFIX = " (proxied)";
123 private static final String HTTP_PUT_METHOD = "PUT";
125 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
128 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
131 private RepositoryContentFactory repositoryFactory;
133 private RepositoryRequest repositoryRequest;
136 @Named( value = "repositoryProxyConnectors#default" )
137 private RepositoryProxyConnectors connectors;
140 private MetadataTools metadataTools;
143 private MimeTypes mimeTypes;
145 private ArchivaConfiguration archivaConfiguration;
148 private ServletAuthenticator servletAuth;
151 @Named( value = "httpAuthenticator#basic" )
152 private HttpAuthenticator httpAuth;
155 private RemoteRepositoryAdmin remoteRepositoryAdmin;
158 private ManagedRepositoryAdmin managedRepositoryAdmin;
161 private IndexMerger indexMerger;
164 private RepositorySearch repositorySearch;
167 * Lock Manager - use simple implementation from JackRabbit
169 private final LockManager lockManager = new SimpleLockManager();
171 private ChecksumFile checksum;
173 private Digester digestSha1;
175 private Digester digestMd5;
178 @Named( value = "archivaTaskScheduler#repository" )
179 private RepositoryArchivaTaskScheduler scheduler;
181 private ApplicationContext applicationContext;
184 public ArchivaDavResourceFactory( ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge,
185 ArchivaConfiguration archivaConfiguration )
186 throws PlexusSisuBridgeException
188 this.archivaConfiguration = archivaConfiguration;
189 this.applicationContext = applicationContext;
190 this.checksum = plexusSisuBridge.lookup( ChecksumFile.class );
192 this.digestMd5 = plexusSisuBridge.lookup( Digester.class, "md5" );
193 this.digestSha1 = plexusSisuBridge.lookup( Digester.class, "sha1" );
195 // TODO remove this hard dependency on maven !!
196 repositoryRequest = new RepositoryRequest( new LegacyPathParser( archivaConfiguration ) );
200 public void initialize()
205 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
206 final DavServletResponse response )
209 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
211 RepositoryGroupConfiguration repoGroupConfig =
212 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get( archivaLocator.getRepositoryId() );
214 String activePrincipal = getActivePrincipal( request );
216 List<String> resourcesInAbsolutePath = new ArrayList<String>();
218 boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
219 DavResource resource;
220 if ( repoGroupConfig != null )
224 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
225 "Write method not allowed for repository groups." );
228 log.debug( "Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal );
230 // handle browse requests for virtual repos
231 if ( getLogicalResource( archivaLocator, null, true ).endsWith( "/" ) )
235 return getResourceFromGroup( request, repoGroupConfig.getRepositories(), archivaLocator,
238 catch ( RepositoryAdminException e )
240 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
245 // make a copy to avoid potential concurrent modifications (eg. by configuration)
246 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are
248 List<String> repositories = new ArrayList<String>( repoGroupConfig.getRepositories() );
249 resource = processRepositoryGroup( request, archivaLocator, repositories, activePrincipal,
250 resourcesInAbsolutePath, repoGroupConfig );
258 RemoteRepository remoteRepository =
259 remoteRepositoryAdmin.getRemoteRepository( archivaLocator.getRepositoryId() );
261 if ( remoteRepository != null )
263 String logicalResource = getLogicalResource( archivaLocator, null, false );
264 IndexingContext indexingContext = remoteRepositoryAdmin.createIndexContext( remoteRepository );
265 File resourceFile = StringUtils.equals( logicalResource, "/" )
266 ? new File( indexingContext.getIndexDirectoryFile().getParent() )
267 : new File( indexingContext.getIndexDirectoryFile().getParent(), logicalResource );
268 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), locator.getResourcePath(), null,
269 request.getRemoteAddr(), activePrincipal,
270 request.getDavSession(), archivaLocator, this, mimeTypes,
271 auditListeners, scheduler );
272 setHeaders( response, locator, resource );
276 catch ( RepositoryAdminException e )
278 log.debug( "RepositoryException remote repository with d'{}' not found, msg: {}",
279 archivaLocator.getRepositoryId(), e.getMessage() );
282 ManagedRepositoryContent managedRepositoryContent = null;
286 managedRepositoryContent =
287 repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
289 catch ( RepositoryNotFoundException e )
291 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
292 "Invalid repository: " + archivaLocator.getRepositoryId() );
294 catch ( RepositoryException e )
296 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
299 log.debug( "Managed repository '{}' accessed by '{}'", managedRepositoryContent.getId(), activePrincipal );
303 resource = processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
304 managedRepositoryAdmin.getManagedRepository(
305 archivaLocator.getRepositoryId() ) );
307 String logicalResource = getLogicalResource( archivaLocator, null, false );
308 resourcesInAbsolutePath.add(
309 new File( managedRepositoryContent.getRepoRoot(), logicalResource ).getAbsolutePath() );
312 catch ( RepositoryAdminException e )
314 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
318 String requestedResource = request.getRequestURI();
320 // MRM-872 : merge all available metadata
321 // merge metadata only when requested via the repo group
322 if ( ( repositoryRequest.isMetadata( requestedResource ) || repositoryRequest.isMetadataSupportFile(
323 requestedResource ) ) && repoGroupConfig != null )
325 // this should only be at the project level not version level!
326 if ( isProjectReference( requestedResource ) )
329 ArchivaDavResource res = (ArchivaDavResource) resource;
331 StringUtils.substringBeforeLast( res.getLocalResource().getAbsolutePath().replace( '\\', '/' ),
333 filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml";
335 // for MRM-872 handle checksums of the merged metadata files
336 if ( repositoryRequest.isSupportFile( requestedResource ) )
338 File metadataChecksum =
339 new File( filePath + "." + StringUtils.substringAfterLast( requestedResource, "." ) );
340 if ( metadataChecksum.exists() )
342 LogicalResource logicalResource =
343 new LogicalResource (getLogicalResource( archivaLocator, null, false ) );
346 new ArchivaDavResource( metadataChecksum.getAbsolutePath(), logicalResource.getPath(), null,
347 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
348 archivaLocator, this, mimeTypes, auditListeners, scheduler );
353 if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 )
355 // merge the metadata of all repos under group
356 ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata();
357 for ( String resourceAbsPath : resourcesInAbsolutePath )
361 File metadataFile = new File( resourceAbsPath );
362 ArchivaRepositoryMetadata repoMetadata = MavenMetadataReader.read( metadataFile );
363 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata );
365 catch ( XMLException e )
367 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
368 "Error occurred while reading metadata file." );
370 catch ( RepositoryMetadataException r )
372 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
373 "Error occurred while merging metadata file." );
379 File resourceFile = writeMergedMetadataToFile( mergedMetadata, filePath );
381 LogicalResource logicalResource = new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
384 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), null,
385 request.getRemoteAddr(), activePrincipal,
386 request.getDavSession(), archivaLocator, this, mimeTypes,
387 auditListeners, scheduler );
389 catch ( RepositoryMetadataException r )
391 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
392 "Error occurred while writing metadata file." );
394 catch ( IOException ie )
396 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
397 "Error occurred while generating checksum files." );
399 catch ( DigesterException de )
401 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
402 "Error occurred while generating checksum files." + de.getMessage() );
409 setHeaders( response, locator, resource );
411 // compatibility with MRM-440 to ensure browsing the repository works ok
412 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
414 throw new BrowserRedirectException( resource.getHref() );
416 resource.addLockManager( lockManager );
420 private DavResource processRepositoryGroup( final DavServletRequest request,
421 ArchivaDavResourceLocator archivaLocator, List<String> repositories,
422 String activePrincipal, List<String> resourcesInAbsolutePath,
423 RepositoryGroupConfiguration repoGroupConfig )
426 DavResource resource = null;
427 List<DavException> storedExceptions = new ArrayList<DavException>();
429 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
431 String rootPath = StringUtils.substringBeforeLast( pathInfo, "/" );
433 if ( StringUtils.endsWith( rootPath, repoGroupConfig.getMergedIndexPath() ) )
435 // we are in the case of index file request
436 String requestedFileName = StringUtils.substringAfterLast( pathInfo, "/" );
437 File temporaryIndexDirectory =
438 buildMergedIndexDirectory( repositories, activePrincipal, request, repoGroupConfig );
440 File resourceFile = new File( temporaryIndexDirectory, requestedFileName );
441 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), requestedFileName, null,
442 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
443 archivaLocator, this, mimeTypes, auditListeners, scheduler );
448 for ( String repositoryId : repositories )
450 ManagedRepositoryContent managedRepositoryContent;
453 managedRepositoryContent = repositoryFactory.getManagedRepositoryContent( repositoryId );
455 catch ( RepositoryNotFoundException e )
457 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
459 catch ( RepositoryException e )
461 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
466 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
467 DavResource updatedResource =
468 processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
470 if ( resource == null )
472 resource = updatedResource;
475 String logicalResource = getLogicalResource( archivaLocator, null, false );
476 if ( logicalResource.endsWith( "/" ) )
478 logicalResource = logicalResource.substring( 1 );
480 resourcesInAbsolutePath.add(
481 new File( managedRepositoryContent.getRepoRoot(), logicalResource ).getAbsolutePath() );
483 catch ( DavException e )
485 storedExceptions.add( e );
487 catch ( RepositoryAdminException e )
489 storedExceptions.add( new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ) );
493 if ( resource == null )
495 if ( !storedExceptions.isEmpty() )
498 for ( DavException e : storedExceptions )
500 if ( 401 == e.getErrorCode() )
506 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
510 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
516 private String getLogicalResource( ArchivaDavResourceLocator archivaLocator, ManagedRepository managedRepository,
517 boolean useOrigResourcePath )
519 // FIXME remove this hack
520 // but currently managedRepository can be null in case of group
521 String layout = managedRepository == null ? new ManagedRepository( ).getLayout() : managedRepository.getLayout();
522 RepositoryStorage repositoryStorage =
523 this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
524 String path = repositoryStorage.getFilePath(
525 useOrigResourcePath ? archivaLocator.getOrigResourcePath() : archivaLocator.getResourcePath(), managedRepository );
526 log.debug( "found path {} for resourcePath: '{}' with managedRepo '{}' and layout '{}'", path,
527 archivaLocator.getResourcePath(), managedRepository == null ? "null" : managedRepository.getId(), layout );
531 private String evaluatePathWithVersion( ArchivaDavResourceLocator archivaLocator, ManagedRepositoryContent managedRepositoryContent )
534 String layout = managedRepositoryContent.getRepository() == null ? new ManagedRepository( ).getLayout() : managedRepositoryContent.getRepository().getLayout();
535 RepositoryStorage repositoryStorage =
536 this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
539 return repositoryStorage.getFilePathWithVersion( archivaLocator.getResourcePath(), managedRepositoryContent );
541 catch ( RelocationException e )
543 log.error( e.getMessage(), e );
544 throw new BrowserRedirectException( e.getPath() );
546 catch ( XMLException e )
548 log.error( e.getMessage(), e );
549 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
553 private DavResource processRepository( final DavServletRequest request, ArchivaDavResourceLocator archivaLocator,
554 String activePrincipal, ManagedRepositoryContent managedRepositoryContent,
555 ManagedRepository managedRepository )
558 DavResource resource = null;
559 if ( isAuthorized( request, managedRepositoryContent.getId() ) )
561 // Maven Centric part ask evaluation if -SNAPSHOT
562 String path = evaluatePathWithVersion(archivaLocator, managedRepositoryContent);
563 if ( path.startsWith( "/" ) )
565 path = path.substring( 1 );
567 LogicalResource logicalResource = new LogicalResource( path );
568 File resourceFile = new File( managedRepositoryContent.getRepoRoot(), path );
570 new ArchivaDavResource( resourceFile.getAbsolutePath(), path, managedRepositoryContent.getRepository(),
571 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
572 archivaLocator, this, mimeTypes, auditListeners, scheduler );
574 if ( WebdavMethodUtil.isReadMethod( request.getMethod() ) )
576 if ( archivaLocator.getHref( false ).endsWith( "/" ) && !resourceFile.isDirectory() )
578 // force a resource not found
579 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
583 if ( !resource.isCollection() )
585 boolean previouslyExisted = resourceFile.exists();
587 // Attempt to fetch the resource from any defined proxy.
589 fetchContentFromProxies( managedRepositoryContent, request, logicalResource );
591 // At this point the incoming request can either be in default or
592 // legacy layout format.
595 // Perform an adjustment of the resource to the managed
596 // repository expected path.
597 String localResourcePath =
598 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepositoryContent );
599 resourceFile = new File( managedRepositoryContent.getRepoRoot(), localResourcePath );
601 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(),
602 managedRepositoryContent.getRepository(),
603 request.getRemoteAddr(), activePrincipal,
604 request.getDavSession(), archivaLocator, this, mimeTypes,
605 auditListeners, scheduler );
607 catch ( LayoutException e )
609 if ( !resourceFile.exists() )
611 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
617 String event = ( previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE )
620 log.debug( "Proxied artifact '{}' in repository '{}' (current user '{}')",
621 resourceFile.getName(), managedRepositoryContent.getId(), activePrincipal );
623 triggerAuditEvent( request.getRemoteAddr(), archivaLocator.getRepositoryId(),
624 logicalResource.getPath(), event, activePrincipal );
627 if ( !resourceFile.exists() )
629 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
635 if ( request.getMethod().equals( HTTP_PUT_METHOD ) )
637 String resourcePath = logicalResource.getPath();
639 // check if target repo is enabled for releases
640 // we suppose that release-artifacts can be deployed only to repos enabled for releases
641 if ( managedRepositoryContent.getRepository().isReleases() && !repositoryRequest.isMetadata(
642 resourcePath ) && !repositoryRequest.isSupportFile( resourcePath ) )
644 ArtifactReference artifact = null;
647 artifact = managedRepositoryContent.toArtifactReference( resourcePath );
649 if ( !VersionUtil.isSnapshot( artifact.getVersion() ) )
651 // check if artifact already exists and if artifact re-deployment to the repository is allowed
652 if ( managedRepositoryContent.hasContent( artifact )
653 && managedRepositoryContent.getRepository().isBlockRedeployments() )
655 log.warn( "Overwriting released artifacts in repository '{}' is not allowed.",
656 managedRepositoryContent.getId() );
657 throw new DavException( HttpServletResponse.SC_CONFLICT,
658 "Overwriting released artifacts is not allowed." );
662 catch ( LayoutException e )
664 log.warn( "Artifact path '{}' is invalid.", resourcePath );
669 * Create parent directories that don't exist when writing a file This actually makes this
670 * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the
671 * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly
672 * create the collections themselves.
675 File rootDirectory = new File( managedRepositoryContent.getRepoRoot() );
676 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
678 if ( !destDir.exists() )
681 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
683 log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getName(),
686 triggerAuditEvent( request.getRemoteAddr(), managedRepositoryContent.getId(), relPath,
687 AuditEvent.CREATE_DIR, activePrincipal );
694 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
697 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
699 ManagedRepositoryContent managedRepositoryContent;
702 managedRepositoryContent =
703 repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
705 catch ( RepositoryNotFoundException e )
707 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
708 "Invalid repository: " + archivaLocator.getRepositoryId() );
710 catch ( RepositoryException e )
712 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
715 DavResource resource = null;
718 String logicalResource = getLogicalResource( archivaLocator, managedRepositoryAdmin.getManagedRepository(
719 archivaLocator.getRepositoryId() ), false );
720 if ( logicalResource.startsWith( "/" ) )
722 logicalResource = logicalResource.substring( 1 );
724 File resourceFile = new File( managedRepositoryContent.getRepoRoot(), logicalResource );
725 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource,
726 managedRepositoryContent.getRepository(), davSession, archivaLocator,
727 this, mimeTypes, auditListeners, scheduler );
729 resource.addLockManager( lockManager );
731 catch ( RepositoryAdminException e )
733 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
738 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
739 LogicalResource resource )
742 String path = resource.getPath();
743 if ( repositoryRequest.isSupportFile( path ) )
745 File proxiedFile = connectors.fetchFromProxies( managedRepository, path );
747 return ( proxiedFile != null );
750 // Is it a Metadata resource?
751 if ( repositoryRequest.isDefault( path ) && repositoryRequest.isMetadata( path ) )
753 return connectors.fetchMetatadaFromProxies( managedRepository, path ) != null;
756 // Is it an Archetype Catalog?
757 if ( repositoryRequest.isArchetypeCatalog( path ) )
759 // FIXME we must implement a merge of remote archetype catalog from remote servers.
760 File proxiedFile = connectors.fetchFromProxies( managedRepository, path );
762 return ( proxiedFile != null );
765 // Not any of the above? Then it's gotta be an artifact reference.
768 // Get the artifact reference in a layout neutral way.
769 ArtifactReference artifact = repositoryRequest.toArtifactReference( path );
771 if ( artifact != null )
773 String repositoryLayout = managedRepository.getRepository().getLayout();
775 RepositoryStorage repositoryStorage =
776 this.applicationContext.getBean( "repositoryStorage#" + repositoryLayout, RepositoryStorage.class );
777 repositoryStorage.applyServerSideRelocation( managedRepository, artifact );
779 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
781 resource.setPath( managedRepository.toPath( artifact ) );
783 log.debug( "Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(),
784 artifact.getVersion() );
786 return ( proxiedFile != null );
789 catch ( LayoutException e )
793 catch ( ProxyDownloadException e )
795 log.error( e.getMessage(), e );
796 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
797 "Unable to fetch artifact resource." );
804 private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action,
807 AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
808 event.setRemoteIP( remoteIP );
810 for ( AuditListener listener : auditListeners )
812 listener.auditEvent( event );
816 public void addAuditListener( AuditListener listener )
818 this.auditListeners.add( listener );
821 public void clearAuditListeners()
823 this.auditListeners.clear();
826 public void removeAuditListener( AuditListener listener )
828 this.auditListeners.remove( listener );
831 private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource )
833 // [MRM-503] - Metadata file need Pragma:no-cache response
835 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" )
836 || ( (ArchivaDavResource) resource ).getLocalResource().isDirectory() )
838 response.setHeader( "Pragma", "no-cache" );
839 response.setHeader( "Cache-Control", "no-cache" );
840 response.setDateHeader( "Last-Modified", new Date().getTime() );
842 // if the resource is a directory don't cache it as new groupId deployed will be available
843 // without need of refreshing browser
846 // We need to specify this so connecting wagons can work correctly
847 response.setDateHeader( "Last-Modified", resource.getModificationTime() );
849 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
852 private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
855 if ( !( locator instanceof ArchivaDavResourceLocator ) )
857 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
858 "Locator does not implement RepositoryLocator" );
862 if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
864 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
867 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
869 // MRM-419 - Windows Webdav support. Should not 404 if there is no content.
870 if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) )
872 throw new DavException( HttpServletResponse.SC_NO_CONTENT );
874 return archivaLocator;
877 private static class LogicalResource
881 public LogicalResource( String path )
886 public String getPath()
891 public void setPath( String path )
897 protected boolean isAuthorized( DavServletRequest request, String repositoryId )
902 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
903 SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) );
905 return servletAuth.isAuthenticated( request, result ) && servletAuth.isAuthorized( request, securitySession,
907 WebdavMethodUtil.getMethodPermission(
908 request.getMethod() ) );
910 catch ( AuthenticationException e )
912 // safety check for MRM-911
913 String guest = UserManager.GUEST_USERNAME;
916 if ( servletAuth.isAuthorized( guest,
917 ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(),
918 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
923 catch ( UnauthorizedException ae )
925 throw new UnauthorizedDavException( repositoryId,
926 "You are not authenticated and authorized to access any repository." );
929 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
931 catch ( MustChangePasswordException e )
933 throw new UnauthorizedDavException( repositoryId, "You must change your password." );
935 catch ( AccountLockedException e )
937 throw new UnauthorizedDavException( repositoryId, "User account is locked." );
939 catch ( AuthorizationException e )
941 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
942 "Fatal Authorization Subsystem Error." );
944 catch ( UnauthorizedException e )
946 throw new UnauthorizedDavException( repositoryId, e.getMessage() );
950 private DavResource getResourceFromGroup( DavServletRequest request, List<String> repositories,
951 ArchivaDavResourceLocator locator,
952 RepositoryGroupConfiguration repositoryGroupConfiguration )
953 throws DavException, RepositoryAdminException
955 List<File> mergedRepositoryContents = new ArrayList<File>();
956 // multiple repo types so we guess they are all the same type
957 // so use the first one
958 // FIXME add a method with group in the repository storage
959 String firstRepoId = repositoryGroupConfiguration.getRepositories().get( 1 );
961 String path = getLogicalResource( locator, managedRepositoryAdmin.getManagedRepository( firstRepoId ), false );
962 if ( path.startsWith( "/" ) )
964 path = path.substring( 1 );
966 LogicalResource logicalResource = new LogicalResource( path );
969 // if the current user logged in has permission to any of the repositories, allow user to
970 // browse the repo group but displaying only the repositories which the user has permission to access.
971 // otherwise, prompt for authentication.
973 String activePrincipal = getActivePrincipal( request );
975 boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
978 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
983 if ( StringUtils.endsWith( pathInfo, repositoryGroupConfiguration.getMergedIndexPath() ) )
986 buildMergedIndexDirectory( repositories, activePrincipal, request, repositoryGroupConfiguration );
987 mergedRepositoryContents.add( mergedRepoDir );
991 if ( StringUtils.equalsIgnoreCase( pathInfo, "/" + repositoryGroupConfiguration.getId() ) )
993 File tmpDirectory = new File( SystemUtils.getJavaIoTmpDir(),
994 repositoryGroupConfiguration.getId() + "/"
995 + repositoryGroupConfiguration.getMergedIndexPath() );
996 if ( !tmpDirectory.exists() )
998 synchronized ( tmpDirectory.getAbsolutePath() )
1000 if ( !tmpDirectory.exists() )
1002 tmpDirectory.mkdirs();
1006 mergedRepositoryContents.add( tmpDirectory.getParentFile() );
1008 for ( String repository : repositories )
1010 ManagedRepositoryContent managedRepository = null;
1014 managedRepository = repositoryFactory.getManagedRepositoryContent( repository );
1016 catch ( RepositoryNotFoundException e )
1018 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1019 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1021 catch ( RepositoryException e )
1023 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1024 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1027 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
1028 if ( resourceFile.exists() )
1030 // in case of group displaying index directory doesn't have sense !!
1031 String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory();
1032 if ( StringUtils.isNotEmpty( repoIndexDirectory ) )
1034 if ( !new File( repoIndexDirectory ).isAbsolute() )
1036 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1037 StringUtils.isEmpty( repoIndexDirectory )
1039 : repoIndexDirectory ).getAbsolutePath();
1042 if ( StringUtils.isEmpty( repoIndexDirectory ) )
1044 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(),
1045 ".indexer" ).getAbsolutePath();
1048 if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory ),
1049 FilenameUtils.normalize( resourceFile.getAbsolutePath() ) ) )
1051 // for prompted authentication
1052 if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null )
1056 if ( isAuthorized( request, repository ) )
1058 mergedRepositoryContents.add( resourceFile );
1059 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1062 catch ( DavException e )
1064 // TODO: review exception handling
1066 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1067 activePrincipal, e.getMessage() );
1074 // for the current user logged in
1077 if ( servletAuth.isAuthorized( activePrincipal, repository,
1078 WebdavMethodUtil.getMethodPermission(
1079 request.getMethod() ) ) )
1081 mergedRepositoryContents.add( resourceFile );
1082 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1085 catch ( UnauthorizedException e )
1087 // TODO: review exception handling
1089 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1090 activePrincipal, e.getMessage() );
1101 throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
1104 ArchivaVirtualDavResource resource =
1105 new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator,
1108 // compatibility with MRM-440 to ensure browsing the repository group works ok
1109 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
1111 throw new BrowserRedirectException( resource.getHref() );
1117 protected String getActivePrincipal( DavServletRequest request )
1119 User sessionUser = httpAuth.getSessionUser( request.getSession() );
1120 return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME;
1124 * Check if the current user is authorized to access any of the repos
1127 * @param repositories
1128 * @param activePrincipal
1131 private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
1133 // when no repositories configured it's impossible to browse nothing !
1134 // at least make possible to see nothing :-)
1135 if ( repositories == null || repositories.isEmpty() )
1140 boolean allow = false;
1142 // if securitySession != null, it means that the user was prompted for authentication
1143 if ( httpAuth.getSecuritySession( request.getSession() ) != null )
1145 for ( String repository : repositories )
1149 if ( isAuthorized( request, repository ) )
1155 catch ( DavException e )
1163 for ( String repository : repositories )
1167 if ( servletAuth.isAuthorized( activePrincipal, repository,
1168 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1174 catch ( UnauthorizedException e )
1184 private File writeMergedMetadataToFile( ArchivaRepositoryMetadata mergedMetadata, String outputFilename )
1185 throws RepositoryMetadataException, DigesterException, IOException
1187 File outputFile = new File( outputFilename );
1188 if ( outputFile.exists() )
1190 FileUtils.deleteQuietly( outputFile );
1193 outputFile.getParentFile().mkdirs();
1194 RepositoryMetadataWriter.write( mergedMetadata, outputFile );
1196 createChecksumFile( outputFilename, digestSha1 );
1197 createChecksumFile( outputFilename, digestMd5 );
1202 private void createChecksumFile( String path, Digester digester )
1203 throws DigesterException, IOException
1205 File checksumFile = new File( path + digester.getFilenameExtension() );
1206 if ( !checksumFile.exists() )
1208 FileUtils.deleteQuietly( checksumFile );
1209 checksum.createChecksum( new File( path ), digester );
1211 else if ( !checksumFile.isFile() )
1213 log.error( "Checksum file is not a file." );
1217 private boolean isProjectReference( String requestedResource )
1221 metadataTools.toVersionedReference( requestedResource );
1224 catch ( RepositoryMetadataException re )
1230 protected File buildMergedIndexDirectory( List<String> repositories, String activePrincipal,
1231 DavServletRequest request,
1232 RepositoryGroupConfiguration repositoryGroupConfiguration )
1238 HttpSession session = request.getSession();
1240 Map<String, TemporaryGroupIndex> temporaryGroupIndexMap =
1241 (Map<String, TemporaryGroupIndex>) session.getAttribute(
1242 TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY );
1243 if ( temporaryGroupIndexMap == null )
1245 temporaryGroupIndexMap = new HashMap<String, TemporaryGroupIndex>();
1248 TemporaryGroupIndex tmp = temporaryGroupIndexMap.get( repositoryGroupConfiguration.getId() );
1250 if ( tmp != null && tmp.getDirectory() != null && tmp.getDirectory().exists() )
1252 if ( System.currentTimeMillis() - tmp.getCreationTime() > (
1253 repositoryGroupConfiguration.getMergedIndexTtl() * 60 * 1000 ) )
1255 log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1256 "tmp group index '{}' is too old so delete it", repositoryGroupConfiguration.getId() );
1257 indexMerger.cleanTemporaryGroupIndex( tmp );
1261 log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1262 "merged index for group '{}' found in cache", repositoryGroupConfiguration.getId() );
1263 return tmp.getDirectory();
1267 Set<String> authzRepos = new HashSet<String>();
1268 for ( String repository : repositories )
1272 if ( servletAuth.isAuthorized( activePrincipal, repository,
1273 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1275 authzRepos.add( repository );
1276 authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository ) );
1279 catch ( UnauthorizedException e )
1281 // TODO: review exception handling
1283 log.debug( "Skipping repository '{}' for user '{}': {}", repository, activePrincipal,
1287 log.info( "generate temporary merged index for repository group '{}' for repositories '{}'",
1288 repositoryGroupConfiguration.getId(), authzRepos );
1289 IndexingContext indexingContext = indexMerger.buildMergedIndex(
1290 new IndexMergerRequest( authzRepos, true, repositoryGroupConfiguration.getId(),
1291 repositoryGroupConfiguration.getMergedIndexPath(),
1292 repositoryGroupConfiguration.getMergedIndexTtl() ) );
1293 File mergedRepoDir = indexingContext.getIndexDirectoryFile();
1294 TemporaryGroupIndex temporaryGroupIndex =
1295 new TemporaryGroupIndex( mergedRepoDir, indexingContext.getId(), repositoryGroupConfiguration.getId(),
1296 repositoryGroupConfiguration.getMergedIndexTtl() ).setCreationTime(
1297 new Date().getTime() );
1298 temporaryGroupIndexMap.put( repositoryGroupConfiguration.getId(), temporaryGroupIndex );
1299 session.setAttribute( TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY,
1300 temporaryGroupIndexMap );
1301 return mergedRepoDir;
1303 catch ( RepositoryAdminException e )
1305 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1307 catch ( IndexMergerException e )
1309 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1314 public void setServletAuth( ServletAuthenticator servletAuth )
1316 this.servletAuth = servletAuth;
1319 public void setHttpAuth( HttpAuthenticator httpAuth )
1321 this.httpAuth = httpAuth;
1324 public void setScheduler( RepositoryArchivaTaskScheduler scheduler )
1326 this.scheduler = scheduler;
1329 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1331 this.archivaConfiguration = archivaConfiguration;
1334 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1336 this.repositoryFactory = repositoryFactory;
1339 public void setRepositoryRequest( RepositoryRequest repositoryRequest )
1341 this.repositoryRequest = repositoryRequest;
1344 public void setConnectors( RepositoryProxyConnectors connectors )
1346 this.connectors = connectors;
1349 public RemoteRepositoryAdmin getRemoteRepositoryAdmin()
1351 return remoteRepositoryAdmin;
1354 public void setRemoteRepositoryAdmin( RemoteRepositoryAdmin remoteRepositoryAdmin )
1356 this.remoteRepositoryAdmin = remoteRepositoryAdmin;
1359 public ManagedRepositoryAdmin getManagedRepositoryAdmin()
1361 return managedRepositoryAdmin;
1364 public void setManagedRepositoryAdmin( ManagedRepositoryAdmin managedRepositoryAdmin )
1366 this.managedRepositoryAdmin = managedRepositoryAdmin;