Java tutorial
package org.apache.archiva.webdav; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.archiva.admin.model.RepositoryAdminException; import org.apache.archiva.admin.model.beans.ManagedRepository; import org.apache.archiva.admin.model.beans.RemoteRepository; import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin; import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin; import org.apache.archiva.audit.Auditable; import org.apache.archiva.common.filelock.FileLockManager; import org.apache.archiva.common.plexusbridge.PlexusSisuBridge; import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException; import org.apache.archiva.common.utils.PathUtil; import org.apache.archiva.common.utils.VersionUtil; import org.apache.archiva.configuration.ArchivaConfiguration; import org.apache.archiva.configuration.RepositoryGroupConfiguration; import org.apache.archiva.indexer.merger.IndexMerger; import org.apache.archiva.indexer.merger.IndexMergerException; import org.apache.archiva.indexer.merger.IndexMergerRequest; import org.apache.archiva.indexer.merger.MergedRemoteIndexesTask; import org.apache.archiva.indexer.merger.MergedRemoteIndexesTaskRequest; import org.apache.archiva.indexer.merger.TemporaryGroupIndex; import org.apache.archiva.indexer.search.RepositorySearch; import org.apache.archiva.maven2.metadata.MavenMetadataReader; import org.apache.archiva.metadata.model.facets.AuditEvent; import org.apache.archiva.metadata.repository.storage.RelocationException; import org.apache.archiva.metadata.repository.storage.RepositoryStorage; import org.apache.archiva.model.ArchivaRepositoryMetadata; import org.apache.archiva.model.ArtifactReference; import org.apache.archiva.policies.ProxyDownloadException; import org.apache.archiva.proxy.model.RepositoryProxyConnectors; import org.apache.archiva.redback.authentication.AuthenticationException; import org.apache.archiva.redback.authentication.AuthenticationResult; import org.apache.archiva.redback.authorization.AuthorizationException; import org.apache.archiva.redback.authorization.UnauthorizedException; import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator; import org.apache.archiva.redback.policy.AccountLockedException; import org.apache.archiva.redback.policy.MustChangePasswordException; import org.apache.archiva.redback.system.SecuritySession; import org.apache.archiva.redback.users.User; import org.apache.archiva.redback.users.UserManager; import org.apache.archiva.repository.ManagedRepositoryContent; import org.apache.archiva.repository.RepositoryContentFactory; import org.apache.archiva.repository.RepositoryException; import org.apache.archiva.repository.RepositoryNotFoundException; import org.apache.archiva.repository.content.maven2.RepositoryRequest; import org.apache.archiva.repository.events.AuditListener; import org.apache.archiva.repository.layout.LayoutException; import org.apache.archiva.repository.metadata.MetadataTools; import org.apache.archiva.repository.metadata.RepositoryMetadataException; import org.apache.archiva.repository.metadata.RepositoryMetadataMerge; import org.apache.archiva.repository.metadata.RepositoryMetadataWriter; import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler; import org.apache.archiva.security.ServletAuthenticator; import org.apache.archiva.webdav.util.MimeTypes; import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner; import org.apache.archiva.webdav.util.WebdavMethodUtil; import org.apache.archiva.xml.XMLException; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceFactory; import org.apache.jackrabbit.webdav.DavResourceLocator; import org.apache.jackrabbit.webdav.DavServletRequest; import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.SimpleLockManager; import org.apache.maven.index.context.IndexingContext; import org.codehaus.plexus.digest.ChecksumFile; import org.codehaus.plexus.digest.Digester; import org.codehaus.plexus.digest.DigesterException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MarkerFactory; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * */ @Service("davResourceFactory#archiva") public class ArchivaDavResourceFactory implements DavResourceFactory, Auditable { private static final String PROXIED_SUFFIX = " (proxied)"; private static final String HTTP_PUT_METHOD = "PUT"; private Logger log = LoggerFactory.getLogger(ArchivaDavResourceFactory.class); @Inject private List<AuditListener> auditListeners = new ArrayList<>(); @Inject private RepositoryContentFactory repositoryFactory; private RepositoryRequest repositoryRequest; @Inject @Named(value = "repositoryProxyConnectors#default") private RepositoryProxyConnectors connectors; @Inject private MetadataTools metadataTools; @Inject private MimeTypes mimeTypes; private ArchivaConfiguration archivaConfiguration; @Inject private ServletAuthenticator servletAuth; @Inject @Named(value = "httpAuthenticator#basic") private HttpAuthenticator httpAuth; @Inject private RemoteRepositoryAdmin remoteRepositoryAdmin; @Inject private ManagedRepositoryAdmin managedRepositoryAdmin; @Inject private IndexMerger indexMerger; @Inject private RepositorySearch repositorySearch; /** * Lock Manager - use simple implementation from JackRabbit */ private final LockManager lockManager = new SimpleLockManager(); private ChecksumFile checksum; private Digester digestSha1; private Digester digestMd5; @Inject @Named(value = "archivaTaskScheduler#repository") private RepositoryArchivaTaskScheduler scheduler; @Inject @Named(value = "fileLockManager#default") private FileLockManager fileLockManager; private ApplicationContext applicationContext; @Inject public ArchivaDavResourceFactory(ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge, ArchivaConfiguration archivaConfiguration) throws PlexusSisuBridgeException { this.archivaConfiguration = archivaConfiguration; this.applicationContext = applicationContext; this.checksum = plexusSisuBridge.lookup(ChecksumFile.class); this.digestMd5 = plexusSisuBridge.lookup(Digester.class, "md5"); this.digestSha1 = plexusSisuBridge.lookup(Digester.class, "sha1"); // TODO remove this hard dependency on maven !! repositoryRequest = new RepositoryRequest(); } @PostConstruct public void initialize() { // no op } @Override public DavResource createResource(final DavResourceLocator locator, final DavServletRequest request, final DavServletResponse response) throws DavException { ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator(locator); RepositoryGroupConfiguration repoGroupConfig = archivaConfiguration.getConfiguration() .getRepositoryGroupsAsMap().get(archivaLocator.getRepositoryId()); String activePrincipal = getActivePrincipal(request); List<String> resourcesInAbsolutePath = new ArrayList<>(); boolean readMethod = WebdavMethodUtil.isReadMethod(request.getMethod()); DavResource resource; if (repoGroupConfig != null) { if (!readMethod) { throw new DavException(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "Write method not allowed for repository groups."); } log.debug("Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal); // handle browse requests for virtual repos if (getLogicalResource(archivaLocator, null, true).endsWith("/")) { try { DavResource davResource = getResourceFromGroup(request, repoGroupConfig.getRepositories(), archivaLocator, repoGroupConfig); setHeaders(response, locator, davResource, true); return davResource; } catch (RepositoryAdminException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } else { // make a copy to avoid potential concurrent modifications (eg. by configuration) // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are // infrequent List<String> repositories = new ArrayList<>(repoGroupConfig.getRepositories()); resource = processRepositoryGroup(request, archivaLocator, repositories, activePrincipal, resourcesInAbsolutePath, repoGroupConfig); } } else { try { RemoteRepository remoteRepository = remoteRepositoryAdmin .getRemoteRepository(archivaLocator.getRepositoryId()); if (remoteRepository != null) { String logicalResource = getLogicalResource(archivaLocator, null, false); IndexingContext indexingContext = remoteRepositoryAdmin.createIndexContext(remoteRepository); File resourceFile = StringUtils.equals(logicalResource, "/") ? new File(indexingContext.getIndexDirectoryFile().getParent()) : new File(indexingContext.getIndexDirectoryFile().getParent(), logicalResource); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), // locator.getResourcePath(), // null, // request.getRemoteAddr(), // activePrincipal, // request.getDavSession(), // archivaLocator, // this, // mimeTypes, // auditListeners, // scheduler, // fileLockManager); setHeaders(response, locator, resource, false); return resource; } } catch (RepositoryAdminException e) { log.debug("RepositoryException remote repository with d'{}' not found, msg: {}", archivaLocator.getRepositoryId(), e.getMessage()); } ManagedRepositoryContent managedRepositoryContent = null; try { managedRepositoryContent = repositoryFactory .getManagedRepositoryContent(archivaLocator.getRepositoryId()); } catch (RepositoryNotFoundException e) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Invalid repository: " + archivaLocator.getRepositoryId()); } catch (RepositoryException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } log.debug("Managed repository '{}' accessed by '{}'", managedRepositoryContent.getId(), activePrincipal); try { resource = processRepository(request, archivaLocator, activePrincipal, managedRepositoryContent, managedRepositoryAdmin.getManagedRepository(archivaLocator.getRepositoryId())); String logicalResource = getLogicalResource(archivaLocator, null, false); resourcesInAbsolutePath .add(new File(managedRepositoryContent.getRepoRoot(), logicalResource).getAbsolutePath()); } catch (RepositoryAdminException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } String requestedResource = request.getRequestURI(); // MRM-872 : merge all available metadata // merge metadata only when requested via the repo group if ((repositoryRequest.isMetadata(requestedResource) || repositoryRequest.isMetadataSupportFile(requestedResource)) && repoGroupConfig != null) { // this should only be at the project level not version level! if (isProjectReference(requestedResource)) { ArchivaDavResource res = (ArchivaDavResource) resource; String filePath = StringUtils .substringBeforeLast(res.getLocalResource().getAbsolutePath().replace('\\', '/'), "/"); filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml"; // for MRM-872 handle checksums of the merged metadata files if (repositoryRequest.isSupportFile(requestedResource)) { File metadataChecksum = new File( filePath + "." + StringUtils.substringAfterLast(requestedResource, ".")); if (metadataChecksum.exists()) { LogicalResource logicalResource = new LogicalResource( getLogicalResource(archivaLocator, null, false)); resource = new ArchivaDavResource(metadataChecksum.getAbsolutePath(), logicalResource.getPath(), null, request.getRemoteAddr(), activePrincipal, request.getDavSession(), archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); } } else { if (resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1) { // merge the metadata of all repos under group ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata(); for (String resourceAbsPath : resourcesInAbsolutePath) { try { File metadataFile = new File(resourceAbsPath); ArchivaRepositoryMetadata repoMetadata = MavenMetadataReader.read(metadataFile); mergedMetadata = RepositoryMetadataMerge.merge(mergedMetadata, repoMetadata); } catch (XMLException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error occurred while reading metadata file."); } catch (RepositoryMetadataException r) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error occurred while merging metadata file."); } } try { File resourceFile = writeMergedMetadataToFile(mergedMetadata, filePath); LogicalResource logicalResource = new LogicalResource( getLogicalResource(archivaLocator, null, false)); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), null, request.getRemoteAddr(), activePrincipal, request.getDavSession(), archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); } catch (RepositoryMetadataException r) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error occurred while writing metadata file."); } catch (IOException ie) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error occurred while generating checksum files."); } catch (DigesterException de) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error occurred while generating checksum files." + de.getMessage()); } } } } } setHeaders(response, locator, resource, false); // compatibility with MRM-440 to ensure browsing the repository works ok if (resource.isCollection() && !request.getRequestURI().endsWith("/")) { throw new BrowserRedirectException(resource.getHref()); } resource.addLockManager(lockManager); return resource; } private DavResource processRepositoryGroup(final DavServletRequest request, ArchivaDavResourceLocator archivaLocator, List<String> repositories, String activePrincipal, List<String> resourcesInAbsolutePath, RepositoryGroupConfiguration repoGroupConfig) throws DavException { DavResource resource = null; List<DavException> storedExceptions = new ArrayList<>(); String pathInfo = StringUtils.removeEnd(request.getPathInfo(), "/"); String rootPath = StringUtils.substringBeforeLast(pathInfo, "/"); if (StringUtils.endsWith(rootPath, repoGroupConfig.getMergedIndexPath())) { // we are in the case of index file request String requestedFileName = StringUtils.substringAfterLast(pathInfo, "/"); File temporaryIndexDirectory = buildMergedIndexDirectory(repositories, activePrincipal, request, repoGroupConfig); File resourceFile = new File(temporaryIndexDirectory, requestedFileName); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), requestedFileName, null, request.getRemoteAddr(), activePrincipal, request.getDavSession(), archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); } else { for (String repositoryId : repositories) { ManagedRepositoryContent managedRepositoryContent; try { managedRepositoryContent = repositoryFactory.getManagedRepositoryContent(repositoryId); } catch (RepositoryNotFoundException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } catch (RepositoryException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } try { ManagedRepository managedRepository = managedRepositoryAdmin.getManagedRepository(repositoryId); DavResource updatedResource = processRepository(request, archivaLocator, activePrincipal, managedRepositoryContent, managedRepository); if (resource == null) { resource = updatedResource; } String logicalResource = getLogicalResource(archivaLocator, null, false); if (logicalResource.endsWith("/")) { logicalResource = logicalResource.substring(1); } resourcesInAbsolutePath.add( new File(managedRepositoryContent.getRepoRoot(), logicalResource).getAbsolutePath()); } catch (DavException e) { storedExceptions.add(e); } catch (RepositoryAdminException e) { storedExceptions.add(new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e)); } } } if (resource == null) { if (!storedExceptions.isEmpty()) { // MRM-1232 for (DavException e : storedExceptions) { if (401 == e.getErrorCode()) { throw e; } } throw new DavException(HttpServletResponse.SC_NOT_FOUND); } else { throw new DavException(HttpServletResponse.SC_NOT_FOUND); } } return resource; } private String getLogicalResource(ArchivaDavResourceLocator archivaLocator, ManagedRepository managedRepository, boolean useOrigResourcePath) { // FIXME remove this hack // but currently managedRepository can be null in case of group String layout = managedRepository == null ? new ManagedRepository().getLayout() : managedRepository.getLayout(); RepositoryStorage repositoryStorage = this.applicationContext.getBean("repositoryStorage#" + layout, RepositoryStorage.class); String path = repositoryStorage.getFilePath( useOrigResourcePath ? archivaLocator.getOrigResourcePath() : archivaLocator.getResourcePath(), managedRepository); log.debug("found path {} for resourcePath: '{}' with managedRepo '{}' and layout '{}'", path, archivaLocator.getResourcePath(), managedRepository == null ? "null" : managedRepository.getId(), layout); return path; } private String evaluatePathWithVersion(ArchivaDavResourceLocator archivaLocator, // ManagedRepositoryContent managedRepositoryContent, // String contextPath) throws DavException { String layout = managedRepositoryContent.getRepository() == null ? new ManagedRepository().getLayout() : managedRepositoryContent.getRepository().getLayout(); RepositoryStorage repositoryStorage = this.applicationContext.getBean("repositoryStorage#" + layout, RepositoryStorage.class); try { return repositoryStorage.getFilePathWithVersion(archivaLocator.getResourcePath(), // managedRepositoryContent); } catch (RelocationException e) { String path = e.getPath(); log.debug("Relocation to {}", path); throw new BrowserRedirectException(addHrefPrefix(contextPath, path), e.getRelocationType()); } catch (XMLException e) { log.error(e.getMessage(), e); throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } private DavResource processRepository(final DavServletRequest request, ArchivaDavResourceLocator archivaLocator, String activePrincipal, ManagedRepositoryContent managedRepositoryContent, ManagedRepository managedRepository) throws DavException { DavResource resource = null; if (isAuthorized(request, managedRepositoryContent.getId())) { boolean readMethod = WebdavMethodUtil.isReadMethod(request.getMethod()); // Maven Centric part ask evaluation if -SNAPSHOT // MRM-1846 test if read method to prevent issue with maven 2.2.1 and uniqueVersion false String path = readMethod ? evaluatePathWithVersion(archivaLocator, managedRepositoryContent, request.getContextPath()) : getLogicalResource(archivaLocator, managedRepository, false); if (path.startsWith("/")) { path = path.substring(1); } LogicalResource logicalResource = new LogicalResource(path); File resourceFile = new File(managedRepositoryContent.getRepoRoot(), path); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), path, managedRepositoryContent.getRepository(), request.getRemoteAddr(), activePrincipal, request.getDavSession(), archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); if (WebdavMethodUtil.isReadMethod(request.getMethod())) { if (archivaLocator.getHref(false).endsWith("/") && !resourceFile.isDirectory()) { // force a resource not found throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Resource does not exist"); } else { if (!resource.isCollection()) { boolean previouslyExisted = resourceFile.exists(); boolean fromProxy = fetchContentFromProxies(managedRepositoryContent, request, logicalResource); // At this point the incoming request can either be in default or // legacy layout format. try { // Perform an adjustment of the resource to the managed // repository expected path. String localResourcePath = repositoryRequest.toNativePath(logicalResource.getPath(), managedRepositoryContent); resourceFile = new File(managedRepositoryContent.getRepoRoot(), localResourcePath); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource.getPath(), managedRepositoryContent.getRepository(), request.getRemoteAddr(), activePrincipal, request.getDavSession(), archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); } catch (LayoutException e) { if (!resourceFile.exists()) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, e); } } if (fromProxy) { String action = (previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE) + PROXIED_SUFFIX; log.debug("Proxied artifact '{}' in repository '{}' (current user '{}')", resourceFile.getName(), managedRepositoryContent.getId(), activePrincipal); triggerAuditEvent(request.getRemoteAddr(), archivaLocator.getRepositoryId(), logicalResource.getPath(), action, activePrincipal); } if (!resourceFile.exists()) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Resource does not exist"); } } } } if (request.getMethod().equals(HTTP_PUT_METHOD)) { String resourcePath = logicalResource.getPath(); // check if target repo is enabled for releases // we suppose that release-artifacts can be deployed only to repos enabled for releases if (managedRepositoryContent.getRepository().isReleases() && !repositoryRequest.isMetadata(resourcePath) && !repositoryRequest.isSupportFile(resourcePath)) { ArtifactReference artifact = null; try { artifact = managedRepositoryContent.toArtifactReference(resourcePath); if (!VersionUtil.isSnapshot(artifact.getVersion())) { // check if artifact already exists and if artifact re-deployment to the repository is allowed if (managedRepositoryContent.hasContent(artifact) && managedRepositoryContent.getRepository().isBlockRedeployments()) { log.warn("Overwriting released artifacts in repository '{}' is not allowed.", managedRepositoryContent.getId()); throw new DavException(HttpServletResponse.SC_CONFLICT, "Overwriting released artifacts is not allowed."); } } } catch (LayoutException e) { log.warn("Artifact path '{}' is invalid.", resourcePath); } } /* * Create parent directories that don't exist when writing a file This actually makes this * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly * create the collections themselves. */ File rootDirectory = new File(managedRepositoryContent.getRepoRoot()); File destDir = new File(rootDirectory, logicalResource.getPath()).getParentFile(); if (!destDir.exists()) { destDir.mkdirs(); String relPath = PathUtil.getRelative(rootDirectory.getAbsolutePath(), destDir); log.debug("Creating destination directory '{}' (current user '{}')", destDir.getName(), activePrincipal); triggerAuditEvent(request.getRemoteAddr(), managedRepositoryContent.getId(), relPath, AuditEvent.CREATE_DIR, activePrincipal); } } } return resource; } @Override public DavResource createResource(final DavResourceLocator locator, final DavSession davSession) throws DavException { ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator(locator); ManagedRepositoryContent managedRepositoryContent; try { managedRepositoryContent = repositoryFactory .getManagedRepositoryContent(archivaLocator.getRepositoryId()); } catch (RepositoryNotFoundException e) { throw new DavException(HttpServletResponse.SC_NOT_FOUND, "Invalid repository: " + archivaLocator.getRepositoryId()); } catch (RepositoryException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } DavResource resource = null; try { String logicalResource = getLogicalResource(archivaLocator, managedRepositoryAdmin.getManagedRepository(archivaLocator.getRepositoryId()), false); if (logicalResource.startsWith("/")) { logicalResource = logicalResource.substring(1); } File resourceFile = new File(managedRepositoryContent.getRepoRoot(), logicalResource); resource = new ArchivaDavResource(resourceFile.getAbsolutePath(), logicalResource, managedRepositoryContent.getRepository(), davSession, archivaLocator, this, mimeTypes, auditListeners, scheduler, fileLockManager); resource.addLockManager(lockManager); } catch (RepositoryAdminException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } return resource; } private boolean fetchContentFromProxies(ManagedRepositoryContent managedRepository, DavServletRequest request, LogicalResource resource) throws DavException { String path = resource.getPath(); if (repositoryRequest.isSupportFile(path)) { File proxiedFile = connectors.fetchFromProxies(managedRepository, path); return (proxiedFile != null); } // Is it a Metadata resource? if (repositoryRequest.isDefault(path) && repositoryRequest.isMetadata(path)) { return connectors.fetchMetadataFromProxies(managedRepository, path).isModified(); } // Is it an Archetype Catalog? if (repositoryRequest.isArchetypeCatalog(path)) { // FIXME we must implement a merge of remote archetype catalog from remote servers. File proxiedFile = connectors.fetchFromProxies(managedRepository, path); return (proxiedFile != null); } // Not any of the above? Then it's gotta be an artifact reference. try { // Get the artifact reference in a layout neutral way. ArtifactReference artifact = repositoryRequest.toArtifactReference(path); if (artifact != null) { String repositoryLayout = managedRepository.getRepository().getLayout(); RepositoryStorage repositoryStorage = this.applicationContext .getBean("repositoryStorage#" + repositoryLayout, RepositoryStorage.class); repositoryStorage.applyServerSideRelocation(managedRepository, artifact); File proxiedFile = connectors.fetchFromProxies(managedRepository, artifact); resource.setPath(managedRepository.toPath(artifact)); log.debug("Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()); return (proxiedFile != null); } } catch (LayoutException e) { /* eat it */ } catch (ProxyDownloadException e) { log.error(e.getMessage(), e); throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource."); } return false; } // TODO: remove? private void triggerAuditEvent(String remoteIP, String repositoryId, String resource, String action, String principal) { AuditEvent event = new AuditEvent(repositoryId, principal, resource, action); event.setRemoteIP(remoteIP); for (AuditListener listener : auditListeners) { listener.auditEvent(event); } } @Override public void addAuditListener(AuditListener listener) { this.auditListeners.add(listener); } @Override public void clearAuditListeners() { this.auditListeners.clear(); } @Override public void removeAuditListener(AuditListener listener) { this.auditListeners.remove(listener); } private void setHeaders(DavServletResponse response, DavResourceLocator locator, DavResource resource, boolean group) { // [MRM-503] - Metadata file need Pragma:no-cache response // header. if (locator.getResourcePath().endsWith("/maven-metadata.xml") || (resource instanceof ArchivaDavResource && (ArchivaDavResource.class.cast(resource).getLocalResource().isDirectory()))) { response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Last-Modified", new Date().getTime()); } // if the resource is a directory don't cache it as new groupId deployed will be available // without need of refreshing browser else if (locator.getResourcePath().endsWith("/maven-metadata.xml") || (resource instanceof ArchivaVirtualDavResource && (new File(ArchivaVirtualDavResource.class.cast(resource).getLogicalResource()) .isDirectory()))) { response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Last-Modified", new Date().getTime()); } else if (group) { if (resource instanceof ArchivaVirtualDavResource) { //MRM-1854 here we have a directory so force "Last-Modified" response.setDateHeader("Last-Modified", new Date().getTime()); } } else { // We need to specify this so connecting wagons can work correctly response.setDateHeader("Last-Modified", resource.getModificationTime()); } // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots) } private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator(DavResourceLocator locator) throws DavException { if (!(locator instanceof ArchivaDavResourceLocator)) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Locator does not implement RepositoryLocator"); } // Hidden paths if (locator.getResourcePath().startsWith(ArchivaDavResource.HIDDEN_PATH_PREFIX)) { throw new DavException(HttpServletResponse.SC_NOT_FOUND); } ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator; // MRM-419 - Windows Webdav support. Should not 404 if there is no content. if (StringUtils.isEmpty(archivaLocator.getRepositoryId())) { throw new DavException(HttpServletResponse.SC_NO_CONTENT); } return archivaLocator; } private String addHrefPrefix(String contextPath, String path) { String prefix = archivaConfiguration.getConfiguration().getWebapp().getUi().getApplicationUrl(); if (prefix == null || prefix.isEmpty()) { prefix = contextPath; } return prefix + (StringUtils.startsWith(path, "/") ? "" : (StringUtils.endsWith(prefix, "/") ? "" : "/")) + path; } private static class LogicalResource { private String path; public LogicalResource(String path) { this.path = path; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } } protected boolean isAuthorized(DavServletRequest request, String repositoryId) throws DavException { try { AuthenticationResult result = httpAuth.getAuthenticationResult(request, null); SecuritySession securitySession = httpAuth.getSecuritySession(request.getSession(true)); return servletAuth.isAuthenticated(request, result) // && servletAuth.isAuthorized(request, securitySession, repositoryId, // WebdavMethodUtil.getMethodPermission(request.getMethod())); } catch (AuthenticationException e) { // safety check for MRM-911 String guest = UserManager.GUEST_USERNAME; try { if (servletAuth.isAuthorized(guest, ((ArchivaDavResourceLocator) request.getRequestLocator()).getRepositoryId(), WebdavMethodUtil.getMethodPermission(request.getMethod()))) { return true; } } catch (UnauthorizedException ae) { throw new UnauthorizedDavException(repositoryId, "You are not authenticated and authorized to access any repository."); } throw new UnauthorizedDavException(repositoryId, "You are not authenticated"); } catch (MustChangePasswordException e) { throw new UnauthorizedDavException(repositoryId, "You must change your password."); } catch (AccountLockedException e) { throw new UnauthorizedDavException(repositoryId, "User account is locked."); } catch (AuthorizationException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Fatal Authorization Subsystem Error."); } catch (UnauthorizedException e) { throw new UnauthorizedDavException(repositoryId, e.getMessage()); } } private DavResource getResourceFromGroup(DavServletRequest request, List<String> repositories, ArchivaDavResourceLocator locator, RepositoryGroupConfiguration repositoryGroupConfiguration) throws DavException, RepositoryAdminException { if (repositoryGroupConfiguration.getRepositories() == null || repositoryGroupConfiguration.getRepositories().isEmpty()) { File file = new File(System.getProperty("appserver.base"), "groups/" + repositoryGroupConfiguration.getId()); return new ArchivaDavResource(file.getPath(), "groups/" + repositoryGroupConfiguration.getId(), null, request.getDavSession(), locator, this, mimeTypes, auditListeners, scheduler, fileLockManager); } List<File> mergedRepositoryContents = new ArrayList<>(); // multiple repo types so we guess they are all the same type // so use the first one // FIXME add a method with group in the repository storage String firstRepoId = repositoryGroupConfiguration.getRepositories().get(0); String path = getLogicalResource(locator, managedRepositoryAdmin.getManagedRepository(firstRepoId), false); if (path.startsWith("/")) { path = path.substring(1); } LogicalResource logicalResource = new LogicalResource(path); // flow: // if the current user logged in has permission to any of the repositories, allow user to // browse the repo group but displaying only the repositories which the user has permission to access. // otherwise, prompt for authentication. String activePrincipal = getActivePrincipal(request); boolean allow = isAllowedToContinue(request, repositories, activePrincipal); // remove last / String pathInfo = StringUtils.removeEnd(request.getPathInfo(), "/"); if (allow) { if (StringUtils.endsWith(pathInfo, repositoryGroupConfiguration.getMergedIndexPath())) { File mergedRepoDir = buildMergedIndexDirectory(repositories, activePrincipal, request, repositoryGroupConfiguration); mergedRepositoryContents.add(mergedRepoDir); } else { if (StringUtils.equalsIgnoreCase(pathInfo, "/" + repositoryGroupConfiguration.getId())) { File tmpDirectory = new File(SystemUtils.getJavaIoTmpDir(), repositoryGroupConfiguration.getId() + "/" + repositoryGroupConfiguration.getMergedIndexPath()); if (!tmpDirectory.exists()) { synchronized (tmpDirectory.getAbsolutePath()) { if (!tmpDirectory.exists()) { tmpDirectory.mkdirs(); } } } mergedRepositoryContents.add(tmpDirectory.getParentFile()); } for (String repository : repositories) { ManagedRepositoryContent managedRepository = null; try { managedRepository = repositoryFactory.getManagedRepositoryContent(repository); } catch (RepositoryNotFoundException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid managed repository <" + repository + ">: " + e.getMessage()); } catch (RepositoryException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Invalid managed repository <" + repository + ">: " + e.getMessage()); } File resourceFile = new File(managedRepository.getRepoRoot(), logicalResource.getPath()); if (resourceFile.exists()) { // in case of group displaying index directory doesn't have sense !! String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory(); if (StringUtils.isNotEmpty(repoIndexDirectory)) { if (!new File(repoIndexDirectory).isAbsolute()) { repoIndexDirectory = new File(managedRepository.getRepository().getLocation(), StringUtils.isEmpty(repoIndexDirectory) ? ".indexer" : repoIndexDirectory) .getAbsolutePath(); } } if (StringUtils.isEmpty(repoIndexDirectory)) { repoIndexDirectory = new File(managedRepository.getRepository().getLocation(), ".indexer").getAbsolutePath(); } if (!StringUtils.equals(FilenameUtils.normalize(repoIndexDirectory), FilenameUtils.normalize(resourceFile.getAbsolutePath()))) { // for prompted authentication if (httpAuth.getSecuritySession(request.getSession(true)) != null) { try { if (isAuthorized(request, repository)) { mergedRepositoryContents.add(resourceFile); log.debug("Repository '{}' accessed by '{}'", repository, activePrincipal); } } catch (DavException e) { // TODO: review exception handling log.debug("Skipping repository '{}' for user '{}': {}", managedRepository, activePrincipal, e.getMessage()); } } else { // for the current user logged in try { if (servletAuth.isAuthorized(activePrincipal, repository, WebdavMethodUtil.getMethodPermission(request.getMethod()))) { mergedRepositoryContents.add(resourceFile); log.debug("Repository '{}' accessed by '{}'", repository, activePrincipal); } } catch (UnauthorizedException e) { // TODO: review exception handling log.debug("Skipping repository '{}' for user '{}': {}", managedRepository, activePrincipal, e.getMessage()); } } } } } } } else { throw new UnauthorizedDavException(locator.getRepositoryId(), "User not authorized."); } ArchivaVirtualDavResource resource = new ArchivaVirtualDavResource(mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator, this); // compatibility with MRM-440 to ensure browsing the repository group works ok if (resource.isCollection() && !request.getRequestURI().endsWith("/")) { throw new BrowserRedirectException(resource.getHref()); } return resource; } protected String getActivePrincipal(DavServletRequest request) { User sessionUser = httpAuth.getSessionUser(request.getSession()); return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME; } /** * Check if the current user is authorized to access any of the repos * * @param request * @param repositories * @param activePrincipal * @return */ private boolean isAllowedToContinue(DavServletRequest request, List<String> repositories, String activePrincipal) { // when no repositories configured it's impossible to browse nothing ! // at least make possible to see nothing :-) if (repositories == null || repositories.isEmpty()) { return true; } boolean allow = false; // if securitySession != null, it means that the user was prompted for authentication if (httpAuth.getSecuritySession(request.getSession()) != null) { for (String repository : repositories) { try { if (isAuthorized(request, repository)) { allow = true; break; } } catch (DavException e) { continue; } } } else { for (String repository : repositories) { try { if (servletAuth.isAuthorized(activePrincipal, repository, WebdavMethodUtil.getMethodPermission(request.getMethod()))) { allow = true; break; } } catch (UnauthorizedException e) { continue; } } } return allow; } private File writeMergedMetadataToFile(ArchivaRepositoryMetadata mergedMetadata, String outputFilename) throws RepositoryMetadataException, DigesterException, IOException { File outputFile = new File(outputFilename); if (outputFile.exists()) { FileUtils.deleteQuietly(outputFile); } outputFile.getParentFile().mkdirs(); RepositoryMetadataWriter.write(mergedMetadata, outputFile); createChecksumFile(outputFilename, digestSha1); createChecksumFile(outputFilename, digestMd5); return outputFile; } private void createChecksumFile(String path, Digester digester) throws DigesterException, IOException { File checksumFile = new File(path + digester.getFilenameExtension()); if (!checksumFile.exists()) { FileUtils.deleteQuietly(checksumFile); checksum.createChecksum(new File(path), digester); } else if (!checksumFile.isFile()) { log.error("Checksum file is not a file."); } } private boolean isProjectReference(String requestedResource) { try { metadataTools.toVersionedReference(requestedResource); return false; } catch (RepositoryMetadataException re) { return true; } } protected File buildMergedIndexDirectory(List<String> repositories, String activePrincipal, DavServletRequest request, RepositoryGroupConfiguration repositoryGroupConfiguration) throws DavException { try { HttpSession session = request.getSession(); Map<String, TemporaryGroupIndex> temporaryGroupIndexMap = (Map<String, TemporaryGroupIndex>) session .getAttribute(TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY); if (temporaryGroupIndexMap == null) { temporaryGroupIndexMap = new HashMap<>(); } TemporaryGroupIndex tmp = temporaryGroupIndexMap.get(repositoryGroupConfiguration.getId()); if (tmp != null && tmp.getDirectory() != null && tmp.getDirectory().exists()) { if (System.currentTimeMillis() - tmp.getCreationTime() > (repositoryGroupConfiguration.getMergedIndexTtl() * 60 * 1000)) { log.debug(MarkerFactory.getMarker("group.merged.index"), "tmp group index '{}' is too old so delete it", repositoryGroupConfiguration.getId()); indexMerger.cleanTemporaryGroupIndex(tmp); } else { log.debug(MarkerFactory.getMarker("group.merged.index"), "merged index for group '{}' found in cache", repositoryGroupConfiguration.getId()); return tmp.getDirectory(); } } Set<String> authzRepos = new HashSet<String>(); String permission = WebdavMethodUtil.getMethodPermission(request.getMethod()); for (String repository : repositories) { try { if (servletAuth.isAuthorized(activePrincipal, repository, permission)) { authzRepos.add(repository); authzRepos.addAll(this.repositorySearch.getRemoteIndexingContextIds(repository)); } } catch (UnauthorizedException e) { // TODO: review exception handling log.debug("Skipping repository '{}' for user '{}': {}", repository, activePrincipal, e.getMessage()); } } log.info("generate temporary merged index for repository group '{}' for repositories '{}'", repositoryGroupConfiguration.getId(), authzRepos); File tempRepoFile = Files.createTempDirectory("temp").toFile(); tempRepoFile.deleteOnExit(); IndexMergerRequest indexMergerRequest = new IndexMergerRequest(authzRepos, true, repositoryGroupConfiguration.getId(), repositoryGroupConfiguration.getMergedIndexPath(), repositoryGroupConfiguration.getMergedIndexTtl()).mergedIndexDirectory(tempRepoFile) .temporary(true); MergedRemoteIndexesTaskRequest taskRequest = new MergedRemoteIndexesTaskRequest(indexMergerRequest, indexMerger); MergedRemoteIndexesTask job = new MergedRemoteIndexesTask(taskRequest); IndexingContext indexingContext = job.execute().getIndexingContext(); File mergedRepoDir = indexingContext.getIndexDirectoryFile(); TemporaryGroupIndex temporaryGroupIndex = new TemporaryGroupIndex(mergedRepoDir, indexingContext.getId(), repositoryGroupConfiguration.getId(), repositoryGroupConfiguration.getMergedIndexTtl()) // .setCreationTime(new Date().getTime()); temporaryGroupIndexMap.put(repositoryGroupConfiguration.getId(), temporaryGroupIndex); session.setAttribute(TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY, temporaryGroupIndexMap); return mergedRepoDir; } catch (RepositoryAdminException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } catch (IndexMergerException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } catch (IOException e) { throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } public void setServletAuth(ServletAuthenticator servletAuth) { this.servletAuth = servletAuth; } public void setHttpAuth(HttpAuthenticator httpAuth) { this.httpAuth = httpAuth; } public void setScheduler(RepositoryArchivaTaskScheduler scheduler) { this.scheduler = scheduler; } public void setArchivaConfiguration(ArchivaConfiguration archivaConfiguration) { this.archivaConfiguration = archivaConfiguration; } public void setRepositoryFactory(RepositoryContentFactory repositoryFactory) { this.repositoryFactory = repositoryFactory; } public void setRepositoryRequest(RepositoryRequest repositoryRequest) { this.repositoryRequest = repositoryRequest; } public void setConnectors(RepositoryProxyConnectors connectors) { this.connectors = connectors; } public RemoteRepositoryAdmin getRemoteRepositoryAdmin() { return remoteRepositoryAdmin; } public void setRemoteRepositoryAdmin(RemoteRepositoryAdmin remoteRepositoryAdmin) { this.remoteRepositoryAdmin = remoteRepositoryAdmin; } public ManagedRepositoryAdmin getManagedRepositoryAdmin() { return managedRepositoryAdmin; } public void setManagedRepositoryAdmin(ManagedRepositoryAdmin managedRepositoryAdmin) { this.managedRepositoryAdmin = managedRepositoryAdmin; } }