Merge branch 'oopinning' into 2025.12-test

This commit is contained in:
yflory 2026-01-30 16:17:35 +01:00
commit beedf2e2f7
14 changed files with 150 additions and 39 deletions

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Multistage build to reduce image size and increase security
FROM node:lts-slim AS build
FROM node:lts-alpine AS build
# Create folder for CryptPad
RUN mkdir /cryptpad
@ -20,19 +20,14 @@ RUN npm install --production \
&& npm run install:components
# Create actual CryptPad image
FROM node:lts-slim
ENV DEBIAN_FRONTEND=noninteractive
FROM node:lts-alpine
# Create user and group for CryptPad so it does not run as root
RUN groupadd cryptpad -g 4001
RUN useradd cryptpad -u 4001 -g 4001 -d /cryptpad
RUN addgroup -S cryptpad -g 4001 && adduser -S cryptpad -G cryptpad --uid 4001 -h /cryptpad
# Install curl for healthcheck
# Install git, rdfind and unzip for install-onlyoffice.sh
RUN apt-get update && apt-get install --no-install-recommends -y \
curl ca-certificates git rdfind unzip && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache ca-certificates git rdfind unzip bash curl
# Copy cryptpad with installed modules
COPY --from=build --chown=cryptpad /cryptpad /cryptpad

View File

