Skip to content

Commit

Permalink
Media handler #10869
Browse files Browse the repository at this point in the history
  • Loading branch information
anatol-sialitski committed Jan 23, 2025
1 parent 2b56c4e commit 47ecf86
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 158 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ protected WebResponse doHandle( final WebRequest webRequest, final WebResponse w
doHandleLegacyHandler( webResponse, legacyAttachmentServiceEnabled, () -> attachmentHandler.handle( webRequest ) );
case "image" -> doHandleLegacyHandler( webResponse, legacyImageServiceEnabled, () -> imageHandler.handle( webRequest ) );
case "service" -> doHandleLegacyHandler( webResponse, legacyHttpServiceEnabled, () -> serviceHandler.handle( webRequest ) );
case "media" -> mediaHandler.handle( webRequest, webResponse );
case "media" -> mediaHandler.handle( webRequest );
case "error" -> errorHandler.handle( webRequest );
case "idprovider" -> identityHandler.handle( webRequest, webResponse );
case "asset" -> assetHandler.handle( webRequest );
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
import com.enonic.xp.media.MediaInfoService;
import com.enonic.xp.portal.PortalRequest;
import com.enonic.xp.portal.PortalResponse;
import com.enonic.xp.portal.impl.ContentResolver;
import com.enonic.xp.portal.impl.ContentResolverResult;
import com.enonic.xp.portal.impl.PortalConfig;
import com.enonic.xp.portal.impl.VirtualHostContextHelper;
import com.enonic.xp.portal.impl.handler.attachment.RangeRequestHelper;
Expand Down Expand Up @@ -76,7 +78,7 @@ public class MediaHandler

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() );
private static final Predicate<PortalRequest> IS_ALLOWED_METHOD = req -> ALLOWED_METHODS.contains( req.getMethod() );

private static final MediaType SVG_MEDIA_TYPE = MediaType.SVG_UTF_8.withoutParameters();

Expand All @@ -90,8 +92,6 @@ public class MediaHandler

private final MediaInfoService mediaInfoService;

private final DefaultContextPathVerifier defaultContextPathVerifier;

private volatile String privateCacheControlHeaderConfig;

private volatile String publicCacheControlHeaderConfig;
Expand All @@ -109,7 +109,6 @@ public MediaHandler( @Reference final ContentService contentService, @Reference
this.contentService = contentService;
this.imageService = imageService;
this.mediaInfoService = mediaInfoService;
this.defaultContextPathVerifier = new DefaultContextPathVerifier( contentService );
}

@Activate
Expand All @@ -126,25 +125,43 @@ public void activate( final PortalConfig config )
.collect( Collectors.toList() );
}

