mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Server: Allow any account to share with any other account
This commit is contained in:
parent
9756f64c11
commit
13c4eba3af
Binary file not shown.
@ -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');
|
||||||
|
});
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { Item, Share, ShareType, ShareUser, ShareUserStatus, User, Uuid } from '../services/database/types';
|
import { Item, Share, ShareType, ShareUser, ShareUserStatus, User, Uuid } from '../services/database/types';
|
||||||
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from '../utils/errors';
|
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from '../utils/errors';
|
||||||
import BaseModel, { AclAction, DeleteOptions } from './BaseModel';
|
import BaseModel, { AclAction, DeleteOptions } from './BaseModel';
|
||||||
import { getCanShareFolder } from './utils/user';
|
import { getCanReceiveFolder } from './utils/user';
|
||||||
|
|
||||||
export default class ShareUserModel extends BaseModel<ShareUser> {
|
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> {
|
public async checkIfAllowed(user: User, action: AclAction, resource: ShareUser = null): Promise<void> {
|
||||||
if (action === AclAction.Create) {
|
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 (!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);
|
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');
|
if (share.owner_id !== user.id) throw new ErrorForbidden('no access to the share object');
|
||||||
|
@ -47,6 +47,7 @@ export enum AccountType {
|
|||||||
export interface Account {
|
export interface Account {
|
||||||
account_type: number;
|
account_type: number;
|
||||||
can_share_folder: number;
|
can_share_folder: number;
|
||||||
|
can_receive_folder: number;
|
||||||
max_item_size: number;
|
max_item_size: number;
|
||||||
max_total_item_size: number;
|
max_total_item_size: number;
|
||||||
}
|
}
|
||||||
@ -61,18 +62,21 @@ export function accountByType(accountType: AccountType): Account {
|
|||||||
{
|
{
|
||||||
account_type: AccountType.Default,
|
account_type: AccountType.Default,
|
||||||
can_share_folder: 1,
|
can_share_folder: 1,
|
||||||
|
can_receive_folder: 1,
|
||||||
max_item_size: 0,
|
max_item_size: 0,
|
||||||
max_total_item_size: 0,
|
max_total_item_size: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
account_type: AccountType.Basic,
|
account_type: AccountType.Basic,
|
||||||
can_share_folder: 0,
|
can_share_folder: 0,
|
||||||
|
can_receive_folder: 1,
|
||||||
max_item_size: 10 * MB,
|
max_item_size: 10 * MB,
|
||||||
max_total_item_size: 1 * GB,
|
max_total_item_size: 1 * GB,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
account_type: AccountType.Pro,
|
account_type: AccountType.Pro,
|
||||||
can_share_folder: 1,
|
can_share_folder: 1,
|
||||||
|
can_receive_folder: 1,
|
||||||
max_item_size: 200 * MB,
|
max_item_size: 200 * MB,
|
||||||
max_total_item_size: 10 * GB,
|
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_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 ('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_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 ('can_upload' in object) user.can_upload = object.can_upload;
|
||||||
if ('account_type' in object) user.account_type = object.account_type;
|
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;
|
if ('must_set_password' in object) user.must_set_password = object.must_set_password;
|
||||||
|
@ -7,6 +7,12 @@ export function getCanShareFolder(user: User): number {
|
|||||||
return user.can_share_folder !== null ? user.can_share_folder : account.can_share_folder;
|
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 {
|
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');
|
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);
|
const account = accountByType(user.account_type);
|
||||||
|
@ -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_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 ('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_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 ('can_upload' in fields) user.can_upload = intOrDefaultToValue(fields, 'can_upload');
|
||||||
if ('account_type' in fields) user.account_type = Number(fields.account_type);
|
if ('account_type' in fields) user.account_type = Number(fields.account_type);
|
||||||
|
|
||||||
|
@ -896,7 +896,7 @@ describe('shares.folder', () => {
|
|||||||
test('should check permissions - cannot share if share feature not enabled for recipient', async () => {
|
test('should check permissions - cannot share if share feature not enabled for recipient', async () => {
|
||||||
const { session: session1 } = await createUserAndSession(1);
|
const { session: session1 } = await createUserAndSession(1);
|
||||||
const { user: user2, session: session2 } = await createUserAndSession(2);
|
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 () =>
|
await expectHttpError(async () =>
|
||||||
shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', [
|
shareFolderWithUser(session1.id, session2.id, '000000000000000000000000000000F1', [
|
||||||
|
@ -7,7 +7,7 @@ import { ErrorMethodNotAllowed } from '../../utils/errors';
|
|||||||
import defaultView from '../../utils/defaultView';
|
import defaultView from '../../utils/defaultView';
|
||||||
import { AccountType, accountTypeToString } from '../../models/UserModel';
|
import { AccountType, accountTypeToString } from '../../models/UserModel';
|
||||||
import { formatMaxItemSize, formatMaxTotalSize, formatTotalSize, formatTotalSizePercent, yesOrNo } from '../../utils/strings';
|
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 config from '../../config';
|
||||||
import { escapeHtml } from '../../utils/htmlUtils';
|
import { escapeHtml } from '../../utils/htmlUtils';
|
||||||
import { betaStartSubUrl, betaUserTrialPeriodDays, isBetaUser } from '../../utils/stripe';
|
import { betaStartSubUrl, betaUserTrialPeriodDays, isBetaUser } from '../../utils/stripe';
|
||||||
@ -33,41 +33,46 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
|
|||||||
view.content = {
|
view.content = {
|
||||||
userProps: [
|
userProps: [
|
||||||
{
|
{
|
||||||
label: 'Account Type',
|
label: 'Account type',
|
||||||
value: accountTypeToString(user.account_type),
|
value: accountTypeToString(user.account_type),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Is Admin',
|
label: 'Is admin',
|
||||||
value: yesOrNo(user.is_admin),
|
value: yesOrNo(user.is_admin),
|
||||||
show: !!user.is_admin,
|
show: !!user.is_admin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Max Item Size',
|
label: 'Max item size',
|
||||||
value: formatMaxItemSize(user),
|
value: formatMaxItemSize(user),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Total Size',
|
label: 'Total size',
|
||||||
classes: [totalSizeClass(user)],
|
classes: [totalSizeClass(user)],
|
||||||
value: `${formatTotalSize(user)} (${formatTotalSizePercent(user)})`,
|
value: `${formatTotalSize(user)} (${formatTotalSizePercent(user)})`,
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Max Total Size',
|
label: 'Max total size',
|
||||||
value: formatMaxTotalSize(user),
|
value: formatMaxTotalSize(user),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Can Publish Note',
|
label: 'Can publish notes',
|
||||||
value: yesOrNo(true),
|
value: yesOrNo(true),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Can Share Notebook',
|
label: 'Can share notebooks',
|
||||||
value: yesOrNo(getCanShareFolder(user)),
|
value: yesOrNo(getCanShareFolder(user)),
|
||||||
show: true,
|
show: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Can receive notebooks',
|
||||||
|
value: yesOrNo(getCanReceiveFolder(user)),
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
showUpgradeProButton: subscription && user.account_type === AccountType.Basic,
|
showUpgradeProButton: subscription && user.account_type === AccountType.Basic,
|
||||||
showBetaMessage: await isBetaUser(ctx.joplin.models, user.id),
|
showBetaMessage: await isBetaUser(ctx.joplin.models, user.id),
|
||||||
|
@ -246,6 +246,7 @@ export interface User extends WithDates, WithUuid {
|
|||||||
total_item_size?: number;
|
total_item_size?: number;
|
||||||
enabled?: number;
|
enabled?: number;
|
||||||
disabled_time?: number;
|
disabled_time?: number;
|
||||||
|
can_receive_folder?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserFlag extends WithDates {
|
export interface UserFlag extends WithDates {
|
||||||
@ -454,6 +455,7 @@ export const databaseSchema: DatabaseTables = {
|
|||||||
total_item_size: { type: 'string' },
|
total_item_size: { type: 'string' },
|
||||||
enabled: { type: 'number' },
|
enabled: { type: 'number' },
|
||||||
disabled_time: { type: 'string' },
|
disabled_time: { type: 'string' },
|
||||||
|
can_receive_folder: { type: 'number' },
|
||||||
},
|
},
|
||||||
user_flags: {
|
user_flags: {
|
||||||
id: { type: 'number' },
|
id: { type: 'number' },
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
import sqlts from '@rmp135/sql-ts';
|
import sqlts, { Config } from '@rmp135/sql-ts';
|
||||||
|
|
||||||
require('source-map-support').install();
|
require('source-map-support').install();
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ const dbFilePath = `${__dirname}/../../src/services/database/types.ts`;
|
|||||||
|
|
||||||
const fileReplaceWithinMarker = '// AUTO-GENERATED-TYPES';
|
const fileReplaceWithinMarker = '// AUTO-GENERATED-TYPES';
|
||||||
|
|
||||||
const config = {
|
const config: Config = {
|
||||||
'client': 'sqlite3',
|
'client': 'sqlite3',
|
||||||
'connection': {
|
'connection': {
|
||||||
'filename': './db-buildTypes.sqlite',
|
'filename': './db-buildTypes.sqlite',
|
||||||
@ -23,6 +23,7 @@ const config = {
|
|||||||
'singularTableNames': true,
|
'singularTableNames': true,
|
||||||
'tableNameCasing': 'pascal' as any,
|
'tableNameCasing': 'pascal' as any,
|
||||||
'filename': './db',
|
'filename': './db',
|
||||||
|
'columnSortOrder': 'source',
|
||||||
'extends': {
|
'extends': {
|
||||||
'main.api_clients': 'WithDates, WithUuid',
|
'main.api_clients': 'WithDates, WithUuid',
|
||||||
'main.backup_items': 'WithCreatedDate',
|
'main.backup_items': 'WithCreatedDate',
|
||||||
|
Loading…
Reference in New Issue
Block a user