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.Auditable;
28 import org.apache.archiva.common.filelock.FileLockManager;
29 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge;
30 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
31 import org.apache.archiva.common.utils.PathUtil;
32 import org.apache.archiva.common.utils.VersionUtil;
33 import org.apache.archiva.configuration.ArchivaConfiguration;
34 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
35 import org.apache.archiva.indexer.merger.*;
36 import org.apache.archiva.indexer.search.RepositorySearch;
37 import org.apache.archiva.maven2.metadata.MavenMetadataReader;
38 import org.apache.archiva.metadata.model.facets.AuditEvent;
39 import org.apache.archiva.metadata.repository.storage.RelocationException;
40 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
41 import org.apache.archiva.model.ArchivaRepositoryMetadata;
42 import org.apache.archiva.model.ArtifactReference;
43 import org.apache.archiva.policies.ProxyDownloadException;
44 import org.apache.archiva.proxy.model.RepositoryProxyConnectors;
45 import org.apache.archiva.redback.authentication.AuthenticationException;
46 import org.apache.archiva.redback.authentication.AuthenticationResult;
47 import org.apache.archiva.redback.authorization.AuthorizationException;
48 import org.apache.archiva.redback.authorization.UnauthorizedException;
49 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
50 import org.apache.archiva.redback.policy.AccountLockedException;
51 import org.apache.archiva.redback.policy.MustChangePasswordException;
52 import org.apache.archiva.redback.system.SecuritySession;
53 import org.apache.archiva.redback.users.User;
54 import org.apache.archiva.redback.users.UserManager;
55 import org.apache.archiva.repository.ManagedRepositoryContent;
56 import org.apache.archiva.repository.RepositoryContentFactory;
57 import org.apache.archiva.repository.RepositoryException;
58 import org.apache.archiva.repository.RepositoryNotFoundException;
59 import org.apache.archiva.repository.content.maven2.RepositoryRequest;
60 import org.apache.archiva.repository.events.AuditListener;
61 import org.apache.archiva.repository.layout.LayoutException;
62 import org.apache.archiva.repository.metadata.MetadataTools;
63 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
64 import org.apache.archiva.repository.metadata.RepositoryMetadataMerge;
65 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter;
66 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
67 import org.apache.archiva.security.ServletAuthenticator;
68 import org.apache.archiva.webdav.util.MimeTypes;
69 import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner;
70 import org.apache.archiva.webdav.util.WebdavMethodUtil;
71 import org.apache.archiva.xml.XMLException;
72 import org.apache.commons.io.FilenameUtils;
73 import org.apache.commons.lang.StringUtils;
74 import org.apache.commons.lang.SystemUtils;
75 import org.apache.jackrabbit.webdav.*;
76 import org.apache.jackrabbit.webdav.lock.LockManager;
77 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
78 import org.apache.maven.index.context.IndexingContext;
79 import org.codehaus.plexus.digest.ChecksumFile;
80 import org.codehaus.plexus.digest.Digester;
81 import org.codehaus.plexus.digest.DigesterException;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84 import org.slf4j.MarkerFactory;
85 import org.springframework.context.ApplicationContext;
86 import org.springframework.stereotype.Service;
88 import javax.annotation.PostConstruct;
89 import javax.inject.Inject;
90 import javax.inject.Named;
91 import javax.servlet.http.HttpServletResponse;
92 import javax.servlet.http.HttpSession;
93 import java.io.IOException;
94 import java.nio.file.Files;
95 import java.nio.file.Path;
96 import java.nio.file.Paths;
102 @Service( "davResourceFactory#archiva" )
103 public class ArchivaDavResourceFactory
104 implements DavResourceFactory, Auditable
106 private static final String PROXIED_SUFFIX = " (proxied)";
108 private static final String HTTP_PUT_METHOD = "PUT";
110 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
113 private List<AuditListener> auditListeners = new ArrayList<>();
116 private RepositoryContentFactory repositoryFactory;
118 private RepositoryRequest repositoryRequest;
121 @Named( value = "repositoryProxyConnectors#default" )
122 private RepositoryProxyConnectors connectors;
125 private MetadataTools metadataTools;
128 private MimeTypes mimeTypes;
130 private ArchivaConfiguration archivaConfiguration;
133 private ServletAuthenticator servletAuth;
136 @Named( value = "httpAuthenticator#basic" )
137 private HttpAuthenticator httpAuth;
140 private RemoteRepositoryAdmin remoteRepositoryAdmin;
143 private ManagedRepositoryAdmin managedRepositoryAdmin;
146 private IndexMerger indexMerger;
149 private RepositorySearch repositorySearch;
152 * Lock Manager - use simple implementation from JackRabbit
154 private final LockManager lockManager = new SimpleLockManager();
156 private ChecksumFile checksum;
158 private Digester digestSha1;
160 private Digester digestMd5;
163 @Named( value = "archivaTaskScheduler#repository" )
164 private RepositoryArchivaTaskScheduler scheduler;
167 @Named( value = "fileLockManager#default" )
168 private FileLockManager fileLockManager;
170 private ApplicationContext applicationContext;
173 public ArchivaDavResourceFactory( ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge,
174 ArchivaConfiguration archivaConfiguration )
175 throws PlexusSisuBridgeException
177 this.archivaConfiguration = archivaConfiguration;
178 this.applicationContext = applicationContext;
179 this.checksum = plexusSisuBridge.lookup( ChecksumFile.class );
181 this.digestMd5 = plexusSisuBridge.lookup( Digester.class, "md5" );
182 this.digestSha1 = plexusSisuBridge.lookup( Digester.class, "sha1" );
184 // TODO remove this hard dependency on maven !!
185 repositoryRequest = new RepositoryRequest( );
189 public void initialize()
195 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
196 final DavServletResponse response )
199 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
201 RepositoryGroupConfiguration repoGroupConfig =
202 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get( archivaLocator.getRepositoryId() );
204 String activePrincipal = getActivePrincipal( request );
206 List<String> resourcesInAbsolutePath = new ArrayList<>();
208 boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
209 DavResource resource;
210 if ( repoGroupConfig != null )
214 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
215 "Write method not allowed for repository groups." );
218 log.debug( "Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal );
220 // handle browse requests for virtual repos
221 if ( getLogicalResource( archivaLocator, null, true ).endsWith( "/" ) )
225 DavResource davResource =
226 getResourceFromGroup( request, repoGroupConfig.getRepositories(), archivaLocator,
229 setHeaders( response, locator, davResource, true );
234 catch ( RepositoryAdminException e )
236 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
241 // make a copy to avoid potential concurrent modifications (eg. by configuration)
242 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are
244 List<String> repositories = new ArrayList<>( repoGroupConfig.getRepositories() );
245 resource = processRepositoryGroup( request, archivaLocator, repositories, activePrincipal,
246 resourcesInAbsolutePath, repoGroupConfig );
254 RemoteRepository remoteRepository =
255 remoteRepositoryAdmin.getRemoteRepository( archivaLocator.getRepositoryId() );
257 if ( remoteRepository != null )
259 String logicalResource = getLogicalResource( archivaLocator, null, false );
260 IndexingContext indexingContext = remoteRepositoryAdmin.createIndexContext( remoteRepository );
261 Path resourceFile = StringUtils.equals( logicalResource, "/" )
262 ? Paths.get( indexingContext.getIndexDirectoryFile().getParent() )
263 : Paths.get( indexingContext.getIndexDirectoryFile().getParent(), logicalResource );
264 resource = new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), //
265 locator.getResourcePath(), //
267 request.getRemoteAddr(), //
269 request.getDavSession(), //
276 setHeaders( response, locator, resource, false );
280 catch ( RepositoryAdminException e )
282 log.debug( "RepositoryException remote repository with d'{}' not found, msg: {}",
283 archivaLocator.getRepositoryId(), e.getMessage() );
286 ManagedRepositoryContent managedRepositoryContent = null;
290 managedRepositoryContent =
291 repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
293 catch ( RepositoryNotFoundException e )
295 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
296 "Invalid repository: " + archivaLocator.getRepositoryId() );
298 catch ( RepositoryException e )
300 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
303 log.debug( "Managed repository '{}' accessed by '{}'", managedRepositoryContent.getId(), activePrincipal );
307 resource = processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
308 managedRepositoryAdmin.getManagedRepository(
309 archivaLocator.getRepositoryId() ) );
311 String logicalResource = getLogicalResource( archivaLocator, null, false );
312 resourcesInAbsolutePath.add(
313 Paths.get( managedRepositoryContent.getRepoRoot(), logicalResource ).toAbsolutePath().toString() );
316 catch ( RepositoryAdminException e )
318 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
322 String requestedResource = request.getRequestURI();
324 // MRM-872 : merge all available metadata
325 // merge metadata only when requested via the repo group
326 if ( ( repositoryRequest.isMetadata( requestedResource ) || repositoryRequest.isMetadataSupportFile(
327 requestedResource ) ) && repoGroupConfig != null )
329 // this should only be at the project level not version level!
330 if ( isProjectReference( requestedResource ) )
333 ArchivaDavResource res = (ArchivaDavResource) resource;
335 StringUtils.substringBeforeLast( res.getLocalResource().toAbsolutePath().toString().replace( '\\', '/' ),
337 filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml";
339 // for MRM-872 handle checksums of the merged metadata files
340 if ( repositoryRequest.isSupportFile( requestedResource ) )
342 Path metadataChecksum =
343 Paths.get( filePath + "." + StringUtils.substringAfterLast( requestedResource, "." ) );
345 if ( Files.exists(metadataChecksum) )
347 LogicalResource logicalResource =
348 new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
351 new ArchivaDavResource( metadataChecksum.toAbsolutePath().toString(), logicalResource.getPath(), null,
352 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
353 archivaLocator, this, mimeTypes, auditListeners, scheduler,
359 if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 )
361 // merge the metadata of all repos under group
362 ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata();
363 for ( String resourceAbsPath : resourcesInAbsolutePath )
367 Path metadataFile = Paths.get( resourceAbsPath );
368 ArchivaRepositoryMetadata repoMetadata = MavenMetadataReader.read( metadataFile );
369 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata );
371 catch ( XMLException e )
373 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
374 "Error occurred while reading metadata file." );
376 catch ( RepositoryMetadataException r )
378 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
379 "Error occurred while merging metadata file." );
385 Path resourceFile = writeMergedMetadataToFile( mergedMetadata, filePath );
387 LogicalResource logicalResource =
388 new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
391 new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), logicalResource.getPath(), null,
392 request.getRemoteAddr(), activePrincipal,
393 request.getDavSession(), archivaLocator, this, mimeTypes,
394 auditListeners, scheduler, fileLockManager );
396 catch ( RepositoryMetadataException r )
398 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
399 "Error occurred while writing metadata file." );
401 catch ( IOException ie )
403 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
404 "Error occurred while generating checksum files." );
406 catch ( DigesterException de )
408 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
409 "Error occurred while generating checksum files."
417 setHeaders( response, locator, resource, false );
419 // compatibility with MRM-440 to ensure browsing the repository works ok
420 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
422 throw new BrowserRedirectException( resource.getHref() );
424 resource.addLockManager( lockManager );
428 private DavResource processRepositoryGroup( final DavServletRequest request,
429 ArchivaDavResourceLocator archivaLocator, List<String> repositories,
430 String activePrincipal, List<String> resourcesInAbsolutePath,
431 RepositoryGroupConfiguration repoGroupConfig )
434 DavResource resource = null;
435 List<DavException> storedExceptions = new ArrayList<>();
437 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
439 String rootPath = StringUtils.substringBeforeLast( pathInfo, "/" );
441 if ( StringUtils.endsWith( rootPath, repoGroupConfig.getMergedIndexPath() ) )
443 // we are in the case of index file request
444 String requestedFileName = StringUtils.substringAfterLast( pathInfo, "/" );
445 Path temporaryIndexDirectory =
446 buildMergedIndexDirectory( repositories, activePrincipal, request, repoGroupConfig );
448 Path resourceFile = temporaryIndexDirectory.resolve( requestedFileName );
449 resource = new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), requestedFileName, null,
450 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
451 archivaLocator, this, mimeTypes, auditListeners, scheduler,
457 for ( String repositoryId : repositories )
459 ManagedRepositoryContent managedRepositoryContent;
462 managedRepositoryContent = repositoryFactory.getManagedRepositoryContent( repositoryId );
464 catch ( RepositoryNotFoundException e )
466 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
468 catch ( RepositoryException e )
470 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
475 ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository( repositoryId );
476 DavResource updatedResource =
477 processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
479 if ( resource == null )
481 resource = updatedResource;
484 String logicalResource = getLogicalResource( archivaLocator, null, false );
485 if ( logicalResource.endsWith( "/" ) )
487 logicalResource = logicalResource.substring( 1 );
489 resourcesInAbsolutePath.add(
490 Paths.get( managedRepositoryContent.getRepoRoot(), logicalResource ).toAbsolutePath().toString() );
492 catch ( DavException e )
494 storedExceptions.add( e );
496 catch ( RepositoryAdminException e )
498 storedExceptions.add( new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ) );
502 if ( resource == null )
504 if ( !storedExceptions.isEmpty() )
507 for ( DavException e : storedExceptions )
509 if ( 401 == e.getErrorCode() )
515 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
519 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
525 private String getLogicalResource( ArchivaDavResourceLocator archivaLocator, ManagedRepository managedRepository,
526 boolean useOrigResourcePath )
528 // FIXME remove this hack
529 // but currently managedRepository can be null in case of group
530 String layout = managedRepository == null ? new ManagedRepository().getLayout() : managedRepository.getLayout();
531 RepositoryStorage repositoryStorage =
532 this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
533 String path = repositoryStorage.getFilePath(
534 useOrigResourcePath ? archivaLocator.getOrigResourcePath() : archivaLocator.getResourcePath(),
536 log.debug( "found path {} for resourcePath: '{}' with managedRepo '{}' and layout '{}'", path,
537 archivaLocator.getResourcePath(), managedRepository == null ? "null" : managedRepository.getId(),
542 private String evaluatePathWithVersion( ArchivaDavResourceLocator archivaLocator, //
543 ManagedRepositoryContent managedRepositoryContent, //
547 String layout = managedRepositoryContent.getRepository() == null
548 ? new ManagedRepository().getLayout()
549 : managedRepositoryContent.getRepository().getLayout();
550 RepositoryStorage repositoryStorage =
551 this.applicationContext.getBean( "repositoryStorage#" + layout, RepositoryStorage.class );
554 return repositoryStorage.getFilePathWithVersion( archivaLocator.getResourcePath(), //
555 managedRepositoryContent );
557 catch ( RelocationException e )
559 String path = e.getPath();
560 log.debug( "Relocation to {}", path );
562 throw new BrowserRedirectException( addHrefPrefix( contextPath, path ), e.getRelocationType() );
564 catch ( XMLException e )
566 log.error( e.getMessage(), e );
567 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
571 private DavResource processRepository( final DavServletRequest request, ArchivaDavResourceLocator archivaLocator,
572 String activePrincipal, ManagedRepositoryContent managedRepositoryContent,
573 ManagedRepository managedRepository )
576 DavResource resource = null;
577 if ( isAuthorized( request, managedRepositoryContent.getId() ) )
579 boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
580 // Maven Centric part ask evaluation if -SNAPSHOT
581 // MRM-1846 test if read method to prevent issue with maven 2.2.1 and uniqueVersion false
583 String path = readMethod
584 ? evaluatePathWithVersion( archivaLocator, managedRepositoryContent, request.getContextPath() )
585 : getLogicalResource( archivaLocator, managedRepository, false );
586 if ( path.startsWith( "/" ) )
588 path = path.substring( 1 );
590 LogicalResource logicalResource = new LogicalResource( path );
591 Path resourceFile = Paths.get( managedRepositoryContent.getRepoRoot(), path );
593 new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), path, managedRepositoryContent.getRepository(),
594 request.getRemoteAddr(), activePrincipal, request.getDavSession(),
595 archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager );
597 if ( WebdavMethodUtil.isReadMethod( request.getMethod() ) )
599 if ( archivaLocator.getHref( false ).endsWith( "/" ) && !Files.isDirectory( resourceFile ) )
601 // force a resource not found
602 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
606 if ( !resource.isCollection() )
608 boolean previouslyExisted = Files.exists(resourceFile);
610 boolean fromProxy = fetchContentFromProxies( managedRepositoryContent, request, logicalResource );
612 // At this point the incoming request can either be in default or
613 // legacy layout format.
616 // Perform an adjustment of the resource to the managed
617 // repository expected path.
618 String localResourcePath =
619 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepositoryContent );
620 resourceFile = Paths.get( managedRepositoryContent.getRepoRoot(), localResourcePath );
622 new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), logicalResource.getPath(),
623 managedRepositoryContent.getRepository(),
624 request.getRemoteAddr(), activePrincipal,
625 request.getDavSession(), archivaLocator, this, mimeTypes,
626 auditListeners, scheduler, fileLockManager );
628 catch ( LayoutException e )
630 if ( !Files.exists(resourceFile) )
632 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
638 String action = ( previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE )
641 log.debug( "Proxied artifact '{}' in repository '{}' (current user '{}')",
642 resourceFile.getFileName(), managedRepositoryContent.getId(), activePrincipal );
644 triggerAuditEvent( request.getRemoteAddr(), archivaLocator.getRepositoryId(),
645 logicalResource.getPath(), action, activePrincipal );
648 if ( !Files.exists(resourceFile) )
650 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
656 if ( request.getMethod().equals( HTTP_PUT_METHOD ) )
658 String resourcePath = logicalResource.getPath();
660 // check if target repo is enabled for releases
661 // we suppose that release-artifacts can be deployed only to repos enabled for releases
662 if ( managedRepositoryContent.getRepository().isReleases() && !repositoryRequest.isMetadata(
663 resourcePath ) && !repositoryRequest.isSupportFile( resourcePath ) )
665 ArtifactReference artifact = null;
668 artifact = managedRepositoryContent.toArtifactReference( resourcePath );
670 if ( !VersionUtil.isSnapshot( artifact.getVersion() ) )
672 // check if artifact already exists and if artifact re-deployment to the repository is allowed
673 if ( managedRepositoryContent.hasContent( artifact )
674 && managedRepositoryContent.getRepository().isBlockRedeployments() )
676 log.warn( "Overwriting released artifacts in repository '{}' is not allowed.",
677 managedRepositoryContent.getId() );
678 throw new DavException( HttpServletResponse.SC_CONFLICT,
679 "Overwriting released artifacts is not allowed." );
683 catch ( LayoutException e )
685 log.warn( "Artifact path '{}' is invalid.", resourcePath );
690 * Create parent directories that don't exist when writing a file This actually makes this
691 * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the
692 * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly
693 * create the collections themselves.
696 Path rootDirectory = Paths.get( managedRepositoryContent.getRepoRoot() );
697 Path destDir = rootDirectory.resolve( logicalResource.getPath() ).getParent();
699 if ( !Files.exists(destDir) )
703 Files.createDirectories( destDir );
705 catch ( IOException e )
707 log.error("Could not create directory {}: {}", destDir, e.getMessage(), e);
708 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create directory "+destDir );
710 String relPath = PathUtil.getRelative( rootDirectory.toAbsolutePath().toString(), destDir );
712 log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getFileName(),
715 triggerAuditEvent( request.getRemoteAddr(), managedRepositoryContent.getId(), relPath,
716 AuditEvent.CREATE_DIR, activePrincipal );
724 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
727 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
729 ManagedRepositoryContent managedRepositoryContent;
732 managedRepositoryContent =
733 repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() );
735 catch ( RepositoryNotFoundException e )
737 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
738 "Invalid repository: " + archivaLocator.getRepositoryId() );
740 catch ( RepositoryException e )
742 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
745 DavResource resource = null;
748 String logicalResource = getLogicalResource( archivaLocator, managedRepositoryAdmin.getManagedRepository(
749 archivaLocator.getRepositoryId() ), false );
750 if ( logicalResource.startsWith( "/" ) )
752 logicalResource = logicalResource.substring( 1 );
754 Path resourceFile = Paths.get( managedRepositoryContent.getRepoRoot(), logicalResource );
755 resource = new ArchivaDavResource( resourceFile.toAbsolutePath().toString(), logicalResource,
756 managedRepositoryContent.getRepository(), davSession, archivaLocator,
757 this, mimeTypes, auditListeners, scheduler, fileLockManager );
759 resource.addLockManager( lockManager );
761 catch ( RepositoryAdminException e )
763 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
768 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
769 LogicalResource resource )
772 String path = resource.getPath();
773 if ( repositoryRequest.isSupportFile( path ) )
775 Path proxiedFile = connectors.fetchFromProxies( managedRepository, path );
777 return ( proxiedFile != null );
780 // Is it a Metadata resource?
781 if ( repositoryRequest.isDefault( path ) && repositoryRequest.isMetadata( path ) )
783 return connectors.fetchMetadataFromProxies( managedRepository, path ).isModified();
786 // Is it an Archetype Catalog?
787 if ( repositoryRequest.isArchetypeCatalog( path ) )
789 // FIXME we must implement a merge of remote archetype catalog from remote servers.
790 Path proxiedFile = connectors.fetchFromProxies( managedRepository, path );
792 return ( proxiedFile != null );
795 // Not any of the above? Then it's gotta be an artifact reference.
798 // Get the artifact reference in a layout neutral way.
799 ArtifactReference artifact = repositoryRequest.toArtifactReference( path );
801 if ( artifact != null )
803 String repositoryLayout = managedRepository.getRepository().getLayout();
805 RepositoryStorage repositoryStorage =
806 this.applicationContext.getBean( "repositoryStorage#" + repositoryLayout, RepositoryStorage.class );
807 repositoryStorage.applyServerSideRelocation( managedRepository, artifact );
809 Path proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
811 resource.setPath( managedRepository.toPath( artifact ) );
813 log.debug( "Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(),
814 artifact.getVersion() );
816 return ( proxiedFile != null );
819 catch ( LayoutException e )
823 catch ( ProxyDownloadException e )
825 log.error( e.getMessage(), e );
826 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
827 "Unable to fetch artifact resource." );
834 private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action,
837 AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
838 event.setRemoteIP( remoteIP );
840 for ( AuditListener listener : auditListeners )
842 listener.auditEvent( event );
847 public void addAuditListener( AuditListener listener )
849 this.auditListeners.add( listener );
853 public void clearAuditListeners()
855 this.auditListeners.clear();
859 public void removeAuditListener( AuditListener listener )
861 this.auditListeners.remove( listener );
864 private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource,
867 // [MRM-503] - Metadata file need Pragma:no-cache response
869 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || ( resource instanceof ArchivaDavResource
870 && ( Files.isDirectory( ArchivaDavResource.class.cast( resource ).getLocalResource()) ) ) )
872 response.setHeader( "Pragma", "no-cache" );
873 response.setHeader( "Cache-Control", "no-cache" );
874 response.setDateHeader( "Last-Modified", new Date().getTime() );
876 // if the resource is a directory don't cache it as new groupId deployed will be available
877 // without need of refreshing browser
878 else if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || (
879 resource instanceof ArchivaVirtualDavResource && ( Files.isDirectory(Paths.get(
880 ArchivaVirtualDavResource.class.cast( resource ).getLogicalResource() )) ) ) )
882 response.setHeader( "Pragma", "no-cache" );
883 response.setHeader( "Cache-Control", "no-cache" );
884 response.setDateHeader( "Last-Modified", new Date().getTime() );
888 if ( resource instanceof ArchivaVirtualDavResource )
890 //MRM-1854 here we have a directory so force "Last-Modified"
891 response.setDateHeader( "Last-Modified", new Date().getTime() );
896 // We need to specify this so connecting wagons can work correctly
897 response.setDateHeader( "Last-Modified", resource.getModificationTime() );
899 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
902 private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
905 if ( !( locator instanceof ArchivaDavResourceLocator ) )
907 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
908 "Locator does not implement RepositoryLocator" );
912 if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
914 throw new DavException( HttpServletResponse.SC_NOT_FOUND );
917 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
919 // MRM-419 - Windows Webdav support. Should not 404 if there is no content.
920 if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) )
922 throw new DavException( HttpServletResponse.SC_NO_CONTENT );
924 return archivaLocator;
927 private String addHrefPrefix( String contextPath, String path ) {
928 String prefix = archivaConfiguration.getConfiguration().getWebapp().getUi().getApplicationUrl();
929 if (prefix == null || prefix.isEmpty()) {
930 prefix = contextPath;
932 return prefix + ( StringUtils.startsWith( path, "/" ) ? "" :
933 ( StringUtils.endsWith( prefix, "/" ) ? "" : "/" ) )
937 private static class LogicalResource
941 public LogicalResource( String path )
946 public String getPath()
951 public void setPath( String path )
957 protected boolean isAuthorized( DavServletRequest request, String repositoryId )
962 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
963 SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) );
965 return servletAuth.isAuthenticated( request, result ) //
966 && servletAuth.isAuthorized( request, securitySession, repositoryId, //
967 WebdavMethodUtil.getMethodPermission( request.getMethod() ) );
969 catch ( AuthenticationException e )
971 // safety check for MRM-911
972 String guest = UserManager.GUEST_USERNAME;
975 if ( servletAuth.isAuthorized( guest,
976 ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(),
977 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
982 catch ( UnauthorizedException ae )
984 throw new UnauthorizedDavException( repositoryId,
985 "You are not authenticated and authorized to access any repository." );
988 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
990 catch ( MustChangePasswordException e )
992 throw new UnauthorizedDavException( repositoryId, "You must change your password." );
994 catch ( AccountLockedException e )
996 throw new UnauthorizedDavException( repositoryId, "User account is locked." );
998 catch ( AuthorizationException e )
1000 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1001 "Fatal Authorization Subsystem Error." );
1003 catch ( UnauthorizedException e )
1005 throw new UnauthorizedDavException( repositoryId, e.getMessage() );
1009 private DavResource getResourceFromGroup( DavServletRequest request, List<String> repositories,
1010 ArchivaDavResourceLocator locator,
1011 RepositoryGroupConfiguration repositoryGroupConfiguration )
1012 throws DavException, RepositoryAdminException
1014 if ( repositoryGroupConfiguration.getRepositories() == null
1015 || repositoryGroupConfiguration.getRepositories().isEmpty() )
1018 Paths.get( System.getProperty( "appserver.base" ), "groups/" + repositoryGroupConfiguration.getId() );
1020 return new ArchivaDavResource( file.toString(), "groups/" + repositoryGroupConfiguration.getId(), null,
1021 request.getDavSession(), locator, this, mimeTypes, auditListeners, scheduler,
1024 List<Path> mergedRepositoryContents = new ArrayList<>();
1025 // multiple repo types so we guess they are all the same type
1026 // so use the first one
1027 // FIXME add a method with group in the repository storage
1028 String firstRepoId = repositoryGroupConfiguration.getRepositories().get( 0 );
1030 String path = getLogicalResource( locator, managedRepositoryAdmin.getManagedRepository( firstRepoId ), false );
1031 if ( path.startsWith( "/" ) )
1033 path = path.substring( 1 );
1035 LogicalResource logicalResource = new LogicalResource( path );
1038 // if the current user logged in has permission to any of the repositories, allow user to
1039 // browse the repo group but displaying only the repositories which the user has permission to access.
1040 // otherwise, prompt for authentication.
1042 String activePrincipal = getActivePrincipal( request );
1044 boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
1047 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
1052 if ( StringUtils.endsWith( pathInfo, repositoryGroupConfiguration.getMergedIndexPath() ) )
1054 Path mergedRepoDir =
1055 buildMergedIndexDirectory( repositories, activePrincipal, request, repositoryGroupConfiguration );
1056 mergedRepositoryContents.add( mergedRepoDir );
1060 if ( StringUtils.equalsIgnoreCase( pathInfo, "/" + repositoryGroupConfiguration.getId() ) )
1062 Path tmpDirectory = Paths.get( SystemUtils.getJavaIoTmpDir().toString(),
1063 repositoryGroupConfiguration.getId(),
1064 repositoryGroupConfiguration.getMergedIndexPath() );
1065 if ( !Files.exists(tmpDirectory) )
1067 synchronized ( tmpDirectory.toAbsolutePath().toString() )
1069 if ( !Files.exists(tmpDirectory) )
1073 Files.createDirectories( tmpDirectory );
1075 catch ( IOException e )
1077 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create direcotory "+tmpDirectory );
1082 mergedRepositoryContents.add( tmpDirectory.getParent() );
1084 for ( String repository : repositories )
1086 ManagedRepositoryContent managedRepository = null;
1090 managedRepository = repositoryFactory.getManagedRepositoryContent( repository );
1092 catch ( RepositoryNotFoundException e )
1094 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1095 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1097 catch ( RepositoryException e )
1099 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1100 "Invalid managed repository <" + repository + ">: " + e.getMessage() );
1103 Path resourceFile = Paths.get( managedRepository.getRepoRoot(), logicalResource.getPath() );
1104 if ( Files.exists(resourceFile) )
1106 // in case of group displaying index directory doesn't have sense !!
1107 String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory();
1108 if ( StringUtils.isNotEmpty( repoIndexDirectory ) )
1110 if ( !Paths.get( repoIndexDirectory ).isAbsolute() )
1112 repoIndexDirectory = Paths.get( managedRepository.getRepository().getLocation(),
1113 StringUtils.isEmpty( repoIndexDirectory )
1115 : repoIndexDirectory ).toAbsolutePath().toString();
1118 if ( StringUtils.isEmpty( repoIndexDirectory ) )
1120 repoIndexDirectory = Paths.get( managedRepository.getRepository().getLocation(),
1121 ".indexer" ).toAbsolutePath().toString();
1124 if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory ),
1125 FilenameUtils.normalize( resourceFile.toAbsolutePath().toString() ) ) )
1127 // for prompted authentication
1128 if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null )
1132 if ( isAuthorized( request, repository ) )
1134 mergedRepositoryContents.add( resourceFile );
1135 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1138 catch ( DavException e )
1140 // TODO: review exception handling
1142 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1143 activePrincipal, e.getMessage() );
1150 // for the current user logged in
1153 if ( servletAuth.isAuthorized( activePrincipal, repository,
1154 WebdavMethodUtil.getMethodPermission(
1155 request.getMethod() ) ) )
1157 mergedRepositoryContents.add( resourceFile );
1158 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal );
1161 catch ( UnauthorizedException e )
1163 // TODO: review exception handling
1165 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1166 activePrincipal, e.getMessage() );
1177 throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
1180 ArchivaVirtualDavResource resource =
1181 new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator,
1184 // compatibility with MRM-440 to ensure browsing the repository group works ok
1185 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
1187 throw new BrowserRedirectException( resource.getHref() );
1193 protected String getActivePrincipal( DavServletRequest request )
1195 User sessionUser = httpAuth.getSessionUser( request.getSession() );
1196 return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME;
1200 * Check if the current user is authorized to access any of the repos
1203 * @param repositories
1204 * @param activePrincipal
1207 private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
1209 // when no repositories configured it's impossible to browse nothing !
1210 // at least make possible to see nothing :-)
1211 if ( repositories == null || repositories.isEmpty() )
1216 boolean allow = false;
1218 // if securitySession != null, it means that the user was prompted for authentication
1219 if ( httpAuth.getSecuritySession( request.getSession() ) != null )
1221 for ( String repository : repositories )
1225 if ( isAuthorized( request, repository ) )
1231 catch ( DavException e )
1239 for ( String repository : repositories )
1243 if ( servletAuth.isAuthorized( activePrincipal, repository,
1244 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1250 catch ( UnauthorizedException e )
1260 private Path writeMergedMetadataToFile( ArchivaRepositoryMetadata mergedMetadata, String outputFilename )
1261 throws RepositoryMetadataException, DigesterException, IOException
1263 Path outputFile = Paths.get( outputFilename );
1264 if ( Files.exists(outputFile) )
1266 org.apache.archiva.common.utils.FileUtils.deleteQuietly( outputFile );
1269 Files.createDirectories(outputFile.getParent());
1270 RepositoryMetadataWriter.write( mergedMetadata, outputFile );
1272 createChecksumFile( outputFilename, digestSha1 );
1273 createChecksumFile( outputFilename, digestMd5 );
1278 private void createChecksumFile( String path, Digester digester )
1279 throws DigesterException, IOException
1281 Path checksumFile = Paths.get( path + digester.getFilenameExtension() );
1282 if ( !Files.exists(checksumFile) )
1284 org.apache.archiva.common.utils.FileUtils.deleteQuietly( checksumFile );
1285 checksum.createChecksum( Paths.get( path ).toFile(), digester );
1287 else if ( !Files.isRegularFile( checksumFile) )
1289 log.error( "Checksum file is not a file." );
1293 private boolean isProjectReference( String requestedResource )
1297 metadataTools.toVersionedReference( requestedResource );
1300 catch ( RepositoryMetadataException re )
1306 protected Path buildMergedIndexDirectory( List<String> repositories, String activePrincipal,
1307 DavServletRequest request,
1308 RepositoryGroupConfiguration repositoryGroupConfiguration )
1314 HttpSession session = request.getSession();
1316 Map<String, TemporaryGroupIndex> temporaryGroupIndexMap =
1317 (Map<String, TemporaryGroupIndex>) session.getAttribute(
1318 TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY );
1319 if ( temporaryGroupIndexMap == null )
1321 temporaryGroupIndexMap = new HashMap<>();
1324 TemporaryGroupIndex tmp = temporaryGroupIndexMap.get( repositoryGroupConfiguration.getId() );
1326 if ( tmp != null && tmp.getDirectory() != null && Files.exists(tmp.getDirectory()))
1328 if ( System.currentTimeMillis() - tmp.getCreationTime() > (
1329 repositoryGroupConfiguration.getMergedIndexTtl() * 60 * 1000 ) )
1331 log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1332 "tmp group index '{}' is too old so delete it", repositoryGroupConfiguration.getId() );
1333 indexMerger.cleanTemporaryGroupIndex( tmp );
1337 log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1338 "merged index for group '{}' found in cache", repositoryGroupConfiguration.getId() );
1339 return tmp.getDirectory();
1343 Set<String> authzRepos = new HashSet<String>();
1345 String permission = WebdavMethodUtil.getMethodPermission( request.getMethod() );
1347 for ( String repository : repositories )
1351 if ( servletAuth.isAuthorized( activePrincipal, repository, permission ) )
1353 authzRepos.add( repository );
1354 authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository ) );
1357 catch ( UnauthorizedException e )
1359 // TODO: review exception handling
1361 log.debug( "Skipping repository '{}' for user '{}': {}", repository, activePrincipal,
1365 log.info( "generate temporary merged index for repository group '{}' for repositories '{}'",
1366 repositoryGroupConfiguration.getId(), authzRepos );
1368 Path tempRepoFile = Files.createTempDirectory( "temp" );
1369 tempRepoFile.toFile().deleteOnExit();
1371 IndexMergerRequest indexMergerRequest =
1372 new IndexMergerRequest( authzRepos, true, repositoryGroupConfiguration.getId(),
1373 repositoryGroupConfiguration.getMergedIndexPath(),
1374 repositoryGroupConfiguration.getMergedIndexTtl() ).mergedIndexDirectory(
1375 tempRepoFile ).temporary( true );
1377 MergedRemoteIndexesTaskRequest taskRequest =
1378 new MergedRemoteIndexesTaskRequest( indexMergerRequest, indexMerger );
1380 MergedRemoteIndexesTask job = new MergedRemoteIndexesTask( taskRequest );
1382 IndexingContext indexingContext = job.execute().getIndexingContext();
1384 Path mergedRepoDir = indexingContext.getIndexDirectoryFile().toPath();
1385 TemporaryGroupIndex temporaryGroupIndex =
1386 new TemporaryGroupIndex( mergedRepoDir, indexingContext.getId(), repositoryGroupConfiguration.getId(),
1387 repositoryGroupConfiguration.getMergedIndexTtl() ) //
1388 .setCreationTime( new Date().getTime() );
1389 temporaryGroupIndexMap.put( repositoryGroupConfiguration.getId(), temporaryGroupIndex );
1390 session.setAttribute( TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY,
1391 temporaryGroupIndexMap );
1392 return mergedRepoDir;
1394 catch ( RepositoryAdminException e )
1396 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1398 catch ( IndexMergerException e )
1400 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1402 catch ( IOException e )
1404 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1409 public void setServletAuth( ServletAuthenticator servletAuth )
1411 this.servletAuth = servletAuth;
1414 public void setHttpAuth( HttpAuthenticator httpAuth )
1416 this.httpAuth = httpAuth;
1419 public void setScheduler( RepositoryArchivaTaskScheduler scheduler )
1421 this.scheduler = scheduler;
1424 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1426 this.archivaConfiguration = archivaConfiguration;
1429 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory )
1431 this.repositoryFactory = repositoryFactory;
1434 public void setRepositoryRequest( RepositoryRequest repositoryRequest )
1436 this.repositoryRequest = repositoryRequest;
1439 public void setConnectors( RepositoryProxyConnectors connectors )
1441 this.connectors = connectors;
1444 public RemoteRepositoryAdmin getRemoteRepositoryAdmin()
1446 return remoteRepositoryAdmin;
1449 public void setRemoteRepositoryAdmin( RemoteRepositoryAdmin remoteRepositoryAdmin )
1451 this.remoteRepositoryAdmin = remoteRepositoryAdmin;
1454 public ManagedRepositoryAdmin getManagedRepositoryAdmin()
1456 return managedRepositoryAdmin;
1459 public void setManagedRepositoryAdmin( ManagedRepositoryAdmin managedRepositoryAdmin )
1461 this.managedRepositoryAdmin = managedRepositoryAdmin;