Skip to content

Commit

Permalink
Media scope #10607
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Jun 28, 2024
1 parent 0790e56 commit 7e9ecb8
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.enonic.xp.portal.impl;

import com.enonic.xp.context.ContextAccessor;

public final class VirtualHostContextHelper
{
public static final String MEDIA_SERVICE_BASE_URL = "mediaService.baseUrl";

public static final String MEDIA_SERVICE_SCOPE = "mediaService.scope";

private VirtualHostContextHelper()
{
}

public static String getProperty( final String property )
{
return (String) ContextAccessor.current().getAttribute( property );
}

public static String getMediaServiceBaseUrl()
{
return getProperty( MEDIA_SERVICE_BASE_URL );
}

public static String getMediaServiceScope()
{
return getProperty( MEDIA_SERVICE_SCOPE );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@
import com.google.common.net.HttpHeaders;
import com.google.common.primitives.Longs;

import com.enonic.xp.app.ApplicationKey;
import com.enonic.xp.branch.Branch;
import com.enonic.xp.portal.PortalRequest;
import com.enonic.xp.portal.PortalResponse;
import com.enonic.xp.repository.RepositoryId;
import com.enonic.xp.resource.Resource;
import com.enonic.xp.trace.Trace;
import com.enonic.xp.web.HttpMethod;
import com.enonic.xp.web.HttpStatus;
import com.enonic.xp.web.WebException;
import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;

Expand Down Expand Up @@ -120,4 +124,40 @@ public static void addTraceInfo( final Trace trace, final WebResponse webRespons
}
}

public static RepositoryId resolveRepositoryId( final String value )
{
try
{
return RepositoryId.from( value );
}
catch ( Exception e )
{
throw WebException.notFound( String.format( "Repository [%s] not found", value ) );
}
}

public static Branch resolveBranch( final String value )
{
try
{
return Branch.from( value );
}
catch ( Exception e )
{
throw WebException.notFound( String.format( "Branch [%s] not found", value ) );
}
}

public static ApplicationKey resolveApplicationKey( final String appKey )
{
try
{
return ApplicationKey.from( appKey );
}
catch ( Exception e )
{
throw WebException.notFound( String.format( "Application key [%s] not found", appKey ) );
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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.VirtualHostContextHelper;
import com.enonic.xp.portal.impl.handler.attachment.AttachmentHandlerWorker;
import com.enonic.xp.portal.impl.handler.image.ImageHandlerWorker;
import com.enonic.xp.project.ProjectConstants;
Expand All @@ -46,12 +47,14 @@
public class MediaHandler
{
private static final Pattern PATTERN = Pattern.compile(
"^/(_|api)/media/(?<mediaType>image|attachment)/(?<project>[^/:]+)(?::(?<branch>draft))?/(?<id>[^/:]+)(?::(?<fingerprint>[^/]+))?/(?<restPath>.*)$" );
"^/(_|api)/media/(?<mediaType>image|attachment)/(?<context>(?<project>[^/:]+)(?::(?<branch>draft))?)/(?<id>[^/:]+)(?::(?<fingerprint>[^/]+))?/(?<restPath>.*)$" );

private static final Pattern ATTACHMENT_REST_PATH_PATTERN = Pattern.compile( "^(?<name>[^/?]+)(\\?(?<params>.*))?$" );

private static final Pattern IMAGE_REST_PATH_PATTERN = Pattern.compile( "^(?<scaleParams>[^/]+)/(?<name>[^/]+)$" );

private static final Pattern MEDIA_SCOPE_DELIMITER_PATTERN = Pattern.compile( "," );

private static final EnumSet<HttpMethod> ALLOWED_METHODS = EnumSet.of( HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS );

private static final Predicate<WebRequest> IS_GET_HEAD_OPTIONS_METHOD = req -> ALLOWED_METHODS.contains( req.getMethod() );
Expand Down Expand Up @@ -117,27 +120,58 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes
return HandlerHelper.handleDefaultOptions( ALLOWED_METHODS );
}

final RepositoryId repositoryId =
HandlerHelper.resolveRepositoryId( ProjectConstants.PROJECT_REPO_ID_PREFIX + matcher.group( "project" ) );
final Branch branch = HandlerHelper.resolveBranch( Objects.requireNonNullElse( matcher.group( "branch" ), "master" ) );
final String type = matcher.group( "mediaType" );
final ContentId id = ContentId.from( matcher.group( "id" ) );
final String fingerprint = matcher.group( "fingerprint" );
final String restPath = matcher.group( "restPath" );

if ( !defaultContextPathVerifier.verify( webRequest ) )
{
throw WebException.notFound( "Not a valid media url pattern" );
}

String project = matcher.group( "project" );
String branch = Objects.requireNonNullElse( matcher.group( "branch" ), "master" );
String type = matcher.group( "mediaType" );
ContentId id = ContentId.from( matcher.group( "id" ) );
String fingerprint = matcher.group( "fingerprint" );
String restPath = matcher.group( "restPath" );

RepositoryId repositoryId = RepositoryId.from( ProjectConstants.PROJECT_REPO_ID_PREFIX + project );
verifyMediaScope( matcher.group( "context" ), repositoryId, branch, webRequest );

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

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

private void verifyMediaScope( final String projectContext, final RepositoryId repositoryId, final Branch branch,
final WebRequest webRequest )
{
if ( webRequest.getEndpointPath() == null )
{
return;
}

final String mediaServiceScope = VirtualHostContextHelper.getMediaServiceScope();
if ( mediaServiceScope != null )
{
if ( MEDIA_SCOPE_DELIMITER_PATTERN.splitAsStream( mediaServiceScope ).map( String::trim ).noneMatch( projectContext::equals ) )
{
throw WebException.notFound( "Not a valid media url pattern" );
}
}
else
{
if ( webRequest instanceof PortalRequest )
{
final PortalRequest portalRequest = (PortalRequest) webRequest;
if ( portalRequest.isSiteBase() &&
!( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) )
{
throw WebException.notFound( "Not a valid media url pattern" );
}
}
}
}

private PortalRequest createPortalRequest( final WebRequest webRequest, final RepositoryId repositoryId, final Branch branch )
{
if ( ContentConstants.BRANCH_DRAFT.equals( branch ) )
Expand All @@ -155,7 +189,7 @@ private PortalRequest createPortalRequest( final WebRequest webRequest, final Re
return portalRequest;
}

private PortalResponse executeInContext( final RepositoryId repositoryId, final String branch, final Callable<PortalResponse> callable )
private PortalResponse executeInContext( final RepositoryId repositoryId, final Branch branch, final Callable<PortalResponse> callable )
{
return ContextBuilder.copyOf( ContextAccessor.current() )
.repositoryId( repositoryId )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public WebResponse handle( final WebRequest webRequest )
throw new IllegalStateException( "Invalid API path: " + path );
}

final ApplicationKey applicationKey = resolveApplicationKey( matcher.group( "appKey" ) );
final ApplicationKey applicationKey = HandlerHelper.resolveApplicationKey( matcher.group( "appKey" ) );

if ( RESERVED_APP_KEYS.contains( applicationKey.getName() ) )
{
Expand Down Expand Up @@ -196,7 +196,7 @@ private boolean verifyPathMountedOnWebapps( final ApiDescriptor apiDescriptor, f
return false;
}

final ApplicationKey baseAppKey = resolveApplicationKey( webappMatcher.group( "baseAppKey" ) );
final ApplicationKey baseAppKey = HandlerHelper.resolveApplicationKey( webappMatcher.group( "baseAppKey" ) );
return baseAppKey.equals( apiDescriptor.key().getApplicationKey() );
}

Expand Down Expand Up @@ -347,16 +347,4 @@ private WebResponse handleError( final WebRequest webRequest, final Exception e
webRequest.getRawRequest().setAttribute( "error.handled", Boolean.TRUE );
return webResponse;
}

private ApplicationKey resolveApplicationKey( final String appKey )
{
try
{
return ApplicationKey.from( appKey );
}
catch ( Exception e )
{
throw WebException.notFound( String.format( "Application key [%s] not found", appKey ) );
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.enonic.xp.content.Content;
import com.enonic.xp.content.ContentConstants;
import com.enonic.xp.portal.impl.ContentResolverResult;
import com.enonic.xp.portal.impl.VirtualHostContextHelper;
import com.enonic.xp.portal.url.AttachmentUrlParams;
import com.enonic.xp.repository.RepositoryUtils;

Expand Down Expand Up @@ -83,7 +84,7 @@ protected void buildUrl( final StringBuilder url, final Multimap<String, String>
@Override
protected String getBaseUrl()
{
return UrlContextHelper.getMediaServiceBaseUrl();
return VirtualHostContextHelper.getMediaServiceBaseUrl();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.enonic.xp.context.ContextAccessor;
import com.enonic.xp.exception.NotFoundException;
import com.enonic.xp.portal.impl.ContentResolverResult;
import com.enonic.xp.portal.impl.VirtualHostContextHelper;
import com.enonic.xp.portal.url.ImageUrlParams;
import com.enonic.xp.repository.RepositoryUtils;

Expand Down Expand Up @@ -94,7 +95,7 @@ protected void buildUrl( final StringBuilder url, final Multimap<String, String>
@Override
protected String getBaseUrl()
{
return UrlContextHelper.getMediaServiceBaseUrl();
return VirtualHostContextHelper.getMediaServiceBaseUrl();
}

@Override
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
import com.google.common.io.ByteSource;
import com.google.common.net.HttpHeaders;

import com.enonic.xp.app.ApplicationKey;
import com.enonic.xp.branch.Branch;
import com.enonic.xp.portal.PortalResponse;
import com.enonic.xp.repository.RepositoryId;
import com.enonic.xp.resource.Resource;
import com.enonic.xp.web.HttpMethod;
import com.enonic.xp.web.HttpStatus;
import com.enonic.xp.web.WebException;
import com.enonic.xp.web.WebRequest;
import com.enonic.xp.web.WebResponse;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -103,4 +108,43 @@ public void testGetSize()
webResponse = WebResponse.create().body( "body" ).build();
assertEquals( 4, HandlerHelper.getSize( webResponse ) );
}

@Test
public void testInvalidProjectName()
{
final WebException ex = assertThrows( WebException.class, () -> HandlerHelper.resolveRepositoryId( "#!@$" ) );
assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() );
}

@Test
public void testProjectName()
{
assertEquals( RepositoryId.from( "name" ), HandlerHelper.resolveRepositoryId( "name" ) );
}

@Test
public void testInvalidBranchName()
{
final WebException ex = assertThrows( WebException.class, () -> HandlerHelper.resolveBranch( "#!@$" ) );
assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() );
}

@Test
public void testBranchName()
{
assertEquals( Branch.from( "name" ), HandlerHelper.resolveBranch( "name" ) );
}

@Test
public void testInvalidApplicationKey()
{
final WebException ex = assertThrows( WebException.class, () -> HandlerHelper.resolveApplicationKey( "<>" ) );
assertEquals( HttpStatus.NOT_FOUND, ex.getStatus() );
}

@Test
public void testApplicationName()
{
assertEquals( ApplicationKey.from( "name" ), HandlerHelper.resolveApplicationKey( "name" ) );
}
}
Loading

0 comments on commit 7e9ecb8

Please sign in to comment.