mediacms-io / mediacms

MediaCMS is a modern, fully featured open source video and media CMS, written in Python/Django and React, featuring a REST API.
https://mediacms.io
GNU Affero General Public License v3.0
2.86k stars 533 forks source link

Allow media embed for certain domains with Global Login #1090

Open yatesdr opened 1 month ago

yatesdr commented 1 month ago

Feature request: Allow embedded media to play when requested by white-listed referers, when the GLOBAL_LOGIN_REQUIRED option is enabled. This allows for the use-case of private media management for use as a back-end in a separate user-controlled system, with some degree of hot-linking protection.

Although request.META['HTTP_REFERER'] can be spoofed and therefore this proposal does not offer perfect security, it's beneficial as an opt-in config in that it's a simple way to allow hosting of videos while not making them fully public (unlisted) or otherwise easily hot-linkable, and limits the ability to browse or otherwise parse media if not authenticated in mediaCMS. Combining this with CORS settings and upstream proxy referer settings can give reasonable assurance that the videos will only be viewed in the intended way.

Proposed solution I have a working local installation, and the changes roughly consist of:

1) Add a config variable in cms/settings.py: GLOBAL_LOGIN_ALLOW_EMBED_DOMAINS = ["my-domain.com", "cdn.my-domain.com"]

2) Further down in cms/settings.py, if the above variable is set, patch additional LOGIN_REQUIRED_IGNORE_PATHS to include r'^/embed.*' and r'^/media/.*' for Django auth middleware, which allows bypassing global auth for these paths.

3) In files/views.py Add logic in embed_media() to check configuration, parse the request's HTTP_REFERER hostname, and deny serving to non-whitelisted domains. After adding the bypass in step 2, all embed links would be served to any referer. This will limit it to the white listed domains only, but does require that the referering system properly set the header.

Alternatives considered: I investigated patching the middleware to allow white-listed domains, but that fix seemed too broad for all the deployed applications. Using this method you can limit the exposure more granularly by path.

My current use-case for this platform is to allow for distributed media management by a number of content managers, and host the relevant media in a content consumption site that will permit viewing the media via iframe embed. We do not own the copyright to every piece of media that will be used, and thus can not allow it to be publicly hosted without some safeguards. End-user authorization is handled by Canvas-LMS in this case, and all pages on the referring system are permitted to embed any media hosted on MediaCMS.

Refinements needed:
1) The regex expressions need further refinement to limit creative pathing attacks. 2) Review the error pages, possibly add a response other than redirect to login, to fail without leaking information about the backend service.

I would like feedback on this proposal before creating a pull request: 1) Is this something you'd like in the main branch? 2) Is there a better or more secure way to achieve similar functionality? I'm happy to chase down other proposed implementations to keep it in line with the creator's vision for this package. 3) Reviewing the embedded media links, it looks like media/.* is required to basically be open, is there a better way to limit this only to serve media in embeds?

mgogoulos commented 1 month ago

Hi @yatesdr ,

I like your suggestions, it's a workflow I haven't thought of much until now, when you are ready with a draft PR I can test it and gain a better understanding and provide more suggestions. Definitely go ahead and work on a draft PR!

Specifically for item 3. , a sample URL is https://demo.mediacms.io/media/hls/a3c5642e13624149897f193981ebccf3/media-4/segment-0.ts . Because of the identifier used, there's no risk to expose private files (realistically speaking, since it's a uuid), however it's true that ther's no further access control on it, if you see it through the embed and share it then its visible without the HTTP_REFERER, as this file is served through nginx and doesn't know of the custom checks. I guess more complicated functionality around time based tokens that expire and restrict access to the media files, but we could probably work out (or at least brainstorm) something like this as a v2.

yatesdr commented 4 weeks ago

I submitted a draft PR, but it seems to have merged with my previous PR for allowing only specific domains to register. I'm sure I messed something up but I'm not sure what exactly, I rarely do PR's on github. Both are fairly minor changes though, so you can review the suggestions and I'll work on separating them for a clean PR when I get a bit more time.

yatesdr commented 4 weeks ago

I think I got the PR sorted, you should see a draft for review and further discussion.

mgogoulos commented 3 weeks ago

Hi, I saw the PR that was opened and closed, but you didn't reopen it after that!