@ -295,7 +295,7 @@ install_version() {
curl "https://github.com/cryptpad/onlyoffice-editor/releases/download/$VERSION/onlyoffice-editor.zip" --location --output "onlyoffice-editor.zip"
echo "$HASH onlyoffice-editor.zip" >onlyoffice-editor.zip.sha512
if ! sha512sum --check onlyoffice-editor.zip.sha512; then
if ! sha512sum -c onlyoffice-editor.zip.sha512; then
echo "onlyoffice-editor.zip does not match expected checksum"
exit 1
fi
@ -338,7 +338,7 @@ install_x2t() {
ensure_command_available unzip
curl "https://github.com/cryptpad/onlyoffice-x2t-wasm/releases/download/$VERSION/x2t.zip" --location --output x2t.zip
echo "$HASH x2t.zip" >x2t.zip.sha512
if ! sha512sum --check x2t.zip.sha512; then
if ! sha512sum -c x2t.zip.sha512; then
echo "x2t.zip does not match expected checksum"
exit 1
fi

8
package-lock.json generated
View File

@ -1198,7 +1198,9 @@
}
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.32",
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@ -1418,7 +1420,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001763",
"version": "1.0.30001766",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz",
"integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==",
"dev": true,
"funding": [
{

View File

@ -1435,6 +1435,17 @@ const factory = (UserObject, Util, Hash,
});
};
const getMissingRtChannel = (Env) => {
const userObjects = _getUserObjects(Env);
const all = [];
userObjects.forEach(function (uo) {
const missing = uo.getMissingRtChannel();
if (!missing) { return; }
all.push(missing);
});
return all;
};
var create = function (proxy, data, uoConfig) {
var Env = {
pinPads: data.pin,
@ -1501,7 +1512,9 @@ const factory = (UserObject, Util, Hash,
findFile: callWithEnv(findFile),
getEditHash: callWithEnv(getEditHash),
user: Env.user,
folders: Env.folders
folders: Env.folders,
// Fix rtChannel
getMissingRtChannel: callWithEnv(getMissingRtChannel)
};
};

View File

@ -34,6 +34,8 @@ const factory = (Util, Hash, Realtime) => {
var debug = exp.debug;
let missingRtChannel = {};
exp._setReadOnly = function (state) {
readOnly = state;
if (!readOnly) { exp.fixFiles(); }
@ -883,6 +885,10 @@ const factory = (Util, Hash, Realtime) => {
// toClean.push(id);
}
if (['sheet', 'doc', 'presentation'].includes(parsed.type) && !el.rtChannel) {
missingRtChannel[el.channel] = el;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
@ -991,6 +997,14 @@ const factory = (Util, Hash, Realtime) => {
debug("File system was clean.", ms);
};
exp.getMissingRtChannel = () => {
const channels = missingRtChannel;
missingRtChannel = {};
if (readOnly) { return; }
if (!Object.keys(channels).length) { return; }
return channels;
};
return exp;
};

View File

@ -4,7 +4,7 @@
const factory = (Sortify, UserObject, ProxyManager,
Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Rpc, Cache,
Realtime, Messaging, Pinpad, Rpc, Cryptget, Cache,
SF, AccountTS, DriveTS, PadTS, Cursor,
Support, Integration, OnlyOffice,
Mailbox, Profile, Team, Messenger, History,
@ -2231,6 +2231,62 @@ const factory = (Sortify, UserObject, ProxyManager,
/////////////////////// Init /////////////////////////////////////
//////////////////////////////////////////////////////////////////
Store.fixMissingRtChannel = (cb) => {
const all = {};
const addMissing = (missing) => {
missing.forEach(obj => {
Object.keys(obj).forEach(chan => {
let proxy = obj[chan];
let href = proxy.roHref || proxy.href;
all[chan] ||= {
href,
password: proxy.password,
list: []
};
all[chan].list.push(proxy);
});
});
};
const mine = store.manager.getMissingRtChannel();
addMissing(mine);
const teamsId = store.modules.team.getTeams();
teamsId.forEach(id => {
const team = store.modules.team.getTeam(id);
const teamMissing = team.manager.getMissingRtChannel();
addMissing(teamMissing);
});
if (Object.keys(all).length) {
Feedback.send('MISSING_RT_CHANNEL', true);
}
let n = nThen;
const opts = {
network: store.network
};
Object.keys(all).forEach(chan => {
n = n(waitFor => {
const data = all[chan];
const href = data.href;
opts.password = data.password;
const parsed = Hash.parsePadUrl(href);
Cryptget.get(parsed.hash, waitFor((err, content) => {
if (err) { return; }
const p = Util.tryParse(content);
const rtChannel = p?.content?.channel;
if (!rtChannel) { return; }
data.list.forEach(proxy => {
proxy.rtChannel = rtChannel;
});
}), opts);
}).nThen;
});
n(() => {
setTimeout(cb);
});
};
Store.refreshDriveUI = function () {
getAllStores().forEach(function (_s) {
var send = _s.id ? _s.sendEvent : sendDriveEvent;
@ -2384,6 +2440,8 @@ const factory = (Sortify, UserObject, ProxyManager,
// Make teams non-blocking
if (store.modules['team']) { store.modules['team'].onReady(waitFor); }
});
}).nThen(function (waitFor) {
Store.fixMissingRtChannel(waitFor());
}).nThen(function () {
var requestLogin = function () {
broadcast([], "REQUEST_LOGIN");
@ -2983,6 +3041,7 @@ module.exports = factory(
require('./components/messaging'),
require('../common/pinpad'),
require('../common/rpc'),
require('../common/cryptget'),
require('../common/cache-store'),
require('./components/sharedfolder'),
require('./components/account'), // .ts

View File

@ -93,7 +93,7 @@ const _setMetadata: Callback = (ctx, clientId, data, cb) => {
otherChannels.forEach(chan => {
var _d = Util.clone(data);
_d.channel = chan;
Store.setPadMetadata(clientId, _d, () => {});
s.rpc.setMetadata(_d, () => { });
});
}
};

View File

@ -915,6 +915,7 @@ define([
href: href,
title: data.title,
owners: optsPut.owners,
attributes: common?.otherPadAttrs || {},
path: ['template']
}, function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
@ -1170,7 +1171,7 @@ define([
// Make sure we also store additionnal data to pin all the channels
// (OO and forms)
data.attributes = {};
data.attributes ||= {};
Object.keys(common?.otherPadAttrs || {}).forEach(k => {
data.attributes[k] = common.otherPadAttrs[k];
});
@ -1197,7 +1198,7 @@ define([
return;
}
let attributes = {};
let attributes = data.attributes || {};
nThen(function (waitFor) {
if (parsed.hashData.type !== 'pad') { return; }
// Set the correct owner and expiration time if we can find them

View File

@ -4940,6 +4940,12 @@ define([
opts.noExpiration = true;
}
let attr = opts.attributes = {};
if (data.rtChannel) { attr.rtChannel = data.rtChannel; }
if (data.lastVersion) { attr.lastVersion = data.lastVersion; }
if (data.lastCpHash) { attr.lastCpHash = data.lastCpHash; }
if (data.answersChannel) { attr.answersChannel = data.answersChannel; }
Access.getAccessModal(common, opts, cb);
};

View File

@ -18,6 +18,25 @@ define([
Messages, nThen, Icons) {
var Access = {};
const getOtherChans = (priv, opts) => {
// "attributes" contains the additional channels that the other user
// has to store (rtChannel, answersChannels, lastVersion, lastCpHash)
// - opts.attributes when access modal is created from the drive
// - liveAttr when access modal is created from the document
let liveAttr = Util.clone(priv.propChannels || {});
delete liveAttr.channel;
let attributes = opts.attributes || liveAttr;
// "otherChan" contains the list of channels that should also receive
// the ownership changes
let otherChan = [];
if (attributes?.answersChannel) { otherChan.push(attributes.answersChannel); }
if (attributes?.rtChannel) { otherChan.push(attributes.rtChannel); }
if (!otherChan.length) { otherChan = undefined; }
return { otherChan, attributes };
};
var getOwnersTab = function (Env, data, opts, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var common = Env.common;
@ -37,13 +56,10 @@ define([
var teamOwner = data.teamId;
var title = opts.title;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
opts = opts || {};
const { attributes, otherChan } = getOtherChans(priv, opts);
var redrawAll = function () {};
var addBtn = h('button.btn.btn-primary.cp-access-add', [Icons.get('arrow-left'), Icons.get('arrow-up')]);
@ -337,6 +353,7 @@ define([
: Messages.error;
return void UI.warn(text);
}
data.attributes = attributes;
sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
if (err || (res && res.error)) {
return void console.error(err || res.error);
@ -354,6 +371,7 @@ define([
if (!friend) { return; }
common.mailbox.sendTo("ADD_OWNER", {
channel: data.channel || priv.channel,
attributes,
channels: otherChan,
href: href,
calendar: opts.calendar,
@ -440,11 +458,7 @@ define([
var allowed = data.allowed || [];
var teamOwner = data.teamId;
var p = priv.propChannels;
var otherChan;
if (p && p.answersChannel) {
otherChan = [p.answersChannel];
}
const { otherChan } = getOtherChans(priv, opts);
var redrawAll = function () {};
@ -854,6 +868,8 @@ define([
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
const { otherChan } = getOtherChans(priv, opts);
var $div = $(h('div.cp-share-columns'));
if (priv.offline) {
@ -1069,13 +1085,15 @@ define([
if (err || (obj && obj.error)) { UI.warn(Messages.error); }
});
// If this is a form wiht a answer channel, delete it too
var p = priv.propChannels;
if (p && p.answersChannel) {
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: p.answersChannel
}, function () {});
// If this is a form with an answer channel or an office
// doc with an rt channel, delete it too
if (otherChan) {
otherChan.forEach(chan => {
sframeChan.query('Q_DELETE_OWNED', {
teamId: typeof(owned) !== "boolean" ? owned : undefined,
channel: chan
}, function () {});
});
}
});
if (!opts.noEditPassword) { $d.append(h('br')); }

View File

@ -723,7 +723,7 @@ define([
if (window.sendCredentials) { xhr.withCredentials = true; }
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
if (/^4/.test('' + this.status)) {
if (/^[45]/.test('' + this.status)) {
reject(this.status);
return void console.error('XHR error', this.status);
}

View File

@ -74,9 +74,9 @@ define([
Cryptpad.getPadAttribute('rtChannel', function (err, res) {
// If already stored, don't pin it again
channels.rtChannel = data.channel;
if (res && res === data.channel) { return; }
Cryptpad.pinPads([data.channel], function () {
channels.rtChannel = data.channel;
Cryptpad.setPadAttribute('rtChannel', data.channel, function () {});
});
});

View File

@ -1117,6 +1117,7 @@ define([
href: data.href,
channel: data.channel,
title: data.title,
attributes: data.attributes,
owners: data.metadata ? data.metadata.owners : data.owners,
expire: data.metadata ? data.metadata.expire : data.expire,
forceSave: true

File diff suppressed because one or more lines are too long