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;
73 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
74 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
76 public class ArchivaDavResourceFactory
77 implements DavResourceFactory, Auditable
79 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
82 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
84 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
89 private RepositoryContentFactory repositoryFactory;
94 private RepositoryRequest repositoryRequest;
97 * @plexus.requirement role-hint="default"
99 private RepositoryProxyConnectors connectors;
102 * @plexus.requirement
104 private MetadataTools metadataTools;
107 * @plexus.requirement
109 private MimeTypes mimeTypes;
112 * @plexus.requirement
114 private ArchivaConfiguration archivaConfiguration;
117 * @plexus.requirement
119 private ServletAuthenticator servletAuth;
122 * @plexus.requirement role-hint="basic"
124 private HttpAuthenticator httpAuth;
126 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
127 final DavServletResponse response )
130 checkLocatorIsInstanceOfRepositoryLocator( locator );
131 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
133 RepositoryGroupConfiguration repoGroupConfig =
134 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get(
135 ( (RepositoryLocator) locator ).getRepositoryId() );
136 List<String> repositories = new ArrayList<String>();
138 boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
139 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
141 if ( repoGroupConfig != null )
143 if( WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
145 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Bad request to repository group <" +
146 repoGroupConfig.getId() + ">" );
148 repositories.addAll( repoGroupConfig.getRepositories() );
150 // handle browse requests for virtual repos
151 if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).endsWith( "/" ) )
153 return getResource( request, repositories, archivaLocator );
158 repositories.add( ( (RepositoryLocator) locator ).getRepositoryId() );
161 DavResource resource = null;
162 DavException e = null;
164 for ( String repositoryId : repositories )
166 ManagedRepositoryContent managedRepository = null;
170 managedRepository = getManagedRepository( repositoryId );
172 catch ( DavException de )
174 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
175 repositoryId + ">" );
178 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
180 if ( managedRepository != null )
184 if( isAuthorized( request, repositoryId ) )
186 LogicalResource logicalResource =
187 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
191 resource = doGet( managedRepository, request, archivaLocator, logicalResource );
196 resource = doPut( managedRepository, request, archivaLocator, logicalResource );
200 catch ( DavException de )
206 if( resource == null )
208 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
212 setHeaders( locator, response );
214 // compatibility with MRM-440 to ensure browsing the repository works ok
215 if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
217 throw new BrowserRedirectException( resource.getHref() );
225 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
233 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
236 checkLocatorIsInstanceOfRepositoryLocator( locator );
237 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
239 DavResource resource = null;
240 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
242 ManagedRepositoryContent managedRepository = getManagedRepository( archivaLocator.getRepositoryId() );
243 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
244 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource );
246 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, mimeTypes, archivaLocator,
252 private DavResource doGet( ManagedRepositoryContent managedRepository, DavServletRequest request,
253 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
256 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
257 ArchivaDavResource resource =
258 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this );
260 if ( !resource.isCollection() )
262 // At this point the incoming request can either be in default or
263 // legacy layout format.
264 boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource );
266 boolean previouslyExisted = resourceFile.exists();
270 // Perform an adjustment of the resource to the managed
271 // repository expected path.
272 String localResourcePath =
273 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
274 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
276 catch ( LayoutException e )
278 if ( previouslyExisted )
282 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
285 // Attempt to fetch the resource from any defined proxy.
288 processAuditEvents( request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted,
289 resourceFile, " (proxied)" );
292 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
295 if ( !resourceFile.exists() )
303 private DavResource doPut( ManagedRepositoryContent managedRepository, DavServletRequest request,
304 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
308 * Create parent directories that don't exist when writing a file This actually makes this implementation not
309 * compliant to the WebDAV RFC - but we have enough knowledge about how the collection is being used to do this
310 * reasonably and some versions of Maven's WebDAV don't correctly create the collections themselves.
313 File rootDirectory = new File( managedRepository.getRepoRoot() );
314 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
315 if ( !destDir.exists() )
318 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
319 triggerAuditEvent( request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
322 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
324 boolean previouslyExisted = resourceFile.exists();
326 processAuditEvents( request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted,
327 resourceFile, null );
329 return new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
333 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
334 LogicalResource resource )
337 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
339 // Checksums are fetched with artifact / metadata.
341 // Need to adjust the path for the checksum resource.
345 // Is it a Metadata resource?
346 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
348 return fetchMetadataFromProxies( managedRepository, request, resource );
351 // Not any of the above? Then it's gotta be an artifact reference.
354 // Get the artifact reference in a layout neutral way.
355 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
357 if ( artifact != null )
359 applyServerSideRelocation( managedRepository, artifact );
361 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
363 resource.setPath( managedRepository.toPath( artifact ) );
365 return ( proxiedFile != null );
368 catch ( LayoutException e )
372 catch ( ProxyDownloadException e )
374 log.error( e.getMessage(), e );
375 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource." );
380 private boolean fetchMetadataFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
381 LogicalResource resource )
384 ProjectReference project;
385 VersionedReference versioned;
390 versioned = metadataTools.toVersionedReference( resource.getPath() );
391 if ( versioned != null )
393 connectors.fetchFromProxies( managedRepository, versioned );
397 catch ( RepositoryMetadataException e )
404 project = metadataTools.toProjectReference( resource.getPath() );
405 if ( project != null )
407 connectors.fetchFromProxies( managedRepository, project );
411 catch ( RepositoryMetadataException e )
420 * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do
421 * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the
424 * For such clients, archiva does server-side relocation by reading itself the <relocation> element in
425 * metadatas and serving the expected artifact.
427 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
428 throws ProxyDownloadException
430 if ( "pom".equals( artifact.getType() ) )
435 // Build the artifact POM reference
436 ArtifactReference pomReference = new ArtifactReference();
437 pomReference.setGroupId( artifact.getGroupId() );
438 pomReference.setArtifactId( artifact.getArtifactId() );
439 pomReference.setVersion( artifact.getVersion() );
440 pomReference.setType( "pom" );
442 // Get the artifact POM from proxied repositories if needed
443 connectors.fetchFromProxies( managedRepository, pomReference );
445 // Open and read the POM from the managed repo
446 File pom = managedRepository.toFile( pomReference );
455 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
456 DistributionManagement dist = model.getDistributionManagement();
459 Relocation relocation = dist.getRelocation();
460 if ( relocation != null )
462 // artifact is relocated : update the repositoryPath
463 if ( relocation.getGroupId() != null )
465 artifact.setGroupId( relocation.getGroupId() );
467 if ( relocation.getArtifactId() != null )
469 artifact.setArtifactId( relocation.getArtifactId() );
471 if ( relocation.getVersion() != null )
473 artifact.setVersion( relocation.getVersion() );
478 catch ( FileNotFoundException e )
480 // Artifact has no POM in repo : ignore
482 catch ( IOException e )
484 // Unable to read POM : ignore.
486 catch ( XmlPullParserException e )
488 // Invalid POM : ignore
492 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
493 boolean previouslyExisted, File resourceFile, String suffix )
495 if ( suffix == null )
500 // Process Create Audit Events.
501 if ( !previouslyExisted && resourceFile.exists() )
503 if ( resourceFile.isFile() )
505 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
507 else if ( resourceFile.isDirectory() )
509 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
512 // Process Remove Audit Events.
513 else if ( previouslyExisted && !resourceFile.exists() )
515 if ( resourceFile.isFile() )
517 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
519 else if ( resourceFile.isDirectory() )
521 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
524 // Process modify events.
527 if ( resourceFile.isFile() )
529 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
534 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
536 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
537 event.setRemoteIP( remoteIP );
539 for ( AuditListener listener : auditListeners )
541 listener.auditEvent( event );
545 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
547 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ),
548 getRemoteIP( request ), repositoryId, resource, action );
551 private String getRemoteIP( DavServletRequest request )
553 return request.getRemoteAddr();
556 public void addAuditListener( AuditListener listener )
558 this.auditListeners.add( listener );
561 public void clearAuditListeners()
563 this.auditListeners.clear();
566 public void removeAuditListener( AuditListener listener )
568 this.auditListeners.remove( listener );
571 private void setHeaders( DavResourceLocator locator, DavServletResponse response )
573 // [MRM-503] - Metadata file need Pragma:no-cache response
575 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
577 response.addHeader( "Pragma", "no-cache" );
578 response.addHeader( "Cache-Control", "no-cache" );
581 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
584 private ManagedRepositoryContent getManagedRepository( String respositoryId )
587 if ( respositoryId != null )
591 return repositoryFactory.getManagedRepositoryContent( respositoryId );
593 catch ( RepositoryNotFoundException e )
595 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
597 catch ( RepositoryException e )
599 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
605 private void checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
608 if ( !( locator instanceof RepositoryLocator ) )
610 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
611 "Locator does not implement RepositoryLocator" );
615 class LogicalResource
619 public LogicalResource( String path )
624 public String getPath()
629 public void setPath( String path )
635 protected boolean isAuthorized( DavServletRequest request, String repositoryId )
640 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
641 SecuritySession securitySession = httpAuth.getSecuritySession();
643 return servletAuth.isAuthenticated( request, result ) &&
644 servletAuth.isAuthorized( request, securitySession, repositoryId,
645 WebdavMethodUtil.isWriteMethod( request.getMethod() ) );
647 catch ( AuthenticationException e )
649 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
651 catch ( MustChangePasswordException e )
653 throw new UnauthorizedDavException( repositoryId, "You must change your password." );
655 catch ( AccountLockedException e )
657 throw new UnauthorizedDavException( repositoryId, "User account is locked." );
659 catch ( AuthorizationException e )
661 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
662 "Fatal Authorization Subsystem Error." );
664 catch ( UnauthorizedException e )
666 throw new UnauthorizedDavException( repositoryId, e.getMessage() );
670 private DavResource getResource( DavServletRequest request, List<String> repositories, ArchivaDavResourceLocator locator )
673 List<File> mergedRepositoryContents = new ArrayList<File>();
674 LogicalResource logicalResource =
675 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
678 // if the current user logged in has permission to any of the repositories, allow user to
679 // browse the repo group but displaying only the repositories which the user has permission to access.
680 // otherwise, prompt for authentication.
682 // put the current session in the session map which will be passed to ArchivaXworkUser
683 Map<String, Object> sessionMap = new HashMap<String, Object>();
684 if( request.getSession().getAttribute( SecuritySystemConstants.SECURITY_SESSION_KEY ) != null )
686 sessionMap.put( SecuritySystemConstants.SECURITY_SESSION_KEY,
687 request.getSession().getAttribute( SecuritySystemConstants.SECURITY_SESSION_KEY ) );
690 String activePrincipal = ArchivaXworkUser.getActivePrincipal( sessionMap );
691 boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
695 for( String repository : repositories )
697 // for prompted authentication
698 if( httpAuth.getSecuritySession() != null )
702 if( isAuthorized( request, repository ) )
704 getResource( locator, mergedRepositoryContents, logicalResource, repository );
707 catch ( DavException e )
714 // for the current user logged in
717 if( servletAuth.isAuthorizedToAccessVirtualRepository( activePrincipal, repository ) )
719 getResource( locator, mergedRepositoryContents, logicalResource, repository );
722 catch ( UnauthorizedException e )
731 throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
734 ArchivaVirtualDavResource resource =
735 new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator, this );
737 // compatibility with MRM-440 to ensure browsing the repository group works ok
738 if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
740 throw new BrowserRedirectException( resource.getHref() );
746 private void getResource( ArchivaDavResourceLocator locator, List<File> mergedRepositoryContents,
747 LogicalResource logicalResource, String repository )
750 ManagedRepositoryContent managedRepository = null;
754 managedRepository = getManagedRepository( repository );
756 catch ( DavException de )
758 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
762 if ( !locator.getResourcePath().startsWith( ArchivaVirtualDavResource.HIDDEN_PATH_PREFIX ) )
764 if( managedRepository != null )
766 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
767 if( resourceFile.exists() )
769 mergedRepositoryContents.add( resourceFile );
776 * Check if the current user is authorized to access any of the repos
779 * @param repositories
780 * @param activePrincipal
783 private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal )
785 boolean allow = false;
788 // if securitySession != null, it means that the user was prompted for authentication
789 if( httpAuth.getSecuritySession() != null )
791 for( String repository : repositories )
795 if( isAuthorized( request, repository ) )
801 catch( DavException e )
809 for( String repository : repositories )
813 if( servletAuth.isAuthorizedToAccessVirtualRepository( activePrincipal, repository ) )
819 catch ( UnauthorizedException e )