Skip to content

Commit

Permalink
(core) updates from grist-core
Browse files Browse the repository at this point in the history
  • Loading branch information
paulfitz committed Jul 29, 2024
2 parents bb0213e + db26f3b commit 5dbdb5c
Show file tree
Hide file tree
Showing 11 changed files with 1,181 additions and 136 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/fly-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ jobs:
- name: Build and export Docker image
id: docker-build
run: >
docker build -t grist-core:preview . &&
./buildtools/checkout-ext-directory.sh grist-ee &&
docker build -t grist-core:preview . --build-context ext=ext &&
docker image save grist-core:preview -o grist-core.tar
- name: Save PR information
run: |
Expand Down
19 changes: 19 additions & 0 deletions app/client/apiconsole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion app/server/lib/BootProbes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 19 additions & 9 deletions app/server/lib/GranularAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ const SPECIAL_ACTIONS = new Set(['InitNewDoc',
'FillTransformRuleColIds',
'TransformAndFinishImport',
'AddView',
'CopyFromColumn',
'ConvertFromColumn',
'AddHiddenColumn',
'RespondToRequests',
]);
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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);
}
Expand Down
38 changes: 33 additions & 5 deletions app/server/lib/ICreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 || []) {
Expand Down
6 changes: 0 additions & 6 deletions app/server/lib/coreCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion buildtools/.grist-ee-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.9.5
0.9.6
36 changes: 20 additions & 16 deletions documentation/grist-data-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,32 @@ Note that this combination of rules allows tables and column names to be valid i

## Value Types

> [!WARNING]
> This section is out of date.
The format supports a number of data types. Some types have a short representation (e.g. `Numeric` as a JSON `number`, and `Text` as a JSON `string`), but all types have an explicit representation as well.

The explicit representation of a value is an array `[typeCode, args...]`. The first member of the array is a string code that defines the type of the value. The rest of the elements are arguments used to construct the actual value.

The following table lists currently supported types and their short and explicit representations.

| **Type Name** | **Short Repr** | **[Type Code, Args...]** | **Description** |
| `Numeric` | `number`* | `['n',number]` | double-precision floating point number |
| `Text` | `string`* | `['s',string]` | Unicode string |
| `Bool` | `bool`* | `['b',bool]` | Boolean value (true or false) |
| `Null` | `null`* | `null` | Null value (no special explicit representation) |
| `Int` | `number` | `['i',number]` | 32-bit integer |
| `Date` | `number` | `['d',number]` | Calendar date, represented as seconds since Epoch to 00:00 UTC on that date. |
| `DateTime` | `number` | `['D',number]` | Instance in time, represented as seconds since Epoch |
| `Reference` | `number` | `['R',number]` | Identifier of a record in a table. |
| `ReferenceList` | | `['L',number,...]` | List of record identifiers |
| `Choice` | `string` | `['C',string]` | Unicode string selected from a list of choices. |
| `PositionNumber` | `number` | `['P',number]` | a double used to order records relative to each other. |
| `Image` | | `['I',string]` | Binary data representing an image, encoded as base64 |
| `List` | | `['l',values,...]` | List of values of any type. |
| `JSON` | | `['J',object]` | JSON-serializable object |
| `Error` | | `['E',string,string?,value?]` | Exception, with first argument exception type, second an optional message, and optionally a third containing additional info. |
| **Type Name** | **Short Repr** | **[Type Code, Args...]** | **Description** |
|------------------|----------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------|
| `Numeric` | `number`* | `['n',number]` | double-precision floating point number |
| `Text` | `string`* | `['s',string]` | Unicode string |
| `Bool` | `bool`* | `['b',bool]` | Boolean value (true or false) |
| `Null` | `null`* | `null` | Null value (no special explicit representation) |
| `Int` | `number` | `['i',number]` | 32-bit integer |
| `Date` | `number` | `['d',number]` | Calendar date, represented as seconds since Epoch to 00:00 UTC on that date. |
| `DateTime` | `number` | `['D',number]` | Instance in time, represented as seconds since Epoch |
| `Reference` | `number` | `['R',number]` | Identifier of a record in a table. |
| `ReferenceList` | | `['L',number,...]` | List of record identifiers |
| `Choice` | `string` | `['C',string]` | Unicode string selected from a list of choices. |
| `PositionNumber` | `number` | `['P',number]` | a double used to order records relative to each other. |
| `Image` | | `['I',string]` | Binary data representing an image, encoded as base64 |
| `List` | | `['l',values,...]` | List of values of any type. |
| `JSON` | | `['J',object]` | JSON-serializable object |
| `Error` | | `['E',string,string?,value?]` | Exception, with first argument exception type, second an optional message, and optionally a third containing additional info. |

An important goal is to represent data efficiently in the common case. When a value matches the column's type, the short representation is used. For example, in a Numeric column, a Numeric value is represented as a `number`, and in a Date column, a Date value is represented as a `number`.

Expand Down
32 changes: 32 additions & 0 deletions static/locales/en.client.json
Original file line number Diff line number Diff line change
Expand Up @@ -1633,5 +1633,37 @@
"Number of Calls": "Number of Calls",
"Table ID": "Table ID",
"Total Time (s)": "Total Time (s)"
},
"DocTutorial": {
"Click to expand": "Click to expand",
"Do you want to restart the tutorial? All progress will be lost.": "Do you want to restart the tutorial? All progress will be lost.",
"End tutorial": "End tutorial",
"Finish": "Finish",
"Next": "Next",
"Previous": "Previous",
"Restart": "Restart"
},
"OnboardingCards": {
"3 minute video tour": "3 minute video tour",
"Complete our basics tutorial": "Complete our basics tutorial",
"Complete the tutorial": "Complete the tutorial",
"Learn the basic of reference columns, linked widgets, column types, & cards.": "Learn the basic of reference columns, linked widgets, column types, & cards."
},
"OnboardingPage": {
"Back": "Back",
"Discover Grist in 3 minutes": "Discover Grist in 3 minutes",
"Go hands-on with the Grist Basics tutorial": "Go hands-on with the Grist Basics tutorial",
"Go to the tutorial!": "Go to the tutorial!",
"Next step": "Next step",
"Skip step": "Skip step",
"Skip tutorial": "Skip tutorial",
"Tell us who you are": "Tell us who you are",
"Type here": "Type here",
"Welcome": "Welcome",
"What brings you to Grist (you can select multiple)?": "What brings you to Grist (you can select multiple)?",
"What is your role?": "What is your role?",
"What organization are you with?": "What organization are you with?",
"Your organization": "Your organization",
"Your role": "Your role"
}
}
Loading

0 comments on commit 5dbdb5c

Please sign in to comment.