001 package org.apache.archiva.webdav; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import org.apache.archiva.admin.model.RepositoryAdminException; 023 import org.apache.archiva.admin.model.beans.RemoteRepository; 024 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin; 025 import org.apache.archiva.audit.AuditEvent; 026 import org.apache.archiva.audit.AuditListener; 027 import org.apache.archiva.audit.Auditable; 028 import org.apache.archiva.common.plexusbridge.PlexusSisuBridge; 029 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException; 030 import org.apache.archiva.common.utils.PathUtil; 031 import org.apache.archiva.common.utils.VersionUtil; 032 import org.apache.archiva.configuration.ArchivaConfiguration; 033 import org.apache.archiva.configuration.RepositoryGroupConfiguration; 034 import org.apache.archiva.indexer.merger.IndexMerger; 035 import org.apache.archiva.indexer.merger.IndexMergerException; 036 import org.apache.archiva.indexer.merger.IndexMergerRequest; 037 import org.apache.archiva.indexer.merger.TemporaryGroupIndex; 038 import org.apache.archiva.indexer.search.RepositorySearch; 039 import org.apache.archiva.maven2.metadata.MavenMetadataReader; 040 import org.apache.archiva.metadata.repository.storage.RepositoryStorage; 041 import org.apache.archiva.model.ArchivaRepositoryMetadata; 042 import org.apache.archiva.model.ArtifactReference; 043 import org.apache.archiva.policies.ProxyDownloadException; 044 import org.apache.archiva.proxy.model.RepositoryProxyConnectors; 045 import org.apache.archiva.redback.authentication.AuthenticationException; 046 import org.apache.archiva.redback.authentication.AuthenticationResult; 047 import org.apache.archiva.redback.authorization.AuthorizationException; 048 import org.apache.archiva.redback.authorization.UnauthorizedException; 049 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator; 050 import org.apache.archiva.redback.policy.AccountLockedException; 051 import org.apache.archiva.redback.policy.MustChangePasswordException; 052 import org.apache.archiva.redback.system.SecuritySession; 053 import org.apache.archiva.redback.users.User; 054 import org.apache.archiva.redback.users.UserManager; 055 import org.apache.archiva.repository.ManagedRepositoryContent; 056 import org.apache.archiva.repository.RepositoryContentFactory; 057 import org.apache.archiva.repository.RepositoryException; 058 import org.apache.archiva.repository.RepositoryNotFoundException; 059 import org.apache.archiva.repository.content.legacy.LegacyPathParser; 060 import org.apache.archiva.repository.content.maven2.RepositoryRequest; 061 import org.apache.archiva.repository.layout.LayoutException; 062 import org.apache.archiva.repository.metadata.MetadataTools; 063 import org.apache.archiva.repository.metadata.RepositoryMetadataException; 064 import org.apache.archiva.repository.metadata.RepositoryMetadataMerge; 065 import org.apache.archiva.repository.metadata.RepositoryMetadataWriter; 066 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler; 067 import org.apache.archiva.security.ServletAuthenticator; 068 import org.apache.archiva.webdav.util.MimeTypes; 069 import org.apache.archiva.webdav.util.RepositoryPathUtil; 070 import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner; 071 import org.apache.archiva.webdav.util.WebdavMethodUtil; 072 import org.apache.archiva.xml.XMLException; 073 import org.apache.commons.io.FileUtils; 074 import org.apache.commons.io.FilenameUtils; 075 import org.apache.commons.lang.StringUtils; 076 import org.apache.commons.lang.SystemUtils; 077 import org.apache.jackrabbit.webdav.DavException; 078 import org.apache.jackrabbit.webdav.DavResource; 079 import org.apache.jackrabbit.webdav.DavResourceFactory; 080 import org.apache.jackrabbit.webdav.DavResourceLocator; 081 import org.apache.jackrabbit.webdav.DavServletRequest; 082 import org.apache.jackrabbit.webdav.DavServletResponse; 083 import org.apache.jackrabbit.webdav.DavSession; 084 import org.apache.jackrabbit.webdav.lock.LockManager; 085 import org.apache.jackrabbit.webdav.lock.SimpleLockManager; 086 import org.apache.maven.index.context.IndexingContext; 087 import org.codehaus.plexus.digest.ChecksumFile; 088 import org.codehaus.plexus.digest.Digester; 089 import org.codehaus.plexus.digest.DigesterException; 090 import org.slf4j.Logger; 091 import org.slf4j.LoggerFactory; 092 import org.slf4j.MarkerFactory; 093 import org.springframework.context.ApplicationContext; 094 import org.springframework.stereotype.Service; 095 096 import javax.annotation.PostConstruct; 097 import javax.inject.Inject; 098 import javax.inject.Named; 099 import javax.servlet.http.HttpServletResponse; 100 import javax.servlet.http.HttpSession; 101 import java.io.File; 102 import java.io.IOException; 103 import java.util.ArrayList; 104 import java.util.Date; 105 import java.util.HashMap; 106 import java.util.HashSet; 107 import java.util.List; 108 import java.util.Map; 109 import java.util.Set; 110 111 /** 112 * 113 */ 114 @Service("davResourceFactory#archiva") 115 public class ArchivaDavResourceFactory 116 implements DavResourceFactory, Auditable 117 { 118 private static final String PROXIED_SUFFIX = " (proxied)"; 119 120 private static final String HTTP_PUT_METHOD = "PUT"; 121 122 private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class ); 123 124 /** 125 * 126 */ 127 @Inject 128 private List<AuditListener> auditListeners = new ArrayList<AuditListener>(); 129 130 /** 131 * 132 */ 133 @Inject 134 private RepositoryContentFactory repositoryFactory; 135 136 /** 137 * 138 */ 139 private RepositoryRequest repositoryRequest; 140 141 /** 142 * 143 */ 144 @Inject 145 @Named(value = "repositoryProxyConnectors#default") 146 private RepositoryProxyConnectors connectors; 147 148 /** 149 * 150 */ 151 @Inject 152 private MetadataTools metadataTools; 153 154 /** 155 * 156 */ 157 @Inject 158 private MimeTypes mimeTypes; 159 160 /** 161 * 162 */ 163 private ArchivaConfiguration archivaConfiguration; 164 165 /** 166 * 167 */ 168 @Inject 169 private ServletAuthenticator servletAuth; 170 171 /** 172 * 173 */ 174 @Inject 175 @Named(value = "httpAuthenticator#basic") 176 private HttpAuthenticator httpAuth; 177 178 @Inject 179 private RemoteRepositoryAdmin remoteRepositoryAdmin; 180 181 @Inject 182 private IndexMerger indexMerger; 183 184 @Inject 185 private RepositorySearch repositorySearch; 186 187 /** 188 * Lock Manager - use simple implementation from JackRabbit 189 */ 190 private final LockManager lockManager = new SimpleLockManager(); 191 192 /** 193 * 194 */ 195 private ChecksumFile checksum; 196 197 /** 198 * 199 */ 200 private Digester digestSha1; 201 202 /** 203 * 204 */ 205 private Digester digestMd5; 206 207 /** 208 * 209 */ 210 @Inject 211 @Named(value = "archivaTaskScheduler#repository") 212 private RepositoryArchivaTaskScheduler scheduler; 213 214 private ApplicationContext applicationContext; 215 216 @Inject 217 public ArchivaDavResourceFactory( ApplicationContext applicationContext, PlexusSisuBridge plexusSisuBridge, 218 ArchivaConfiguration archivaConfiguration ) 219 throws PlexusSisuBridgeException 220 { 221 this.archivaConfiguration = archivaConfiguration; 222 this.applicationContext = applicationContext; 223 this.checksum = plexusSisuBridge.lookup( ChecksumFile.class ); 224 225 this.digestMd5 = plexusSisuBridge.lookup( Digester.class, "md5" ); 226 this.digestSha1 = plexusSisuBridge.lookup( Digester.class, "sha1" ); 227 228 // TODO remove this hard dependency on maven !! 229 repositoryRequest = new RepositoryRequest( new LegacyPathParser( archivaConfiguration ) ); 230 } 231 232 @PostConstruct 233 public void initialize() 234 { 235 236 } 237 238 public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request, 239 final DavServletResponse response ) 240 throws DavException 241 { 242 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator ); 243 244 RepositoryGroupConfiguration repoGroupConfig = 245 archivaConfiguration.getConfiguration().getRepositoryGroupsAsMap().get( archivaLocator.getRepositoryId() ); 246 247 String activePrincipal = getActivePrincipal( request ); 248 249 List<String> resourcesInAbsolutePath = new ArrayList<String>(); 250 251 boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() ); 252 DavResource resource; 253 if ( repoGroupConfig != null ) 254 { 255 if ( !readMethod ) 256 { 257 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED, 258 "Write method not allowed for repository groups." ); 259 } 260 261 log.debug( "Repository group '{}' accessed by '{}", repoGroupConfig.getId(), activePrincipal ); 262 263 // handle browse requests for virtual repos 264 if ( RepositoryPathUtil.getLogicalResource( archivaLocator.getOrigResourcePath() ).endsWith( "/" ) ) 265 { 266 return getResourceFromGroup( request, repoGroupConfig.getRepositories(), archivaLocator, 267 repoGroupConfig ); 268 } 269 else 270 { 271 // make a copy to avoid potential concurrent modifications (eg. by configuration) 272 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are 273 // infrequent 274 List<String> repositories = new ArrayList<String>( repoGroupConfig.getRepositories() ); 275 resource = processRepositoryGroup( request, archivaLocator, repositories, activePrincipal, 276 resourcesInAbsolutePath, repoGroupConfig ); 277 } 278 } 279 else 280 { 281 282 try 283 { 284 RemoteRepository remoteRepository = 285 remoteRepositoryAdmin.getRemoteRepository( archivaLocator.getRepositoryId() ); 286 287 if ( remoteRepository != null ) 288 { 289 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ); 290 IndexingContext indexingContext = remoteRepositoryAdmin.createIndexContext( remoteRepository ); 291 File resourceFile = StringUtils.equals( logicalResource, "/" ) 292 ? new File( indexingContext.getIndexDirectoryFile().getParent() ) 293 : new File( indexingContext.getIndexDirectoryFile().getParent(), logicalResource ); 294 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), locator.getResourcePath(), null, 295 request.getRemoteAddr(), activePrincipal, 296 request.getDavSession(), archivaLocator, this, mimeTypes, 297 auditListeners, scheduler ); 298 setHeaders( response, locator, resource ); 299 return resource; 300 } 301 } 302 catch ( RepositoryAdminException e ) 303 { 304 log.debug( "RepositoryException remote repository with d'{}' not found, msg: {}", 305 archivaLocator.getRepositoryId(), e.getMessage() ); 306 } 307 308 ManagedRepositoryContent managedRepository = null; 309 310 try 311 { 312 managedRepository = repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() ); 313 } 314 catch ( RepositoryNotFoundException e ) 315 { 316 throw new DavException( HttpServletResponse.SC_NOT_FOUND, 317 "Invalid repository: " + archivaLocator.getRepositoryId() ); 318 } 319 catch ( RepositoryException e ) 320 { 321 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); 322 } 323 324 log.debug( "Managed repository '{}' accessed by '{}'", managedRepository.getId(), activePrincipal ); 325 326 resource = processRepository( request, archivaLocator, activePrincipal, managedRepository ); 327 328 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ); 329 resourcesInAbsolutePath.add( 330 new File( managedRepository.getRepoRoot(), logicalResource ).getAbsolutePath() ); 331 } 332 333 String requestedResource = request.getRequestURI(); 334 335 // MRM-872 : merge all available metadata 336 // merge metadata only when requested via the repo group 337 if ( ( repositoryRequest.isMetadata( requestedResource ) || repositoryRequest.isMetadataSupportFile( 338 requestedResource ) ) && repoGroupConfig != null ) 339 { 340 // this should only be at the project level not version level! 341 if ( isProjectReference( requestedResource ) ) 342 { 343 344 ArchivaDavResource res = (ArchivaDavResource) resource; 345 String filePath = 346 StringUtils.substringBeforeLast( res.getLocalResource().getAbsolutePath().replace( '\\', '/' ), 347 "/" ); 348 filePath = filePath + "/maven-metadata-" + repoGroupConfig.getId() + ".xml"; 349 350 // for MRM-872 handle checksums of the merged metadata files 351 if ( repositoryRequest.isSupportFile( requestedResource ) ) 352 { 353 File metadataChecksum = 354 new File( filePath + "." + StringUtils.substringAfterLast( requestedResource, "." ) ); 355 if ( metadataChecksum.exists() ) 356 { 357 LogicalResource logicalResource = 358 new LogicalResource( RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) ); 359 360 resource = 361 new ArchivaDavResource( metadataChecksum.getAbsolutePath(), logicalResource.getPath(), null, 362 request.getRemoteAddr(), activePrincipal, request.getDavSession(), 363 archivaLocator, this, mimeTypes, auditListeners, scheduler ); 364 } 365 } 366 else 367 { 368 if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 ) 369 { 370 // merge the metadata of all repos under group 371 ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata(); 372 for ( String resourceAbsPath : resourcesInAbsolutePath ) 373 { 374 try 375 { 376 File metadataFile = new File( resourceAbsPath ); 377 ArchivaRepositoryMetadata repoMetadata = MavenMetadataReader.read( metadataFile ); 378 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata ); 379 } 380 catch ( XMLException e ) 381 { 382 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 383 "Error occurred while reading metadata file." ); 384 } 385 catch ( RepositoryMetadataException r ) 386 { 387 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 388 "Error occurred while merging metadata file." ); 389 } 390 } 391 392 try 393 { 394 File resourceFile = writeMergedMetadataToFile( mergedMetadata, filePath ); 395 396 LogicalResource logicalResource = new LogicalResource( 397 RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ) ); 398 399 resource = 400 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), null, 401 request.getRemoteAddr(), activePrincipal, 402 request.getDavSession(), archivaLocator, this, mimeTypes, 403 auditListeners, scheduler ); 404 } 405 catch ( RepositoryMetadataException r ) 406 { 407 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 408 "Error occurred while writing metadata file." ); 409 } 410 catch ( IOException ie ) 411 { 412 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 413 "Error occurred while generating checksum files." ); 414 } 415 catch ( DigesterException de ) 416 { 417 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 418 "Error occurred while generating checksum files." ); 419 } 420 } 421 } 422 } 423 } 424 425 setHeaders( response, locator, resource ); 426 427 // compatibility with MRM-440 to ensure browsing the repository works ok 428 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) ) 429 { 430 throw new BrowserRedirectException( resource.getHref() ); 431 } 432 resource.addLockManager( lockManager ); 433 return resource; 434 } 435 436 private DavResource processRepositoryGroup( final DavServletRequest request, 437 ArchivaDavResourceLocator archivaLocator, List<String> repositories, 438 String activePrincipal, List<String> resourcesInAbsolutePath, 439 RepositoryGroupConfiguration repoGroupConfig ) 440 throws DavException 441 { 442 DavResource resource = null; 443 List<DavException> storedExceptions = new ArrayList<DavException>(); 444 445 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" ); 446 447 String rootPath = StringUtils.substringBeforeLast( pathInfo, "/" ); 448 449 if ( StringUtils.endsWith( rootPath, repoGroupConfig.getMergedIndexPath() ) ) 450 { 451 // we are in the case of index file request 452 String requestedFileName = StringUtils.substringAfterLast( pathInfo, "/" ); 453 File temporaryIndexDirectory = 454 buildMergedIndexDirectory( repositories, activePrincipal, request, repoGroupConfig ); 455 456 File resourceFile = new File( temporaryIndexDirectory, requestedFileName ); 457 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), requestedFileName, null, 458 request.getRemoteAddr(), activePrincipal, request.getDavSession(), 459 archivaLocator, this, mimeTypes, auditListeners, scheduler ); 460 461 } 462 else 463 { 464 for ( String repositoryId : repositories ) 465 { 466 ManagedRepositoryContent managedRepository; 467 try 468 { 469 managedRepository = repositoryFactory.getManagedRepositoryContent( repositoryId ); 470 } 471 catch ( RepositoryNotFoundException e ) 472 { 473 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); 474 } 475 catch ( RepositoryException e ) 476 { 477 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); 478 } 479 480 try 481 { 482 DavResource updatedResource = 483 processRepository( request, archivaLocator, activePrincipal, managedRepository ); 484 if ( resource == null ) 485 { 486 resource = updatedResource; 487 } 488 489 String logicalResource = RepositoryPathUtil.getLogicalResource( archivaLocator.getResourcePath() ); 490 if ( logicalResource.endsWith( "/" ) ) 491 { 492 logicalResource = logicalResource.substring( 1 ); 493 } 494 resourcesInAbsolutePath.add( 495 new File( managedRepository.getRepoRoot(), logicalResource ).getAbsolutePath() ); 496 } 497 catch ( DavException e ) 498 { 499 storedExceptions.add( e ); 500 } 501 } 502 } 503 if ( resource == null ) 504 { 505 if ( !storedExceptions.isEmpty() ) 506 { 507 // MRM-1232 508 for ( DavException e : storedExceptions ) 509 { 510 if ( 401 == e.getErrorCode() ) 511 { 512 throw e; 513 } 514 } 515 516 throw new DavException( HttpServletResponse.SC_NOT_FOUND ); 517 } 518 else 519 { 520 throw new DavException( HttpServletResponse.SC_NOT_FOUND ); 521 } 522 } 523 return resource; 524 } 525 526 private DavResource processRepository( final DavServletRequest request, ArchivaDavResourceLocator archivaLocator, 527 String activePrincipal, ManagedRepositoryContent managedRepository ) 528 throws DavException 529 { 530 DavResource resource = null; 531 if ( isAuthorized( request, managedRepository.getId() ) ) 532 { 533 String path = RepositoryPathUtil.getLogicalResource( archivaLocator.getResourcePath() ); 534 if ( path.startsWith( "/" ) ) 535 { 536 path = path.substring( 1 ); 537 } 538 LogicalResource logicalResource = new LogicalResource( path ); 539 File resourceFile = new File( managedRepository.getRepoRoot(), path ); 540 resource = new ArchivaDavResource( resourceFile.getAbsolutePath(), path, managedRepository.getRepository(), 541 request.getRemoteAddr(), activePrincipal, request.getDavSession(), 542 archivaLocator, this, mimeTypes, auditListeners, scheduler ); 543 544 if ( WebdavMethodUtil.isReadMethod( request.getMethod() ) ) 545 { 546 if ( archivaLocator.getHref( false ).endsWith( "/" ) && !resourceFile.isDirectory() ) 547 { 548 // force a resource not found 549 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" ); 550 } 551 else 552 { 553 if ( !resource.isCollection() ) 554 { 555 boolean previouslyExisted = resourceFile.exists(); 556 557 // Attempt to fetch the resource from any defined proxy. 558 boolean fromProxy = fetchContentFromProxies( managedRepository, request, logicalResource ); 559 560 // At this point the incoming request can either be in default or 561 // legacy layout format. 562 try 563 { 564 // Perform an adjustment of the resource to the managed 565 // repository expected path. 566 String localResourcePath = 567 repositoryRequest.toNativePath( logicalResource.getPath(), managedRepository ); 568 resourceFile = new File( managedRepository.getRepoRoot(), localResourcePath ); 569 resource = 570 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource.getPath(), 571 managedRepository.getRepository(), request.getRemoteAddr(), 572 activePrincipal, request.getDavSession(), archivaLocator, this, 573 mimeTypes, auditListeners, scheduler ); 574 } 575 catch ( LayoutException e ) 576 { 577 if ( !resourceFile.exists() ) 578 { 579 throw new DavException( HttpServletResponse.SC_NOT_FOUND, e ); 580 } 581 } 582 583 if ( fromProxy ) 584 { 585 String event = ( previouslyExisted ? AuditEvent.MODIFY_FILE : AuditEvent.CREATE_FILE ) 586 + PROXIED_SUFFIX; 587 588 log.debug( "Proxied artifact '{}' in repository '{}' (current user '{}')", 589 resourceFile.getName(), managedRepository.getId(), activePrincipal ); 590 591 triggerAuditEvent( request.getRemoteAddr(), archivaLocator.getRepositoryId(), 592 logicalResource.getPath(), event, activePrincipal ); 593 } 594 595 if ( !resourceFile.exists() ) 596 { 597 throw new DavException( HttpServletResponse.SC_NOT_FOUND, "Resource does not exist" ); 598 } 599 } 600 } 601 } 602 603 if ( request.getMethod().equals( HTTP_PUT_METHOD ) ) 604 { 605 String resourcePath = logicalResource.getPath(); 606 607 // check if target repo is enabled for releases 608 // we suppose that release-artifacts can be deployed only to repos enabled for releases 609 if ( managedRepository.getRepository().isReleases() && !repositoryRequest.isMetadata( resourcePath ) 610 && !repositoryRequest.isSupportFile( resourcePath ) ) 611 { 612 ArtifactReference artifact = null; 613 try 614 { 615 artifact = managedRepository.toArtifactReference( resourcePath ); 616 617 if ( !VersionUtil.isSnapshot( artifact.getVersion() ) ) 618 { 619 // check if artifact already exists and if artifact re-deployment to the repository is allowed 620 if ( managedRepository.hasContent( artifact ) 621 && managedRepository.getRepository().isBlockRedeployments() ) 622 { 623 log.warn( "Overwriting released artifacts in repository '{}' is not allowed.", 624 managedRepository.getId() ); 625 throw new DavException( HttpServletResponse.SC_CONFLICT, 626 "Overwriting released artifacts is not allowed." ); 627 } 628 } 629 } 630 catch ( LayoutException e ) 631 { 632 log.warn( "Artifact path '" + resourcePath + "' is invalid." ); 633 } 634 } 635 636 /* 637 * Create parent directories that don't exist when writing a file This actually makes this 638 * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the 639 * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly 640 * create the collections themselves. 641 */ 642 643 File rootDirectory = new File( managedRepository.getRepoRoot() ); 644 File destDir = new File( rootDirectory, logicalResource.getPath() ).getParentFile(); 645 646 if ( !destDir.exists() ) 647 { 648 destDir.mkdirs(); 649 String relPath = PathUtil.getRelative( rootDirectory.getAbsolutePath(), destDir ); 650 651 log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getName(), 652 activePrincipal ); 653 654 triggerAuditEvent( request.getRemoteAddr(), managedRepository.getId(), relPath, 655 AuditEvent.CREATE_DIR, activePrincipal ); 656 } 657 } 658 } 659 return resource; 660 } 661 662 public DavResource createResource( final DavResourceLocator locator, final DavSession davSession ) 663 throws DavException 664 { 665 ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator ); 666 667 ManagedRepositoryContent managedRepository; 668 try 669 { 670 managedRepository = repositoryFactory.getManagedRepositoryContent( archivaLocator.getRepositoryId() ); 671 } 672 catch ( RepositoryNotFoundException e ) 673 { 674 throw new DavException( HttpServletResponse.SC_NOT_FOUND, 675 "Invalid repository: " + archivaLocator.getRepositoryId() ); 676 } 677 catch ( RepositoryException e ) 678 { 679 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); 680 } 681 682 String logicalResource = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ); 683 if ( logicalResource.startsWith( "/" ) ) 684 { 685 logicalResource = logicalResource.substring( 1 ); 686 } 687 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource ); 688 DavResource resource = 689 new ArchivaDavResource( resourceFile.getAbsolutePath(), logicalResource, managedRepository.getRepository(), 690 davSession, archivaLocator, this, mimeTypes, auditListeners, scheduler ); 691 692 resource.addLockManager( lockManager ); 693 return resource; 694 } 695 696 private boolean fetchContentFromProxies( ManagedRepositoryContent managedRepository, DavServletRequest request, 697 LogicalResource resource ) 698 throws DavException 699 { 700 String path = resource.getPath(); 701 if ( repositoryRequest.isSupportFile( path ) ) 702 { 703 File proxiedFile = connectors.fetchFromProxies( managedRepository, path ); 704 705 return ( proxiedFile != null ); 706 } 707 708 // Is it a Metadata resource? 709 if ( repositoryRequest.isDefault( path ) && repositoryRequest.isMetadata( path ) ) 710 { 711 return connectors.fetchMetatadaFromProxies( managedRepository, path ) != null; 712 } 713 714 // Is it an Archetype Catalog? 715 if ( repositoryRequest.isArchetypeCatalog( path ) ) 716 { 717 // FIXME we must implement a merge of remote archetype catalog from remote servers. 718 File proxiedFile = connectors.fetchFromProxies( managedRepository, path ); 719 720 return ( proxiedFile != null ); 721 } 722 723 // Not any of the above? Then it's gotta be an artifact reference. 724 try 725 { 726 // Get the artifact reference in a layout neutral way. 727 ArtifactReference artifact = repositoryRequest.toArtifactReference( path ); 728 729 if ( artifact != null ) 730 { 731 String repositoryLayout = managedRepository.getRepository().getLayout(); 732 733 RepositoryStorage repositoryStorage = 734 this.applicationContext.getBean( "repositoryStorage#" + repositoryLayout, RepositoryStorage.class ); 735 repositoryStorage.applyServerSideRelocation( managedRepository, artifact ); 736 737 File proxiedFile = connectors.fetchFromProxies( managedRepository, artifact ); 738 739 resource.setPath( managedRepository.toPath( artifact ) ); 740 741 log.debug( "Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(), 742 artifact.getVersion() ); 743 744 return ( proxiedFile != null ); 745 } 746 } 747 catch ( LayoutException e ) 748 { 749 /* eat it */ 750 } 751 catch ( ProxyDownloadException e ) 752 { 753 log.error( e.getMessage(), e ); 754 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 755 "Unable to fetch artifact resource." ); 756 } 757 return false; 758 } 759 760 // TODO: remove? 761 762 private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action, 763 String principal ) 764 { 765 AuditEvent event = new AuditEvent( repositoryId, principal, resource, action ); 766 event.setRemoteIP( remoteIP ); 767 768 for ( AuditListener listener : auditListeners ) 769 { 770 listener.auditEvent( event ); 771 } 772 } 773 774 public void addAuditListener( AuditListener listener ) 775 { 776 this.auditListeners.add( listener ); 777 } 778 779 public void clearAuditListeners() 780 { 781 this.auditListeners.clear(); 782 } 783 784 public void removeAuditListener( AuditListener listener ) 785 { 786 this.auditListeners.remove( listener ); 787 } 788 789 private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource ) 790 { 791 // [MRM-503] - Metadata file need Pragma:no-cache response 792 // header. 793 if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) 794 || ( (ArchivaDavResource) resource ).getLocalResource().isDirectory() ) 795 { 796 response.setHeader( "Pragma", "no-cache" ); 797 response.setHeader( "Cache-Control", "no-cache" ); 798 response.setDateHeader( "Last-Modified", new Date().getTime() ); 799 } 800 // if the resource is a directory don't cache it as new groupId deployed will be available 801 // without need of refreshing browser 802 else 803 { 804 // We need to specify this so connecting wagons can work correctly 805 response.setDateHeader( "Last-Modified", resource.getModificationTime() ); 806 } 807 // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots) 808 } 809 810 private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator ) 811 throws DavException 812 { 813 if ( !( locator instanceof ArchivaDavResourceLocator ) ) 814 { 815 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 816 "Locator does not implement RepositoryLocator" ); 817 } 818 819 // Hidden paths 820 if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) ) 821 { 822 throw new DavException( HttpServletResponse.SC_NOT_FOUND ); 823 } 824 825 ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator; 826 827 // MRM-419 - Windows Webdav support. Should not 404 if there is no content. 828 if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) ) 829 { 830 throw new DavException( HttpServletResponse.SC_NO_CONTENT ); 831 } 832 return archivaLocator; 833 } 834 835 private static class LogicalResource 836 { 837 private String path; 838 839 public LogicalResource( String path ) 840 { 841 this.path = path; 842 } 843 844 public String getPath() 845 { 846 return path; 847 } 848 849 public void setPath( String path ) 850 { 851 this.path = path; 852 } 853 } 854 855 protected boolean isAuthorized( DavServletRequest request, String repositoryId ) 856 throws DavException 857 { 858 try 859 { 860 AuthenticationResult result = httpAuth.getAuthenticationResult( request, null ); 861 SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) ); 862 863 return servletAuth.isAuthenticated( request, result ) && servletAuth.isAuthorized( request, securitySession, 864 repositoryId, 865 WebdavMethodUtil.getMethodPermission( 866 request.getMethod() ) ); 867 } 868 catch ( AuthenticationException e ) 869 { 870 // safety check for MRM-911 871 String guest = UserManager.GUEST_USERNAME; 872 try 873 { 874 if ( servletAuth.isAuthorized( guest, 875 ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(), 876 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) ) 877 { 878 return true; 879 } 880 } 881 catch ( UnauthorizedException ae ) 882 { 883 throw new UnauthorizedDavException( repositoryId, 884 "You are not authenticated and authorized to access any repository." ); 885 } 886 887 throw new UnauthorizedDavException( repositoryId, "You are not authenticated" ); 888 } 889 catch ( MustChangePasswordException e ) 890 { 891 throw new UnauthorizedDavException( repositoryId, "You must change your password." ); 892 } 893 catch ( AccountLockedException e ) 894 { 895 throw new UnauthorizedDavException( repositoryId, "User account is locked." ); 896 } 897 catch ( AuthorizationException e ) 898 { 899 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 900 "Fatal Authorization Subsystem Error." ); 901 } 902 catch ( UnauthorizedException e ) 903 { 904 throw new UnauthorizedDavException( repositoryId, e.getMessage() ); 905 } 906 } 907 908 private DavResource getResourceFromGroup( DavServletRequest request, List<String> repositories, 909 ArchivaDavResourceLocator locator, 910 RepositoryGroupConfiguration repositoryGroupConfiguration ) 911 throws DavException 912 { 913 List<File> mergedRepositoryContents = new ArrayList<File>(); 914 String path = RepositoryPathUtil.getLogicalResource( locator.getResourcePath() ); 915 if ( path.startsWith( "/" ) ) 916 { 917 path = path.substring( 1 ); 918 } 919 LogicalResource logicalResource = new LogicalResource( path ); 920 921 // flow: 922 // if the current user logged in has permission to any of the repositories, allow user to 923 // browse the repo group but displaying only the repositories which the user has permission to access. 924 // otherwise, prompt for authentication. 925 926 String activePrincipal = getActivePrincipal( request ); 927 928 boolean allow = isAllowedToContinue( request, repositories, activePrincipal ); 929 930 // remove last / 931 String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" ); 932 933 if ( allow ) 934 { 935 936 if ( StringUtils.endsWith( pathInfo, repositoryGroupConfiguration.getMergedIndexPath() ) ) 937 { 938 File mergedRepoDir = 939 buildMergedIndexDirectory( repositories, activePrincipal, request, repositoryGroupConfiguration ); 940 mergedRepositoryContents.add( mergedRepoDir ); 941 } 942 else 943 { 944 if ( StringUtils.equalsIgnoreCase( pathInfo, "/" + repositoryGroupConfiguration.getId() ) ) 945 { 946 File tmpDirectory = new File( SystemUtils.getJavaIoTmpDir(), 947 repositoryGroupConfiguration.getId() + "/" 948 + repositoryGroupConfiguration.getMergedIndexPath() ); 949 if ( !tmpDirectory.exists() ) 950 { 951 synchronized ( tmpDirectory.getAbsolutePath() ) 952 { 953 if ( !tmpDirectory.exists() ) 954 { 955 tmpDirectory.mkdirs(); 956 } 957 } 958 } 959 mergedRepositoryContents.add( tmpDirectory.getParentFile() ); 960 } 961 for ( String repository : repositories ) 962 { 963 ManagedRepositoryContent managedRepository = null; 964 965 try 966 { 967 managedRepository = repositoryFactory.getManagedRepositoryContent( repository ); 968 } 969 catch ( RepositoryNotFoundException e ) 970 { 971 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 972 "Invalid managed repository <" + repository + ">: " + e.getMessage() ); 973 } 974 catch ( RepositoryException e ) 975 { 976 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 977 "Invalid managed repository <" + repository + ">: " + e.getMessage() ); 978 } 979 980 File resourceFile = new File( managedRepository.getRepoRoot(), logicalResource.getPath() ); 981 if ( resourceFile.exists() ) 982 { 983 // in case of group displaying index directory doesn't have sense !! 984 String repoIndexDirectory = managedRepository.getRepository().getIndexDirectory(); 985 if ( StringUtils.isNotEmpty( repoIndexDirectory ) ) 986 { 987 if ( !new File( repoIndexDirectory ).isAbsolute() ) 988 { 989 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(), 990 StringUtils.isEmpty( repoIndexDirectory ) 991 ? ".indexer" 992 : repoIndexDirectory ).getAbsolutePath(); 993 } 994 } 995 if ( StringUtils.isEmpty( repoIndexDirectory ) ) 996 { 997 repoIndexDirectory = new File( managedRepository.getRepository().getLocation(), 998 ".indexer" ).getAbsolutePath(); 999 } 1000 1001 if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory ), 1002 FilenameUtils.normalize( resourceFile.getAbsolutePath() ) ) ) 1003 { 1004 // for prompted authentication 1005 if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null ) 1006 { 1007 try 1008 { 1009 if ( isAuthorized( request, repository ) ) 1010 { 1011 mergedRepositoryContents.add( resourceFile ); 1012 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal ); 1013 } 1014 } 1015 catch ( DavException e ) 1016 { 1017 // TODO: review exception handling 1018 1019 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository, 1020 activePrincipal, e.getMessage() ); 1021 1022 } 1023 1024 } 1025 else 1026 { 1027 // for the current user logged in 1028 try 1029 { 1030 if ( servletAuth.isAuthorized( activePrincipal, repository, 1031 WebdavMethodUtil.getMethodPermission( 1032 request.getMethod() ) ) ) 1033 { 1034 mergedRepositoryContents.add( resourceFile ); 1035 log.debug( "Repository '{}' accessed by '{}'", repository, activePrincipal ); 1036 } 1037 } 1038 catch ( UnauthorizedException e ) 1039 { 1040 // TODO: review exception handling 1041 1042 log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository, 1043 activePrincipal, e.getMessage() ); 1044 1045 } 1046 } 1047 } 1048 } 1049 } 1050 } 1051 } 1052 else 1053 { 1054 throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." ); 1055 } 1056 1057 ArchivaVirtualDavResource resource = 1058 new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator, 1059 this ); 1060 1061 // compatibility with MRM-440 to ensure browsing the repository group works ok 1062 if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) ) 1063 { 1064 throw new BrowserRedirectException( resource.getHref() ); 1065 } 1066 1067 return resource; 1068 } 1069 1070 protected String getActivePrincipal( DavServletRequest request ) 1071 { 1072 User sessionUser = httpAuth.getSessionUser( request.getSession() ); 1073 return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME; 1074 } 1075 1076 /** 1077 * Check if the current user is authorized to access any of the repos 1078 * 1079 * @param request 1080 * @param repositories 1081 * @param activePrincipal 1082 * @return 1083 */ 1084 private boolean isAllowedToContinue( DavServletRequest request, List<String> repositories, String activePrincipal ) 1085 { 1086 // when no repositories configured it's impossible to browse nothing ! 1087 // at least make possible to see nothing :-) 1088 if ( repositories == null || repositories.isEmpty() ) 1089 { 1090 return true; 1091 } 1092 1093 boolean allow = false; 1094 1095 // if securitySession != null, it means that the user was prompted for authentication 1096 if ( httpAuth.getSecuritySession( request.getSession() ) != null ) 1097 { 1098 for ( String repository : repositories ) 1099 { 1100 try 1101 { 1102 if ( isAuthorized( request, repository ) ) 1103 { 1104 allow = true; 1105 break; 1106 } 1107 } 1108 catch ( DavException e ) 1109 { 1110 continue; 1111 } 1112 } 1113 } 1114 else 1115 { 1116 for ( String repository : repositories ) 1117 { 1118 try 1119 { 1120 if ( servletAuth.isAuthorized( activePrincipal, repository, 1121 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) ) 1122 { 1123 allow = true; 1124 break; 1125 } 1126 } 1127 catch ( UnauthorizedException e ) 1128 { 1129 continue; 1130 } 1131 } 1132 } 1133 1134 return allow; 1135 } 1136 1137 private File writeMergedMetadataToFile( ArchivaRepositoryMetadata mergedMetadata, String outputFilename ) 1138 throws RepositoryMetadataException, DigesterException, IOException 1139 { 1140 File outputFile = new File( outputFilename ); 1141 if ( outputFile.exists() ) 1142 { 1143 FileUtils.deleteQuietly( outputFile ); 1144 } 1145 1146 outputFile.getParentFile().mkdirs(); 1147 RepositoryMetadataWriter.write( mergedMetadata, outputFile ); 1148 1149 createChecksumFile( outputFilename, digestSha1 ); 1150 createChecksumFile( outputFilename, digestMd5 ); 1151 1152 return outputFile; 1153 } 1154 1155 private void createChecksumFile( String path, Digester digester ) 1156 throws DigesterException, IOException 1157 { 1158 File checksumFile = new File( path + digester.getFilenameExtension() ); 1159 if ( !checksumFile.exists() ) 1160 { 1161 FileUtils.deleteQuietly( checksumFile ); 1162 checksum.createChecksum( new File( path ), digester ); 1163 } 1164 else if ( !checksumFile.isFile() ) 1165 { 1166 log.error( "Checksum file is not a file." ); 1167 } 1168 } 1169 1170 private boolean isProjectReference( String requestedResource ) 1171 { 1172 try 1173 { 1174 metadataTools.toVersionedReference( requestedResource ); 1175 return false; 1176 } 1177 catch ( RepositoryMetadataException re ) 1178 { 1179 return true; 1180 } 1181 } 1182 1183 protected File buildMergedIndexDirectory( List<String> repositories, String activePrincipal, 1184 DavServletRequest request, 1185 RepositoryGroupConfiguration repositoryGroupConfiguration ) 1186 throws DavException 1187 { 1188 1189 try 1190 { 1191 HttpSession session = request.getSession(); 1192 1193 Map<String, TemporaryGroupIndex> temporaryGroupIndexMap = 1194 (Map<String, TemporaryGroupIndex>) session.getAttribute( 1195 TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY ); 1196 if ( temporaryGroupIndexMap == null ) 1197 { 1198 temporaryGroupIndexMap = new HashMap<String, TemporaryGroupIndex>(); 1199 } 1200 1201 TemporaryGroupIndex tmp = temporaryGroupIndexMap.get( repositoryGroupConfiguration.getId() ); 1202 1203 if ( tmp != null && tmp.getDirectory() != null && tmp.getDirectory().exists() ) 1204 { 1205 if ( System.currentTimeMillis() - tmp.getCreationTime() > ( repositoryGroupConfiguration.getMergedIndexTtl() * 60 * 1000 ) ) 1206 { 1207 log.debug( MarkerFactory.getMarker( "group.merged.index" ), 1208 "tmp group index '{}' is too old so delete it", repositoryGroupConfiguration.getId() ); 1209 indexMerger.cleanTemporaryGroupIndex( tmp ); 1210 } 1211 else 1212 { 1213 log.debug( MarkerFactory.getMarker( "group.merged.index" ), 1214 "merged index for group '{}' found in cache", repositoryGroupConfiguration.getId() ); 1215 return tmp.getDirectory(); 1216 } 1217 } 1218 1219 Set<String> authzRepos = new HashSet<String>(); 1220 for ( String repository : repositories ) 1221 { 1222 try 1223 { 1224 if ( servletAuth.isAuthorized( activePrincipal, repository, 1225 WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) ) 1226 { 1227 authzRepos.add( repository ); 1228 authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository ) ); 1229 } 1230 } 1231 catch ( UnauthorizedException e ) 1232 { 1233 // TODO: review exception handling 1234 1235 log.debug( "Skipping repository '{}' for user '{}': {}", repository, activePrincipal, 1236 e.getMessage() ); 1237 } 1238 } 1239 log.info( "generate temporary merged index for repository group '{}' for repositories '{}'", 1240 repositoryGroupConfiguration.getId(), authzRepos ); 1241 IndexingContext indexingContext = indexMerger.buildMergedIndex( 1242 new IndexMergerRequest( authzRepos, true, repositoryGroupConfiguration.getId(), 1243 repositoryGroupConfiguration.getMergedIndexPath(), repositoryGroupConfiguration.getMergedIndexTtl() ) ); 1244 File mergedRepoDir = indexingContext.getIndexDirectoryFile(); 1245 TemporaryGroupIndex temporaryGroupIndex = new TemporaryGroupIndex( mergedRepoDir, indexingContext.getId(), 1246 repositoryGroupConfiguration.getId(),repositoryGroupConfiguration.getMergedIndexTtl() ) 1247 .setCreationTime(new Date().getTime() ); 1248 temporaryGroupIndexMap.put( repositoryGroupConfiguration.getId(), temporaryGroupIndex ); 1249 session.setAttribute( TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY, 1250 temporaryGroupIndexMap ); 1251 return mergedRepoDir; 1252 } 1253 catch ( RepositoryAdminException e ) 1254 { 1255 throw new DavException( 500, e ); 1256 } 1257 catch ( IndexMergerException e ) 1258 { 1259 throw new DavException( 500, e ); 1260 } 1261 } 1262 1263 1264 public void setServletAuth( ServletAuthenticator servletAuth ) 1265 { 1266 this.servletAuth = servletAuth; 1267 } 1268 1269 public void setHttpAuth( HttpAuthenticator httpAuth ) 1270 { 1271 this.httpAuth = httpAuth; 1272 } 1273 1274 public void setScheduler( RepositoryArchivaTaskScheduler scheduler ) 1275 { 1276 this.scheduler = scheduler; 1277 } 1278 1279 public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration ) 1280 { 1281 this.archivaConfiguration = archivaConfiguration; 1282 } 1283 1284 public void setRepositoryFactory( RepositoryContentFactory repositoryFactory ) 1285 { 1286 this.repositoryFactory = repositoryFactory; 1287 } 1288 1289 public void setRepositoryRequest( RepositoryRequest repositoryRequest ) 1290 { 1291 this.repositoryRequest = repositoryRequest; 1292 } 1293 1294 public void setConnectors( RepositoryProxyConnectors connectors ) 1295 { 1296 this.connectors = connectors; 1297 } 1298 1299 public RemoteRepositoryAdmin getRemoteRepositoryAdmin() 1300 { 1301 return remoteRepositoryAdmin; 1302 } 1303 1304 public void setRemoteRepositoryAdmin( RemoteRepositoryAdmin remoteRepositoryAdmin ) 1305 { 1306 this.remoteRepositoryAdmin = remoteRepositoryAdmin; 1307 } 1308 }