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