- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 491
 
Attach user from access token after checks #1614
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
base: main
Are you sure you want to change the base?
Attach user from access token after checks #1614
Conversation
| const tokenResult = (await cliEditor.send("getAccessToken", 0, {readOnly: false})).data; | ||
| const i7 = await postAttachment(editor, docId, tokenResult.token, "data7", "7.txt"); | ||
| await assert.isFulfilled(getAttachment(owner, docId, i7)); | ||
| //await assert.isFulfilled(getAttachment(editor, docId, i7)); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be discussed, it fails because it is impossible to set maybeNew from the web api. To me, in should be made possible. I can open another PR for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure of what you mean here, especially about the maybeNew. Can you provide more context please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was, to me, counterintuitive that as the editor uploader of an attachment I can't access it after upload.
For the attachment to be accessible to me, I need to add it to a row accessible to me (cf lines below this comment).
I refer to the maybeNew because it is linked to a specific permissive use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this seems fairly straightforward to add support for, although I wonder what use-cases there would be apart from testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the commented out line, it would be good to add a comment explaining why it is commented out.
4a3f00b    to
    ffe3068      
    Compare
  
    | } | ||
| const userId = tokenObj.userId; | ||
| const user = await dbManager.getUser(userId); | ||
| setRequestUser(mreq, dbManager, user!); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If, given an access token, you fully grant the request the identity of the user, it is important to check that this does not accidentally permit more than you intend.
For example, can you add a test showing that a request accessing /api/profile/apiKey using a token will be denied?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this is already tested with https://github.com/gristlabs/grist-core/blob/main/test/server/lib/AccessTokens.ts#L112-L118 (with a slightly different context/check).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please indulge me? The family of endpoints having to do with documents and the endpoints that don't deal with documents operate quite differently. I see a check in
grist-core/app/server/lib/Authorizer.ts
Lines 524 to 529 in fb65d72
| if (tokenObj) { | |
| // Sanity check: does the current document match the document the token is | |
| // for? If not, fail. | |
| if (!mreq.docAuth.docId || mreq.docAuth.docId !== tokenObj.docId) { | |
| throw new ApiError('token misuse', 401); | |
| } | 
/api/profile/apiKey from returning a value once the user has been vouched for as your code does? Maybe there is something. It is worth a test. If it is already blocked, great, if not, you'd just need to reject use of an access token outside of the context of documents.
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I didn't mean not to do what is important here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to add a test to ensure resquests outside of the document endpoint would fail but they actually do not generate responses with error code because of anonymous access. cf. https://github.com/gristlabs/grist-core/pull/1614/files#diff-1aeb508e4b5874641fe6a997ac27fa7315a9c05513aa0695bb889b9c5de4ca49R120
I added a test that ensure that the request is considered from a anonymous user and not the use linked to the provided token.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it is be enough for you? Let me know what I can add to this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @guillett, it all looks ok. I hadn't carefully checked where your intervention was placed.
5a287e0    to
    6db6701      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From my prospective, LGTM
Co-authored-by: Florent <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good, thanks @guillett. One small request.
| const tokenResult = (await cliEditor.send("getAccessToken", 0, {readOnly: false})).data; | ||
| const i7 = await postAttachment(editor, docId, tokenResult.token, "data7", "7.txt"); | ||
| await assert.isFulfilled(getAttachment(owner, docId, i7)); | ||
| //await assert.isFulfilled(getAttachment(editor, docId, i7)); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this seems fairly straightforward to add support for, although I wonder what use-cases there would be apart from testing.
| const tokenResult = (await cliEditor.send("getAccessToken", 0, {readOnly: false})).data; | ||
| const i7 = await postAttachment(editor, docId, tokenResult.token, "data7", "7.txt"); | ||
| await assert.isFulfilled(getAttachment(owner, docId, i7)); | ||
| //await assert.isFulfilled(getAttachment(editor, docId, i7)); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to the commented out line, it would be good to add a comment explaining why it is commented out.
| } | ||
| const userId = tokenObj.userId; | ||
| const user = await dbManager.getUser(userId); | ||
| setRequestUser(mreq, dbManager, user!); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am uncomfortable with this setRequestUser call. It changes the request object, and makes it hard to know elsewhere which user the request object will contain. An accessToken should allow a limited scope to act as the given user, but so much code relies on the user in the request that it's hard to know what might unintentionally get authorized.
I need more time to look into this.
(One idea to look into is adding info for the effective user (user/userId/fullUser) as an extra property to mreq or to mreq.docAuth, and then update just the code that should be affected by accessToken to pay attention to that.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried the last idea I mentioned (adding a property to mreq.docAuth) and it seems to work well, at least for the test cases in this PR: https://gist.github.com/dsagal/ebd7a0b5e8bce37caae4a335cf744caa.
What do you think? @paulfitz, interested in your feedback too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, using mreq.docAuth feels a clear way to do it.
Context and related issues
I built a custom widget to upload documents.
It relies on access tokens based requests.
Access token generated unauthenticated requests (logged in as anonymous) which leads to impossible attachment manipulation. (Anonymous user "made" the upload, but I want to add it to a row as user X, user X is not authorized, because of a check that limits who can access a yet-unreferenced attachment.)
To replicate the issue
1, 2 and 3 are ok but 4 fails. Well actually it fails when you are not the owner of the doc (because as a owner you have all the rights).
That's the reason why I would like the access token to authenticate as the user of the widget and therefore attach user information to the request in such a case.
(Many thanks to @fflorent)
To give more context, here is a working exemple. It highlights a slightly different issue.
https://grist.numerique.gouv.fr/o/docs/daZkkLK5fpoZ/Demo-limit-widget
https://docs.getgrist.com/c3nzPGK4VHbZ/Demo-limit-widget1
Here is the custom widget code.
I created this custom widget to show the difference between record addition via the widget and via the access token.
As an unlogged user both buttons generate record related to [email protected].
As a logged-in user records added with the widget are correcly linked but via the access token, records are linked to [email protected].
Proposed solution
Attach the user after verification that the access is legitimate at the document level.
Has this been tested?