Skip to content

fix: _Installation update crashes or orphans row when clearing installationId#10455

Open
AdrianCurtin wants to merge 3 commits into
parse-community:alphafrom
AdrianCurtin:fix_installation_id_error
Open

fix: _Installation update crashes or orphans row when clearing installationId#10455
AdrianCurtin wants to merge 3 commits into
parse-community:alphafrom
AdrianCurtin:fix_installation_id_error

Conversation

@AdrianCurtin
Copy link
Copy Markdown

@AdrianCurtin AdrianCurtin commented May 4, 2026

Issue

RestWrite.handleInstallation runs before Parse __op operators are processed. Two latent bugs in the installationId path:

  • { installationId: { __op: 'Delete' } }this.data.installationId.toLowerCase() is called on the operator object, throws an uncaught TypeError, server returns 500 to the client.
  • { installationId: null } — falls through the lowercase guard, silently nullifies the row's primary identifier and orphans it (no client SDK using X-Parse-Installation-Id can ever find it again).

installationId is the _Installation row's identity — the SDK auth header (X-Parse-Installation-Id, surfaced as this.auth.installationId) binds a non-master client request to its row. The codebase already throws Parse error 136 (installationId may not be changed in this operation) on any change of installationId between values. Clearing is the strongest form of changing and should be rejected for the same reason.

Approach

Detect both clearing shapes at the top of handleInstallation:

const clearingInstallationId =
  this.data.installationId === null ||
  (typeof this.data.installationId === 'object' &&
    this.data.installationId !== null &&
    this.data.installationId.__op === 'Delete');
  • On update (this.query set), throw Parse error 136 with the existing message — unconditional, including under master key, matching the existing change-guard at the post-fix equivalent of line 1418.
  • On create, delete this.data.installationId so the existing 135 'at least one ID field ... must be specified' guard fires cleanly.

Setting an installationId for the first time on a row that lacks one, and creating a row with only deviceToken, are both unaffected — the detector ignores string values, and the existing 136 guard at line 1418 only fires when both old and new are non-empty and differ.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

  • Bug Fixes

    • Prevent clearing or setting invalid Installation IDs via REST create or update requests, including requests with elevated credentials, ensuring device identification remains intact.
    • Reject non-string Installation ID values with validation errors.
  • Tests

    • Added tests covering rejection of clearing or invalid Installation IDs for both create and update paths, including elevated-credential scenarios.

Review Change Stack

…lationId

handleInstallation runs before Parse __op operators are processed. Two
latent bugs in the installationId path:

- { installationId: { __op: 'Delete' } }: the operator object hits
  this.data.installationId.toLowerCase() and throws an uncaught
  TypeError, returning 500 to the client.
- { installationId: null }: silently nullifies the row's primary
  identifier, orphaning it (no client SDK using X-Parse-Installation-Id
  can ever find it again).

installationId is the Installation row's identity, used by the SDK auth
header to bind a request to its row. The codebase already throws
Parse error 136 ("installationId may not be changed in this operation")
on any change between values; clearing is the strongest form of
changing and is rejected for the same reason — even with master key,
matching the existing unconditional 136 guard.

Detect both clearing shapes at the top of handleInstallation. On
update (this.query set), throw 136. On create, drop the value so the
existing 135 "must specify ID" guard fires cleanly. Setting an
installationId for the first time on a row that lacks one, and creating
a row with only deviceToken, both remain unaffected.
Copilot AI review requested due to automatic review settings May 4, 2026 17:49
@parse-github-assistant
Copy link
Copy Markdown

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement. Our CI and AI review are safeguards, not development tools. If many issues are flagged, rethink your development approach. Invest more effort in planning and design rather than using review cycles to fix low-quality code.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44913415-1d28-4d51-bba2-a37cfe290ad1

📥 Commits

Reviewing files that changed from the base of the PR and between 5289c0b and d0fe8e9.

📒 Files selected for processing (2)
  • spec/ParseInstallation.spec.js
  • src/RestWrite.js

📝 Walkthrough

Walkthrough

Detects attempts to clear _Installation.installationId when this.data.installationId is null or { __op: 'Delete' }; updates are rejected with error 136, creates drop the field to trigger existing "must specify ID" validation (error 135). Also validates installationId must be a string and rejects non-strings with Parse.Error.INVALID_JSON. Tests assert these errors, including when using the master key.

Changes

Installation ID Protection

