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.xwork.filter.authentication.HttpAuthenticator;
60 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import javax.servlet.http.HttpServletResponse;
65 import java.util.ArrayList;
66 import java.util.List;
70 * @author <a href="mailto:james@atlassian.com">James William Dumay</a>
71 * @plexus.component role="org.apache.maven.archiva.webdav.ArchivaDavResourceFactory"
73 public class ArchivaDavResourceFactory
74 implements DavResourceFactory, Auditable
76 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
79 * @plexus.requirement role="org.apache.maven.archiva.repository.audit.AuditListener"
81 private List<AuditListener> auditListeners = new ArrayList<AuditListener>();
86 private RepositoryContentFactory repositoryFactory;
91 private RepositoryRequest repositoryRequest;
94 * @plexus.requirement role-hint="default"
96 private RepositoryProxyConnectors connectors;
101 private MetadataTools metadataTools;
104 * @plexus.requirement
106 private MimeTypes mimeTypes;
109 * @plexus.requirement
111 private ArchivaConfiguration archivaConfiguration;
114 * @plexus.requirement
116 private ServletAuthenticator servletAuth;
119 * @plexus.requirement role-hint="basic"
121 private HttpAuthenticator httpAuth;
123 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
124 final DavServletResponse response )
127 checkLocatorIsInstanceOfRepositoryLocator( locator );
128 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
130 RepositoryGroupConfiguration repoGroupConfig =
131 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get(
132 ( (RepositoryLocator) locator ).getRepositoryId() );
133 List<String> repositories = new ArrayList<String>();
135 boolean isGet = WebdavMethodUtil.isReadMethod( request.getMethod() );
136 boolean isPut = WebdavMethodUtil.isWriteMethod( request.getMethod() );
138 if ( repoGroupConfig != null )
140 if ( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ).equals( "/" ) ||
141 WebdavMethodUtil.isWriteMethod( request.getMethod() ) )
143 throw new DavException( HttpServletResponse.SC_BAD_REQUEST, "Bad request to repository group <" +
144 repoGroupConfig.getId() + ">" );
146 repositories.addAll( repoGroupConfig.getRepositories() );
148 // do not allow write request for repo groups
151 throw new DavException( HttpServletResponse.SC_FORBIDDEN, "Write request is not allowed for <" +
152 repoGroupConfig.getId() + ">" );
157 repositories.add( ( (RepositoryLocator) locator ).getRepositoryId() );
160 DavResource resource = null;
161 DavException e = null;
163 for ( String repositoryId : repositories )
165 ManagedRepositoryContent managedRepository = null;
169 managedRepository = getManagedRepository( repositoryId );
171 catch ( DavException de )
173 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Invalid managed repository <" +
174 repositoryId + ">" );
177 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
179 if ( managedRepository != null )
183 if( isAuthorized( request, repositoryId ) )
185 LogicalResource logicalResource =
186 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) );
190 resource = doGet( managedRepository, request, archivaLocator, logicalResource );
195 resource = doPut( managedRepository, request, archivaLocator, logicalResource );
199 catch ( DavException de )
205 if( resource == null )
207 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
211 setHeaders( locator, response );
213 // compatibility with MRM-440 to ensure browsing the repository works ok
214 if ( resource.isCollection() && !resource.getLocator().getResourcePath().endsWith( "/" ) )
216 throw new BrowserRedirectException( resource.getHref() );
224 e = new DavException( HttpServletResponse.SC_NOT_FOUND, "Repository does not exist" );
232 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
235 checkLocatorIsInstanceOfRepositoryLocator( locator );
236 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
238 DavResource resource = null;
239 if ( !locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
241 ManagedRepositoryContent managedRepository = getManagedRepository( archivaLocator.getRepositoryId() );
242 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() );
243 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource );
245 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, mimeTypes, archivaLocator,
251 private DavResource doGet( ManagedRepositoryContent managedRepository, DavServletRequest request,
252 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
255 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
256 ArchivaDavResource resource =
257 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator, this );
259 if ( !resource.isCollection() )
261 // At this point the incoming request can either be in default or
262 // legacy layout format.
263 boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource );
265 boolean previouslyExisted = resourceFile.exists();
269 // Perform an adjustment of the resource to the managed
270 // repository expected path.
271 String localResourcePath =
272 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository );
273 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath );
275 catch ( LayoutException e )
277 if ( previouslyExisted )
281 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
284 // Attempt to fetch the resource from any defined proxy.
287 processAuditEvents( request, locator.getWorkspaceName(), logicalResource.getPath(), previouslyExisted,
288 resourceFile, " (proxied)" );
291 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
294 if ( !resourceFile.exists() )
302 private DavResource doPut( ManagedRepositoryContent managedRepository, DavServletRequest request,
303 ArchivaDavResourceLocator locator, LogicalResource logicalResource )
307 * Create parent directories that don't exist when writing a file This actually makes this implementation not
308 * compliant to the WebDAV RFC - but we have enough knowledge about how the collection is being used to do this
309 * reasonably and some versions of Maven's WebDAV don't correctly create the collections themselves.
312 File rootDirectory = new File( managedRepository.getRepoRoot() );
313 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile();
314 if ( !destDir.exists() )
317 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir );
318 triggerAuditEvent( request, logicalResource.getPath(), relPath, AuditEvent.CREATE_DIR );
321 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() );
323 boolean previouslyExisted = resourceFile.exists();
325 processAuditEvents( request, locator.getRepositoryId(), logicalResource.getPath(), previouslyExisted,
326 resourceFile, null );
328 return new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), mimeTypes, locator,
332 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
333 LogicalResource resource )
336 if ( repositoryRequest.isSupportFile( resource.getPath() ) )
338 // Checksums are fetched with artifact / metadata.
340 // Need to adjust the path for the checksum resource.
344 // Is it a Metadata resource?
345 if ( repositoryRequest.isDefault( resource.getPath() ) && repositoryRequest.isMetadata( resource.getPath() ) )
347 return fetchMetadataFromProxies( managedRepository, request, resource );
350 // Not any of the above? Then it's gotta be an artifact reference.
353 // Get the artifact reference in a layout neutral way.
354 ArtifactReference artifact = repositoryRequest.toArtifactReference( resource.getPath() );
356 if ( artifact != null )
358 applyServerSideRelocation( managedRepository, artifact );
360 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact );
362 resource.setPath( managedRepository.toPath( artifact ) );
364 return ( proxiedFile != null );
367 catch ( LayoutException e )
371 catch ( ProxyDownloadException e )
373 log.error( e.getMessage(), e );
374 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to fetch artifact resource." );
379 private boolean fetchMetadataFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request,
380 LogicalResource resource )
383 ProjectReference project;
384 VersionedReference versioned;
389 versioned = metadataTools.toVersionedReference( resource.getPath() );
390 if ( versioned != null )
392 connectors.fetchFromProxies( managedRepository, versioned );
396 catch ( RepositoryMetadataException e )
403 project = metadataTools.toProjectReference( resource.getPath() );
404 if ( project != null )
406 connectors.fetchFromProxies( managedRepository, project );
410 catch ( RepositoryMetadataException e )
419 * A relocation capable client will request the POM prior to the artifact, and will then read meta-data and do
420 * client side relocation. A simplier client (like maven 1) will only request the artifact and not use the
423 * For such clients, archiva does server-side relocation by reading itself the <relocation> element in
424 * metadatas and serving the expected artifact.
426 protected void applyServerSideRelocation( ManagedRepositoryContent managedRepository, ArtifactReference artifact )
427 throws ProxyDownloadException
429 if ( "pom".equals( artifact.getType() ) )
434 // Build the artifact POM reference
435 ArtifactReference pomReference = new ArtifactReference();
436 pomReference.setGroupId( artifact.getGroupId() );
437 pomReference.setArtifactId( artifact.getArtifactId() );
438 pomReference.setVersion( artifact.getVersion() );
439 pomReference.setType( "pom" );
441 // Get the artifact POM from proxied repositories if needed
442 connectors.fetchFromProxies( managedRepository, pomReference );
444 // Open and read the POM from the managed repo
445 File pom = managedRepository.toFile( pomReference );
454 Model model = new MavenXpp3Reader().read( new FileReader( pom ) );
455 DistributionManagement dist = model.getDistributionManagement();
458 Relocation relocation = dist.getRelocation();
459 if ( relocation != null )
461 // artifact is relocated : update the repositoryPath
462 if ( relocation.getGroupId() != null )
464 artifact.setGroupId( relocation.getGroupId() );
466 if ( relocation.getArtifactId() != null )
468 artifact.setArtifactId( relocation.getArtifactId() );
470 if ( relocation.getVersion() != null )
472 artifact.setVersion( relocation.getVersion() );
477 catch ( FileNotFoundException e )
479 // Artifact has no POM in repo : ignore
481 catch ( IOException e )
483 // Unable to read POM : ignore.
485 catch ( XmlPullParserException e )
487 // Invalid POM : ignore
491 private void processAuditEvents( DavServletRequest request, String repositoryId, String resource,
492 boolean previouslyExisted, File resourceFile, String suffix )
494 if ( suffix == null )
499 // Process Create Audit Events.
500 if ( !previouslyExisted && resourceFile.exists() )
502 if ( resourceFile.isFile() )
504 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_FILE + suffix );
506 else if ( resourceFile.isDirectory() )
508 triggerAuditEvent( request, repositoryId, resource, AuditEvent.CREATE_DIR + suffix );
511 // Process Remove Audit Events.
512 else if ( previouslyExisted && !resourceFile.exists() )
514 if ( resourceFile.isFile() )
516 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_FILE + suffix );
518 else if ( resourceFile.isDirectory() )
520 triggerAuditEvent( request, repositoryId, resource, AuditEvent.REMOVE_DIR + suffix );
523 // Process modify events.
526 if ( resourceFile.isFile() )
528 triggerAuditEvent( request, repositoryId, resource, AuditEvent.MODIFY_FILE + suffix );
533 private void triggerAuditEvent( String user, String remoteIP, String repositoryId, String resource, String action )
535 AuditEvent event = new AuditEvent( repositoryId, user, resource, action );
536 event.setRemoteIP( remoteIP );
538 for ( AuditListener listener : auditListeners )
540 listener.auditEvent( event );
544 private void triggerAuditEvent( DavServletRequest request, String repositoryId, String resource, String action )
546 triggerAuditEvent( ArchivaXworkUser.getActivePrincipal( ActionContext.getContext().getSession() ),
547 getRemoteIP( request ), repositoryId, resource, action );
550 private String getRemoteIP( DavServletRequest request )
552 return request.getRemoteAddr();
555 public void addAuditListener( AuditListener listener )
557 this.auditListeners.add( listener );
560 public void clearAuditListeners()
562 this.auditListeners.clear();
565 public void removeAuditListener( AuditListener listener )
567 this.auditListeners.remove( listener );
570 private void setHeaders( DavResourceLocator locator, DavServletResponse response )
572 // [MRM-503] - Metadata file need Pragma:no-cache response
574 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) )
576 response.addHeader( "Pragma", "no-cache" );
577 response.addHeader( "Cache-Control", "no-cache" );
580 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
583 private ManagedRepositoryContent getManagedRepository( String respositoryId )
586 if ( respositoryId != null )
590 return repositoryFactory.getManagedRepositoryContent( respositoryId );
592 catch ( RepositoryNotFoundException e )
594 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
596 catch ( RepositoryException e )
598 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e );
604 private void checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
607 if ( !( locator instanceof RepositoryLocator ) )
609 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
610 "Locator does not implement RepositoryLocator" );
614 class LogicalResource
618 public LogicalResource( String path )
623 public String getPath()
628 public void setPath( String path )
634 protected boolean isAuthorized( DavServletRequest request, String repositoryId )
639 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
640 SecuritySession securitySession = httpAuth.getSecuritySession();
642 return servletAuth.isAuthenticated( request, result ) &&
643 servletAuth.isAuthorized( request, securitySession, repositoryId,
644 WebdavMethodUtil.isWriteMethod( request.getMethod() ) );
646 catch ( AuthenticationException e )
648 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
650 catch ( MustChangePasswordException e )
652 throw new UnauthorizedDavException( repositoryId, "You must change your password." );
654 catch ( AccountLockedException e )
656 throw new UnauthorizedDavException( repositoryId, "User account is locked." );
658 catch ( AuthorizationException e )
660 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
661 "Fatal Authorization Subsystem Error." );
663 catch ( UnauthorizedException e )
665 throw new UnauthorizedDavException( repositoryId, e.getMessage() );