diff --git a/app/client/ui/DocHistory.ts b/app/client/ui/DocHistory.ts index 14d705b5..587ec1c5 100644 --- a/app/client/ui/DocHistory.ts +++ b/app/client/ui/DocHistory.ts @@ -68,11 +68,19 @@ export class DocHistory extends Disposable implements IDomComponent { } const snapshots = Observable.create(owner, []); + const snapshotsDenied = Observable.create(owner, false); const userApi = this._docPageModel.appModel.api; const docApi = userApi.getDocAPI(origUrlId); docApi.getSnapshots().then(result => - snapshots.isDisposed() || snapshots.set(result.snapshots)).catch(reportError); - return dom('div', + snapshots.isDisposed() || snapshots.set(result.snapshots)).catch(err => { + snapshotsDenied.set(true); + reportError(err); + }); + return dom( + 'div', + dom.maybe(snapshotsDenied, () => cssSnapshotDenied( + t('SnapshotsUnavailable'), + testId('doc-history-error'))), // Note that most recent snapshots are first. dom.domComputed(snapshots, (snapshotList) => snapshotList.map((snapshot, index) => { const modified = moment(snapshot.lastModified); @@ -118,6 +126,10 @@ const cssSnapshot = styled('div', ` margin: 8px 16px; `); +const cssSnapshotDenied = styled('div', ` + margin: 8px 16px; +`); + const cssSnapshotTime = styled('div', ` text-align: right; color: ${theme.lightText}; diff --git a/app/server/lib/ActiveDoc.ts b/app/server/lib/ActiveDoc.ts index fa172a83..df9f7e03 100644 --- a/app/server/lib/ActiveDoc.ts +++ b/app/server/lib/ActiveDoc.ts @@ -1624,8 +1624,10 @@ export class ActiveDoc extends EventEmitter { this._inactivityTimer.ping(); } - public async getSnapshots(skipMetadataCache?: boolean): Promise { - // Assume any viewer can access this list. + public async getSnapshots(docSession: OptDocSession, skipMetadataCache?: boolean): Promise { + if (await this._granularAccess.hasNuancedAccess(docSession)) { + throw new Error('cannot confirm access to snapshots'); + } return this._docManager.storageManager.getSnapshots(this.docName, skipMetadataCache); } diff --git a/app/server/lib/DocApi.ts b/app/server/lib/DocApi.ts index 6ba3bbd3..5a2f0aa9 100644 --- a/app/server/lib/DocApi.ts +++ b/app/server/lib/DocApi.ts @@ -651,7 +651,8 @@ export class DocWorkerApi { })); this._app.get('/api/docs/:docId/snapshots', canView, withDoc(async (activeDoc, req, res) => { - const {snapshots} = await activeDoc.getSnapshots(isAffirmative(req.query.raw)); + const docSession = docSessionFromRequest(req); + const {snapshots} = await activeDoc.getSnapshots(docSession, isAffirmative(req.query.raw)); res.json({snapshots}); })); @@ -666,8 +667,8 @@ export class DocWorkerApi { if (req.body.select === 'unlisted') { // Remove any snapshots not listed in inventory. Ideally, there should be no // snapshots, and this undocumented feature is just for fixing up problems. - const full = (await activeDoc.getSnapshots(true)).snapshots.map(s => s.snapshotId); - const listed = new Set((await activeDoc.getSnapshots()).snapshots.map(s => s.snapshotId)); + const full = (await activeDoc.getSnapshots(docSession, true)).snapshots.map(s => s.snapshotId); + const listed = new Set((await activeDoc.getSnapshots(docSession)).snapshots.map(s => s.snapshotId)); const unlisted = full.filter(snapshotId => !listed.has(snapshotId)); await activeDoc.removeSnapshots(docSession, unlisted); res.json({snapshotIds: unlisted}); @@ -676,7 +677,7 @@ export class DocWorkerApi { if (req.body.select === 'past') { // Remove all but the latest snapshot. Useful for sanitizing history if something // bad snuck into previous snapshots and they are not valuable to preserve. - const past = (await activeDoc.getSnapshots(true)).snapshots.map(s => s.snapshotId); + const past = (await activeDoc.getSnapshots(docSession, true)).snapshots.map(s => s.snapshotId); past.shift(); // remove current version. await activeDoc.removeSnapshots(docSession, past); res.json({snapshotIds: past}); diff --git a/static/locales/en.client.json b/static/locales/en.client.json index 3d0ba848..b2bd8434 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -111,6 +111,7 @@ "DocHistory": { "Activity": "Activity", "Snapshots": "Snapshots", + "SnapshotsUnavailable": "Snapshots are unavailable.", "OpenSnapshot": "Open Snapshot", "CompareToCurrent": "Compare to Current", "CompareToPrevious": "Compare to Previous",