Skip to content

beforeGetFile trigger #6572

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
stevestencil opened this issue Apr 6, 2020 · 35 comments · Fixed by #8700
Closed

beforeGetFile trigger #6572

stevestencil opened this issue Apr 6, 2020 · 35 comments · Fixed by #8700
Labels
state:released Released as stable version state:released-alpha Released as alpha version type:feature New feature or improvement of existing feature

Comments

@stevestencil
Copy link
Contributor

Is your feature request related to a problem? Please describe.
With the addition of file triggers, the only one that is missing is beforeGetFile. Let's say that I want to restrict fetching a file to only the user who initially created it. Currently there is no way to do that since anyone with the file url can access it.

Describe the solution you'd like
Add a beforeGetFile file trigger so that an additional layer of security can be added when fetching files

Describe alternatives you've considered
None

Additional context
Currently anyone who has the file name/url can retrieve a file. Adding a beforeGetFile trigger will allow for additional file security. My plan is to use the beforeSaveFile and afterSaveFile triggers to keep track of files and who created them. I can then use the beforeGetFile to restrict access to only the files they have created. The challenge is there is no session token passed in when retrieving files. I have no problem tackling this feature request, but I would like to get feedback on the best way to approach this?

@mman
Copy link
Contributor

mman commented Apr 7, 2020

This sounds like an interesting idea but it is hard to implement systematically since for example with S3 adapter the URL points directly towards S3 after upload and the file download does not go through the parse server, which is a good thing IMHO for performance reasons. Could probably work with GridFSAdapter.

@stevestencil
Copy link
Contributor Author

If you're using direct access to your S3 adapter you are correct. The only way to implement this would be if you do not use direct access

@TomWFox TomWFox added the type:feature New feature or improvement of existing feature label Apr 15, 2020
@mtrezza
Copy link
Member

mtrezza commented May 16, 2020

I am all for beforeFindFile and afterFindFile triggers. I think this is a very valid feature request, in fact I just posted a SO question before finding this.

Of course direct access for S3 means that the 404's would have to be detected from the S3 server access logs, but that is something Parse Server doesn't have to account for.

@stale
Copy link

stale bot commented Nov 8, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 8, 2020
@mtrezza mtrezza removed the stale label Nov 8, 2020
This was referenced Nov 10, 2020
@dsyrstad
Copy link

dsyrstad commented Jul 14, 2021

I'm all for this request. With parsed-based file URLs the way they are now, it is a security issue. Once someone has the URL, they have it forever and can access it without any kind of authentication or authorization.

@mtrezza
Copy link
Member

mtrezza commented Jul 15, 2021

@dsyrstad If it helps, it is possible to create temporary URLs, depending on how you store your files. For example, for AWS S3 there is an open feature request to support pre-signed URLs.

@dsyrstad
Copy link

dsyrstad commented Jul 15, 2021

@dsyrstad If it helps, it is possible to create temporary URLs, depending on how you store your files...

@mtrezza Are you saying a temp URL could be created in the file store adapter, or elsewhere?

@mtrezza
Copy link
Member

mtrezza commented Jul 15, 2021

The expiration would be managed by the storage service, but the Parse Server storage adapter would request the temporary URL from the service. I think this is already implemented in the Azure storage adapter, but if you want that for S3, you could continue the PR that already exists.

@dblythy
Copy link
Member

dblythy commented Mar 26, 2022

It would be good to have an option for something like req.file.download = true so you can force files to download.

