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