Layer / File(s) Summary
Core Implementation
src/RestWrite.js
RestWrite.prototype.handleInstallation detects installationId === null or { __op: 'Delete' }; update paths throw Parse error 136, create paths remove the field to allow existing validation to produce error 135. Also validates installationId is a string and throws Parse.Error.INVALID_JSON for non-strings.
Tests & Verification
spec/ParseInstallation.spec.js
Eight tests added: update attempts to clear installationId via Delete op or null expect error 136 (including master-key authenticated updates); create attempts with Delete op or null expect error 135; create and update attempts with non-string installationId expect Parse.Error.INVALID_JSON.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 6 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Engage In Review Feedback ❓ Inconclusive Unable to verify review feedback engagement: PR review comments and discussions are not accessible in the local git repository; only commit history is available. Access the actual PR #10455 on GitHub directly to review: (1) whether review comments exist, (2) evidence of engagement/discussion, and (3) whether feedback was addressed in commits.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title begins with 'fix:' prefix as required and accurately describes the main change: preventing installationId from being cleared or set to invalid values in _Installation update/create operations.
Description check ✅ Passed The pull request description provides a clear Issue section explaining the two latent bugs, an Approach section detailing the fix implementation, and a Tasks section indicating which items were completed. All critical sections are present and substantive.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed Input validation added before string operations prevents TypeError crashes; clearing guard is unconditional with no privilege escalation; proper error handling replaces 500 responses.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.21.0)

OpenGrep fatal error (exit code 2): [00.15][ERROR]: Error: exception Unix_error: No such file or directory stat spec/ParseInstallation.spec.js
Raised by primitive operation at UTmp.replace_named_pipe_by_regular_file_if_needed in file "libs/commons/UTmp.ml", line 145, characters 8-27
Called from Scan_CLI.replace_target_roots_by_regular_files_where_needed.(fun) in file "src/osemgrep/cli_scan/Scan_CLI.ml", lines 1086-1087, characters 19-65
Called from List_.fast_map in file "libs/commons/List_.ml", line 81, characters 17-20
Called fr


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens _Installation handling in RestWrite.handleInstallation to prevent invalid attempts to clear installationId (via null or { __op: 'Delete' }) from causing a server crash or orphaning an installation row. It aligns “clear” behavior with the existing protection that disallows changing installationId during updates, and adds regression tests for these scenarios.

Changes:

  • Add an early guard in handleInstallation to detect installationId clearing attempts and reject them on update (136) or drop the field on create so the existing “must specify ID” check returns 135.
  • Add spec coverage for update + create clearing shapes, including a master-key update case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/RestWrite.js Adds early detection of installationId clearing to avoid crashes/orphaned rows and to return consistent Parse error codes.
spec/ParseInstallation.spec.js Adds regression tests covering clearing installationId via null and Delete op on create/update paths.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/RestWrite.js
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
spec/ParseInstallation.spec.js (1)

639-666: ⚡ Quick win

Consider adding a null-variant master-key test to complete the coverage matrix

The master-key test covers only { __op: 'Delete' }. Adding a parallel test for installationId: null would verify the full matrix (both clearing shapes × with/without master key), at the cost of ~15 lines. Both shapes share the same post-predicate code path, so this is low-risk but adds documentation value.

