1 package org.apache.maven.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 com.opensymphony.xwork.ActionContext;
23 import org.apache.jackrabbit.webdav.*;
24 import org.apache.maven.archiva.repository.ManagedRepositoryContent;
25 import org.apache.maven.archiva.repository.RepositoryNotFoundException;
26 import org.apache.maven.archiva.repository.RepositoryException;
27 import org.apache.maven.archiva.repository.RepositoryContentFactory;
28 import org.apache.maven.archiva.repository.layout.LayoutException;
29 import org.apache.maven.archiva.repository.content.RepositoryRequest;
30 import org.apache.maven.archiva.repository.audit.AuditListener;
31 import org.apache.maven.archiva.repository.audit.Auditable;
32 import org.apache.maven.archiva.repository.audit.AuditEvent;
33 import org.apache.maven.archiva.repository.metadata.MetadataTools;
34 import org.apache.maven.archiva.repository.metadata.RepositoryMetadataException;
35 import org.apache.maven.archiva.webdav.util.WebdavMethodUtil;
36 import org.apache.maven.archiva.webdav.util.MimeTypes;
37 import org.apache.maven.archiva.webdav.util.RepositoryPathUtil;
38 import org.apache.maven.archiva.proxy.RepositoryProxyConnectors;
39 import org.apache.maven.archiva.common.utils.PathUtil;
40 import org.apache.maven.archiva.configuration.ArchivaConfiguration;
41 import org.apache.maven.archiva.configuration.RepositoryGroupConfiguration;
42 import org.apache.maven.archiva.model.ArtifactReference;
43 import org.apache.maven.archiva.model.ProjectReference;
44 import org.apache.maven.archiva.model.VersionedReference;
45 import org.apache.maven.archiva.policies.ProxyDownloadException;
46 import org.apache.maven.archiva.security.ArchivaXworkUser;
47 import org.apache.maven.archiva.security.ServletAuthenticator;
48 import org.apache.maven.model.DistributionManagement;
49 import org.apache.maven.model.Model;
50 import org.apache.maven.model.Relocation;
51 import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
52 import org.codehaus.plexus.redback.authentication.AuthenticationException;
53 import org.codehaus.plexus.redback.authentication.AuthenticationResult;
54 import org.codehaus.plexus.redback.authorization.AuthorizationException;
55 import org.codehaus.plexus.redback.authorization.UnauthorizedException;
56 import org.codehaus.plexus.redback.policy.AccountLockedException;
57 import org.codehaus.plexus.redback.policy.MustChangePasswordException;
58 import org.codehaus.plexus.redback.system.SecuritySession;
59 import org.codehaus.plexus.redback.system.SecuritySystemConstants;
60 import org.codehaus.plexus.redback.xwork.filter.authentication.HttpAuthenticator;
61 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
65 import javax.servlet.http.HttpServletResponse;
66 import java.util.ArrayList;
67 import java.util.HashMap;
68 import java.util.List;
71 import org.apache.jackrabbit.webdav.lock.LockManager;
72 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
75 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
76 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
78 public class ArchivaDavResourceFactory
79 implements DavResourceFactory, Auditable
81 private static final String HTTP_PUT_METHOD = "PUT";
83 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
86 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
88 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
93 private RepositoryContentFactory repositoryFactory;
98 private RepositoryRequest repositoryRequest;
101 * @plexus.requirement role-hint="default"
103 private RepositoryProxyConnectors connectors;
106 * @plexus.requirement
108 private MetadataTools metadataTools;
111 * @plexus.requirement
113 private MimeTypes mimeTypes;
116 * @plexus.requirement
118 private ArchivaConfiguration archivaConfiguration;
121 * @plexus.requirement
123 private ServletAuthenticator servletAuth;
126 * @plexus.requirement role-hint="basic"
128 private HttpAuthenticator httpAuth;
132 * Lock Manager - use simple implementation from JackRabbit
134 private final LockManager lockManager = new SimpleLockManager();
136 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
137 final DavServletResponse response )
140 checkLocatorIsInstanceOfRepositoryLocator( locator );
141 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
143 RepositoryGroupConfiguration repoGroupConfig =
144 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get(
145 ( (RepositoryLocator) locator ).getRepositoryId() );
146 List<String> repositories = new ArrayList<String>();
148 boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
149 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
151 if ( repoGroupConfig != null )
153 if( WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
155 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
156 "Write method not allowed for repository groups." );
158 repositories.addAll( repoGroupConfig.getRepositories() );
160 // handle browse requests for virtual repos
161 if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).endsWith( "/" ) )
163 return getResource( request, repositories, archivaLocator );
168 repositories.add( ( (RepositoryLocator) locator ).getRepositoryId() );
171 DavResource resource = null;
172 DavException e = null;
174 for ( String repositoryId : repositories )
176 ManagedRepositoryContent managedRepository = null;
180 managedRepository = getManagedRepository( repositoryId );
182 catch ( DavException de )
184 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
185 repositoryId + ">" );
188 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
190 if ( managedRepository != null )
194 if( isAuthorized( request, repositoryId ) )
196 LogicalResource logicalResource =
197 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
201 resource = doGet( managedRepository, request, archivaLocator, logicalResource );
206 resource = doPut( managedRepository, request, archivaLocator, logicalResource );
210 catch ( DavException de )
216 if( resource == null )
218 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" );
222 setHeaders( locator, response );
224 // compatibility with MRM-440 to ensure browsing the repository works ok
225 if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
227 throw new BrowserRedirectException( resource.getHref() );
229 resource.addLockManager(lockManager);
235 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
243 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
246 checkLocatorIsInstanceOfRepositoryLocator( locator );
247 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
249 DavResource resource = null;
250 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
252 ManagedRepositoryContent managedRepository = getManagedRepository( archivaLocator.getRepositoryId() );
253 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
254 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource );
256 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, mimeTypes, davSession, archivaLocator,
259 resource.addLockManager(lockManager);
263 private DavResource doGet( ManagedRepositoryContent managedRepository, DavServletRequest request,
264 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
267 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
268 ArchivaDavResource resource =
269 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, request.getDavSession(), locator, this );
271 if ( !resource.isCollection() )
273 // At this point the incoming request can either be in default or
274 // legacy layout format.
275 boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource );
277 boolean previouslyExisted = resourceFile.exists();
281 // Perform an adjustment of the resource to the managed
282 // repository expected path.
283 String localResourcePath =
284 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
285 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
287 catch ( LayoutException e )
289 if ( previouslyExisted )
293 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
296 // Attempt to fetch the resource from any defined proxy.
299 processAuditEvents( request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted,
300 resourceFile, " (proxied)" );
303 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, request.getDavSession(), locator,
306 if ( !resourceFile.exists() )
314 private DavResource doPut( ManagedRepositoryContent managedRepository, DavServletRequest request,
315 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
319 * Create parent directories that don't exist when writing a file This actually makes this implementation not
320 * compliant to the WebDAV RFC - but we have enough knowledge about how the collection is being used to do this
321 * reasonably and some versions of Maven's WebDAV don't correctly create the collections themselves.
324 File rootDirectory = new File( managedRepository.getRepoRoot() );
325 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
326 if ( request.getMethod().equals(HTTP_PUT_METHOD) && !destDir.exists() )
329 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
330 triggerAuditEvent( request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
333 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
335 boolean previouslyExisted = resourceFile.exists();
337 processAuditEvents( request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted,
338 resourceFile, null );
340 return new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, request.getDavSession(), locator,
344 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
345 LogicalResource resource )
348 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
350 // Checksums are fetched with artifact / metadata.
352 // Need to adjust the path for the checksum resource.
356 // Is it a Metadata resource?
357 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
359 return fetchMetadataFromProxies( managedRepository, request, resource );
362 // Not any of the above? Then it's gotta be an artifact reference.
365 // Get the artifact reference in a layout neutral way.
366 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
368 if ( artifact != null )
370 applyServerSideRelocation( managedRepository, artifact );
372 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
374 resource.setPath( managedRepository.toPath( artifact ) );
376 return ( proxiedFile != null );
379 catch ( LayoutException e )
383 catch ( ProxyDownloadException e )
385 log.error( e.getMessage(), e );
386 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource." );
391 private boolean fetchMetadataFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
392 LogicalResource resource )
395 ProjectReference project;
396 VersionedReference versioned;
401 versioned = metadataTools.toVersionedReference( resource.getPath() );
402 if ( versioned != null )
404 connectors.fetchFromProxies( managedRepository, versioned );
408 catch ( RepositoryMetadataException e )
415 project = metadataTools.toProjectReference( resource.getPath() );
416 if ( project != null )
418 connectors.fetchFromProxies( managedRepository, project );
422 catch ( RepositoryMetadataException e )
431 * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do
432 * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the
435 * For such clients, archiva does server-side relocation by reading itself the <relocation> element in
436 * metadatas and serving the expected artifact.
438 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
439 throws ProxyDownloadException
441 if ( "pom".equals( artifact.getType() ) )
446 // Build the artifact POM reference
447 ArtifactReference pomReference = new ArtifactReference();
448 pomReference.setGroupId( artifact.getGroupId() );
449 pomReference.setArtifactId( artifact.getArtifactId() );
450 pomReference.setVersion( artifact.getVersion() );
451 pomReference.setType( "pom" );
453 // Get the artifact POM from proxied repositories if needed
454 connectors.fetchFromProxies( managedRepository, pomReference );
456 // Open and read the POM from the managed repo
457 File pom = managedRepository.toFile( pomReference );
466 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
467 DistributionManagement dist = model.getDistributionManagement();
470 Relocation relocation = dist.getRelocation();
471 if ( relocation != null )
473 // artifact is relocated : update the repositoryPath
474 if ( relocation.getGroupId() != null )
476 artifact.setGroupId( relocation.getGroupId() );
478 if ( relocation.getArtifactId() != null )
480 artifact.setArtifactId( relocation.getArtifactId() );
482 if ( relocation.getVersion() != null )
484 artifact.setVersion( relocation.getVersion() );
489 catch ( FileNotFoundException e )
491 // Artifact has no POM in repo : ignore
493 catch ( IOException e )
495 // Unable to read POM : ignore.
497 catch ( XmlPullParserException e )
499 // Invalid POM : ignore
503 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
504 boolean previouslyExisted, File resourceFile, String suffix )
506 if ( suffix == null )
511 // Process Create Audit Events.
512 if ( !previouslyExisted && resourceFile.exists() )
514 if ( resourceFile.isFile() )
516 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
518 else if ( resourceFile.isDirectory() )
520 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
523 // Process Remove Audit Events.
524 else if ( previouslyExisted && !resourceFile.exists() )
526 if ( resourceFile.isFile() )
528 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
530 else if ( resourceFile.isDirectory() )
532 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
535 // Process modify events.
538 if ( resourceFile.isFile() )
540 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
545 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
547 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
548 event.setRemoteIP( remoteIP );
550 for ( AuditListener listener : auditListeners )
552 listener.auditEvent( event );
556 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
558 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ),
559 getRemoteIP( request ), repositoryId, resource, action );
562 private String getRemoteIP( DavServletRequest request )
564 return request.getRemoteAddr();
567 public void addAuditListener( AuditListener listener )
569 this.auditListeners.add( listener );
572 public void clearAuditListeners()
574 this.auditListeners.clear();
577 public void removeAuditListener( AuditListener listener )
579 this.auditListeners.remove( listener );
582 private void setHeaders( DavResourceLocator locator, DavServletResponse response )
584 // [MRM-503] - Metadata file need Pragma:no-cache response
586 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
588 response.addHeader( "Pragma", "no-cache" );
589 response.addHeader( "Cache-Control", "no-cache" );
592 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
595 private ManagedRepositoryContent getManagedRepository( String respositoryId )
598 if ( respositoryId != null )
602 return repositoryFactory.getManagedRepositoryContent( respositoryId );
604 catch ( RepositoryNotFoundException e )
606 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
608 catch ( RepositoryException e )
610 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
616 private void checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
619 if ( !( locator instanceof RepositoryLocator ) )
621 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
622 "Locator does not implement RepositoryLocator" );
626 class LogicalResource
630 public LogicalResource( String path )
635 public String getPath()
640 public void setPath( String path )
646 protected boolean isAuthorized( DavServletRequest request, String repositoryId )
651 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
652 SecuritySession securitySession = httpAuth.getSecuritySession();
654 return servletAuth.isAuthenticated( request, result ) &&
655 servletAuth.isAuthorized( request, securitySession, repositoryId,
656 WebdavMethodUtil.isWriteMethod( request.getMethod() ) );
658 catch ( AuthenticationException e )
660 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
662 catch ( MustChangePasswordException e )
664 throw new UnauthorizedDavException( repositoryId, "You must change your password." );
666 catch ( AccountLockedException e )
668 throw new UnauthorizedDavException( repositoryId, "User account is locked." );
670 catch ( AuthorizationException e )
672 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
673 "Fatal Authorization Subsystem Error." );
675 catch ( UnauthorizedException e )
677 throw new UnauthorizedDavException( repositoryId, e.getMessage() );
681 private DavResource getResource( DavServletRequest request, List<String> repositories, ArchivaDavResourceLocator locator )
684 List<File> mergedRepositoryContents = new ArrayList<File>();
685 LogicalResource logicalResource =
686 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
689 // if the current user logged in has permission to any of the repositories, allow user to
690 // browse the repo group but displaying only the repositories which the user has permission to access.
691 // otherwise, prompt for authentication.
693 // put the current session in the session map which will be passed to ArchivaXworkUser
694 Map<String, Object> sessionMap = new HashMap<String, Object>();
695 if( request.getSession().getAttribute( SecuritySystemConstants.SECURITY_SESSION_KEY ) != null )
697 sessionMap.put( SecuritySystemConstants.SECURITY_SESSION_KEY,
698 request.getSession().getAttribute( SecuritySystemConstants.SECURITY_SESSION_KEY ) );
701 String activePrincipal = ArchivaXworkUser.getActivePrincipal( sessionMap );
702 boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
706 for( String repository : repositories )
708 // for prompted authentication
709 if( httpAuth.getSecuritySession() != null )
713 if( isAuthorized( request, repository ) )
715 getResource( locator, mergedRepositoryContents, logicalResource, repository );
718 catch ( DavException e )
725 // for the current user logged in
728 if( servletAuth.isAuthorizedToAccessVirtualRepository( activePrincipal, repository ) )
730 getResource( locator, mergedRepositoryContents, logicalResource, repository );
733 catch ( UnauthorizedException e )
742 throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
745 ArchivaVirtualDavResource resource =
746 new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator, this );
748 // compatibility with MRM-440 to ensure browsing the repository group works ok
749 if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
751 throw new BrowserRedirectException( resource.getHref() );
757 private void getResource( ArchivaDavResourceLocator locator, List<File> mergedRepositoryContents,
758 LogicalResource logicalResource, String repository )
761 ManagedRepositoryContent managedRepository = null;
765 managedRepository = getManagedRepository( repository );
767 catch ( DavException de )
769 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
773 if ( !locator.getResourcePath().startsWith( ArchivaVirtualDavResource.HIDDEN_PATH_PREFIX ) )
775 if( managedRepository != null )
777 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
778 if( resourceFile.exists() )
780 mergedRepositoryContents.add( resourceFile );
787 * Check if the current user is authorized to access any of the repos
790 * @param repositories
791 * @param activePrincipal
794 private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
796 boolean allow = false;
799 // if securitySession != null, it means that the user was prompted for authentication
800 if( httpAuth.getSecuritySession() != null )
802 for( String repository : repositories )
806 if( isAuthorized( request, repository ) )
812 catch( DavException e )
820 for( String repository : repositories )
824 if( servletAuth.isAuthorizedToAccessVirtualRepository( activePrincipal, repository ) )
830 catch ( UnauthorizedException e )