I'm happy to work on this, but I believe the only concern is it's impossible to determine req.user when files are accessed via a get request to a URL (as browsers don't pass in req headers for <img src="parsefilurl" />). Adding req.user would require some sort of FileObject class as well as tokens in the URL param to determine ID of user requesting the get.

Do you have any ideas @mtrezza?

@mtrezza
Copy link
Member

mtrezza commented Mar 26, 2022

I think Parse Server could restrict certain files to certain users while allowing anonymous downloads for other files.

  • If you have an image that will be embedded on a website, you simply don't have to restrict it.
  • If you a have a sensitive document then you can restrict it (and provide header auth info in the request).

Setting permission per file may be something that should really be done on the resource site. For example, if you store a file on AWS S3, you could use AWS IAM and link that to the Parse Server User class via an identity broker, so that you can reference S3 file permissions using Parse Server User IDs. That's the safest way, because it's on the lowest resource level, regardless from where the access request comes.

If we want to "fake" these storage permissions in Parse Server, we'd probably need a central list of files. Because a Parse File can be referenced in many places, but binding a permission to only one of many references doesn't make much sense. We could create a new internal class File and if a file should be restricted, add an entry there with the file pointer and a fileACL field. So every time a file is requested, a lookup happens in that class and the ACL is applied. Files would be compared by filename as their UID.

Btw, this File class is something we have also discussed for a file cleanup feature, so it would be a step into that direction.

@dblythy
Copy link
Member

dblythy commented Mar 26, 2022

So every time a file is requested, a lookup happens in that class and the ACL is applied. Files would be compared by filename as their UID.

I think the problem with this approach is ParseFile urls are static (unless you are using presigned URLs PR on the S3 adapter, which is great by the way), so if you had ACL permission to the File class, you can share the URL with anyone publicly and there is no way to verify ACL of the file being accessed by its public link, unless we add some sort of internal generated ID and expiry time for when the File is requested.

My thinking is that when someone accesses the public Parse File link, that is when the trigger would fire and try to find the associated File Class and permissions.

Btw, this File class is something we have also discussed for a file cleanup feature, so it would be a step into that direction.

Agree!

@dblythy
Copy link
Member

dblythy commented Mar 26, 2022

Related #7001

@mtrezza
Copy link
Member

mtrezza commented Mar 26, 2022

you can share the URL with anyone publicly and there is no way to verify ACL of the file being accessed by its public link

There are 2 ways to share a file:

  • (a) via shadow URL that points to Parse Server which fetches the file from the resource
  • (b) via direct access URL to the storage resource

In case of (a) you can easily do the File class lookup on request as Parse Server sits between client and storage resource.

In case of (b) you can only restrict that on the resource side, that's what I mentioned above with the identity broker. Currently it means you are giving up Parse Server control over the file access.

@dblythy
Copy link
Member

dblythy commented Mar 26, 2022

Forgive me I’m sure we’ve had this discussion before around the best approach to this issue.

In case of (a) you can easily do the File class lookup on request as Parse Server sits between client and storage resource.

Yes, you can easily identify the file through the request, but how do you identify the req.user? In order to determine ACL you need to know the auth of the user, which can’t be determined via simple get requests of the file url.

@mtrezza
Copy link
Member

mtrezza commented Mar 26, 2022

If a file has an ACL set, then you look for auth headers. If there are none, you deny the request. In practice we would try to re-use existing auth logic of other routes and add it to the file route of Parse Server.

@dblythy
Copy link
Member

dblythy commented Mar 26, 2022

Yes, but if my understanding a file is embedded or opened via hyperlink, headers aren’t set, meaning that the file would be denied if embedded in a html file, imageview, etc, rendering it practically useless unless fetched via a library such as axios where you can set headers

@mtrezza
Copy link
Member

mtrezza commented Mar 26, 2022

That's correct. I would say that's the default behavior of a browser. As a website provider you don't control the headers that a browser is sending to your server. It seems you have a specific scenario in your head; if you want to share that scenario we can look at how and whether this feature fits into your scenario.

@dblythy
Copy link
Member

dblythy commented Mar 27, 2022

Yes, so my thoughts are that if you’re currently using Parse Files to serve a users Photos or Passport (for example), you most likely have that embedded via a src link, or an action that opens the Parse File URL in a new window.

If you use this new feature which requires headers, this functionality would break and you would have to embed the image via:


const options = {
  headers: {
    '…': '...'
  }
};

fetch(src, options)
.then(res => res.blob())
.then(blob => {
  imgElement.src = URL.createObjectURL(blob);
});

I’m suggesting that we use URL parameters to pass a token so that the File URLs can still be embedded without needing a specific method and code refactoring across every SDK, but I guess if this is a security feature recommended for specific secure files, developers will probably be happy to refactor their client code.

@mtrezza
Copy link
Member

mtrezza commented Mar 27, 2022

I assume the token is to authenticate the request as a specific user. In your scenario, what should be the characteristics of that token, e.g. how long should it be valid, etc?

@dblythy
Copy link
Member

dblythy commented Mar 28, 2022

Well initially I was thinking assigning each File class object a TOTP secret that can be used to generate TOTPs that auto-expire in a dedicated period (e.g 300 seconds). That way all the auth can happen at the FileObject level by Parse Server's internal CLPs / ACLs, then if valid, generate the TOTP code, assign it to the URL param, then delete the secret from the payload. It would be a way to handle "presigned URLs" without having to store tokens.

Alternatively, we could generate tokens and expiry times aligned to a specific file (again handling the ACLs as Parse normally does with pointers), and store them as a relation between the FileObject and a FileToken object (which would contain expiry_time, token and file_id

@mtrezza
Copy link
Member

mtrezza commented Mar 28, 2022

What is the relation between expiring token and user, where would that be stored?

@dblythy
Copy link
Member

dblythy commented Mar 28, 2022

Well I'd assume it would just be a relation between a FileObject and FileToken, and I think that all would need to be stored on FileToken is objectId, expiry_date, and maybe user.

So, when a FileObject is saved, it has ACLs.

When a file is retrieved if the permissions are valid, generate a random token, append it to the file URL, and create a FileToken obj with token, file, expiration, and user. This could also be stored on the FileObject class, however if a file is access regularly by many different users, it might result in performance issues

@mtrezza
Copy link
Member

mtrezza commented Mar 28, 2022

OK, so these are two separate features:

  • (a) File ACL to be accessible only for certain users / roles
  • (b) Expiring access tokens

Maybe we should look at them separately and look at (a) first. (b) may be something Parse Server doesn't need to implement, as many storage providers already have such a feature and it may be something that can be incorporated into the storage adapter's getUrl to take some work off Parse Server.

Can you outline the process for (a) you have in mind in a 1. / 2. / 3 step-by-step format, from file upload to file download, so we understand when should happen what, where is info stored, where are potential perf implications.

@brunobg
Copy link

brunobg commented Mar 28, 2022

What about allowing a simple {parseFileUrl}/download endpoint, perhaps with a callback to check if it is allowed?

@mtrezza
Copy link
Member

mtrezza commented Mar 28, 2022

I think @brunobg has a point. Maybe we can start off with adding simple triggers, so anyone can implement their own concept of file access permission. Then we can think about a built-in ACL for files in a separate step.

There could be 2 triggers, 1 before a file URL that is generated by the storage adapter is returned and 1 before a file URL is parsed in the process of a file request. So it's possible to customize the URL that is sent to the client and customize the URL interpretation when a file request is triggered with the customized URL.

You could for example replace the filename with a token that includes the user ID, the filename and an expiration date and you need the master key to decrypt that information. Then when a file is requested, the server can try to decrypt and give permission if the requesting user is correct and the token has not expired. Or one can create a simple lookup function in a custom class for the filename and the associated user relation. Anyone can build their own algorithm.

@dblythy
Copy link
Member

dblythy commented Mar 28, 2022

Well as I said that if we add the trigger, req.user will always be undefined if the file is accessed via URL. The access tokens are so that the beforeGetFile trigger can infer req.user via a URL get request.

I just think from a completeness perspective, it would be better to introduce the trigger with req.user functioning as the other triggers do, rather than the trigger having it's own caveats.

@mtrezza
Copy link
Member

mtrezza commented Mar 29, 2022

I have edited my previous comment a couple of times, so you may want to reread it.

Well as I said that if we add the trigger, req.user will always be undefined if the file is accessed via URL.

If a session token is sent in the request header, then the req.user should be set. If the session is missing (e. reference in an <img> HTML tag), then one could implement their custom token strategy via the triggers I proposed above.

Parse Server could of course offer a token functionality OOTB, but I think the most versatile step may be to open Parse Server up for URL customization via file triggers. These triggers are likely required internally to implement any OOTB solution, so we may as well break this down into smaller PRs and expose them first.

@dblythy
Copy link
Member

dblythy commented Mar 30, 2022

Sounds like a good idea to me. What would you propose the secondary trigger to be named?

@mtrezza
Copy link
Member

mtrezza commented Mar 30, 2022

If the beforeGetFile trigger is where the request URL is where the auth is verified, then something like a beforeComposeFile could be where the file URL (and maybe other Parse.File properties) can be manipulated.

@dblythy
Copy link
Member

dblythy commented Apr 5, 2022

What would you think about

beforeCompose: runs on compose file
beforeFind(Parse.File): runs on get handler

and then add:
beforeSave(Parse.File)
afterSave(Parse.File)

@mtrezza
Copy link
Member

mtrezza commented Apr 5, 2022

Sounds good, is there another word for "compose" that could be used more universally (also for classes) in the future? Maybe a beforeCreate?

@mbfakourii
Copy link
Member

mbfakourii commented Apr 18, 2023

We use the local system to store files, and we need beforeFind for file security. If possible, please look into this matter further. Thank you.

@dblythy
Copy link
Member

dblythy commented Mar 19, 2025

Just a note that it could be pretty easy to add a service worker to the SDK for the browser to pass in the Session Token header automatically for GET requests to the Parse.ServerUrl

@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 8.1.0-alpha.2

@parseplatformorg parseplatformorg added the state:released-alpha Released as alpha version label Mar 27, 2025
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 8.1.0

@parseplatformorg parseplatformorg added the state:released Released as stable version label Apr 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
state:released Released as stable version state:released-alpha Released as alpha version type:feature New feature or improvement of existing feature
Projects
None yet
9 participants