1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-30 10:36:35 +02:00

Server: Allow any account to share with any other account

This commit is contained in:
Laurent Cozic 2023-08-04 15:55:51 +01:00
parent 9756f64c11
commit 13c4eba3af
10 changed files with 48 additions and 14 deletions

Binary file not shown.

View File

@ -0,0 +1,14 @@
import { Knex } from 'knex';
import { DbConnection } from '../db';
export const up = async (db: DbConnection) => {
await db.schema.alterTable('users', (table: Knex.CreateTableBuilder) => {
table.specificType('can_receive_folder', 'smallint').defaultTo(null).nullable();
});
};
export const down = async (db: DbConnection) => {
await db.schema.alterTable('users', (table: Knex.CreateTableBuilder) => {
table.dropColumn('can_receive_folder');
});
};

View File

@ -1,7 +1,7 @@
import { Item, Share, ShareType, ShareUser, ShareUserStatus, User, Uuid } from '../services/database/types';
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from '../utils/errors';
import BaseModel, { AclAction, DeleteOptions } from './BaseModel';
import { getCanShareFolder } from './utils/user';
import { getCanReceiveFolder } from './utils/user';
export default class ShareUserModel extends BaseModel<ShareUser> {
@ -11,9 +11,9 @@ export default class ShareUserModel extends BaseModel<ShareUser> {
public async checkIfAllowed(user: User, action: AclAction, resource: ShareUser = null): Promise<void> {
if (action === AclAction.Create) {
const recipient = await this.models().user().load(resource.user_id, { fields: ['account_type', 'can_share_folder', 'enabled'] });
const recipient = await this.models().user().load(resource.user_id, { fields: ['account_type', 'can_receive_folder', 'enabled'] });
if (!recipient.enabled) throw new ErrorForbidden('the recipient account is disabled');
if (!getCanShareFolder(recipient)) throw new ErrorForbidden('The sharing feature is not enabled for the recipient account');
if (!getCanReceiveFolder(recipient)) throw new ErrorForbidden('The sharing feature is not enabled for the recipient account');
const share = await this.models().share().load(resource.share_id);
if (share.owner_id !== user.id) throw new ErrorForbidden('no access to the share object');

View File

@ -47,6 +47,7 @@ export enum AccountType {
export interface Account {
account_type: number;
can_share_folder: number;
can_receive_folder: number;
max_item_size: number;
max_total_item_size: number;
}
@ -61,18 +62,21 @@ export function accountByType(accountType: AccountType): Account {
{
account_type: AccountType.Default,
can_share_folder: 1,
can_receive_folder: 1,
max_item_size: 0,
max_total_item_size: 0,
},
{
account_type: AccountType.Basic,
can_share_folder: 0,
can_receive_folder: 1,
max_item_size: 10 * MB,
max_total_item_size: 1 * GB,
},
{
account_type: AccountType.Pro,
can_share_folder: 1,
can_receive_folder: 1,
max_item_size: 200 * MB,
max_total_item_size: 10 * GB,
},
@ -136,6 +140,7 @@ export default class UserModel extends BaseModel<User> {
if ('max_item_size' in object) user.max_item_size = object.max_item_size;
if ('max_total_item_size' in object) user.max_total_item_size = object.max_total_item_size;
if ('can_share_folder' in object) user.can_share_folder = object.can_share_folder;
if ('can_receive_folder' in object) user.can_receive_folder = object.can_receive_folder;
if ('can_upload' in object) user.can_upload = object.can_upload;
if ('account_type' in object) user.account_type = object.account_type;
if ('must_set_password' in object) user.must_set_password = object.must_set_password;

View File

@ -7,6 +7,12 @@ export function getCanShareFolder(user: User): number {
return user.can_share_folder !== null ? user.can_share_folder : account.can_share_folder;
}
export function getCanReceiveFolder(user: User): number {
if (!('account_type' in user) || !('can_receive_folder' in user)) throw new Error('Missing account_type or can_receive_folder property');
const account = accountByType(user.account_type);
return user.can_receive_folder !== null ? user.can_receive_folder : account.can_receive_folder;
}
export function getMaxItemSize(user: User): number {
if (!('account_type' in user) || !('max_item_size' in user)) throw new Error('Missing account_type or max_item_size property');
const account = accountByType(user.account_type);

View File

@ -64,6 +64,7 @@ function makeUser(isNew: boolean, fields: any): User {
if ('max_item_size' in fields) user.max_item_size = intOrDefaultToValue(fields, 'max_item_size');
if ('max_total_item_size' in fields) user.max_total_item_size = intOrDefaultToValue(fields, 'max_total_item_size');
if ('can_share_folder' in fields) user.can_share_folder = boolOrDefaultToValue(fields, 'can_share_folder');
if ('can_receive_folder' in fields) user.can_receive_folder = boolOrDefaultToValue(fields, 'can_receive_folder');
if ('can_upload' in fields) user.can_upload = intOrDefaultToValue(fields, 'can_upload');
if ('account_type' in fields) user.account_type = Number(fields.account_type);

View File

@ -896,7 +896,7 @@ describe('shares.folder', () => {
test('should check permissions - cannot share if share feature not enabled for recipient', async () => {
const { session: session1 } = await createUserAndSession(1);
const { user: user2, session: session2 } = await createUserAndSession(2);
await models().user().save({ id: user2.id, can_share_folder: 0 });
await models().user().save({ id: user2.id, can_receive_folder: 0 });
await expectHttpError(async () =>
shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', [

View File

@ -7,7 +7,7 @@ import { ErrorMethodNotAllowed } from '../../utils/errors';
import defaultView from '../../utils/defaultView';
import { AccountType, accountTypeToString } from '../../models/UserModel';
import { formatMaxItemSize, formatMaxTotalSize, formatTotalSize, formatTotalSizePercent, yesOrNo } from '../../utils/strings';
import { getCanShareFolder, totalSizeClass } from '../../models/utils/user';
import { getCanReceiveFolder, getCanShareFolder, totalSizeClass } from '../../models/utils/user';
import config from '../../config';
import { escapeHtml } from '../../utils/htmlUtils';
import { betaStartSubUrl, betaUserTrialPeriodDays, isBetaUser } from '../../utils/stripe';
@ -33,41 +33,46 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
view.content = {
userProps: [
{
label: 'Account Type',
label: 'Account type',
value: accountTypeToString(user.account_type),
show: true,
},
{
label: 'Is Admin',
label: 'Is admin',
value: yesOrNo(user.is_admin),
show: !!user.is_admin,
},
{
label: 'Max Item Size',
label: 'Max item size',
value: formatMaxItemSize(user),
show: true,
},
{
label: 'Total Size',
label: 'Total size',
classes: [totalSizeClass(user)],
value: `${formatTotalSize(user)} (${formatTotalSizePercent(user)})`,
show: true,
},
{
label: 'Max Total Size',
label: 'Max total size',
value: formatMaxTotalSize(user),
show: true,
},
{
label: 'Can Publish Note',
label: 'Can publish notes',
value: yesOrNo(true),
show: true,
},
{
label: 'Can Share Notebook',
label: 'Can share notebooks',
value: yesOrNo(getCanShareFolder(user)),
show: true,
},
{
label: 'Can receive notebooks',
value: yesOrNo(getCanReceiveFolder(user)),
show: true,
},
],
showUpgradeProButton: subscription && user.account_type === AccountType.Basic,
showBetaMessage: await isBetaUser(ctx.joplin.models, user.id),

View File

@ -246,6 +246,7 @@ export interface User extends WithDates, WithUuid {
total_item_size?: number;
enabled?: number;
disabled_time?: number;
can_receive_folder?: number;
}
export interface UserFlag extends WithDates {
@ -454,6 +455,7 @@ export const databaseSchema: DatabaseTables = {
total_item_size: { type: 'string' },
enabled: { type: 'number' },
disabled_time: { type: 'string' },
can_receive_folder: { type: 'number' },
},
user_flags: {
id: { type: 'number' },

View File

@ -1,6 +1,6 @@
/* eslint-disable no-console */
import sqlts from '@rmp135/sql-ts';
import sqlts, { Config } from '@rmp135/sql-ts';
require('source-map-support').install();
@ -8,7 +8,7 @@ const dbFilePath = `${__dirname}/../../src/services/database/types.ts`;
const fileReplaceWithinMarker = '// AUTO-GENERATED-TYPES';
const config = {
const config: Config = {
'client': 'sqlite3',
'connection': {
'filename': './db-buildTypes.sqlite',
@ -23,6 +23,7 @@ const config = {
'singularTableNames': true,
'tableNameCasing': 'pascal' as any,
'filename': './db',
'columnSortOrder': 'source',
'extends': {
'main.api_clients': 'WithDates, WithUuid',
'main.backup_items': 'WithCreatedDate',