2020-07-21 13:20:51 +00:00
|
|
|
import {BrowserSettings} from 'app/common/BrowserSettings';
|
2020-09-02 18:17:17 +00:00
|
|
|
import {Role} from 'app/common/roles';
|
2021-03-18 22:40:02 +00:00
|
|
|
import {FullUser} from 'app/common/UserAPI';
|
|
|
|
import {Document} from 'app/gen-server/entity/Document';
|
2020-07-21 13:20:51 +00:00
|
|
|
import {ActiveDoc} from 'app/server/lib/ActiveDoc';
|
2020-10-19 14:25:21 +00:00
|
|
|
import {Authorizer, getUser, getUserId, RequestWithLogin} from 'app/server/lib/Authorizer';
|
2020-07-21 13:20:51 +00:00
|
|
|
import {Client} from 'app/server/lib/Client';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* OptDocSession allows for certain ActiveDoc operations to work with or without an open document.
|
|
|
|
* It is useful in particular for actions when importing a file to create a new document.
|
|
|
|
*/
|
|
|
|
export interface OptDocSession {
|
|
|
|
client: Client|null;
|
|
|
|
shouldBundleActions?: boolean;
|
|
|
|
linkId?: number;
|
|
|
|
browserSettings?: BrowserSettings;
|
|
|
|
req?: RequestWithLogin;
|
2020-12-07 21:15:58 +00:00
|
|
|
// special permissions for creating, plugins, system, and share access
|
|
|
|
mode?: 'nascent'|'plugin'|'system'|'share';
|
2020-09-02 18:17:17 +00:00
|
|
|
authorizer?: Authorizer;
|
2020-07-21 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function makeOptDocSession(client: Client|null, browserSettings?: BrowserSettings): OptDocSession {
|
|
|
|
if (client && !browserSettings) { browserSettings = client.browserSettings; }
|
|
|
|
return {client, browserSettings};
|
|
|
|
}
|
|
|
|
|
2020-09-02 18:17:17 +00:00
|
|
|
/**
|
|
|
|
* Create an OptDocSession with special access rights.
|
|
|
|
* - nascent: user is treated as owner (because doc is being created)
|
|
|
|
* - plugin: user is treated as editor (because plugin access control is crude)
|
|
|
|
* - system: user is treated as owner (because of some operation bypassing access control)
|
|
|
|
*/
|
2020-12-07 21:15:58 +00:00
|
|
|
export function makeExceptionalDocSession(mode: 'nascent'|'plugin'|'system'|'share',
|
2020-09-02 18:17:17 +00:00
|
|
|
options: {client?: Client,
|
|
|
|
req?: RequestWithLogin,
|
|
|
|
browserSettings?: BrowserSettings} = {}): OptDocSession {
|
|
|
|
const docSession = makeOptDocSession(options.client || null, options.browserSettings);
|
|
|
|
docSession.mode = mode;
|
|
|
|
docSession.req = options.req;
|
|
|
|
return docSession;
|
|
|
|
}
|
|
|
|
|
2020-09-11 20:27:09 +00:00
|
|
|
/**
|
|
|
|
* Create an OptDocSession from a request. Request should have user and doc access
|
|
|
|
* middleware.
|
|
|
|
*/
|
|
|
|
export function docSessionFromRequest(req: RequestWithLogin): OptDocSession {
|
|
|
|
return {client: null, req};
|
|
|
|
}
|
|
|
|
|
2020-07-21 13:20:51 +00:00
|
|
|
/**
|
|
|
|
* DocSession objects maintain information for a single session<->doc instance.
|
|
|
|
*/
|
|
|
|
export class DocSession implements OptDocSession {
|
|
|
|
/**
|
|
|
|
* Flag to indicate that user actions 'bundle' process is started and in progress (`true`),
|
|
|
|
* otherwise it's `false`
|
|
|
|
*/
|
|
|
|
public shouldBundleActions?: boolean;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Indicates the actionNum of the previously applied action
|
|
|
|
* to which the first action in actions should be linked.
|
|
|
|
* Linked actions appear as one action and can be undone/redone in a single step.
|
|
|
|
*/
|
|
|
|
public linkId?: number;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
public readonly activeDoc: ActiveDoc,
|
|
|
|
public readonly client: Client,
|
|
|
|
public readonly fd: number,
|
|
|
|
public readonly authorizer: Authorizer
|
|
|
|
) {}
|
|
|
|
|
|
|
|
// Browser settings (like timezone) obtained from the Client.
|
|
|
|
public get browserSettings(): BrowserSettings { return this.client.browserSettings; }
|
|
|
|
}
|
2020-09-02 18:17:17 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract userId from OptDocSession. Use Authorizer if available (for web socket
|
|
|
|
* sessions), or get it from the Request if that is available (for rest api calls),
|
|
|
|
* or from the Client if that is available. Returns null if userId information is
|
|
|
|
* not available or not cached.
|
|
|
|
*/
|
|
|
|
export function getDocSessionUserId(docSession: OptDocSession): number|null {
|
|
|
|
if (docSession.authorizer) {
|
|
|
|
return docSession.authorizer.getUserId();
|
|
|
|
}
|
|
|
|
if (docSession.req) {
|
|
|
|
return getUserId(docSession.req);
|
|
|
|
}
|
|
|
|
if (docSession.client) {
|
|
|
|
return docSession.client.getCachedUserId();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-10-19 14:25:21 +00:00
|
|
|
/**
|
|
|
|
* Get as much of user profile as we can (id, name, email).
|
|
|
|
*/
|
|
|
|
export function getDocSessionUser(docSession: OptDocSession): FullUser|null {
|
|
|
|
if (docSession.authorizer) {
|
|
|
|
return docSession.authorizer.getUser();
|
|
|
|
}
|
|
|
|
if (docSession.req) {
|
|
|
|
const user = getUser(docSession.req);
|
|
|
|
const email = user.loginEmail;
|
|
|
|
if (email) {
|
|
|
|
return {id: user.id, name: user.name, email};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (docSession.client) {
|
|
|
|
const id = docSession.client.getCachedUserId();
|
|
|
|
const profile = docSession.client.getProfile();
|
|
|
|
if (id && profile) {
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
...profile
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-02 18:17:17 +00:00
|
|
|
/**
|
|
|
|
* Extract user's role from OptDocSession. Method depends on whether using web
|
|
|
|
* sockets or rest api. Assumes that access has already been checked by wrappers
|
|
|
|
* for api methods and that cached access information is therefore available.
|
|
|
|
*/
|
|
|
|
export function getDocSessionAccess(docSession: OptDocSession): Role {
|
|
|
|
// "nascent" DocSessions are for when a document is being created, and user is
|
|
|
|
// its only owner as yet.
|
|
|
|
// "system" DocSessions are for access without access control.
|
|
|
|
if (docSession.mode === 'nascent' || docSession.mode === 'system') { return 'owners'; }
|
|
|
|
// "plugin" DocSessions are for access from plugins, which is currently quite crude,
|
|
|
|
// and granted only to editors.
|
|
|
|
if (docSession.mode === 'plugin') { return 'editors'; }
|
|
|
|
if (docSession.authorizer) {
|
|
|
|
const access = docSession.authorizer.getCachedAuth().access;
|
|
|
|
if (!access) { throw new Error('getDocSessionAccess expected authorizer.getCachedAuth'); }
|
|
|
|
return access;
|
|
|
|
}
|
|
|
|
if (docSession.req) {
|
|
|
|
const access = docSession.req.docAuth?.access;
|
|
|
|
if (!access) { throw new Error('getDocSessionAccess expected req.docAuth.access'); }
|
|
|
|
return access;
|
|
|
|
}
|
|
|
|
throw new Error('getDocSessionAccess could not find access information in DocSession');
|
|
|
|
}
|
2021-03-18 22:40:02 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get cached information about the document, if available. May be stale.
|
|
|
|
*/
|
|
|
|
export function getDocSessionCachedDoc(docSession: OptDocSession): Document|undefined {
|
|
|
|
return (docSession.req as RequestWithLogin)?.docAuth?.cachedDoc;
|
|
|
|
}
|