public WebResponse handle( final WebRequest webRequest, final WebResponse webResponse )
public WebResponse handle( final WebRequest webRequest )
throws Exception
{
final Matcher matcher = PATTERN.matcher( Objects.requireNonNullElse( webRequest.getEndpointPath(), webRequest.getRawPath() ) );
final PortalRequest portalRequest =
webRequest instanceof PortalRequest ? (PortalRequest) webRequest : new PortalRequest( webRequest );

final Matcher matcher =
PATTERN.matcher( Objects.requireNonNullElse( portalRequest.getEndpointPath(), portalRequest.getRawPath() ) );
if ( !matcher.matches() )
{
return PortalResponse.create( webResponse ).status( HttpStatus.NOT_FOUND ).build();
throw createNotFoundException();
}

if ( !IS_GET_HEAD_OPTIONS_METHOD.test( webRequest ) )
if ( !IS_ALLOWED_METHOD.test( portalRequest ) )
{
throw new WebException( HttpStatus.METHOD_NOT_ALLOWED, String.format( "Method %s not allowed", webRequest.getMethod() ) );
throw new WebException( HttpStatus.METHOD_NOT_ALLOWED, String.format( "Method %s not allowed", portalRequest.getMethod() ) );
}

if ( webRequest.getMethod() == HttpMethod.OPTIONS )
if ( portalRequest.getMethod() == HttpMethod.OPTIONS )
{
return HandlerHelper.handleDefaultOptions( ALLOWED_METHODS );
}

if ( !( portalRequest.isSiteBase() || portalRequest.getRawPath().startsWith( "/api/" ) ) )
{
throw createNotFoundException();
}

if ( portalRequest.isSiteBase() )
{
final ContentResolverResult contentResolverResult = new ContentResolver( contentService ).resolve( portalRequest );
if ( !"/".equals( contentResolverResult.getSiteRelativePath() ) )
{
throw createNotFoundException();

Check warning on line 161 in modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java#L161

Added line #L161 was not covered by tests
}
}

final RepositoryId repositoryId =
HandlerHelper.resolveRepositoryId( ProjectConstants.PROJECT_REPO_ID_PREFIX + matcher.group( "project" ) );
final Branch branch = HandlerHelper.resolveBranch( Objects.requireNonNullElse( matcher.group( "branch" ), "master" ) );
Expand All @@ -153,59 +170,38 @@ public WebResponse handle( final WebRequest webRequest, final WebResponse webRes
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" );
}

verifyMediaScope( matcher.group( "context" ), repositoryId, branch, webRequest );
verifyMediaScope( matcher.group( "context" ), repositoryId, branch, portalRequest );
verifyAccessByBranch( branch );

final PortalRequest portalRequest = createPortalRequest( webRequest, repositoryId, branch );
portalRequest.setRepositoryId( repositoryId );
portalRequest.setBranch( 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 )
final PortalRequest portalRequest )
{
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" );
throw createNotFoundException();
}
}
else

if ( portalRequest.getRawPath().startsWith( "/api/" ) )
{
if ( webRequest instanceof final PortalRequest portalRequest )
{
if ( portalRequest.isSiteBase() &&
!( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) )
{
throw WebException.notFound( "Not a valid media url pattern" );
}
}
return;
}
}

private PortalRequest createPortalRequest( final WebRequest webRequest, final RepositoryId repositoryId, final Branch branch )
{
if ( ContentConstants.BRANCH_DRAFT.equals( branch ) )
if ( !( repositoryId.equals( portalRequest.getRepositoryId() ) && branch.equals( portalRequest.getBranch() ) ) )
{
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" );
}
throw createNotFoundException();
}

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 Branch branch, final Callable<PortalResponse> callable )
Expand All @@ -217,14 +213,26 @@ private PortalResponse executeInContext( final RepositoryId repositoryId, final
.callWith( callable );
}

private void verifyAccessByBranch( 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" );
}
}
}

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

Check warning on line 235 in modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java

View check run for this annotation

Codecov / codecov/patch

modules/portal/portal-impl/src/main/java/com/enonic/xp/portal/impl/handler/MediaHandler.java#L235

Added line #L235 was not covered by tests
}

final Content content = getContent( id );
Expand Down Expand Up @@ -470,4 +478,9 @@ private ByteSource transform( final Media content, final BinaryReference binaryR
throw new WebException( HttpStatus.TOO_MANY_REQUESTS, "Try again later", e );
}
}

private WebException createNotFoundException()
{
return WebException.notFound( "Not a valid media url pattern" );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ public void testHandleMedia()
when( webRequest.getRawPath() ).thenReturn( "/api/media/image/project:branch/id:fingerprint/name" );

final WebResponse webResponse = WebResponse.create().build();
when( mediaHandler.handle( any( WebRequest.class ), any( WebResponse.class ) ) ).thenReturn( webResponse );
when( mediaHandler.handle( any( WebRequest.class ) ) ).thenReturn( webResponse );

// test handle
assertEquals( webResponse, this.handler.doHandle( webRequest, webResponse, null ) );
Expand Down
Loading

0 comments on commit 47ecf86

Please sign in to comment.