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