Skip to content

Commit 6356a0f

Browse files
Merge pull request #53522 from nextcloud/backport/53326/stable31
[stable31] fix: refactor request token handling and do not update with invalid result
2 parents 1a7ff4b + 4f3d3d3 commit 6356a0f

24 files changed

+853
-367
lines changed

core/js/tests/specHelper.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ window._oc_appswebroots = {
8585
"files": window.webroot + '/apps/files/',
8686
"files_sharing": window.webroot + '/apps/files_sharing/'
8787
};
88+
89+
window.OC ??= {};
90+
8891
OC.config = {
8992
session_lifetime: 600 * 1000,
9093
session_keepalive: false,
@@ -111,6 +114,10 @@ window.Snap.prototype = {
111114

112115
window.isPhantom = /phantom/i.test(navigator.userAgent);
113116
document.documentElement.lang = navigator.language;
117+
const el = document.createElement('input');
118+
el.id = 'initial-state-core-config';
119+
el.value = btoa(JSON.stringify(window.OC.config))
120+
document.body.append(el);
114121

115122
// global setup for all tests
116123
(function setupTests() {

core/js/tests/specs/coreSpec.js

Lines changed: 0 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -119,93 +119,6 @@ describe('Core base tests', function() {
119119
})).toEqual('number=123');
120120
});
121121
});
122-
describe('Session heartbeat', function() {
123-
var clock,
124-
oldConfig,
125-
counter;
126-
127-
beforeEach(function() {
128-
clock = sinon.useFakeTimers();
129-
oldConfig = OC.config;
130-
counter = 0;
131-
132-
fakeServer.autoRespond = true;
133-
fakeServer.autoRespondAfter = 0;
134-
fakeServer.respondWith(/\/csrftoken/, function(xhr) {
135-
counter++;
136-
xhr.respond(200, {'Content-Type': 'application/json'}, '{"token": "pgBEsb3MzTb1ZPd2mfDZbQ6/0j3OrXHMEZrghHcOkg8=:3khw5PSa+wKQVo4f26exFD3nplud9ECjJ8/Y5zk5/k4="}');
137-
});
138-
$(document).off('ajaxComplete'); // ignore previously registered heartbeats
139-
});
140-
afterEach(function() {
141-
clock.restore();
142-
/* jshint camelcase: false */
143-
OC.config = oldConfig;
144-
$(document).off('ajaxError');
145-
$(document).off('ajaxComplete');
146-
});
147-
it('sends heartbeat half the session lifetime when heartbeat enabled', function() {
148-
/* jshint camelcase: false */
149-
OC.config = {
150-
session_keepalive: true,
151-
session_lifetime: 300
152-
};
153-
window.initCore();
154-
155-
expect(counter).toEqual(0);
156-
157-
// less than half, still nothing
158-
clock.tick(100 * 1000);
159-
expect(counter).toEqual(0);
160-
161-
// reach past half (160), one call
162-
clock.tick(55 * 1000);
163-
expect(counter).toEqual(1);
164-
165-
// almost there to the next, still one
166-
clock.tick(140 * 1000);
167-
expect(counter).toEqual(1);
168-
169-
// past it, second call
170-
clock.tick(20 * 1000);
171-
expect(counter).toEqual(2);
172-
});
173-
it('does not send heartbeat when heartbeat disabled', function() {
174-
/* jshint camelcase: false */
175-
OC.config = {
176-
session_keepalive: false,
177-
session_lifetime: 300
178-
};
179-
window.initCore();
180-
181-
expect(counter).toEqual(0);
182-
183-
clock.tick(1000000);
184-
185-
// still nothing
186-
expect(counter).toEqual(0);
187-
});
188-
it('limits the heartbeat between one minute and one day', function() {
189-
/* jshint camelcase: false */
190-
var setIntervalStub = sinon.stub(window, 'setInterval');
191-
OC.config = {
192-
session_keepalive: true,
193-
session_lifetime: 5
194-
};
195-
window.initCore();
196-
expect(setIntervalStub.getCall(0).args[1]).toEqual(60 * 1000);
197-
setIntervalStub.reset();
198-
199-
OC.config = {
200-
session_keepalive: true,
201-
session_lifetime: 48 * 3600
202-
};
203-
window.initCore();
204-
expect(setIntervalStub.getCall(0).args[1]).toEqual(24 * 3600 * 1000);
205-
206-
setIntervalStub.restore();
207-
});
208-
});
209122
describe('Parse query string', function() {
210123
it('Parses query string from full URL', function() {
211124
var query = OC.parseQueryString('http://localhost/stuff.php?q=a&b=x');

core/src/OC/eventsource.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/* eslint-disable */
88
import $ from 'jquery'
99

10-
import { getToken } from './requesttoken.js'
10+
import { getToken } from './requesttoken.ts'
1111

1212
/**
1313
* Create a new event source

core/src/OC/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ import {
4949
getPort,
5050
getProtocol,
5151
} from './host.js'
52-
import {
53-
getToken as getRequestToken,
54-
} from './requesttoken.js'
52+
import { getRequestToken } from './requesttoken.ts'
5553
import {
5654
hideMenus,
5755
registerMenu,

core/src/OC/requesttoken.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

core/src/OC/requesttoken.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { emit } from '@nextcloud/event-bus'
7+
import { generateUrl } from '@nextcloud/router'
8+
9+
/**
10+
* Get the current CSRF token.
11+
*/
12+
export function getRequestToken(): string {
13+
return document.head.dataset.requesttoken!
14+
}
15+
16+
/**
17+
* Set a new CSRF token (e.g. because of session refresh).
18+
* This also emits an event bus event for the updated token.
19+
*
20+
* @param token - The new token
21+
* @fires Error - If the passed token is not a potential valid token
22+
*/
23+
export function setRequestToken(token: string): void {
24+
if (!token || typeof token !== 'string') {
25+
throw new Error('Invalid CSRF token given', { cause: { token } })
26+
}
27+
28+
document.head.dataset.requesttoken = token
29+
emit('csrf-token-update', { token })
30+
}
31+
32+
/**
33+
* Fetch the request token from the API.
34+
* This does also set it on the current context, see `setRequestToken`.
35+
*
36+
* @fires Error - If the request failed
37+
*/
38+
export async function fetchRequestToken(): Promise<string> {
39+
const url = generateUrl('/csrftoken')
40+
41+
const response = await fetch(url)
42+
if (!response.ok) {
43+
throw new Error('Could not fetch CSRF token from API', { cause: response })
44+
}
45+
46+
const { token } = await response.json()
47+
setRequestToken(token)
48+
return token
49+
}

core/src/globals.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import 'strengthify/strengthify.css'
2929
import OC from './OC/index.js'
3030
import OCP from './OCP/index.js'
3131
import OCA from './OCA/index.js'
32-
import { getToken as getRequestToken } from './OC/requesttoken.js'
32+
import { getRequestToken } from './OC/requesttoken.ts'
3333

3434
const warnIfNotTesting = function() {
3535
if (window.TESTING === undefined) {

core/src/init.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import _ from 'underscore'
88
import $ from 'jquery'
99
import moment from 'moment'
1010

11-
import { initSessionHeartBeat } from './session-heartbeat.js'
1211
import OC from './OC/index.js'
12+
import { initSessionHeartBeat } from './session-heartbeat.ts'
1313
import { setUp as setUpContactsMenu } from './components/ContactsMenu.js'
1414
import { setUp as setUpMainMenu } from './components/MainMenu.js'
1515
import { setUp as setUpUserMenu } from './components/UserMenu.js'

core/src/install.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import $ from 'jquery'
77
import { translate as t } from '@nextcloud/l10n'
88
import { linkTo } from '@nextcloud/router'
99

10-
import { getToken } from './OC/requesttoken.js'
10+
import { getRequestToken } from './OC/requesttoken.ts'
1111
import getURLParameter from './Util/get-url-parameter.js'
1212

1313
import './jquery/showpassword.js'
@@ -140,7 +140,7 @@ window.addEventListener('DOMContentLoaded', function() {
140140
t('core', 'Strong password'),
141141
],
142142
drawTitles: true,
143-
nonce: btoa(getToken()),
143+
nonce: btoa(getRequestToken()),
144144
})
145145

146146
$('#dbpass').showPassword().keyup()

core/src/jquery/requesttoken.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import $ from 'jquery'
77

8-
import { getToken } from '../OC/requesttoken.js'
8+
import { getRequestToken } from '../OC/requesttoken.ts'
99

1010
$(document).on('ajaxSend', function(elm, xhr, settings) {
1111
if (settings.crossDomain === false) {
12-
xhr.setRequestHeader('requesttoken', getToken())
12+
xhr.setRequestHeader('requesttoken', getRequestToken())
1313
xhr.setRequestHeader('OCS-APIREQUEST', 'true')
1414
}
1515
})

0 commit comments

Comments
 (0)