➕ Suggested additional test
it('master key cannot clear installationId via null', done => {
  const installId = '12345678-abcd-abcd-abcd-123456789abc';
  const input = {
    installationId: installId,
    deviceType: 'ios',
  };
  rest
    .create(config, auth.master(config), '_Installation', input)
    .then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
    .then(results => {
      expect(results.length).toEqual(1);
      return rest.update(
        config,
        auth.master(config),
        '_Installation',
        { objectId: results[0].objectId },
        { installationId: null }
      );
    })
    .then(() => {
      fail('Master key clearing of installationId via null should have been rejected.');
      done();
    })
    .catch(error => {
      expect(error.code).toEqual(136);
      done();
    });
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/ParseInstallation.spec.js` around lines 639 - 666, Add a sibling test to
the existing "master key cannot clear installationId" case that verifies
clearing via null is also rejected; reproduce the same setup used there (use
rest.create with auth.master(config) to create an _Installation with
installationId and deviceType, then find the created row with
database.adapter.find or reuse the existing promise chain) and call rest.update
with auth.master(config) where the update body sets installationId: null, then
assert the update is rejected with error.code === 136 (mirroring the existing
.then/.catch flow and failure message). Ensure you reference the same helpers
used in the diff (rest.create, rest.update, auth.master, installationId) and
mirror the structure of the original test so both delete-op and null-variant are
covered.
src/RestWrite.js (1)

1313-1316: 💤 Low value

Optional: clarify the create-path comment — error 135 isn't always guaranteed

The comment says the Delete-guard fires "so the existing 135 error fires," but 135 only fires when neither deviceToken nor auth.installationId provides an alternative ID. When another ID is present the create legitimately succeeds. This is the correct behavior, but the comment could mislead a future maintainer into expecting an unconditional 135 rejection.

✏️ Suggested comment wording
-    // Create path: drop the operator/null so the "must specify ID"
-    // guard below fires with the correct 135 error.
-    delete this.data.installationId;
+    // Create path: remove the invalid value so the existing "at least
+    // one ID field must be specified" guard (error 135) fires when no
+    // other ID (deviceToken, auth.installationId) is available.
+    delete this.data.installationId;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/RestWrite.js` around lines 1313 - 1316, The comment above the delete
this.data.installationId line is misleading about error 135 being guaranteed;
update the comment to clarify that deleting installationId is to let the "must
specify ID" guard trigger when no alternative ID is supplied, but that error 135
only occurs when neither deviceToken nor auth.installationId (nor any other
valid ID) is present — if another ID exists the create will validly succeed.
Mention the relevant symbols: delete this.data.installationId, deviceToken,
auth.installationId, and error 135 so future maintainers understand the
conditional behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@spec/ParseInstallation.spec.js`:
- Around line 639-666: Add a sibling test to the existing "master key cannot
clear installationId" case that verifies clearing via null is also rejected;
reproduce the same setup used there (use rest.create with auth.master(config) to
create an _Installation with installationId and deviceType, then find the
created row with database.adapter.find or reuse the existing promise chain) and
call rest.update with auth.master(config) where the update body sets
installationId: null, then assert the update is rejected with error.code === 136
(mirroring the existing .then/.catch flow and failure message). Ensure you
reference the same helpers used in the diff (rest.create, rest.update,
auth.master, installationId) and mirror the structure of the original test so
both delete-op and null-variant are covered.

In `@src/RestWrite.js`:
- Around line 1313-1316: The comment above the delete this.data.installationId
line is misleading about error 135 being guaranteed; update the comment to
clarify that deleting installationId is to let the "must specify ID" guard
trigger when no alternative ID is supplied, but that error 135 only occurs when
neither deviceToken nor auth.installationId (nor any other valid ID) is present
— if another ID exists the create will validly succeed. Mention the relevant
symbols: delete this.data.installationId, deviceToken, auth.installationId, and
error 135 so future maintainers understand the conditional behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 969a37bd-f3a5-489d-9950-6fca6b7620fc

📥 Commits

Reviewing files that changed from the base of the PR and between 4f90c5e and 36b1fae.

📒 Files selected for processing (2)
  • spec/ParseInstallation.spec.js
  • src/RestWrite.js

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 4, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/RestWrite.js (1)

1303-1307: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard non-string installationId before lowercasing.

At Line 1337, unsupported object payloads (anything truthy other than { __op: 'Delete' }) can still hit .toLowerCase() and throw a 500. Please reject non-string installationId values explicitly in this block so malformed payloads fail with a Parse error instead of a TypeError.

Suggested patch
   const clearingInstallationId =
     this.data.installationId === null ||
     (typeof this.data.installationId === 'object' &&
       this.data.installationId !== null &&
       this.data.installationId.__op === 'Delete');
+  const hasInstallationId =
+    Object.prototype.hasOwnProperty.call(this.data, 'installationId');
+  if (
+    hasInstallationId &&
+    this.data.installationId !== null &&
+    this.data.installationId !== undefined &&
+    typeof this.data.installationId !== 'string' &&
+    !clearingInstallationId
+  ) {
+    throw new Parse.Error(Parse.Error.INVALID_JSON, 'installationId must be a string');
+  }
   if (clearingInstallationId) {

Also applies to: 1336-1337

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/RestWrite.js` around lines 1303 - 1307, The logic computing
clearingInstallationId (using this.data.installationId and later calling
.toLowerCase()) must explicitly guard against non-string installationId
payloads: before any .toLowerCase() call, check typeof this.data.installationId
=== 'string'; if it's an object that equals { __op: 'Delete' } treat as the
delete case, but for any other non-string/unsupported truthy value reject by
throwing a Parse error (via the same error class used elsewhere) so malformed
installationId values produce a Parse error instead of allowing a TypeError;
update the code paths that reference clearingInstallationId and any subsequent
.toLowerCase() usage to rely on this validated string-only branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/RestWrite.js`:
- Around line 1303-1307: The logic computing clearingInstallationId (using
this.data.installationId and later calling .toLowerCase()) must explicitly guard
against non-string installationId payloads: before any .toLowerCase() call,
check typeof this.data.installationId === 'string'; if it's an object that
equals { __op: 'Delete' } treat as the delete case, but for any other
non-string/unsupported truthy value reject by throwing a Parse error (via the
same error class used elsewhere) so malformed installationId values produce a
Parse error instead of allowing a TypeError; update the code paths that
reference clearingInstallationId and any subsequent .toLowerCase() usage to rely
on this validated string-only branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb78e73c-c4e5-452f-b5c3-b1103d6ca5d8

📥 Commits

Reviewing files that changed from the base of the PR and between 36b1fae and 5289c0b.

📒 Files selected for processing (1)
  • src/RestWrite.js

Prevent non-string installationId values from causing a server error by validating the type in RestWrite: remove invalid installationId on create so the existing "must specify ID" check runs, and throw Parse.Error.INVALID_JSON when installationId exists but is not a string (avoids crashes from .toLowerCase()). Add unit tests to cover create/update failures for non-string installationId inputs and ensure master key cannot clear installationId via null.
@AdrianCurtin
Copy link
Copy Markdown
Author

Current behavior blocks master key editing of installationId so it follows that we should also block it from being nulled, but I do think there are circumstances in which it would be valid to clear an installationId (maintenance etc)

@AdrianCurtin AdrianCurtin requested a review from Copilot May 21, 2026 23:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.

Comment thread src/RestWrite.js
this.data.installationId.__op === 'Delete');
if (clearingInstallationId) {
if (this.query) {
throw new Parse.Error(136, 'installationId may not be changed in this operation');
});
});

it('update fails to clear installationId via Delete op', done => {
});
});

it('update fails to clear installationId via null', done => {
Comment on lines +547 to +636
it('update fails to clear installationId via Delete op', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { __op: 'Delete' } }
);
})
.then(() => {
fail('Updating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(136);
done();
});
});

it('update fails to clear installationId via null', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: null }
);
})
.then(() => {
fail('Updating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(136);
done();
});
});

it('create fails when installationId is the Delete op (no real ID provided)', done => {
const input = {
installationId: { __op: 'Delete' },
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(135);
done();
});
});

it('create fails when installationId is null (no real ID provided)', done => {
const input = {
installationId: null,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(135);
done();
});
});
});

it('create fails when installationId is null (no real ID provided)', done => {
Comment on lines +605 to +684
it('create fails when installationId is the Delete op (no real ID provided)', done => {
const input = {
installationId: { __op: 'Delete' },
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(135);
done();
});
});

it('create fails when installationId is null (no real ID provided)', done => {
const input = {
installationId: null,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(135);
done();
});
});

it('create fails when installationId is a non-string object', done => {
const input = {
installationId: { foo: 'bar' },
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});

it('update fails when installationId is a non-string object', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { foo: 'bar' } }
);
})
.then(() => {
fail('Updating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});

});
});

it('update fails when installationId is a non-string object', done => {
Comment on lines +622 to +711
it('create fails when installationId is null (no real ID provided)', done => {
const input = {
installationId: null,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(135);
done();
});
});

it('create fails when installationId is a non-string object', done => {
const input = {
installationId: { foo: 'bar' },
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => {
fail('Creating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});

it('update fails when installationId is a non-string object', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { foo: 'bar' } }
);
})
.then(() => {
fail('Updating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});

it('master key cannot clear installationId via Delete op', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.master(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.master(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { __op: 'Delete' } }
);
})
.then(() => {
fail('Master key clearing of installationId should have been rejected.');
done();
})
.catch(error => {
expect(error.code).toEqual(136);
done();
});
Comment on lines +656 to +742
it('update fails when installationId is a non-string object', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.nobody(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.nobody(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { foo: 'bar' } }
);
})
.then(() => {
fail('Updating the installation should have failed.');
done();
})
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_JSON);
done();
});
});

it('master key cannot clear installationId via Delete op', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.master(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.master(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: { __op: 'Delete' } }
);
})
.then(() => {
fail('Master key clearing of installationId should have been rejected.');
done();
})
.catch(error => {
expect(error.code).toEqual(136);
done();
});
});

it('master key cannot clear installationId via null', done => {
const installId = '12345678-abcd-abcd-abcd-123456789abc';
const input = {
installationId: installId,
deviceType: 'ios',
};
rest
.create(config, auth.master(config), '_Installation', input)
.then(() => database.adapter.find('_Installation', installationSchema, {}, {}))
.then(results => {
expect(results.length).toEqual(1);
return rest.update(
config,
auth.master(config),
'_Installation',
{ objectId: results[0].objectId },
{ installationId: null }
);
})
.then(() => {
fail('Master key clearing of installationId via null should have been rejected.');
done();
})
.catch(error => {
expect(error.code).toEqual(136);
done();
});
});

Comment thread src/RestWrite.js
Comment on lines +1322 to +1327
if (
this.data.installationId !== undefined &&
typeof this.data.installationId !== 'string'
) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'installationId must be a string');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants