Skip to content
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

Accept context via header X-Parse-Cloud-Context #7437

Merged
merged 16 commits into from
Jul 26, 2021
Merged

Accept context via header X-Parse-Cloud-Context #7437

merged 16 commits into from
Jul 26, 2021

Conversation

cbaker6
Copy link
Contributor

@cbaker6 cbaker6 commented Jun 20, 2021

New Pull Request Checklist

Issue Description

Related issue: Currently context is setup to accept an object in the header, but no header is available. This allows client SDK's to add context via a header instead of injecting _context in the body of JSON object, forcing the parse-server to have to delete it in middleware. Close #7438

Approach

Add a new header for content called X-Parse-Cloud-Context.

TODOs before merging

  • Add test cases
  • Add entry to changelog

@codecov
Copy link

codecov bot commented Jun 20, 2021

Codecov Report

Merging #7437 (a3e6e66) into master (c3b71ba) will increase coverage by 0.02%.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #7437      +/-   ##
==========================================
+ Coverage   93.92%   93.95%   +0.02%     
==========================================
  Files         181      181              
  Lines       13252    13267      +15     
==========================================
+ Hits        12447    12465      +18     
+ Misses        805      802       -3     
Impacted Files Coverage Δ
src/middlewares.js 97.36% <100.00%> (+0.18%) ⬆️
src/RestWrite.js 93.92% <0.00%> (-0.16%) ⬇️
src/Adapters/Storage/Mongo/MongoStorageAdapter.js 93.02% <0.00%> (+0.65%) ⬆️
src/batch.js 93.10% <0.00%> (+1.72%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c3b71ba...a3e6e66. Read the comment docs.

Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One argument for transmitting context in the HTTP body (as it is currently done) instead of header is the size limitation of headers. RFC does not impose a size limitation, but practically the limit in most implementations is much lower for header than for body.~~

At this point I don't think this PR is beneficial. Let's discuss in #7438 (comment) but I wanted to make a note here so the PR doesn't get merged by mistake before the discussion in concluded.

As discussed in #7438 (comment), context transmission via header and body are both valid approaches. It can be considered a feature or at least expected behavior that header values can be overridden with body values as described in #7438 (comment).

@cbaker6 cbaker6 changed the title Accept context via header X-Parse-Context Accept context via header X-Parse-Cloud-Context Jun 20, 2021
@mtrezza
Copy link
Member

mtrezza commented Jun 20, 2021

Since we allow to override header values with body values, should we add a separate test case for that? I don't know whether other parameters have a test case for override, but it would at least document that behavior if we add a test case for context override. And it would probably fix the coverage fail.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 20, 2021

Since we allow to override header values with body values, should we add a separate test case for that? I don't know whether other parameters have a test case for override, but it would at least document that behavior if we add a test case for context override. And it would probably fix the coverage fail.

I can add this later today. I think the coverage fail is codecov has some probability of error in its line of code detection. The commit before this was +2/-2 for lines and passed.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 20, 2021

These get hit when you don't provide provide an X-Parse-Application-Id header and are using _ApplicationId in the body:

if (!info.appId || !AppCache.get(info.appId)) {
// See if we can find the app id on the body.
if (req.body instanceof Buffer) {
// The only chance to find the app id is if this is a file
// upload that actually is a JSON body. So try to parse it.
// https://github.com/parse-community/parse-server/issues/6589
// It is also possible that the client is trying to upload a file but forgot
// to provide x-parse-app-id in header and parse a binary file will fail
try {
req.body = JSON.parse(req.body);
} catch (e) {
return invalidRequest(req, res);
}
fileViaJSON = true;
}
if (req.body) {
delete req.body._RevocableSession;
}
if (
req.body &&
req.body._ApplicationId &&
AppCache.get(req.body._ApplicationId) &&
(!info.masterKey || AppCache.get(req.body._ApplicationId).masterKey === info.masterKey)
) {
info.appId = req.body._ApplicationId;
info.javascriptKey = req.body._JavaScriptKey || '';
delete req.body._ApplicationId;
delete req.body._JavaScriptKey;
// TODO: test that the REST API formats generated by the other
// SDKs are handled ok
if (req.body._ClientVersion) {
info.clientVersion = req.body._ClientVersion;
delete req.body._ClientVersion;
}
if (req.body._InstallationId) {
info.installationId = req.body._InstallationId;
delete req.body._InstallationId;
}
if (req.body._SessionToken) {
info.sessionToken = req.body._SessionToken;
delete req.body._SessionToken;
}
if (req.body._MasterKey) {
info.masterKey = req.body._MasterKey;
delete req.body._MasterKey;
}
if (req.body._context && req.body._context instanceof Object) {
info.context = req.body._context;
delete req.body._context;
}
if (req.body._ContentType) {
req.headers['content-type'] = req.body._ContentType;
delete req.body._ContentType;
}

the request library expects objects to be JSON strings, so the SDKs using body modification must be sending objects directly instead of JSON strings (not the correct way), similar to the fix I made in #7242. Because of this, I can't create the test to override the header with the body without doing a similar fix which is outside the scope of this PR.

  1. I assume those SDKs (only the JS SDK) using context as private body property already work as I don't see reported issues about them
  2. This PR enables context to also be sent via a header and doesn't get in the way of previous implementations

Because of what I mentioned above, I believe this PR can be merged without the additional test.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 20, 2021

My comment about codecov can be seen by observing the actual codecov: https://app.codecov.io/gh/parse-community/parse-server/compare/7437/diff#diff-c3JjL21pZGRsZXdhcmVzLmpz

There's no delta...

@mtrezza
Copy link
Member

mtrezza commented Jun 21, 2021

the SDKs using body modification must be sending objects directly instead of JSON strings

Are you referring to a test using the REST API? Couldn't you write a test using the JS SDK, saving a Parse.Object and setting a context in the save options and override that by setting _context on the Parse.Object?

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 21, 2021

Are you referring to a test using the REST API? Couldn't you write a test using the JS SDK, saving a Parse.Object and setting a context in the save options and override that by setting _context on the Parse.Object?

I'm not sure what you mean by this. Isn't this already done in this test and the tests after it?: https://github.com/cbaker6/parse-server/blob/a1be895cc55519586d2dab9f25a0b7616c3bb54c/spec/CloudCode.spec.js#L2839-L2847

Which ends up passing _context in the JS SDK:
https://github.com/parse-community/Parse-SDK-JS/blob/e6f15fdc09429a2c456ec6387d365914af8a8988/src/RESTController.js#L230-L234

I can add another test that checks if options overrides the _context property set on an object, but this wouldn't have anything to do with the header:

it('context options should override _context object property when saving a new object', async () => {
    Parse.Cloud.beforeSave('TestObject', req => {
      expect(req.context.a).toEqual('a');
      expect(req.context.hello).not.toBeDefined();
      expect(req._context).not.toBeDefined();
    });
    Parse.Cloud.afterSave('TestObject', req => {
      expect(req.context.a).toEqual('a');
      expect(req.context.hello).not.toBeDefined();
      expect(req._context).not.toBeDefined();
    });
    const obj = new TestObject();
    obj.set('_context', { hello: 'world' });
    await obj.save(null, { context: { a: 'a' } });
  });

@mtrezza
Copy link
Member

mtrezza commented Jun 21, 2021

Isn't this already done in this test and the tests after it?:

No, because it doesn't test overriding.

I can add another test that checks if options overrides the _context property set on an object

This is the test I was thinking about. It would test that the context in body overrides the context in header. Since this seems to be expected behavior, I think it would be good to test it.

but this wouldn't have anything to do with the header

Wouldn't obj.save(null, { context: { a: 'a' } }) set the context in the header and obj.set('_context', { hello: 'world' }); set the context in the body for override?

@@ -35,7 +35,7 @@ export function handleParseHeaders(req, res, next) {
dotNetKey: req.get('X-Parse-Windows-Key'),
restAPIKey: req.get('X-Parse-REST-API-Key'),
clientVersion: req.get('X-Parse-Client-Version'),
context: {},
context: req.get('X-Parse-Cloud-Context') == null ? {} : JSON.parse(req.get('X-Parse-Cloud-Context')),
Copy link
Member

@mtrezza mtrezza Jun 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wrap JSON parsing into a try catch and throw an error if necessary? A malformed JSON string would throw an exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 21, 2021

Wouldn't obj.save(null, { context: { a: 'a' } }) set the context in the header and obj.set('_context', { hello: 'world' }); set the context in the body for override?

This wouldn't set context in the header and therefore wouldn't override it, this is because JS uses this to set _context during a save here: https://github.com/parse-community/Parse-SDK-JS/blob/e6f15fdc09429a2c456ec6387d365914af8a8988/src/RESTController.js#L230-L234. From what I can see, JS doesn't seem to use headers at all and instead modifies the body for appId, context, etc.

But I think I have a simple mod that will improve the code and test the overriding.

@mtrezza
Copy link
Member

mtrezza commented Jun 21, 2021

This wouldn't set context in the header and therefore wouldn't override it, but I think I have a simple mod that will improve the code and test the overriding.

I assume this would be a separate PR in the JS SDK. But wouldn't that be the behavior we aim for? The obj.save should set the header, instead of a hidden field in the parse object, which you said seems "hacky". And if someone indeed feels "hacky", they can use obj.set, for whatever reason.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Jun 21, 2021

This wouldn't set context in the header and therefore wouldn't override it, but I think I have a simple mod that will improve the code and test the overriding.

I assume this would be a separate PR in the JS SDK. But wouldn't that be the behavior we aim for? The obj.save should set the header, instead of a hidden field in the parse object, which you said seems "hacky". And if someone indeed feels "hacky", they can use obj.set, for whatever reason.

I didn't intend to create another PR for the JS SDK and not sure if that behavior should be changed since I'm not one of the maintainers of JS nor do I use it directly. Instead, on the server-side, _context only supports objects, so I made it support JSON strings as well to test overriding along with allowing any other SDK in the future that needs to use _contect but can only send JSON strings.

I've added two tests which I xited, these are meant to catch when _context can't make an object. The reason why I xited the tests is because using the JS SDK, I don't know how to make a valid object that would generate incorrect JSON, so the error isn't throwing in those tests. If you know how to make this happen in JS, feel free to suggest.

@@ -454,3 +469,8 @@ function invalidRequest(req, res) {
res.status(403);
res.end('{"error":"unauthorized"}');
}

function malformedContext(req, res) {
res.status(500);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is caused by a developer mistake, so it should be a 4xx response code.

I suggest 400 Bad Request:

The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to 400. I'm assuming you only meant the status change?

spec/CloudCode.spec.js Outdated Show resolved Hide resolved
spec/CloudCode.spec.js Outdated Show resolved Hide resolved
src/middlewares.js Show resolved Hide resolved
src/middlewares.js Outdated Show resolved Hide resolved
@mtrezza mtrezza added type:bug Impaired feature or lacking behavior that is likely assumed and removed type:bug Impaired feature or lacking behavior that is likely assumed labels Jul 11, 2021
@mtrezza
Copy link
Member

mtrezza commented Jul 23, 2021

@cbaker6 Is this ready for review / merge? I've lost track of this. Looking over it, it looks good to merge to me, just maybe add a few words to the change log entry so readers understand what this change is?

CHANGELOG.md Outdated Show resolved Hide resolved
@cbaker6
Copy link
Contributor Author

cbaker6 commented Jul 23, 2021

@cbaker6 Is this ready for review / merge? I've lost track of this. Looking over it, it looks good to merge to me, just maybe add a few words to the change log entry so readers understand what this change is?

Yes, this is ready to go

Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, thanks for this PR! Let's wait for a 2nd review approval...

@mtrezza mtrezza requested a review from davimacedo July 23, 2021 22:47
@mtrezza mtrezza requested a review from dplewis July 24, 2021 11:51
Copy link
Member

@davimacedo davimacedo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good to me

@davimacedo davimacedo merged commit c8e822b into parse-community:master Jul 26, 2021
SebC99 added a commit to hulab/parse-server that referenced this pull request Jul 26, 2021
* master: (55 commits)
  Accept context via header X-Parse-Cloud-Context (parse-community#7437)
  [Snyk] Upgrade ws from 7.4.6 to 7.5.3 (parse-community#7457)
  fix: upgrade @apollographql/graphql-playground-html from 1.6.28 to 1.6.29 (parse-community#7473)
  fix: upgrade @apollographql/graphql-playground-html from 1.6.27 to 1.6.28 (parse-community#7411)
  fix: upgrade graphql from 15.5.0 to 15.5.1 (parse-community#7462)
  [Snyk] Security upgrade parse from 3.2.0 to 3.3.0 (parse-community#7464)
  fix: upgrade apollo-server-express from 2.25.1 to 2.25.2 (parse-community#7465)
  fix: upgrade graphql-tag from 2.12.4 to 2.12.5 (parse-community#7466)
  fix: upgrade graphql-relay from 0.7.0 to 0.8.0 (parse-community#7467)
  Add MongoDB 5.0 support + bump CI env (parse-community#7469)
  changed twitter API endpoint for oauth test (parse-community#7472)
  add runtime deprecation warning (parse-community#7451)
  bumped node (parse-community#7452)
  fix: upgrade apollo-server-express from 2.25.0 to 2.25.1 (parse-community#7449)
  fix: upgrade subscriptions-transport-ws from 0.9.19 to 0.10.0 (parse-community#7450)
  fix: upgrade mongodb from 3.6.8 to 3.6.9 (parse-community#7445)
  fix: upgrade mongodb from 3.6.7 to 3.6.8 (parse-community#7430)
  fix: upgrade apollo-server-express from 2.24.1 to 2.25.0 (parse-community#7435)
  fix: upgrade ldapjs from 2.2.4 to 2.3.0 (parse-community#7436)
  fix: upgrade graphql-relay from 0.6.0 to 0.7.0 (parse-community#7443)
  ...
@parse-github-assistant
Copy link

parse-github-assistant bot commented Oct 17, 2021

Thanks for opening this pull request!

  • ❌ Please link an issue that describes the reason for this pull request, otherwise your pull request will be closed. Make sure to write it as Related issue: #123 in the PR description, so I can recognize it.

@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 5.0.0-beta.1

@parseplatformorg parseplatformorg added the state:released-beta Released as beta version label Nov 1, 2021
@cbaker6 cbaker6 deleted the context branch November 22, 2021 18:43
@mtrezza mtrezza mentioned this pull request Mar 12, 2022
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 5.0.0

@parseplatformorg parseplatformorg added the state:released Released as stable version label Mar 14, 2022
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-beta Released as beta version
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use context header to accept context instead of modifying body
4 participants