Skip to content

Commit

Permalink
Vhost Config #10192
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Sep 19, 2023
1 parent acaf658 commit 5068cc7
Show file tree
Hide file tree
Showing 24 changed files with 431 additions and 64 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package com.enonic.xp.admin.impl.app;

import javax.servlet.http.HttpServletRequest;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;
import com.enonic.xp.web.servlet.ServletRequestHolder;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;

public class MainWebHandlerTest
{
Expand All @@ -18,6 +23,14 @@ public class MainWebHandlerTest
public void setup()
{
this.handler = new MainWebHandler();

ServletRequestHolder.setRequest( mock( HttpServletRequest.class ) );
}

@AfterEach
public void cleanUp()
{
ServletRequestHolder.setRequest( null );
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ public class ApiAppHandler
private final ExceptionRenderer exceptionRenderer;

@Activate
public ApiAppHandler( final @Reference ControllerScriptFactory controllerScriptFactory,
final @Reference ApiDescriptorService apiDescriptorService, final @Reference ExceptionMapper exceptionMapper,
final @Reference ExceptionRenderer exceptionRenderer )
public ApiAppHandler( @Reference final ControllerScriptFactory controllerScriptFactory,
@Reference final ApiDescriptorService apiDescriptorService, @Reference final ExceptionMapper exceptionMapper,
@Reference final ExceptionRenderer exceptionRenderer )
{
this.controllerScriptFactory = controllerScriptFactory;
this.apiDescriptorService = apiDescriptorService;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.enonic.xp.portal.impl.handler.api;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -71,9 +72,14 @@ protected WebResponse doHandle( final WebRequest webRequest, final WebResponse w

private List<String> getApiResources()
{
return applicationService.getInstalledApplications().stream().filter( application -> {
List<String> result = new ArrayList<>();

result.add( "media" );
result.addAll( applicationService.getInstalledApplications().stream().filter( application -> {
ResourceKey resourceKey = ResourceKey.from( application.getKey(), "api/api.js" );
return resourceService.getResource( resourceKey ).exists();
} ).map( application -> application.getKey().getName() ).collect( Collectors.toList() );
} ).map( application -> application.getKey().getName() ).collect( Collectors.toList() ) );

return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package com.enonic.xp.portal.impl.handler.api;

import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;

import com.enonic.xp.branch.Branch;
import com.enonic.xp.content.ContentConstants;
import com.enonic.xp.content.ContentId;
import com.enonic.xp.content.ContentService;
import com.enonic.xp.context.ContextAccessor;
import com.enonic.xp.context.ContextBuilder;
import com.enonic.xp.image.ImageService;
import com.enonic.xp.image.ScaleParamsParser;
import com.enonic.xp.media.MediaInfoService;
import com.enonic.xp.portal.PortalRequest;
import com.enonic.xp.portal.PortalResponse;
import com.enonic.xp.portal.impl.PortalConfig;
import com.enonic.xp.portal.impl.handler.attachment.AttachmentHandlerWorker;
import com.enonic.xp.portal.impl.handler.image.ImageHandlerWorker;
import com.enonic.xp.project.ProjectConstants;
import com.enonic.xp.repository.RepositoryId;
import com.enonic.xp.security.PrincipalKey;
import com.enonic.xp.security.RoleKeys;
import com.enonic.xp.security.auth.AuthenticationInfo;
import com.enonic.xp.web.HttpMethod;
import com.enonic.xp.web.WebException;
import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;
import com.enonic.xp.web.handler.BaseWebHandler;
import com.enonic.xp.web.handler.WebHandler;
import com.enonic.xp.web.handler.WebHandlerChain;

import static com.google.common.base.Strings.nullToEmpty;

@Component(immediate = true, service = WebHandler.class, configurationPid = "com.enonic.xp.portal")
public class PortalApiHandler
extends BaseWebHandler
{
private static final Pattern PATTERN =
Pattern.compile( "^/api/media/(?<mediaType>image|attachment)/(?<repo>[^/]+)/(?<branch>[^/]+)/(?<restPath>.*)$" );

private static final Pattern ATTACHMENT_REST_PATH_PATTERN = Pattern.compile( "([^/]+)/([^/^:]+)(?::([^/]+))?/([^/]+)" );

private static final Pattern IMAGE_REST_PATH_PATTERN = Pattern.compile( "([^/^:]+)(?::([^/]+))?/([^/]+)/([^/]+)" );

private final ContentService contentService;

private final ImageService imageService;

private final MediaInfoService mediaInfoService;

private volatile String privateCacheControlHeaderConfig;

private volatile String publicCacheControlHeaderConfig;

private volatile String contentSecurityPolicy;

private volatile String contentSecurityPolicySvg;

private volatile List<PrincipalKey> draftBranchAllowedFor;

@Activate
public PortalApiHandler( @Reference final ContentService contentService, @Reference final ImageService imageService,
@Reference final MediaInfoService mediaInfoService )
{
super( -2, EnumSet.of( HttpMethod.GET, HttpMethod.OPTIONS ) );

this.contentService = contentService;
this.imageService = imageService;
this.mediaInfoService = mediaInfoService;
}

@Activate
@Modified
public void activate( final PortalConfig config )
{
this.privateCacheControlHeaderConfig = config.media_private_cacheControl();
this.publicCacheControlHeaderConfig = config.media_public_cacheControl();
this.contentSecurityPolicy = config.media_contentSecurityPolicy();
this.contentSecurityPolicySvg = config.media_contentSecurityPolicy_svg();
this.draftBranchAllowedFor = Arrays.stream( nullToEmpty( config.draftBranchAllowedFor() ).split( ",", -1 ) )
.map( String::trim )
.map( PrincipalKey::from )
.collect( Collectors.toList() );
}

@Override
protected boolean canHandle( final WebRequest webRequest )
{
return PATTERN.matcher( webRequest.getRawPath() ).matches();
}

@Override
protected WebResponse doHandle( final WebRequest webRequest, final WebResponse webResponse, final WebHandlerChain webHandlerChain )
throws Exception
{
Matcher matcher = PATTERN.matcher( webRequest.getRawPath() );
matcher.matches();

String repo = matcher.group( "repo" );
String branch = matcher.group( "branch" );
String type = matcher.group( "mediaType" );
String restPath = matcher.group( "restPath" );

RepositoryId repositoryId = RepositoryId.from( ProjectConstants.PROJECT_REPO_ID_PREFIX + repo );

PortalRequest portalRequest = createPortalRequest( webRequest, repositoryId, Branch.from( branch ) );

return executeInContext( repositoryId, branch, () -> type.equals( "attachment" )
? doHandleAttachment( portalRequest, restPath )
: doHandleImage( portalRequest, restPath ) );
}

private PortalRequest createPortalRequest( final WebRequest webRequest, final RepositoryId repositoryId, final Branch branch )
{
if ( ContentConstants.BRANCH_DRAFT.equals( branch ) )
{
final AuthenticationInfo authInfo = ContextAccessor.current().getAuthInfo();
if ( !authInfo.hasRole( RoleKeys.ADMIN ) && authInfo.getPrincipals().stream().noneMatch( draftBranchAllowedFor::contains ) )
{
throw WebException.forbidden( "You don't have permission to access this resource" );
}
}

PortalRequest portalRequest = webRequest instanceof PortalRequest ? (PortalRequest) webRequest : new PortalRequest( webRequest );
portalRequest.setRepositoryId( repositoryId );
portalRequest.setBranch( branch );
return portalRequest;
}

private PortalResponse executeInContext( final RepositoryId repositoryId, final String branch, final Callable<PortalResponse> callable )
{
return ContextBuilder.copyOf( ContextAccessor.current() )
.repositoryId( repositoryId )
.branch( branch )
.build()
.callWith( callable );
}

private PortalResponse doHandleImage( final PortalRequest portalRequest, final String restPath )
throws Exception
{
Matcher matcher = IMAGE_REST_PATH_PATTERN.matcher( restPath );
if ( !matcher.find() )
{
throw WebException.notFound( "Not a valid image url pattern" );
}

final ImageHandlerWorker worker =
new ImageHandlerWorker( portalRequest, this.contentService, this.imageService, this.mediaInfoService );
worker.id = ContentId.from( matcher.group( 1 ) );
worker.fingerprint = matcher.group( 2 );
worker.scaleParams = new ScaleParamsParser().parse( matcher.group( 3 ) );
worker.name = matcher.group( 4 );
worker.filterParam = getParameter( portalRequest, "filter" );
worker.qualityParam = getParameter( portalRequest, "quality" );
worker.backgroundParam = getParameter( portalRequest, "background" );
worker.privateCacheControlHeaderConfig = this.privateCacheControlHeaderConfig;
worker.publicCacheControlHeaderConfig = this.publicCacheControlHeaderConfig;
worker.contentSecurityPolicy = this.contentSecurityPolicy;
worker.contentSecurityPolicySvg = this.contentSecurityPolicySvg;
return worker.execute();
}

private PortalResponse doHandleAttachment( final PortalRequest portalRequest, final String restPath )
throws Exception
{
Matcher matcher = ATTACHMENT_REST_PATH_PATTERN.matcher( restPath );
if ( !matcher.find() )
{
throw WebException.notFound( "Not a valid attachment url pattern" );
}

final AttachmentHandlerWorker worker = new AttachmentHandlerWorker( portalRequest, this.contentService );
worker.download = "download".equals( matcher.group( 1 ) );
worker.id = ContentId.from( matcher.group( 2 ) );
worker.fingerprint = matcher.group( 3 );
worker.name = matcher.group( 4 );
worker.privateCacheControlHeaderConfig = this.privateCacheControlHeaderConfig;
worker.publicCacheControlHeaderConfig = this.publicCacheControlHeaderConfig;
worker.contentSecurityPolicy = this.contentSecurityPolicy;
worker.contentSecurityPolicySvg = this.contentSecurityPolicySvg;
return worker.execute();
}

private String getParameter( final WebRequest req, final String name )
{
final Collection<String> values = req.getParams().get( name );
return values.isEmpty() ? null : values.iterator().next();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import com.enonic.xp.trace.Trace;
import com.enonic.xp.trace.Tracer;

final class AttachmentHandlerWorker
public final class AttachmentHandlerWorker
extends AbstractAttachmentHandlerWorker<Content>
{
AttachmentHandlerWorker( final PortalRequest request, final ContentService contentService )
public AttachmentHandlerWorker( final PortalRequest request, final ContentService contentService )
{
super( request, contentService );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import static com.google.common.base.Strings.nullToEmpty;

final class ImageHandlerWorker
public final class ImageHandlerWorker
extends AbstractAttachmentHandlerWorker<Media>
{
private static final int DEFAULT_BACKGROUND = 0xFFFFFF;
Expand All @@ -41,15 +41,15 @@ final class ImageHandlerWorker

private final MediaInfoService mediaInfoService;

String filterParam;
public String filterParam;

String qualityParam;
public String qualityParam;

String backgroundParam;
public String backgroundParam;

ScaleParams scaleParams;
public ScaleParams scaleParams;

ImageHandlerWorker( final PortalRequest request, final ContentService contentService, final ImageService imageService,
public ImageHandlerWorker( final PortalRequest request, final ContentService contentService, final ImageService imageService,
final MediaInfoService mediaInfoService )
{
super( request, contentService );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,37 @@
import com.enonic.xp.attachment.Attachments;
import com.enonic.xp.content.Content;
import com.enonic.xp.portal.url.AttachmentUrlParams;
import com.enonic.xp.repository.RepositoryUtils;

final class AttachmentUrlBuilder
extends PortalUrlBuilder<AttachmentUrlParams>
{

AttachmentUrlBuilder()
{
super( "attachment" );
}

@Override
protected void buildUrl( final StringBuilder url, final Multimap<String, String> params )
{
super.buildUrl( url, params );
appendPart( url, this.portalRequest.getContentPath().toString() );
appendPart( url, "_" );
appendPart( url, "attachment" );

if ( this.params.isDownload() )
if ( portalRequest.getRawPath() != null && portalRequest.getRawPath().startsWith( "/api/" ) )
{
appendPart( url, "download" );
appendPart( url, resourceType );
appendPart( url, RepositoryUtils.getContentRepoName( this.portalRequest.getRepositoryId() ) );
appendPart( url, this.portalRequest.getBranch().toString() );
}
else
{
appendPart( url, "inline" );
appendPart( url, this.portalRequest.getContentPath().toString() );
appendPart( url, "_" );
appendPart( url, "attachment" );
}

appendPart( url, this.params.isDownload() ? "download" : "inline" );

final Content content = resolveContent();
Attachment attachment = resolveAttachment( content );
String hash = resolveHash( content, attachment );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
final class ComponentUrlBuilder
extends PortalUrlBuilder<ComponentUrlParams>
{
ComponentUrlBuilder()
{
super( "component" );
}

@Override
protected void buildUrl( final StringBuilder url, final Multimap<String, String> params )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
final class GenerateUrlBuilder
extends PortalUrlBuilder<GenerateUrlParams>
{
GenerateUrlBuilder()
{
super( null );
}

@Override
protected void buildUrl( final StringBuilder url, final Multimap<String, String> params )
{
Expand Down
Loading

0 comments on commit 5068cc7

Please sign in to comment.