Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Jamie Attwood 174 posts 427 karma points c-trib
    17 days ago
    Jamie Attwood
    0

    Intercept All Media Requests

    Using Umbraco 9 I need to 1) Intercept all media requests 2) Evaluate if a member is logged in 3) If so, pass along the media requested, if not send a 401 status code.

    So far I have attempted creating a middleware to do this but I am having an issue in that I can't accurately detect media requests and also, it looks like I can't inject the member manager as I think it's too early in the lifecycle of the app. Any thoughts on how to achieve this would be great.

    My Middleware class:

    using Microsoft.AspNetCore.Http;
    using System.Threading.Tasks;
    using Umbraco.Cms.Core.Security;
    
    namespace Ninepoint.Core.Middlewares
    {
        public class AuthorizedMediaAccess
        {
            private readonly RequestDelegate _next;
    
            public AuthorizedMediaAccess(RequestDelegate next)
            {
                _next = next;
            }
    
            public async Task  InvokeAsync(HttpContext context, IMemberManager memberManager) 
            {
                //If its not media, move on...
                if (!context.Request.Path.StartsWithSegments("/media"))
                {
                    await _next(context);
                }
    
                // Don't return a 401 response if the user is already authenticated.
                //if (context.User.Identities.Any(IdentityExtensions => IdentityExtensions.IsAuthenticated))
                if (memberManager.IsLoggedIn())
                {
                    await _next(context);
                }
    
                // Stop processing the request and return a 401 response.
                context.Response.StatusCode = 401;
            }
        }
    }
    

    It is being called in startup.cs here in the "WithMiddleWare" method:

      app.UseUmbraco()
                    .WithMiddleware(u =>
                    {
                        u.UseBackOffice();
                        u.UseWebsite();
                        u.AppBuilder.UseMiddleware<AuthorizedMediaAccess>();
                    })
    
  • Andrea Lovo 3 posts 73 karma points
    16 days ago
    Andrea Lovo
    0

    1,2) try this:

    StatupFilter

       public class StartupFilter : IStartupFilter
        {
            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) => app =>
            {
                app.UseMiddleware<AuthorizedMediaAccess>();
                next(app);
            };
        }
    

    Startup.cs

     public void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<IStartupFilter, StartupFilter>();
    
            services.AddUmbraco(_env, _config)
                .AddBackOffice()
                .AddWebsite()
                .AddComposers()
                .Build();
        }
    

    Middleware(InvokeAsync)

     public async Task InvokeAsync(HttpContext context, IMemberManager memberManager)
        {
            //If its not media, move on...
    
            if (!context.Request.Path.StartsWithSegments("/media"))
            {
                await _next(context);
            }
            else
            {
                if (context.User.Identity.IsAuthenticated)
                {
                    await _next(context);
                }
                else
                {
                    context.Response.StatusCode = 401;
                }
            }
    
        }
    

    And remember to clear the cache!

  • Jamie Attwood 174 posts 427 karma points c-trib
    16 days ago
    Jamie Attwood
    0

    "Clear the cache" YES! that was most of the problem with my first solution. But, Filters to the rescue. Thank you for this suggestion. It's mostly working now. The only issue remailing is that context.User.Identity is always null. Any idea on how to evaluate user this early on in the program's lifecycle?

  • David Zweben 245 posts 703 karma points
    16 days ago
    David Zweben
    0

    I am trying to do this exact same thing right now, so I’m watching this thread with great interest.

  • Jamie Attwood 174 posts 427 karma points c-trib
    16 days ago
    Jamie Attwood
    0

    That issue I think is likely that the startup filters are fired long before the umbraco middleware is fired and as a result we can't get the user. The above works from a filtering standpoint, however filtering on "/media" does not work from within the UseUmbraco(). AppBuilder.UseMiddleware for some reason. The media path is removed out the request pipeline at some point. This is so frustrating. So much obscured filtering and middlewares. I am on day two now... lol.

  • David Zweben 245 posts 703 karma points
    16 days ago
    David Zweben
    0

    I actually have a really hacky approach working for protected images that involves passing the media ID to a web API endpoint, and that endpoint querying the image file path, loading the image into a PhysicalFile, and returning an actual image file as the response from the API. This doesn't protect the images at their real URLs, but provides a way to serve them with protection without revealing the unprotected URL. It's security-through-obscurity, but it could be sufficient in some cases.

    The problem I ran into is that I need to also serve thumbnails that are processed through ImageSharp, and that has basically proven incompatible with the approach because I can't figure out how to get the physical file path of the cached ImageSharp thumbnails.

  • David Zweben 245 posts 703 karma points
    16 days ago
    David Zweben
    0

    Just throwing some more thoughts out there to see if I can help:

    One approach might be to implement a custom FileSystemProvider for ImageSharp that does access checking and then replicates the functionality of the standard PhysicalFileSystemProvider only when access is allowed, or maybe even calls into that existing implementation.

    Relevant discussions:

    https://our.umbraco.com/forum/umbraco-9/106879-umbraco-9-and-media-folder-location

    https://github.com/umbraco/Umbraco-CMS/issues/11580#issuecomment-991716300

  • Jamie Attwood 174 posts 427 karma points c-trib
    16 days ago
    Jamie Attwood
    100

    Ok I have finally come up with a somewhat decent solution... feel free to chime in here if you want. We can't use filters for this as that occurs too early in the pipeline, but we can use a custom middleware in startup.cs Configure() before app.UseUmbraco() get's its hands all over our media endpoints. The only redundant part about this is if we want to authenticate the user we need to call app.UseAuthentication(); prior to our new middleware. Note that in my case, just authenticating the user is good enough. You might be able to add app.UseAuthorization(); to get into roles etc. Anyway here it is and it should not interfere with any of the authentication logic or ImageSharp, etc.

    The middleware (CustomMediaAuthentication.cs)

    using Microsoft.AspNetCore.Http;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace Ninepoint.Core.Middlewares
    {
        public class CustomMediaAuthentication
        {
            private readonly RequestDelegate _next;
    
            public CustomMediaAuthentication(RequestDelegate next)
            {
                _next = next;
            }
    
            public async Task InvokeAsync(HttpContext context)
            {
                //If its not media, move on...
                if (!context.Request.Path.StartsWithSegments("/media"))
                {
                    await _next(context);
                    return;
                }
    
                //Authenticate user
                if (context.User.Identities.Any(IdentityExtensions => IdentityExtensions.IsAuthenticated))
                {
                    await _next(context);
                    return;
                }
    
                // Stop processing the request and return a 401 response.
                context.Response.StatusCode = 401;
                await Task.FromResult(0);
                return;
            }
        }
    }
    

    The extension class (CustomMediaAuthenticationExtension.cs)

    using Microsoft.AspNetCore.Builder;
    
    namespace Ninepoint.Core.Middlewares
    {
        public static class IApplicationBuilderExtention
        {
            public static IApplicationBuilder UseCustomMediaAuthentication(this IApplicationBuilder app)
            {
                return app.UseMiddleware<CustomMediaAuthentication>();
            }
        }
    }
    

    And the call in Startup.cs:

        //UseCustomMediaAuthentication() relies on UseAuthentication()
        //Include before app.UseUmbraco()
        app.UseAuthentication();
        app.UseCustomMediaAuthentication();
    
  • Jamie Attwood 174 posts 427 karma points c-trib
    16 days ago
    Jamie Attwood
    0

    And after a little more experimentation and nudging from @bill-the-duck whos post here has since disappeared, after your call to app.UseAuthentication(); you can then test member related access via either the memberService (which you can inject via the constructor) and also you can method inject the IMemberManager if you want more performant results, Something like this:

    public class CustomMediaAuthentication
        {
            private readonly RequestDelegate _next;
            private readonly IMemberService _memberService;
    
            public CustomMediaAuthentication(RequestDelegate next, IMemberService memberService)
            {
                _next = next;
                _memberService = memberService;
            }
    
            public async Task InvokeAsync(HttpContext context, IMemberManager memberManager)
            {
                //If its not media, move on...
                if (!context.Request.Path.StartsWithSegments("/media"))
                {
                    await _next(context);
                    return;
                }
    
    
                if (memberManager.IsLoggedIn()) //Authenticate user via memberManager through method DI
                {
                    var member = _memberService.GetByUsername(context.User.Identity.Name); //Get member from _memberService
                    var memberManagerMember = memberManager.GetCurrentMemberAsync(); //Get member from memberManager
                    //Check out member permissions and do stuff....
                    await _next(context);
                    return;
                }
    
                // Stop processing the request and return a 401 response.
                context.Response.StatusCode = 401;
                await Task.FromResult(0);
                return;
            }
        }
    

    Cheers!

    Jamie

  • David Zweben 245 posts 703 karma points
    15 days ago
    David Zweben
    0

    This is great! Worked perfectly.

  • David Zweben 245 posts 703 karma points
    15 days ago
    David Zweben
    0

    Hmm, I ran into an issue, maybe one of you know of a way to address it.

    The media protection is working fine on the website's front-end, but in the Umbraco backoffice, if I select an image in the RTE image picker and save, the image URL gets cleared out after save. I think basically the media protection code isn't working correctly from the context of the backoffice, and it's causing things to break.

    I think all I need is a consistent way to identify if a media request is being made by the backoffice so I can put in an exception. Unfortunately, I didn't see anything obvious that would allow me to do that. Any ideas?

    The only other thing I can think of is to perhaps check for a logged in User in addition to a logged in Member, and use that to bypass some of the logic.

  • Jamie Attwood 174 posts 427 karma points c-trib
    15 days ago
    Jamie Attwood
    0

    Crap. I did some very high level testing only. I will look into that a little deeper.

    Have you tried something like this? (not tested)

    public async Task InvokeAsync(HttpContext context, IMemberManager memberManager, IBackOfficeSecurityAccessor backOfficeSecurityAccessor )
            {
                //If its not media, move on...
                if (!context.Request.Path.StartsWithSegments("/media"))
                {
                    await _next(context);
                    return;
                }
    
    
                if (memberManager.IsLoggedIn() || backOfficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated()) 
                {
    
                    await _next(context);
                    return;
                }
    
                // Stop processing the request and return a 401 response.
                context.Response.StatusCode = 401;
                await Task.FromResult(0);
                return;
            }
    
  • David Zweben 245 posts 703 karma points
    15 days ago
    David Zweben
    0

    Unfortunately, backOfficeSecurityAccessor.BackOfficeSecurity.IsAuthenticated() is returning False even from a backoffice context inside this method.

    context.User.GetUmbracoIdentity(); is also null.

  • David Zweben 245 posts 703 karma points
    15 days ago
    David Zweben
    0

    My apologies for the scare, the issue wasn't related to your code. Instead, it seems to be related to one of the additional properties I added to the Image media type. Using a media type with only standard properties got rid of the issue.

  • David Zweben 245 posts 703 karma points
    15 days ago
    David Zweben
    0

    Actually, it may be that the issue with RTE images being cleared out is unrelated... it seems strange, but even after disabling app.UseCustomMediaAuthentication() in Startup.cs and restarting the site, I'm still having the issue. So it seems like there must be something else going on.

  • Jamie Attwood 174 posts 427 karma points c-trib
    14 days ago
    Jamie Attwood
    0

    Thanks David, glad to hear that!

Please Sign in or register to post replies

Write your reply to:

Draft