Copied to clipboard

Flag this post as spam?

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


  • Darren Hunter 103 posts 194 karma points
    Nov 21, 2022 @ 16:47
    Darren Hunter
    0

    Overriding Media/{id}/{file}

    Hi,

    I am trying to write my own media protection system for Umbraco, before any one says purchase one I am not going down that route so save your typing.

    Is there any way to remove the route for Media/{id}/{file}

    I have tried both with a surface controller and a IHttpHandler when I put a url in like:

    https://localhost:44347/media/mjmdicul/1-copy.pdf It not hitting the handler or the controller it going striate to the PDF and I do not want that I want to intercept the URL for the Media folder and then decide if I want to allow download of the file or block it.

    As I said I do not want to pay for a 3rd party tool, I want to be able to do this in code.

    Here is the code for my Routing:

    public class RegisterDependencies : IUserComposer { public void Compose(Composition composition) {

                composition.Register<MediaController>(Lifetime.Request);
    
            }
    
        }
    

    public class RegisterCustomRouteComposer : ComponentComposer

        public class RegisterCustomRouteComponent : IComponent
        {
    
            public void Initialize()
            {
    
            AntiForgeryConfig.SuppressXFrameOptionsHeader = true;
            GlobalConfiguration.Configuration.MapHttpAttributeRoutes();
            GlobalConfiguration.Configuration.Initializer(GlobalConfiguration.Configuration);
            RouteTable.Routes.MapMvcAttributeRoutes();
    
            var r = RouteTable.Routes;
    
            RouteTable.Routes.MapRoute(
            "Media", // Route name
            "Media/{id}/{file}", // URL 
            new { controller = "Media", action = "DownloadFile", id = UrlParameter.Optional, file = UrlParameter.Optional });
    
        }
    
    
            public void Terminate()
            {
                // Nothing to terminate
            }
        }
    

    Here what I have in my web config in the media folder for the IHTTPHandler

  • Huw Reddick 1737 posts 6077 karma points MVP c-trib
    Nov 21, 2022 @ 17:08
    Huw Reddick
    0

    can't see what you have in your media web.config.

    This is how I do it, I have an IHttphandler

    public class ProtectedMediaHttpHandler : IHttpHandler
    

    It is referenced in the media folders web.config

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.webServer>
        <handlers>
          <clear />
          <add name="DOC" path="*.doc" verb="*" type="MyApp.ProtectedMediaHttpHandler" />
          <add name="PDF" path="*.pdf" verb="*" type="MyApp.ProtectedMediaHttpHandler" />
    
        </handlers>
      </system.webServer>
    </configuration>
    

    Where MyApp is the namespace of your httphandler, there is no custome routing involved, just the change to the web.config in the media folder

  • Huw Reddick 1737 posts 6077 karma points MVP c-trib
    Nov 21, 2022 @ 17:18
    Huw Reddick
    0

    This is the fullcode for my Httphandler

    public class ProtectedMediaHttpHandler : IHttpHandler
    {
        /// <summary>
        /// Gets a value indicating whether another request can use the IHttpHandler instance.
        /// </summary>
        public bool IsReusable => false;
    
        /// <summary>
        /// Method to return the file
        /// </summary>
        /// <param name="context"></param>
        /// <param name="strFile"></param>
        /// <returns></returns>
        HttpContext SendContentTypeAndFile(HttpContext context, string strFile)
        {
            context.Response.ContentType = GetContentType(strFile);
            context.Response.TransmitFile(strFile);
            context.Response.End();
            return context;
        }
    
        /// <summary>
        /// Processes the Http request
        /// </summary>
        /// <param name="context">HttpContext</param>
        /// <remarks>
        /// Returns the requested file or Http 403 if the file is protected and user not logged in
        /// </remarks>
        public void ProcessRequest(HttpContext context)
        {
            var user = new HttpContextWrapper(HttpContext.Current).User;
            var ticket = new HttpContextWrapper(HttpContext.Current).GetUmbracoAuthTicket();
            //Allow Bacoffice users if authenticated
            bool isAllowed = ticket != null;
            if (isAllowed) // we are allowed so return the file
            {
                string requestedFile = context.Server.MapPath(context.Request.FilePath);
                SendContentTypeAndFile(context, requestedFile);
                return;
            }
            List<KeyValuePair<string, object>> parameters = new List<KeyValuePair<string, object>>
            {
                new KeyValuePair<string, object>("username", user.Identity.Name),
                new KeyValuePair<string, object>("mediaPath", context.Request.FilePath)
            };
    
            ApiHelper apiHelper = new ApiHelper();
            var port = (context.Request.Url.Port != 443) ? ":" + context.Request.Url.Port : "";
            string url = apiHelper.BuildApiUrl(
                domainAddress: "https://" + context.Request.Url.Host + port,
                apiLocation: "Umbraco/Api/",
                controllerName: "ProtectedMediaApi",
                methodName: "IsAllowed",
                parameters: parameters);
    
            if(!isAllowed)
            {
                try
                {
                    isAllowed = apiHelper.GetResultFromApi<bool>(url);
                }
                catch (Exception e)
                {
                    throw new Exception($"{e.Message} : {context.Request.FilePath}");
                } 
            }
    
    
            if (isAllowed)
            {
                string requestedFile = context.Server.MapPath(context.Request.FilePath);
                SendContentTypeAndFile(context, requestedFile);
                return;
            }
            else
            {
                context.Response.Status = "403 Forbidden";
                context.Response.StatusCode = 403;
                return;
            }
        }
    
        /// <summary>
        /// Method to return the files Content/MimeType
        /// </summary>
        /// <param name="filename"></param>
        /// <returns>A string representing the mime type of the file</returns>
        private string GetContentType(string filename)
        {
            FileInfo fileinfo = new FileInfo(filename);
    
            if (fileinfo.Exists)
            {
                string res;
                switch (fileinfo.Extension.Remove(0, 1).ToLower())
                {
                    case "pdf":
                        {
                            res = "application/pdf";
                            break;
                        }
                    case "doc":
                        {
                            res = "application/msword";
                            break;
                        }
                    case "docx":
                        {
                            res = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
                            break;
                        }
                    case "xls":
                        {
                            res = "application/vnd.ms-excel";
                            break;
                        }
                    case "xlsx":
                        {
                            res = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
                            break;
                        }
                    case "afdesign":
                        {
                            res = "application/octet-stream";
                            break;
                        }
                    default:
                        res = MimeMapping.GetMimeMapping(filename);
                        break;
                }
                return res;
            }
            return null;
        }
    
        /// <summary>
        /// Helper class to send Api calls
        /// </summary>
        private class ApiHelper
        {
            /// <summary>
            /// Builds URL for calling an API method
            /// </summary>
            /// <param name="domainAddress">The domain of the API</param>
            /// <param name="controllerName">The name of the controller</param>
            /// <param name="methodName">The name of the action to call</param>
            /// <param name="parameters">List of keyvalue parameters to send</param>
            /// <param name="apiLocation">Application folder of API (if not at root)</param>
            /// <returns>A string representing the API URL</returns>
            public string BuildApiUrl(string domainAddress, string controllerName, string methodName, List<KeyValuePair<string, object>> parameters, string apiLocation)
            {
                StringBuilder url = new StringBuilder();
                url.Append($"{domainAddress}/{apiLocation}{controllerName}/{methodName}");
                if (parameters != null && parameters.Count > 0)
                {
                    int parameterCount = parameters.Count;
                    for (int i = 0; i < parameterCount; i++)
                    {
                        url.Append(i == 0 ? "?" : "&");
                        url.Append($"{parameters[i].Key}={parameters[i].Value.ToString()}");
                    }
                }
                return url.ToString();
            }
            /// <summary>
            /// Returns the response from an API call
            /// </summary>
            /// <typeparam name="T">Type of result to return</typeparam>
            /// <param name="url">API url</param>
            /// <returns>An object of the Type specified.</returns>
            public T GetResultFromApi<T>(string url)
            {
                using (HttpClient httpClient = new HttpClient())
                {
    
                    Task<string> response = httpClient.GetStringAsync(url);
                    var res = response.Result;
                    var test = Task.Factory.StartNew(() => JsonConvert.DeserializeObject<T>(res));
    
                    return test.Result;
                }
            }
        }
    
    }
    

    It calls an API method which returns true/false depending on user access

  • Darren Hunter 103 posts 194 karma points
    Nov 21, 2022 @ 18:24
    Darren Hunter
    0

    I have tried that using a IHTTPHandler, but if you read what I put I said once you put in a URL for a valid media file say

    http://site/Media/1345/testme.pdf.

    It is not hitting the IHTTPHandler even if the correct settings are in the web config in the media folder per the artical on code share

    https://codeshare.co.uk/blog/how-to-protect-media-items-in-umbraco/

    For some reason it not allowing the IHTTPHandler to fire if it finding the file first.

    I tried it both with a HTTP Handler and a surface control in the latest version of V8 and it not working either way,

  • Huw Reddick 1737 posts 6077 karma points MVP c-trib
    Nov 21, 2022 @ 20:26
    Huw Reddick
    0

    It is working fine for me, so not sure why it doesn't work for you

  • Huw Reddick 1737 posts 6077 karma points MVP c-trib
    Nov 22, 2022 @ 12:19
    Huw Reddick
    0

    Are you still registerring a custom route? this maybe what is causing your problem.

    You should just require the IHttpHandler and the web.config change in the media folder, nothing else is required. If you have done something with the routing then it is probably bypassing the web.config and therfore the handler.

    Could you post the code for your handler and your media web.config?

  • Darren Hunter 103 posts 194 karma points
    Nov 22, 2022 @ 12:43
    Darren Hunter
    0

    I have it working with a IHTTPHandler.

    Looks like some of it was down to caching in Chrome.

  • Huw Reddick 1737 posts 6077 karma points MVP c-trib
    Nov 22, 2022 @ 12:45
    Huw Reddick
    0

    Ah, yes, I have had this issue after succesfully grabbing the pdf while logged in, it will then be in your browser cache so you need to force it to refetch :) otherwise it just grabs it from the cache, it will do this in any browser not just chrome

Please Sign in or register to post replies

Write your reply to:

Draft