mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) updates from grist-core
This commit is contained in:
@@ -293,6 +293,25 @@ function initialize(appModel: AppModel) {
|
||||
|
||||
function requestInterceptor(request: SwaggerUI.Request) {
|
||||
delete request.headers.Authorization;
|
||||
const url = new URL(request.url);
|
||||
// Swagger will use this request interceptor for several kinds of
|
||||
// requests, such as requesting the API YAML spec from Github:
|
||||
//
|
||||
// Function to intercept remote definition, "Try it out",
|
||||
// and OAuth 2.0 requests.
|
||||
//
|
||||
// https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/
|
||||
//
|
||||
// We want to ensure that only "Try it out" requests have XHR, so
|
||||
// that they pass a same origin request, even if they're not GET,
|
||||
// HEAD, or OPTIONS. "Try it out" requests are the requests to the
|
||||
// same origin.
|
||||
if (url.origin === window.origin) {
|
||||
// Without this header, unauthenticated multipart POST requests
|
||||
// (i.e. file uploads) would fail in the API console. We want those
|
||||
// requests to succeed.
|
||||
request.headers['X-Requested-With'] = 'XMLHttpRequest';
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { GristServer } from 'app/server/lib/GristServer';
|
||||
import * as express from 'express';
|
||||
import WS from 'ws';
|
||||
import fetch from 'node-fetch';
|
||||
import { DEFAULT_SESSION_SECRET } from 'app/server/lib/coreCreator';
|
||||
import { DEFAULT_SESSION_SECRET } from 'app/server/lib/ICreate';
|
||||
|
||||
/**
|
||||
* Self-diagnostics useful when installing Grist.
|
||||
|
||||
@@ -113,8 +113,6 @@ const SPECIAL_ACTIONS = new Set(['InitNewDoc',
|
||||
'FillTransformRuleColIds',
|
||||
'TransformAndFinishImport',
|
||||
'AddView',
|
||||
'CopyFromColumn',
|
||||
'ConvertFromColumn',
|
||||
'AddHiddenColumn',
|
||||
'RespondToRequests',
|
||||
]);
|
||||
@@ -132,9 +130,7 @@ const OK_ACTIONS = new Set(['Calculate', 'UpdateCurrentTime']);
|
||||
// Only add an action to OTHER_RECOGNIZED_ACTIONS if you know access control
|
||||
// has been handled for it, or it is clear that access control can be done
|
||||
// by looking at the Create/Update/Delete permissions for the DocActions it
|
||||
// will create. For example, at the time of writing CopyFromColumn should
|
||||
// not be here, since it could read a column the user is not supposed to
|
||||
// have access rights to, and it is not handled specially.
|
||||
// will create.
|
||||
const OTHER_RECOGNIZED_ACTIONS = new Set([
|
||||
// Data actions.
|
||||
'AddRecord',
|
||||
@@ -149,6 +145,11 @@ const OTHER_RECOGNIZED_ACTIONS = new Set([
|
||||
'AddOrUpdateRecord',
|
||||
'BulkAddOrUpdateRecord',
|
||||
|
||||
// Certain column actions are handled specially because of reads that
|
||||
// don't fit the pattern of data actions.
|
||||
'ConvertFromColumn',
|
||||
'CopyFromColumn',
|
||||
|
||||
// Groups of actions.
|
||||
'ApplyDocActions',
|
||||
'ApplyUndoActions',
|
||||
@@ -818,7 +819,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
// Checks are in no particular order.
|
||||
await this._checkSimpleDataActions(docSession, actions);
|
||||
await this._checkForSpecialOrSurprisingActions(docSession, actions);
|
||||
await this._checkPossiblePythonFormulaModification(docSession, actions);
|
||||
await this._checkIfNeedsEarlySchemaPermission(docSession, actions);
|
||||
await this._checkDuplicateTableAccess(docSession, actions);
|
||||
await this._checkAddOrUpdateAccess(docSession, actions);
|
||||
}
|
||||
@@ -912,7 +913,14 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
*/
|
||||
public needEarlySchemaPermission(a: UserAction|DocAction): boolean {
|
||||
const name = a[0] as string;
|
||||
if (name === 'ModifyColumn' || name === 'SetDisplayFormula') {
|
||||
if (name === 'ModifyColumn' || name === 'SetDisplayFormula' ||
|
||||
// ConvertFromColumn and CopyFromColumn are hard to reason
|
||||
// about, especially since they appear in bundles with other
|
||||
// actions. We throw up our hands a bit here, and just make
|
||||
// sure the user has schema permissions. Today, in Grist, that
|
||||
// gives a lot of power. If this gets narrowed down in future,
|
||||
// we'll have to rethink this.
|
||||
name === 'ConvertFromColumn' || name === 'CopyFromColumn') {
|
||||
return true;
|
||||
} else if (isDataAction(a)) {
|
||||
const tableId = getTableId(a);
|
||||
@@ -1362,7 +1370,6 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
}
|
||||
|
||||
await this._assertOnlyBundledWithSimpleDataActions(ADD_OR_UPDATE_RECORD_ACTIONS, actions);
|
||||
|
||||
// Check for read access, and that we're not touching metadata.
|
||||
await applyToActionsRecursively(actions, async (a) => {
|
||||
if (!isAddOrUpdateRecordAction(a)) { return; }
|
||||
@@ -1392,12 +1399,15 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
});
|
||||
}
|
||||
|
||||
private async _checkPossiblePythonFormulaModification(docSession: OptDocSession, actions: UserAction[]) {
|
||||
private async _checkIfNeedsEarlySchemaPermission(docSession: OptDocSession, actions: UserAction[]) {
|
||||
// If changes could include Python formulas, then user must have
|
||||
// +S before we even consider passing these to the data engine.
|
||||
// Since we don't track rule or schema changes at this stage, we
|
||||
// approximate with the user's access rights at beginning of
|
||||
// bundle.
|
||||
// We also check for +S in scenarios that are hard to break down
|
||||
// in a more granular way, for example ConvertFromColumn and
|
||||
// CopyFromColumn.
|
||||
if (scanActionsRecursively(actions, (a) => this.needEarlySchemaPermission(a))) {
|
||||
await this._assertSchemaAccess(docSession);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,29 @@ import {createSandbox, SpawnFn} from 'app/server/lib/NSandbox';
|
||||
import {SqliteVariant} from 'app/server/lib/SqliteCommon';
|
||||
import {ITelemetry} from 'app/server/lib/Telemetry';
|
||||
|
||||
// In the past, the session secret was used as an additional
|
||||
// protection passed on to expressjs-session for security when
|
||||
// generating session IDs, in order to make them less guessable.
|
||||
// Quoting the upstream documentation,
|
||||
//
|
||||
// Using a secret that cannot be guessed will reduce the ability
|
||||
// to hijack a session to only guessing the session ID (as
|
||||
// determined by the genid option).
|
||||
//
|
||||
// https://expressjs.com/en/resources/middleware/session.html
|
||||
//
|
||||
// However, since this change,
|
||||
//
|
||||
// https://github.com/gristlabs/grist-core/commit/24ce54b586e20a260376a9e3d5b6774e3fa2b8b8#diff-d34f5357f09d96e1c2ba63495da16aad7bc4c01e7925ab1e96946eacd1edb094R121-R124
|
||||
//
|
||||
// session IDs are now completely randomly generated in a cryptographically
|
||||
// secure way, so there is no danger of session IDs being guessable.
|
||||
// This makes the value of the session secret less important. The only
|
||||
// concern is that changing the secret will invalidate existing
|
||||
// sessions and force users to log in again.
|
||||
export const DEFAULT_SESSION_SECRET =
|
||||
'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh';
|
||||
|
||||
export interface ICreate {
|
||||
|
||||
Billing(dbManager: HomeDBManager, gristConfig: GristServer): IBilling;
|
||||
@@ -72,6 +95,15 @@ export interface ICreateTelemetryOptions {
|
||||
create(dbManager: HomeDBManager, gristConfig: GristServer): ITelemetry|undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a `create` object that defines various core
|
||||
* aspects of a Grist installation, such as what kind of billing or
|
||||
* sandbox to use, if any.
|
||||
*
|
||||
* The intended use of this function is to initialise Grist with
|
||||
* different settings and providers, to facilitate different editions
|
||||
* such as standard, enterprise or cloud-hosted.
|
||||
*/
|
||||
export function makeSimpleCreator(opts: {
|
||||
deploymentType: GristDeploymentType,
|
||||
sessionSecret?: string,
|
||||
@@ -116,11 +148,7 @@ export function makeSimpleCreator(opts: {
|
||||
return createSandbox(opts.sandboxFlavor || 'unsandboxed', options);
|
||||
},
|
||||
sessionSecret() {
|
||||
const secret = process.env.GRIST_SESSION_SECRET || sessionSecret;
|
||||
if (!secret) {
|
||||
throw new Error('need GRIST_SESSION_SECRET');
|
||||
}
|
||||
return secret;
|
||||
return process.env.GRIST_SESSION_SECRET || sessionSecret || DEFAULT_SESSION_SECRET;
|
||||
},
|
||||
async configure() {
|
||||
for (const s of storage || []) {
|
||||
|
||||
@@ -3,14 +3,8 @@ import { checkMinIOBucket, checkMinIOExternalStorage,
|
||||
import { makeSimpleCreator } from 'app/server/lib/ICreate';
|
||||
import { Telemetry } from 'app/server/lib/Telemetry';
|
||||
|
||||
export const DEFAULT_SESSION_SECRET =
|
||||
'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh';
|
||||
|
||||
export const makeCoreCreator = () => makeSimpleCreator({
|
||||
deploymentType: 'core',
|
||||
// This can and should be overridden by GRIST_SESSION_SECRET
|
||||
// (or generated randomly per install, like grist-omnibus does).
|
||||
sessionSecret: DEFAULT_SESSION_SECRET,
|
||||
storage: [
|
||||
{
|
||||
name: 'minio',
|
||||
|
||||
Reference in New Issue
Block a user