mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -363,6 +363,9 @@ export interface Authorizer {
|
||||
// get the id of the document.
|
||||
getDocId(): string;
|
||||
|
||||
// get any link parameters in place when accessing the resource.
|
||||
getLinkParameters(): Record<string, string>;
|
||||
|
||||
// Fetch the doc metadata from HomeDBManager.
|
||||
getDoc(): Promise<Document>;
|
||||
|
||||
@@ -385,6 +388,7 @@ export class DocAuthorizer implements Authorizer {
|
||||
private _dbManager: HomeDBManager,
|
||||
private _key: DocAuthKey,
|
||||
public readonly openMode: OpenDocMode,
|
||||
public readonly linkParameters: Record<string, string>,
|
||||
private _docAuth?: DocAuthResult,
|
||||
private _profile?: UserProfile
|
||||
) {
|
||||
@@ -403,6 +407,10 @@ export class DocAuthorizer implements Authorizer {
|
||||
return this._key.urlId;
|
||||
}
|
||||
|
||||
public getLinkParameters(): Record<string, string> {
|
||||
return this.linkParameters;
|
||||
}
|
||||
|
||||
public async getDoc(): Promise<Document> {
|
||||
return this._dbManager.getDoc(this._key);
|
||||
}
|
||||
@@ -424,6 +432,7 @@ export class DummyAuthorizer implements Authorizer {
|
||||
public getUserId() { return null; }
|
||||
public getUser() { return null; }
|
||||
public getDocId() { return this.docId; }
|
||||
public getLinkParameters() { return {}; }
|
||||
public async getDoc(): Promise<Document> { throw new Error("Not supported in standalone"); }
|
||||
public async assertAccess() { /* noop */ }
|
||||
public getCachedAuth(): DocAuthResult {
|
||||
@@ -481,11 +490,14 @@ export function getTransitiveHeaders(req: Request): {[key: string]: string} {
|
||||
const PermitHeader = req.get('Permit');
|
||||
const Organization = (req as RequestWithOrg).org;
|
||||
const XRequestedWith = req.get('X-Requested-With');
|
||||
const Origin = req.get('Origin'); // Pass along the original Origin since it may
|
||||
// play a role in granular access control.
|
||||
return {
|
||||
...(Authorization ? { Authorization } : undefined),
|
||||
...(Cookie ? { Cookie } : undefined),
|
||||
...(Organization ? { Organization } : undefined),
|
||||
...(PermitHeader ? { Permit: PermitHeader } : undefined),
|
||||
...(XRequestedWith ? { 'X-Requested-With': XRequestedWith } : undefined),
|
||||
...(Origin ? { Origin } : undefined),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -244,7 +244,8 @@ export class DocManager extends EventEmitter {
|
||||
* `doc` - the object with metadata tables.
|
||||
*/
|
||||
public async openDoc(client: Client, docId: string,
|
||||
mode: OpenDocMode = 'default'): Promise<OpenLocalDocResult> {
|
||||
mode: OpenDocMode = 'default',
|
||||
linkParameters: Record<string, string> = {}): Promise<OpenLocalDocResult> {
|
||||
let auth: Authorizer;
|
||||
const dbManager = this._homeDbManager;
|
||||
if (!isSingleUserMode()) {
|
||||
@@ -266,7 +267,7 @@ export class DocManager extends EventEmitter {
|
||||
// than a docId.
|
||||
throw new Error(`openDoc expected docId ${docAuth.docId} not urlId ${docId}`);
|
||||
}
|
||||
auth = new DocAuthorizer(dbManager, key, mode, docAuth, client.getProfile() || undefined);
|
||||
auth = new DocAuthorizer(dbManager, key, mode, linkParameters, docAuth, client.getProfile() || undefined);
|
||||
} else {
|
||||
log.debug(`DocManager.openDoc not using authorization for ${docId} because GRIST_SINGLE_USER`);
|
||||
auth = new DummyAuthorizer('owners', docId);
|
||||
|
||||
@@ -694,7 +694,7 @@ export class GranularAccess {
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||
value = value.get('id');
|
||||
}
|
||||
return JSON.stringify(value).toLowerCase();
|
||||
return JSON.stringify(value)?.toLowerCase() || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -755,6 +755,12 @@ export class GranularAccess {
|
||||
user.UserID = fullUser?.id || null;
|
||||
user.Email = fullUser?.email || null;
|
||||
user.Name = fullUser?.name || null;
|
||||
// If viewed from a websocket, collect any link parameters included.
|
||||
// TODO: could also get this from rest api access, just via a different route.
|
||||
user.Link = docSession.authorizer?.getLinkParameters() || {};
|
||||
// Include origin info if accessed via the rest api.
|
||||
// TODO: could also get this for websocket access, just via a different route.
|
||||
user.Origin = docSession.req?.get('origin') || null;
|
||||
|
||||
for (const clause of this._ruleCollection.getUserAttributeRules().values()) {
|
||||
if (clause.name in user) {
|
||||
|
||||
Reference in New Issue
Block a user