(core) implement authorization via query parameter

Summary:
This adds any parameters in a document url whose key ends in '_'
into a `user.Link` object available in access control formulas
and in setting up characteristic tables.

This allows, for example, sending links to a document that contain
a hard-to-guess token, and having that link grant access to a
controlled part of the document (invoices for a specific customer
for example).

A `user.Origin` field is also added, set during rest api calls,
but is only tested manually at this point.  It could be elaborated
for embedding use-cases.

Test Plan: added test

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2680
This commit is contained in:
Paul Fitzpatrick
2020-12-09 08:57:35 -05:00
parent 131fbbdb92
commit 8f023a6446
9 changed files with 46 additions and 12 deletions

View File

@@ -267,8 +267,9 @@ export class Comm extends dispose.Disposable implements GristServerAPI, DocListA
* committed to a document that is called in hosted Grist - all other methods
* are called via DocComm.
*/
public async openDoc(docName: string, mode?: string): Promise<OpenLocalDocResult> {
return this._makeRequest(null, docName, 'openDoc', docName, mode);
public async openDoc(docName: string, mode?: string,
linkParameters?: Record<string, string>): Promise<OpenLocalDocResult> {
return this._makeRequest(null, docName, 'openDoc', docName, mode, linkParameters);
}
/**

View File

@@ -189,6 +189,7 @@ export class DocComm extends Disposable implements ActiveDocAPI {
private async _doForkDoc(): Promise<void> {
reportError(new UserError('Preparing your copy...', {key: 'forking'}));
const {urlId, docId} = await this.fork();
// TODO: may want to preserve linkParameters in call to openDoc.
const openResponse = await this._comm.openDoc(docId);
// Close the old doc and release the old connection. Note that the closeDoc call is expected
// to fail, since we close the websocket immediately after it. So let it fail silently.

View File

@@ -116,6 +116,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
this.autoDispose(subscribe(urlState().state, (use, state) => {
const urlId = state.doc;
const urlOpenMode = state.mode || 'default';
const linkParameters = state.params?.linkParameters;
const docKey = this._getDocKey(state);
if (docKey !== this._openerDocKey) {
this._openerDocKey = docKey;
@@ -126,7 +127,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
this._openerHolder.clear();
} else {
FlowRunner.create(this._openerHolder, (flow: AsyncFlow) => this._openDoc(flow, urlId, urlOpenMode,
state.params?.compare))
state.params?.compare, linkParameters))
.resultPromise.catch(err => this._onOpenError(err));
}
}
@@ -207,7 +208,8 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
}
private async _openDoc(flow: AsyncFlow, urlId: string, urlOpenMode: OpenDocMode,
comparisonUrlId: string | undefined): Promise<void> {
comparisonUrlId: string | undefined,
linkParameters: Record<string, string> | undefined): Promise<void> {
console.log(`DocPageModel _openDoc starting for ${urlId} (mode ${urlOpenMode})` +
(comparisonUrlId ? ` (compare ${comparisonUrlId})` : ''));
const gristDocModulePromise = loadGristDoc();
@@ -229,7 +231,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
comm.useDocConnection(doc.id);
flow.onDispose(() => comm.releaseDocConnection(doc.id));
const openDocResponse = await comm.openDoc(doc.id, doc.openMode);
const openDocResponse = await comm.openDoc(doc.id, doc.openMode, linkParameters);
const gdModule = await gristDocModulePromise;
const docComm = gdModule.DocComm.create(flow, comm, openDocResponse, doc.id, this.appModel.notifier);
flow.checkIfCancelled();