mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) uncheck FullCopy special when copying/forking a document
Summary: When a document has an exception to allow copies, unset that option on any copies of the document. Test Plan: added test Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2794
This commit is contained in:
parent
729774552f
commit
0e22716761
@ -15,6 +15,7 @@ import { docSessionFromRequest, makeExceptionalDocSession, OptDocSession } from
|
|||||||
import { DocWorker } from "app/server/lib/DocWorker";
|
import { DocWorker } from "app/server/lib/DocWorker";
|
||||||
import { IDocWorkerMap } from "app/server/lib/DocWorkerMap";
|
import { IDocWorkerMap } from "app/server/lib/DocWorkerMap";
|
||||||
import { expressWrap } from 'app/server/lib/expressWrap';
|
import { expressWrap } from 'app/server/lib/expressWrap';
|
||||||
|
import { filterDocumentInPlace } from "app/server/lib/filterUtils";
|
||||||
import { GristServer } from 'app/server/lib/GristServer';
|
import { GristServer } from 'app/server/lib/GristServer';
|
||||||
import { HashUtil } from 'app/server/lib/HashUtil';
|
import { HashUtil } from 'app/server/lib/HashUtil';
|
||||||
import { makeForkIds } from "app/server/lib/idUtils";
|
import { makeForkIds } from "app/server/lib/idUtils";
|
||||||
@ -218,7 +219,8 @@ export class DocWorkerApi {
|
|||||||
const docId = stringParam(req.params.docId);
|
const docId = stringParam(req.params.docId);
|
||||||
const srcDocId = stringParam(req.body.srcDocId);
|
const srcDocId = stringParam(req.body.srcDocId);
|
||||||
if (srcDocId !== req.specialPermit?.otherDocId) { throw new Error('access denied'); }
|
if (srcDocId !== req.specialPermit?.otherDocId) { throw new Error('access denied'); }
|
||||||
await this._docManager.storageManager.prepareFork(srcDocId, docId);
|
const fname = await this._docManager.storageManager.prepareFork(srcDocId, docId);
|
||||||
|
await filterDocumentInPlace(docSessionFromRequest(req), fname);
|
||||||
res.json({srcDocId, docId});
|
res.json({srcDocId, docId});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -90,8 +90,10 @@ export class DocStorageManager implements IDocStorageManager {
|
|||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
public async prepareFork(srcDocName: string, destDocName: string): Promise<void> {
|
public async prepareFork(srcDocName: string, destDocName: string): Promise<string> {
|
||||||
// nothing to do
|
// This is implemented only to support old tests.
|
||||||
|
await fse.copy(this.getPath(srcDocName), this.getPath(destDocName));
|
||||||
|
return this.getPath(destDocName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,7 +7,8 @@ import {ActionHistoryImpl} from 'app/server/lib/ActionHistoryImpl';
|
|||||||
import {assertAccess, getOrSetDocAuth, getUserId, RequestWithLogin} from 'app/server/lib/Authorizer';
|
import {assertAccess, getOrSetDocAuth, getUserId, RequestWithLogin} from 'app/server/lib/Authorizer';
|
||||||
import {Client} from 'app/server/lib/Client';
|
import {Client} from 'app/server/lib/Client';
|
||||||
import * as Comm from 'app/server/lib/Comm';
|
import * as Comm from 'app/server/lib/Comm';
|
||||||
import {DocSession} from 'app/server/lib/DocSession';
|
import {DocSession, docSessionFromRequest} from 'app/server/lib/DocSession';
|
||||||
|
import {filterDocumentInPlace} from 'app/server/lib/filterUtils';
|
||||||
import {IDocStorageManager} from 'app/server/lib/IDocStorageManager';
|
import {IDocStorageManager} from 'app/server/lib/IDocStorageManager';
|
||||||
import * as log from 'app/server/lib/log';
|
import * as log from 'app/server/lib/log';
|
||||||
import {integerParam, optStringParam, stringParam} from 'app/server/lib/requestUtils';
|
import {integerParam, optStringParam, stringParam} from 'app/server/lib/requestUtils';
|
||||||
@ -75,6 +76,7 @@ export class DocWorker {
|
|||||||
// If template flag is on, remove data and history from the download.
|
// If template flag is on, remove data and history from the download.
|
||||||
await removeData(tmpPath);
|
await removeData(tmpPath);
|
||||||
}
|
}
|
||||||
|
await filterDocumentInPlace(docSessionFromRequest(mreq), tmpPath);
|
||||||
// NOTE: We may want to reconsider the mimeType used for Grist files.
|
// NOTE: We may want to reconsider the mimeType used for Grist files.
|
||||||
return res.type('application/x-sqlite3')
|
return res.type('application/x-sqlite3')
|
||||||
.download(tmpPath, (optStringParam(req.query.title) || docTitle || 'document') + ".grist", async (err: any) => {
|
.download(tmpPath, (optStringParam(req.query.title) || docTitle || 'document') + ".grist", async (err: any) => {
|
||||||
|
@ -248,8 +248,9 @@ export class HostedStorageManager implements IDocStorageManager {
|
|||||||
* Initialize one document from another, associating the result with the current
|
* Initialize one document from another, associating the result with the current
|
||||||
* worker.
|
* worker.
|
||||||
*/
|
*/
|
||||||
public async prepareFork(srcDocName: string, destDocName: string): Promise<void> {
|
public async prepareFork(srcDocName: string, destDocName: string): Promise<string> {
|
||||||
await this.prepareLocalDoc(destDocName, srcDocName);
|
await this.prepareLocalDoc(destDocName, srcDocName);
|
||||||
|
return this.getPath(destDocName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets a copy of the document, eg. for downloading. Returns full file path.
|
// Gets a copy of the document, eg. for downloading. Returns full file path.
|
||||||
|
@ -12,7 +12,7 @@ export interface IDocStorageManager {
|
|||||||
// AsyncCreate[docName].
|
// AsyncCreate[docName].
|
||||||
prepareLocalDoc(docName: string): Promise<boolean>;
|
prepareLocalDoc(docName: string): Promise<boolean>;
|
||||||
prepareToCreateDoc(docName: string): Promise<void>;
|
prepareToCreateDoc(docName: string): Promise<void>;
|
||||||
prepareFork(srcDocName: string, destDocName: string): Promise<void>;
|
prepareFork(srcDocName: string, destDocName: string): Promise<string>; // Returns filename.
|
||||||
|
|
||||||
listDocs(): Promise<DocEntry[]>;
|
listDocs(): Promise<DocEntry[]>;
|
||||||
deleteDoc(docName: string, deletePermanently?: boolean): Promise<void>;
|
deleteDoc(docName: string, deletePermanently?: boolean): Promise<void>;
|
||||||
|
28
app/server/lib/filterUtils.ts
Normal file
28
app/server/lib/filterUtils.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { OpenMode, SQLiteDB } from 'app/server/lib/SQLiteDB';
|
||||||
|
import { OptDocSession } from "app/server/lib/DocSession";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter a Grist document when it is copied or downloaded. Changes made:
|
||||||
|
* - Any FullCopies special rules are removed.
|
||||||
|
* In the future, the changes could be made conditional on the user. This would
|
||||||
|
* allow us for example to permit downloads of documents with row-level filters
|
||||||
|
* in place.
|
||||||
|
*/
|
||||||
|
export async function filterDocumentInPlace(docSession: OptDocSession, filename: string) {
|
||||||
|
// We ignore docSession for now, since no changes are user-dependent yet.
|
||||||
|
// The change we need to make is simple, so we open the doc as a SQLite DB.
|
||||||
|
// Note: the change is not entered in document history.
|
||||||
|
const db = await SQLiteDB.openDBRaw(filename, OpenMode.OPEN_EXISTING);
|
||||||
|
// Fetch ids of any special resources mentioning FullCopies (ideally there would be
|
||||||
|
// at most one).
|
||||||
|
const resourceIds = (await db.all("SELECT id FROM _grist_ACLResources " +
|
||||||
|
"WHERE tableId='*SPECIAL' AND colIds='FullCopies'"))
|
||||||
|
.map(row => row.id as number);
|
||||||
|
if (resourceIds.length > 0) {
|
||||||
|
// Remove any related rules.
|
||||||
|
await db.run(`DELETE FROM _grist_ACLRules WHERE resource IN (${resourceIds})`);
|
||||||
|
// Remove the resources.
|
||||||
|
await db.run(`DELETE FROM _grist_ACLResources WHERE id IN (${resourceIds})`);
|
||||||
|
}
|
||||||
|
await db.close();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user