]> source.dussan.org Git - archiva.git/blob
a2c90ec3ce93eb5209d50db9a8b61fb68c9fe6b2
[archiva.git] /
1 package org.apache.archiva.webdav;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *  http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
23 import org.apache.archiva.admin.model.remote.RemoteRepositoryAdmin;
24 import org.apache.archiva.audit.Auditable;
25 import org.apache.archiva.checksum.ChecksumAlgorithm;
26 import org.apache.archiva.checksum.ChecksumUtil;
27 import org.apache.archiva.checksum.StreamingChecksum;
28 import org.apache.archiva.common.filelock.DefaultFileLockManager;
29 import org.apache.archiva.common.filelock.FileLockManager;
30 import org.apache.archiva.common.plexusbridge.PlexusSisuBridgeException;
31 import org.apache.archiva.common.utils.PathUtil;
32 import org.apache.archiva.common.utils.VersionUtil;
33 import org.apache.archiva.configuration.ArchivaConfiguration;
34 import org.apache.archiva.indexer.ArchivaIndexingContext;
35 import org.apache.archiva.indexer.merger.IndexMerger;
36 import org.apache.archiva.indexer.merger.IndexMergerException;
37 import org.apache.archiva.indexer.merger.IndexMergerRequest;
38 import org.apache.archiva.indexer.merger.base.MergedRemoteIndexesTask;
39 import org.apache.archiva.indexer.merger.base.MergedRemoteIndexesTaskRequest;
40 import org.apache.archiva.indexer.merger.TemporaryGroupIndex;
41 import org.apache.archiva.indexer.search.RepositorySearch;
42 import org.apache.archiva.indexer.search.RepositorySearchException;
43 import org.apache.archiva.metadata.model.facets.AuditEvent;
44 import org.apache.archiva.metadata.repository.storage.RelocationException;
45 import org.apache.archiva.metadata.repository.storage.RepositoryStorage;
46 import org.apache.archiva.model.ArchivaRepositoryMetadata;
47 import org.apache.archiva.model.ArtifactReference;
48 import org.apache.archiva.policies.ProxyDownloadException;
49 import org.apache.archiva.proxy.ProxyRegistry;
50 import org.apache.archiva.proxy.model.RepositoryProxyHandler;
51 import org.apache.archiva.redback.authentication.AuthenticationException;
52 import org.apache.archiva.redback.authentication.AuthenticationResult;
53 import org.apache.archiva.redback.authorization.AuthorizationException;
54 import org.apache.archiva.redback.authorization.UnauthorizedException;
55 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
56 import org.apache.archiva.redback.policy.AccountLockedException;
57 import org.apache.archiva.redback.policy.MustChangePasswordException;
58 import org.apache.archiva.redback.system.SecuritySession;
59 import org.apache.archiva.redback.users.User;
60 import org.apache.archiva.redback.users.UserManager;
61 import org.apache.archiva.repository.LayoutException;
62 import org.apache.archiva.repository.ManagedRepository;
63 import org.apache.archiva.repository.ManagedRepositoryContent;
64 import org.apache.archiva.repository.ReleaseScheme;
65 import org.apache.archiva.repository.RepositoryGroup;
66 import org.apache.archiva.repository.RepositoryRegistry;
67 import org.apache.archiva.repository.RepositoryRequestInfo;
68 import org.apache.archiva.repository.storage.FilesystemStorage;
69 import org.apache.archiva.repository.storage.StorageAsset;
70 import org.apache.archiva.metadata.audit.AuditListener;
71 import org.apache.archiva.repository.features.IndexCreationFeature;
72 import org.apache.archiva.repository.metadata.base.MetadataTools;
73 import org.apache.archiva.repository.metadata.RepositoryMetadataException;
74 import org.apache.archiva.repository.metadata.base.RepositoryMetadataMerge;
75 import org.apache.archiva.repository.metadata.base.RepositoryMetadataWriter;
76 import org.apache.archiva.scheduler.repository.model.RepositoryArchivaTaskScheduler;
77 import org.apache.archiva.security.ServletAuthenticator;
78 import org.apache.archiva.webdav.util.MimeTypes;
79 import org.apache.archiva.webdav.util.TemporaryGroupIndexSessionCleaner;
80 import org.apache.archiva.webdav.util.WebdavMethodUtil;
81 import org.apache.archiva.xml.XMLException;
82 import org.apache.commons.io.FilenameUtils;
83 import org.apache.commons.lang3.StringUtils;
84 import org.apache.commons.lang3.SystemUtils;
85 import org.apache.jackrabbit.webdav.DavException;
86 import org.apache.jackrabbit.webdav.DavResource;
87 import org.apache.jackrabbit.webdav.DavResourceFactory;
88 import org.apache.jackrabbit.webdav.DavResourceLocator;
89 import org.apache.jackrabbit.webdav.DavServletRequest;
90 import org.apache.jackrabbit.webdav.DavServletResponse;
91 import org.apache.jackrabbit.webdav.DavSession;
92 import org.apache.jackrabbit.webdav.lock.LockManager;
93 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
94 import org.slf4j.Logger;
95 import org.slf4j.LoggerFactory;
96 import org.slf4j.MarkerFactory;
97 import org.springframework.context.ApplicationContext;
98 import org.springframework.stereotype.Service;
99
100 import javax.annotation.PostConstruct;
101 import javax.inject.Inject;
102 import javax.inject.Named;
103 import javax.servlet.http.HttpServletResponse;
104 import javax.servlet.http.HttpSession;
105 import java.io.IOException;
106 import java.io.OutputStream;
107 import java.io.OutputStreamWriter;
108 import java.nio.file.Files;
109 import java.nio.file.Path;
110 import java.nio.file.Paths;
111 import java.util.ArrayList;
112 import java.util.Date;
113 import java.util.HashMap;
114 import java.util.HashSet;
115 import java.util.List;
116 import java.util.Map;
117 import java.util.Objects;
118 import java.util.Set;
119 import java.util.stream.Collectors;
120
121 /**
122  *
123  */
124 @Service( "davResourceFactory#archiva" )
125 public class ArchivaDavResourceFactory
126     implements DavResourceFactory, Auditable
127 {
128     private static final String PROXIED_SUFFIX = " (proxied)";
129
130     private static final String HTTP_PUT_METHOD = "PUT";
131
132     private Logger log = LoggerFactory.getLogger( ArchivaDavResourceFactory.class );
133
134     @Inject
135     private List<AuditListener> auditListeners = new ArrayList<>();
136
137     @Inject
138     private ProxyRegistry proxyRegistry;
139
140     @Inject
141     private MetadataTools metadataTools;
142
143     @Inject
144     private MimeTypes mimeTypes;
145
146     private ArchivaConfiguration archivaConfiguration;
147
148     @Inject
149     private ServletAuthenticator servletAuth;
150
151     @Inject
152     @Named( value = "httpAuthenticator#basic" )
153     private HttpAuthenticator httpAuth;
154
155     @Inject
156     private RemoteRepositoryAdmin remoteRepositoryAdmin;
157
158     @Inject
159     private ManagedRepositoryAdmin managedRepositoryAdmin;
160
161     @Inject
162     private RepositoryRegistry repositoryRegistry;
163
164     @Inject
165     private IndexMerger indexMerger;
166
167     @Inject
168     private RepositorySearch repositorySearch;
169
170     /**
171      * Lock Manager - use simple implementation from JackRabbit
172      */
173     private final LockManager lockManager = new SimpleLockManager();
174
175     @Inject
176     @Named( value = "archivaTaskScheduler#repository" )
177     private RepositoryArchivaTaskScheduler scheduler;
178
179     @Inject
180     @Named( value = "fileLockManager#default" )
181     private FileLockManager fileLockManager;
182
183     private ApplicationContext applicationContext;
184
185
186     @Inject
187     public ArchivaDavResourceFactory( ApplicationContext applicationContext, ArchivaConfiguration archivaConfiguration )
188         throws PlexusSisuBridgeException
189     {
190         this.archivaConfiguration = archivaConfiguration;
191         this.applicationContext = applicationContext;
192
193     }
194
195     @PostConstruct
196     public void initialize() throws IOException
197     {
198
199     }
200
201
202     @Override
203     public DavResource createResource( final DavResourceLocator locator, final DavServletRequest request,
204                                        final DavServletResponse response )
205         throws DavException
206     {
207         final ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
208
209         final String sRepoId = archivaLocator.getRepositoryId();
210
211         RepositoryGroup repoGroup = repositoryRegistry.getRepositoryGroup(sRepoId);
212
213         final boolean isGroupRepo = repoGroup != null;
214
215         String activePrincipal = getActivePrincipal( request );
216
217         List<String> resourcesInAbsolutePath = new ArrayList<>();
218
219         boolean readMethod = WebdavMethodUtil.isReadMethod( request.getMethod() );
220         RepositoryRequestInfo repositoryRequestInfo = null;
221         DavResource resource;
222         if ( isGroupRepo )
223         {
224             if ( !readMethod )
225             {
226                 throw new DavException( HttpServletResponse.SC_METHOD_NOT_ALLOWED,
227                                         "Write method not allowed for repository groups." );
228             }
229
230             log.debug( "Repository group '{}' accessed by '{}", repoGroup.getId(), activePrincipal );
231
232             // handle browse requests for virtual repos
233             if ( getLogicalResource( archivaLocator, null, true ).endsWith( "/" ) )
234             {
235                 DavResource davResource =
236                     getResourceFromGroup( request, archivaLocator,
237                                           repoGroup );
238
239                 setHeaders( response, locator, davResource, true );
240
241                 return davResource;
242
243             }
244             else
245             {
246                 // make a copy to avoid potential concurrent modifications (eg. by configuration)
247                 // TODO: ultimately, locking might be more efficient than copying in this fashion since updates are
248                 //  infrequent
249                 resource = processRepositoryGroup( request, archivaLocator, activePrincipal,
250                                                    resourcesInAbsolutePath, repoGroup );
251                 for (ManagedRepository repo : repoGroup.getRepositories() ) {
252                     if (repo!=null) {
253                         repositoryRequestInfo = repo.getRequestInfo();
254                         break;
255                     }
256                 }
257             }
258         }
259         else
260         {
261
262             // We do not provide folders for remote repositories
263
264
265             ManagedRepository repo = repositoryRegistry.getManagedRepository( sRepoId );
266             if (repo==null) {
267                 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
268                     "Invalid repository: " + archivaLocator.getRepositoryId() );
269             }
270             ManagedRepositoryContent managedRepositoryContent = repo.getContent( );
271             if (managedRepositoryContent==null) {
272                 log.error("Inconsistency detected. Repository content not found for '{}'", archivaLocator.getRepositoryId());
273                 throw new DavException( HttpServletResponse.SC_NOT_FOUND,
274                     "Invalid repository: " + archivaLocator.getRepositoryId() );
275             }
276
277             log.debug( "Managed repository '{}' accessed by '{}'", managedRepositoryContent.getId(), activePrincipal );
278
279             resource = processRepository( request, archivaLocator, activePrincipal, managedRepositoryContent,
280                                           repo);
281             repositoryRequestInfo = repo.getRequestInfo();
282             String logicalResource = getLogicalResource( archivaLocator, null, false );
283             resourcesInAbsolutePath.add(
284                 Paths.get( managedRepositoryContent.getRepoRoot(), logicalResource ).toAbsolutePath().toString() );
285
286         }
287
288         String requestedResource = request.getRequestURI();
289
290         // MRM-872 : merge all available metadata
291         // merge metadata only when requested via the repo group
292         if ( ( repositoryRequestInfo.isMetadata( requestedResource ) || repositoryRequestInfo.isMetadataSupportFile(
293             requestedResource ) ) && isGroupRepo )
294         {
295             // this should only be at the project level not version level!
296             if ( isProjectReference( requestedResource ) )
297             {
298
299                 ArchivaDavResource res = (ArchivaDavResource) resource;
300                 String newPath;
301                 if (res.getAsset().hasParent())
302                 {
303                     newPath = res.getAsset( ).getParent( ).getPath( ) + "/maven-metadata-" + sRepoId + ".xml";
304                 } else {
305                     newPath = StringUtils.substringBeforeLast( res.getAsset().getPath(), "/" ) + "/maven-metadata-" + sRepoId + ".xml";;
306                 }
307                 // for MRM-872 handle checksums of the merged metadata files
308                 if ( repositoryRequestInfo.isSupportFile( requestedResource ) )
309                 {
310                     String metadataChecksumPath = newPath + "." + StringUtils.substringAfterLast( requestedResource, "." );
311                     StorageAsset metadataChecksum = repoGroup.getAsset( metadataChecksumPath );
312                     if ( repoGroup.getAsset( metadataChecksumPath ).exists() )
313                     {
314                         LogicalResource logicalResource =
315                             new LogicalResource( getLogicalResource( archivaLocator, null, false ) );
316
317                         try
318                         {
319                             resource =
320                                 new ArchivaDavResource( metadataChecksum, logicalResource.getPath(), repoGroup,
321                                                         request.getRemoteAddr(), activePrincipal, request.getDavSession(),
322                                                         archivaLocator, this, mimeTypes, auditListeners, scheduler);
323                         }
324                         catch ( LayoutException e )
325                         {
326                             log.error("Incompatible layout: {}", e.getMessage(), e);
327                             throw new DavException( 500, e );
328                         }
329                     }
330                 }
331                 else
332                 {
333                     if ( resourcesInAbsolutePath != null && resourcesInAbsolutePath.size() > 1 )
334                     {
335                         // merge the metadata of all repos under group
336                         ArchivaRepositoryMetadata mergedMetadata = new ArchivaRepositoryMetadata();
337                         for ( String resourceAbsPath : resourcesInAbsolutePath )
338                         {
339                             try
340                             {
341                                 Path metadataFile = Paths.get( resourceAbsPath );
342                                 FilesystemStorage storage = new FilesystemStorage( metadataFile.getParent( ), new DefaultFileLockManager( ) );
343                                 ArchivaRepositoryMetadata repoMetadata = repositoryRegistry.getMetadataReader( repoGroup.getType( ) ).read( storage.getAsset( metadataFile.getFileName().toString() ) );
344                                 mergedMetadata = RepositoryMetadataMerge.merge( mergedMetadata, repoMetadata );
345                             }
346                             catch ( RepositoryMetadataException r )
347                             {
348                                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
349                                                         "Error occurred while merging metadata file." );
350                             }
351                             catch ( IOException e )
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                     catch ( org.apache.archiva.repository.ContentAccessException e )
672                     {
673                         e.printStackTrace( );
674                     }
675                 }
676
677                 /*
678                  * Create parent directories that don't exist when writing a file This actually makes this
679                  * implementation not compliant to the WebDAV RFC - but we have enough knowledge about how the
680                  * collection is being used to do this reasonably and some versions of Maven's WebDAV don't correctly
681                  * create the collections themselves.
682                  */
683
684                 Path rootDirectory = Paths.get( managedRepositoryContent.getRepoRoot() );
685                 Path destDir = rootDirectory.resolve( logicalResource.getPath() ).getParent();
686
687                 if ( !Files.exists(destDir) )
688                 {
689                     try
690                     {
691                         Files.createDirectories( destDir );
692                     }
693                     catch ( IOException e )
694                     {
695                         log.error("Could not create directory {}: {}", destDir, e.getMessage(), e);
696                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create directory "+destDir );
697                     }
698                     String relPath = PathUtil.getRelative( rootDirectory.toAbsolutePath().toString(), destDir );
699
700                     log.debug( "Creating destination directory '{}' (current user '{}')", destDir.getFileName(),
701                                activePrincipal );
702
703                     triggerAuditEvent( request.getRemoteAddr(), managedRepositoryContent.getId(), relPath,
704                                        AuditEvent.CREATE_DIR, activePrincipal );
705                 }
706             }
707         }
708         return resource;
709     }
710
711     @Override
712     public DavResource createResource( final DavResourceLocator locator, final DavSession davSession )
713         throws DavException
714     {
715         ArchivaDavResourceLocator archivaLocator = checkLocatorIsInstanceOfRepositoryLocator( locator );
716
717         ManagedRepositoryContent managedRepositoryContent;
718         ManagedRepository repo = repositoryRegistry.getManagedRepository( archivaLocator.getRepositoryId( ) );
719         if (repo==null) {
720             throw new DavException( HttpServletResponse.SC_NOT_FOUND,
721                 "Invalid repository: " + archivaLocator.getRepositoryId() );
722         }
723         managedRepositoryContent = repo.getContent();
724         if (managedRepositoryContent==null) {
725             log.error("Inconsistency detected. Repository content not found for '{}'", archivaLocator.getRepositoryId());
726             throw new DavException( HttpServletResponse.SC_NOT_FOUND,
727                 "Invalid repository: " + archivaLocator.getRepositoryId() );
728         }
729
730         DavResource resource = null;
731         String logicalResource = getLogicalResource( archivaLocator, repo, false );
732         if ( logicalResource.startsWith( "/" ) )
733         {
734             logicalResource = logicalResource.substring( 1 );
735         }
736         StorageAsset resourceAsset = repo.getAsset( logicalResource );
737         try
738         {
739             resource = new ArchivaDavResource( resourceAsset, logicalResource,
740                                                repo, davSession, archivaLocator,
741                                                this, mimeTypes, auditListeners, scheduler);
742         }
743         catch ( LayoutException e )
744         {
745             log.error( "Incompatible layout: {}", e.getMessage( ), e );
746             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
747         }
748
749         resource.addLockManager( lockManager );
750         return resource;
751     }
752
753     private boolean fetchContentFromProxies( ManagedRepository managedRepository, DavServletRequest request,
754                                              LogicalResource resource )
755         throws DavException
756     {
757         String path = resource.getPath();
758         if (!proxyRegistry.hasHandler(managedRepository.getType())) {
759             throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "No proxy handler found for repository type "+managedRepository.getType());
760         }
761         RepositoryRequestInfo repositoryRequestInfo = managedRepository.getRequestInfo();
762         RepositoryProxyHandler proxyHandler = proxyRegistry.getHandler(managedRepository.getType()).get(0);
763         if ( repositoryRequestInfo.isSupportFile( path ) )
764         {
765             StorageAsset proxiedFile = proxyHandler.fetchFromProxies( managedRepository, path );
766
767             return ( proxiedFile != null );
768         }
769
770         // Is it a Metadata resource?
771         if ( "default".equals(repositoryRequestInfo.getLayout( path )) && repositoryRequestInfo.isMetadata( path ) )
772         {
773             return proxyHandler.fetchMetadataFromProxies( managedRepository, path ).isModified();
774         }
775
776         // Is it an Archetype Catalog?
777         if ( repositoryRequestInfo.isArchetypeCatalog( path ) )
778         {
779             // FIXME we must implement a merge of remote archetype catalog from remote servers.
780             StorageAsset proxiedFile = proxyHandler.fetchFromProxies( managedRepository, path );
781
782             return ( proxiedFile != null );
783         }
784
785         // Not any of the above? Then it's gotta be an artifact reference.
786         try
787         {
788             // Get the artifact reference in a layout neutral way.
789             ArtifactReference artifact = repositoryRequestInfo.toArtifactReference( path );
790
791             if ( artifact != null )
792             {
793                 String repositoryLayout = managedRepository.getLayout();
794
795                 RepositoryStorage repositoryStorage =
796                     this.applicationContext.getBean( "repositoryStorage#" + repositoryLayout, RepositoryStorage.class );
797                 repositoryStorage.applyServerSideRelocation( managedRepository, artifact );
798
799                 StorageAsset proxiedFile = proxyHandler.fetchFromProxies( managedRepository, artifact );
800
801                 resource.setPath( managedRepository.getContent().toPath( artifact ) );
802
803                 log.debug( "Proxied artifact '{}:{}:{}'", artifact.getGroupId(), artifact.getArtifactId(),
804                            artifact.getVersion() );
805
806                 return ( proxiedFile != null );
807             }
808         }
809         catch ( LayoutException e )
810         {
811             /* eat it */
812         }
813         catch ( ProxyDownloadException e )
814         {
815             log.error( e.getMessage(), e );
816             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
817                                     "Unable to fetch artifact resource." );
818         }
819         return false;
820     }
821
822     // TODO: remove?
823
824     private void triggerAuditEvent( String remoteIP, String repositoryId, String resource, String action,
825                                     String principal )
826     {
827         AuditEvent event = new AuditEvent( repositoryId, principal, resource, action );
828         event.setRemoteIP( remoteIP );
829
830         for ( AuditListener listener : auditListeners )
831         {
832             listener.auditEvent( event );
833         }
834     }
835
836     @Override
837     public void addAuditListener( AuditListener listener )
838     {
839         this.auditListeners.add( listener );
840     }
841
842     @Override
843     public void clearAuditListeners()
844     {
845         this.auditListeners.clear();
846     }
847
848     @Override
849     public void removeAuditListener( AuditListener listener )
850     {
851         this.auditListeners.remove( listener );
852     }
853
854     private void setHeaders( DavServletResponse response, DavResourceLocator locator, DavResource resource,
855                              boolean group )
856     {
857         // [MRM-503] - Metadata file need Pragma:no-cache response
858         // header.
859         if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || ( resource instanceof ArchivaDavResource
860             && ( ArchivaDavResource.class.cast( resource ).getAsset().isContainer() ) ) )
861         {
862             response.setHeader( "Pragma", "no-cache" );
863             response.setHeader( "Cache-Control", "no-cache" );
864             response.setDateHeader( "Last-Modified", new Date().getTime() );
865         }
866         // if the resource is a directory don't cache it as new groupId deployed will be available
867         // without need of refreshing browser
868         else if ( locator.getResourcePath().endsWith( "/maven-metadata.xml" ) || (
869             resource instanceof ArchivaVirtualDavResource && ( Files.isDirectory(Paths.get(
870                 ArchivaVirtualDavResource.class.cast( resource ).getLogicalResource() )) ) ) )
871         {
872             response.setHeader( "Pragma", "no-cache" );
873             response.setHeader( "Cache-Control", "no-cache" );
874             response.setDateHeader( "Last-Modified", new Date().getTime() );
875         }
876         else if ( group )
877         {
878             if ( resource instanceof ArchivaVirtualDavResource )
879             {
880                 //MRM-1854 here we have a directory so force "Last-Modified"
881                 response.setDateHeader( "Last-Modified", new Date().getTime() );
882             }
883         }
884         else
885         {
886             // We need to specify this so connecting wagons can work correctly
887             response.setDateHeader( "Last-Modified", resource.getModificationTime() );
888         }
889         // TODO: [MRM-524] determine http caching options for other types of files (artifacts, sha1, md5, snapshots)
890     }
891
892     private ArchivaDavResourceLocator checkLocatorIsInstanceOfRepositoryLocator( DavResourceLocator locator )
893         throws DavException
894     {
895         if ( !( locator instanceof ArchivaDavResourceLocator ) )
896         {
897             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
898                                     "Locator does not implement RepositoryLocator" );
899         }
900
901         // Hidden paths
902         if ( locator.getResourcePath().startsWith( ArchivaDavResource.HIDDEN_PATH_PREFIX ) )
903         {
904             throw new DavException( HttpServletResponse.SC_NOT_FOUND );
905         }
906
907         ArchivaDavResourceLocator archivaLocator = (ArchivaDavResourceLocator) locator;
908
909         // MRM-419 - Windows Webdav support. Should not 404 if there is no content.
910         if ( StringUtils.isEmpty( archivaLocator.getRepositoryId() ) )
911         {
912             throw new DavException( HttpServletResponse.SC_NO_CONTENT );
913         }
914         return archivaLocator;
915     }
916
917     private String addHrefPrefix( String contextPath, String path ) {
918         String prefix = archivaConfiguration.getConfiguration().getWebapp().getUi().getApplicationUrl();
919         if (prefix == null || prefix.isEmpty()) {
920             prefix = contextPath;
921         }
922         return prefix + ( StringUtils.startsWith( path, "/" ) ? "" :
923                         ( StringUtils.endsWith( prefix, "/" ) ? "" : "/" ) )
924                       + path;
925     }
926
927     public void setProxyRegistry(ProxyRegistry proxyRegistry) {
928         this.proxyRegistry = proxyRegistry;
929     }
930
931     public ProxyRegistry getProxyRegistry() {
932         return this.proxyRegistry;
933     }
934
935     private static class LogicalResource
936     {
937         private String path;
938
939         public LogicalResource( String path )
940         {
941             this.path = path;
942         }
943
944         public String getPath()
945         {
946             return path;
947         }
948
949         public void setPath( String path )
950         {
951             this.path = path;
952         }
953     }
954
955     protected boolean isAuthorized( DavServletRequest request, String repositoryId )
956         throws DavException
957     {
958         try
959         {
960             AuthenticationResult result = httpAuth.getAuthenticationResult( request, null );
961             SecuritySession securitySession = httpAuth.getSecuritySession( request.getSession( true ) );
962
963             return servletAuth.isAuthenticated( request, result ) //
964                 && servletAuth.isAuthorized( request, securitySession, repositoryId, //
965                                              WebdavMethodUtil.getMethodPermission( request.getMethod() ) );
966         }
967         catch ( AuthenticationException e )
968         {
969             // safety check for MRM-911
970             String guest = UserManager.GUEST_USERNAME;
971             try
972             {
973                 if ( servletAuth.isAuthorized( guest,
974                                                ( (ArchivaDavResourceLocator) request.getRequestLocator() ).getRepositoryId(),
975                                                WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
976                 {
977                     return true;
978                 }
979             }
980             catch ( UnauthorizedException ae )
981             {
982                 throw new UnauthorizedDavException( repositoryId,
983                                                     "You are not authenticated and authorized to access any repository." );
984             }
985
986             throw new UnauthorizedDavException( repositoryId, "You are not authenticated" );
987         }
988         catch ( MustChangePasswordException e )
989         {
990             throw new UnauthorizedDavException( repositoryId, "You must change your password." );
991         }
992         catch ( AccountLockedException e )
993         {
994             throw new UnauthorizedDavException( repositoryId, "User account is locked." );
995         }
996         catch ( AuthorizationException e )
997         {
998             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
999                                     "Fatal Authorization Subsystem Error." );
1000         }
1001         catch ( UnauthorizedException e )
1002         {
1003             throw new UnauthorizedDavException( repositoryId, e.getMessage() );
1004         }
1005     }
1006
1007     private DavResource getResourceFromGroup( DavServletRequest request,
1008                                               ArchivaDavResourceLocator locator,
1009                                               RepositoryGroup repositoryGroup )
1010         throws DavException
1011     {
1012         final String id = repositoryGroup.getId();
1013         final List<ManagedRepository> repositories = repositoryGroup.getRepositories();
1014         if ( repositories == null
1015             || repositories.isEmpty() )
1016         {
1017             try {
1018                 return new ArchivaDavResource( repositoryGroup.getAsset("/"), "groups/" + id, null,
1019                                                request.getDavSession(), locator, this, mimeTypes, auditListeners, scheduler);
1020             } catch (LayoutException e) {
1021                 log.error("Bad repository layout: {}", e.getMessage(), e);
1022                 throw new DavException(500, e);
1023             }
1024         }
1025         List<StorageAsset> mergedRepositoryContents = new ArrayList<>();
1026
1027         ManagedRepository firstRepo = repositories.get( 0 );
1028
1029         String path = getLogicalResource( locator, firstRepo, false );
1030         if ( path.startsWith( "/" ) )
1031         {
1032             path = path.substring( 1 );
1033         }
1034         LogicalResource logicalResource = new LogicalResource( path );
1035
1036         // flow:
1037         // if the current user logged in has permission to any of the repositories, allow user to
1038         // browse the repo group but displaying only the repositories which the user has permission to access.
1039         // otherwise, prompt for authentication.
1040
1041         String activePrincipal = getActivePrincipal( request );
1042
1043         boolean allow = isAllowedToContinue( request, repositories, activePrincipal );
1044
1045         // remove last /
1046         String pathInfo = StringUtils.removeEnd( request.getPathInfo(), "/" );
1047         String mergedIndexPath = "/";
1048         if (repositoryGroup.supportsFeature( IndexCreationFeature.class )) {
1049             IndexCreationFeature indexCreationFeature = repositoryGroup.getFeature( IndexCreationFeature.class ).get();
1050             mergedIndexPath = indexCreationFeature.getIndexPath().getPath();
1051         }
1052
1053         if ( allow )
1054         {
1055
1056             if ( StringUtils.endsWith( pathInfo, mergedIndexPath ) )
1057             {
1058                 StorageAsset mergedRepoDirPath =
1059                     buildMergedIndexDirectory( activePrincipal, request, repositoryGroup );
1060                 mergedRepositoryContents.add( mergedRepoDirPath );
1061             }
1062             else
1063             {
1064                 if ( StringUtils.equalsIgnoreCase( pathInfo, "/" + id ) )
1065                 {
1066                     Path tmpDirectory = Paths.get( SystemUtils.getJavaIoTmpDir().toString(),
1067                                                   id,
1068                                                       mergedIndexPath );
1069                     if ( !Files.exists(tmpDirectory) )
1070                     {
1071                         synchronized ( tmpDirectory.toAbsolutePath().toString() )
1072                         {
1073                             if ( !Files.exists(tmpDirectory) )
1074                             {
1075                                 try
1076                                 {
1077                                     Files.createDirectories( tmpDirectory );
1078                                 }
1079                                 catch ( IOException e )
1080                                 {
1081                                     throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create direcotory "+tmpDirectory );
1082                                 }
1083                             }
1084                         }
1085                     }
1086                     try {
1087                         FilesystemStorage storage = new FilesystemStorage(tmpDirectory.getParent(), new DefaultFileLockManager());
1088                         mergedRepositoryContents.add( storage.getAsset("") );
1089                     } catch (IOException e) {
1090                         throw new DavException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create storage for " + tmpDirectory);
1091                     }
1092                 }
1093                 for ( ManagedRepository repo : repositories )
1094                 {
1095                     ManagedRepositoryContent managedRepository = null;
1096                     if (repo == null) {
1097                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1098                             "Invalid managed repository <" + repo.getId() + ">");
1099                     }
1100                     managedRepository = repo.getContent();
1101                     if (managedRepository==null) {
1102                         log.error("Inconsistency detected. Repository content not found for '{}'",repo.getId());
1103                         throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
1104                             "Invalid managed repository <" + repo.getId() + ">");
1105                     }
1106                     // Path resourceFile = Paths.get( managedRepository.getRepoRoot(), logicalResource.getPath() );
1107                     StorageAsset resourceFile = repo.getAsset(logicalResource.getPath());
1108                     if ( resourceFile.exists() && managedRepository.getRepository().supportsFeature( IndexCreationFeature.class ))
1109                     {
1110                         // in case of group displaying index directory doesn't have sense !!
1111                         IndexCreationFeature idf = managedRepository.getRepository().getFeature(IndexCreationFeature.class).get();
1112                         StorageAsset repoIndexDirectory = idf.getLocalIndexPath();
1113                         if ( !StringUtils.equals( FilenameUtils.normalize( repoIndexDirectory.getPath() ),
1114                                                   FilenameUtils.normalize( logicalResource.getPath() ) ) )
1115                         {
1116                             // for prompted authentication
1117                             if ( httpAuth.getSecuritySession( request.getSession( true ) ) != null )
1118                             {
1119                                 try
1120                                 {
1121                                     if ( isAuthorized( request, repo.getId() ) )
1122                                     {
1123                                         mergedRepositoryContents.add( resourceFile );
1124                                         log.debug( "Repository '{}' accessed by '{}'", repo.getId(), activePrincipal );
1125                                     }
1126                                 }
1127                                 catch ( DavException e )
1128                                 {
1129                                     // TODO: review exception handling
1130
1131                                     log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1132                                                activePrincipal, e.getMessage() );
1133
1134                                 }
1135
1136                             }
1137                             else
1138                             {
1139                                 // for the current user logged in
1140                                 try
1141                                 {
1142                                     if ( servletAuth.isAuthorized( activePrincipal, repo.getId(),
1143                                                                    WebdavMethodUtil.getMethodPermission(
1144                                                                        request.getMethod() ) ) )
1145                                     {
1146                                         mergedRepositoryContents.add( resourceFile );
1147                                         log.debug( "Repository '{}' accessed by '{}'", repo.getId(), activePrincipal );
1148                                     }
1149                                 }
1150                                 catch ( UnauthorizedException e )
1151                                 {
1152                                     // TODO: review exception handling
1153
1154                                     log.debug( "Skipping repository '{}' for user '{}': {}", managedRepository,
1155                                                activePrincipal, e.getMessage() );
1156
1157                                 }
1158                             }
1159                         }
1160                     }
1161                 }
1162             }
1163         }
1164         else
1165         {
1166             throw new UnauthorizedDavException( locator.getRepositoryId(), "User not authorized." );
1167         }
1168
1169         ArchivaVirtualDavResource resource =
1170             new ArchivaVirtualDavResource( mergedRepositoryContents, logicalResource.getPath(), mimeTypes, locator,
1171                                            this );
1172
1173         // compatibility with MRM-440 to ensure browsing the repository group works ok
1174         if ( resource.isCollection() && !request.getRequestURI().endsWith( "/" ) )
1175         {
1176             throw new BrowserRedirectException( resource.getHref() );
1177         }
1178
1179         return resource;
1180     }
1181
1182     protected String getActivePrincipal( DavServletRequest request )
1183     {
1184         User sessionUser = httpAuth.getSessionUser( request.getSession() );
1185         return sessionUser != null ? sessionUser.getUsername() : UserManager.GUEST_USERNAME;
1186     }
1187
1188     /**
1189      * Check if the current user is authorized to access any of the repos
1190      *
1191      * @param request
1192      * @param repositories
1193      * @param activePrincipal
1194      * @return
1195      */
1196     private boolean isAllowedToContinue( DavServletRequest request, List<ManagedRepository> repositories, String activePrincipal )
1197     {
1198         // when no repositories configured it's impossible to browse nothing !
1199         // at least make possible to see nothing :-)
1200         if ( repositories == null || repositories.isEmpty() )
1201         {
1202             return true;
1203         }
1204
1205         boolean allow = false;
1206
1207         // if securitySession != null, it means that the user was prompted for authentication
1208         if ( httpAuth.getSecuritySession( request.getSession() ) != null )
1209         {
1210             for ( ManagedRepository repository : repositories )
1211             {
1212                 try
1213                 {
1214                     if ( isAuthorized( request, repository.getId() ) )
1215                     {
1216                         allow = true;
1217                         break;
1218                     }
1219                 }
1220                 catch ( DavException e )
1221                 {
1222                     continue;
1223                 }
1224             }
1225         }
1226         else
1227         {
1228             for ( ManagedRepository repository : repositories )
1229             {
1230                 try
1231                 {
1232                     if ( servletAuth.isAuthorized( activePrincipal, repository.getId(),
1233                                                    WebdavMethodUtil.getMethodPermission( request.getMethod() ) ) )
1234                     {
1235                         allow = true;
1236                         break;
1237                     }
1238                 }
1239                 catch ( UnauthorizedException e )
1240                 {
1241                     continue;
1242                 }
1243             }
1244         }
1245
1246         return allow;
1247     }
1248
1249     private StorageAsset writeMergedMetadataToFile( RepositoryGroup repoGroup, ArchivaRepositoryMetadata mergedMetadata, String outputFilename )
1250         throws RepositoryMetadataException, IOException
1251     {
1252         StorageAsset asset = repoGroup.addAsset( outputFilename, false );
1253         OutputStream stream = asset.getWriteStream( true );
1254         OutputStreamWriter sw = new OutputStreamWriter( stream, "UTF-8" );
1255         RepositoryMetadataWriter.write( mergedMetadata, sw );
1256
1257         createChecksumFiles( repoGroup, outputFilename );
1258         return asset;
1259     }
1260
1261
1262     private void createChecksumFiles(RepositoryGroup repo, String path) {
1263         List<ChecksumAlgorithm> algorithms = ChecksumUtil.getAlgorithms( archivaConfiguration.getConfiguration( ).getArchivaRuntimeConfiguration( ).getChecksumTypes( ) );
1264         List<OutputStream> outStreams = algorithms.stream( ).map( algo -> {
1265             String ext = algo.getDefaultExtension( );
1266             try
1267             {
1268                 return repo.getAsset( path + "." + ext ).getWriteStream( true );
1269             }
1270             catch ( IOException e )
1271             {
1272                 e.printStackTrace( );
1273                 return null;
1274             }
1275         } ).filter( Objects::nonNull ).collect( Collectors.toList( ) );
1276         try
1277         {
1278             StreamingChecksum.updateChecksums( repo.getAsset(path).getReadStream(), algorithms, outStreams );
1279         }
1280         catch ( IOException e )
1281         {
1282             e.printStackTrace( );
1283         }
1284     }
1285
1286
1287
1288     private boolean isProjectReference( String requestedResource )
1289     {
1290         try
1291         {
1292             metadataTools.toVersionedReference( requestedResource );
1293             return false;
1294         }
1295         catch ( RepositoryMetadataException re )
1296         {
1297             return true;
1298         }
1299     }
1300
1301     protected StorageAsset buildMergedIndexDirectory( String activePrincipal,
1302                                               DavServletRequest request,
1303                                               RepositoryGroup repositoryGroup )
1304         throws DavException
1305     {
1306
1307         try
1308         {
1309             final List<ManagedRepository> repositories = repositoryGroup.getRepositories();
1310             HttpSession session = request.getSession();
1311
1312             @SuppressWarnings( "unchecked" ) Map<String, TemporaryGroupIndex> temporaryGroupIndexMap =
1313                 (Map<String, TemporaryGroupIndex>) session.getAttribute(
1314                     TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY );
1315             if ( temporaryGroupIndexMap == null )
1316             {
1317                 temporaryGroupIndexMap = new HashMap<>();
1318             }
1319
1320             final String id = repositoryGroup.getId();
1321             TemporaryGroupIndex tmp = temporaryGroupIndexMap.get(id);
1322
1323             if ( tmp != null && tmp.getDirectory() != null && tmp.getDirectory().exists())
1324             {
1325                 if ( System.currentTimeMillis() - tmp.getCreationTime() > (
1326                     repositoryGroup.getMergedIndexTTL() * 60 * 1000 ) )
1327                 {
1328                     log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1329                                "tmp group index '{}' is too old so delete it", id);
1330                     indexMerger.cleanTemporaryGroupIndex( tmp );
1331                 }
1332                 else
1333                 {
1334                     log.debug( MarkerFactory.getMarker( "group.merged.index" ),
1335                                "merged index for group '{}' found in cache", id);
1336                     return tmp.getDirectory();
1337                 }
1338             }
1339
1340             Set<String> authzRepos = new HashSet<String>();
1341
1342             String permission = WebdavMethodUtil.getMethodPermission( request.getMethod() );
1343
1344             for ( ManagedRepository repository : repositories )
1345             {
1346                 try
1347                 {
1348                     if ( servletAuth.isAuthorized( activePrincipal, repository.getId(), permission ) )
1349                     {
1350                         authzRepos.add( repository.getId() );
1351                         authzRepos.addAll( this.repositorySearch.getRemoteIndexingContextIds( repository.getId() ) );
1352                     }
1353                 }
1354                 catch ( UnauthorizedException e )
1355                 {
1356                     // TODO: review exception handling
1357
1358                     log.debug( "Skipping repository '{}' for user '{}': {}", repository, activePrincipal,
1359                                e.getMessage() );
1360                 }
1361
1362             }
1363
1364             log.info( "generate temporary merged index for repository group '{}' for repositories '{}'",
1365                     id, authzRepos );
1366
1367             IndexCreationFeature indexCreationFeature = repositoryGroup.getFeature( IndexCreationFeature.class ).get();
1368             Path indexPath = indexCreationFeature.getLocalIndexPath().getFilePath();
1369             if (indexPath!=null)
1370             {
1371                 Path tempRepoFile = Files.createTempDirectory( "temp" );
1372                 tempRepoFile.toFile( ).deleteOnExit( );
1373                 FilesystemStorage storage = new FilesystemStorage(tempRepoFile, new DefaultFileLockManager());
1374                 StorageAsset tmpAsset = storage.getAsset("");
1375
1376                 IndexMergerRequest indexMergerRequest =
1377                     new IndexMergerRequest( authzRepos, true, id,
1378                         indexPath.toString( ),
1379                         repositoryGroup.getMergedIndexTTL( ) ).mergedIndexDirectory(
1380                         tmpAsset ).temporary( true );
1381
1382                 MergedRemoteIndexesTaskRequest taskRequest =
1383                     new MergedRemoteIndexesTaskRequest( indexMergerRequest, indexMerger );
1384
1385                 MergedRemoteIndexesTask job = new MergedRemoteIndexesTask( taskRequest );
1386
1387                 ArchivaIndexingContext indexingContext = job.execute( ).getIndexingContext( );
1388
1389                 StorageAsset mergedRepoDir = indexingContext.getPath( );
1390                 TemporaryGroupIndex temporaryGroupIndex =
1391                     new TemporaryGroupIndex( mergedRepoDir, indexingContext.getId( ), id,
1392                         repositoryGroup.getMergedIndexTTL( ) ) //
1393                         .setCreationTime( new Date( ).getTime( ) );
1394                 temporaryGroupIndexMap.put( id, temporaryGroupIndex );
1395                 session.setAttribute( TemporaryGroupIndexSessionCleaner.TEMPORARY_INDEX_SESSION_KEY,
1396                     temporaryGroupIndexMap );
1397                 return mergedRepoDir;
1398             } else {
1399                 log.error("Local index path for repository group {} does not exist.", repositoryGroup.getId());
1400                 throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
1401             }
1402         }
1403         catch ( RepositorySearchException e )
1404         {
1405             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1406         }
1407         catch ( IndexMergerException e )
1408         {
1409             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1410         }
1411         catch ( IOException e )
1412         {
1413             throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e );
1414         }
1415     }
1416
1417
1418     public void setServletAuth( ServletAuthenticator servletAuth )
1419     {
1420         this.servletAuth = servletAuth;
1421     }
1422
1423     public void setHttpAuth( HttpAuthenticator httpAuth )
1424     {
1425         this.httpAuth = httpAuth;
1426     }
1427
1428     public void setScheduler( RepositoryArchivaTaskScheduler scheduler )
1429     {
1430         this.scheduler = scheduler;
1431     }
1432
1433     public void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration )
1434     {
1435         this.archivaConfiguration = archivaConfiguration;
1436     }
1437
1438     public RemoteRepositoryAdmin getRemoteRepositoryAdmin()
1439     {
1440         return remoteRepositoryAdmin;
1441     }
1442
1443     public void setRemoteRepositoryAdmin( RemoteRepositoryAdmin remoteRepositoryAdmin )
1444     {
1445         this.remoteRepositoryAdmin = remoteRepositoryAdmin;
1446     }
1447
1448     public ManagedRepositoryAdmin getManagedRepositoryAdmin()
1449     {
1450         return managedRepositoryAdmin;
1451     }
1452
1453     public void setManagedRepositoryAdmin( ManagedRepositoryAdmin managedRepositoryAdmin )
1454     {
1455         this.managedRepositoryAdmin = managedRepositoryAdmin;
1456     }
1457
1458     public RepositoryRegistry getRepositoryRegistry( )
1459     {
1460         return repositoryRegistry;
1461     }
1462
1463     public void setRepositoryRegistry( RepositoryRegistry repositoryRegistry )
1464     {
1465         this.repositoryRegistry = repositoryRegistry;
1466     }
1467 }