From e65baebadc3d1153b0deccb935ab27da759c64a2 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 19 Dec 2023 17:59:56 +0100 Subject: [PATCH] Restrict SSO registration --- customize.dist/login.js | 4 +- customize.dist/pages/register.js | 2 + .../src/less2/pages/page-register.less | 3 + lib/commands/admin-rpc.js | 2 + lib/commands/block.js | 8 +- lib/decrees.js | 2 + lib/http-worker.js | 1 + www/admin/inner.js | 96 +++++++++++++++---- www/register/main.js | 5 +- www/ssoauth/main.js | 18 +++- 10 files changed, 111 insertions(+), 30 deletions(-) diff --git a/customize.dist/login.js b/customize.dist/login.js index 9a05d63a0..2781dd0b6 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -49,9 +49,11 @@ define([ Exports.ssoAuth = function (provider, cb) { var keys = Nacl.sign.keyPair(); + var inviteToken = window.location.hash.slice(1); localStorage.CP_sso_auth = JSON.stringify({ s: Nacl.util.encodeBase64(keys.secretKey), - p: Nacl.util.encodeBase64(keys.publicKey) + p: Nacl.util.encodeBase64(keys.publicKey), + token: inviteToken }); ServerCommand(keys, { command: 'SSO_AUTH', diff --git a/customize.dist/pages/register.js b/customize.dist/pages/register.js index cf3fb256a..fde495c49 100644 --- a/customize.dist/pages/register.js +++ b/customize.dist/pages/register.js @@ -56,6 +56,7 @@ define([ h('h2', Msg.register_notes_title), Pages.setHTML(h('div.cp-register-notes'), Msg.register_notes) ]), + h('div.col-md-3.cp-closed-filler'+ssoEnabled, h('div')), h('div.cp-reg-form.col-md-6', [ h('div#userForm.form-group'+ssoEnforced, [ h('div.cp-register-instance', [ @@ -105,6 +106,7 @@ define([ h('div.cp-register-sso', Msg.sso_register_description) ]), ]), + h('div.col-md-3.cp-closed-filler'+ssoEnabled), ]) ]); }; diff --git a/customize.dist/src/less2/pages/page-register.less b/customize.dist/src/less2/pages/page-register.less index 546af842b..9e5d5caee 100644 --- a/customize.dist/src/less2/pages/page-register.less +++ b/customize.dist/src/less2/pages/page-register.less @@ -22,6 +22,9 @@ .cp-restricted-registration { display: none; } + .cp-closed-filler { + display: none; + } } &.cp-register-closed { div.cp-register-det { diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index e87911149..b4b6af536 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -461,6 +461,8 @@ var setLastEviction = function (Env, Server, cb, data, unsafeKey) { var instanceStatus = function (Env, Server, cb) { cb(void 0, { restrictRegistration: Env.restrictRegistration, + restrictSsoRegistration: Env.restrictSsoRegistration, + enableEmbedding: Env.enableEmbedding, launchTime: Env.launchTime, currentTime: +new Date(), diff --git a/lib/commands/block.js b/lib/commands/block.js index 451e9a422..c6723f724 100644 --- a/lib/commands/block.js +++ b/lib/commands/block.js @@ -111,7 +111,7 @@ Block.validateAncestorProof = function (Env, proof, _cb) { Block.writeLoginBlock = function (Env, msg, _cb) { var cb = Util.once(Util.mkAsync(_cb)); - const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken } = msg; + const { publicKey, signature, ciphertext, registrationProof, userData, inviteToken, isSSO } = msg; var previousKey; var validatedBlock, path; @@ -123,9 +123,11 @@ Block.writeLoginBlock = function (Env, msg, _cb) { if (err || !state) { return; } // Invalid token, don't abort, check registration proof validatedInvite = true; })); + }).nThen(function (w) { }).nThen(function (w) { if (!Env.restrictRegistration) { return; } - if (!registrationProof && !validatedInvite) { + var ssoAllowed = isSSO && !Env.restrictSsoRegistration + if (!(registrationProof || validatedInvite || ssoAllowed)) { // we allow users with existing blocks to create new ones // call back with error if registration is restricted and no proof of an existing block was provided w.abort(); @@ -134,9 +136,9 @@ Block.writeLoginBlock = function (Env, msg, _cb) { }); return cb("E_RESTRICTED"); } + if (!registrationProof) { return; } Block.validateAncestorProof(Env, registrationProof, w(function (err, provenKey) { if (err || !provenKey) { // double check that a key was validated - if (validatedInvite) { return; } w.abort(); Env.Log.warn('BLOCK_REJECTED_INVALID_ANCESTOR', { error: err, diff --git a/lib/decrees.js b/lib/decrees.js index 1f81db056..952700aea 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -10,6 +10,7 @@ var Core = require("./commands/core"); IMPLEMENTED: RESTRICT_REGISTRATION() +RESTRICT_SSO_REGISTRATION() UPDATE_DEFAULT_STORAGE() // QUOTA MANAGEMENT @@ -112,6 +113,7 @@ commands.ENFORCE_MFA = makeBooleanSetter('enforceMFA'); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['RESTRICT_REGISTRATION', [true]]], console.log) commands.RESTRICT_REGISTRATION = makeBooleanSetter('restrictRegistration'); +commands.RESTRICT_SSO_REGISTRATION = makeBooleanSetter('restrictSsoRegistration'); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['DISABLE_INTEGRATED_EVICTION', [true]]], console.log) commands.DISABLE_INTEGRATED_EVICTION = makeBooleanSetter('disableIntegratedEviction'); diff --git a/lib/http-worker.js b/lib/http-worker.js index 185ae748e..cdf7678e4 100644 --- a/lib/http-worker.js +++ b/lib/http-worker.js @@ -579,6 +579,7 @@ var serveConfig = makeRouteCache(function () { maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, restrictRegistration: Env.restrictRegistration, + restrictSsoRegistration: Env.restrictSsoRegistration, httpSafeOrigin: Env.httpSafeOrigin, enableEmbedding: Env.enableEmbedding, fileHost: Env.fileHost, diff --git a/www/admin/inner.js b/www/admin/inner.js index 29e04be92..d6af245e8 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -1495,29 +1495,81 @@ Example }; // Msg.admin_registrationHint, .admin_registrationTitle - create['registration'] = makeAdminCheckbox({ - key: 'registration', - getState: function () { - return APP.instanceStatus.restrictRegistration; - }, - query: function (val, setState) { - sFrameChan.query('Q_ADMIN_RPC', { - cmd: 'ADMIN_DECREE', - data: ['RESTRICT_REGISTRATION', [val]] - }, function (e, response) { - if (e || response.error) { - UI.warn(Messages.error); - console.error(e, response); - } - APP.updateStatus(function () { - setState(APP.instanceStatus.restrictRegistration); - flushCacheNotice(); + // Msg.admin_registrationSsoTitle + Messages.admin_registrationSsoTitle = "ALSO CLOSE SSO REGISTRATION"; // XXX + create['registration'] = function () { + var key = 'registration'; + + var refresh = function () {}; + + var $div = makeAdminCheckbox({ + key: 'registration', + getState: function () { + return APP.instanceStatus.restrictRegistration; + }, + query: function (val, setState) { + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'ADMIN_DECREE', + data: ['RESTRICT_REGISTRATION', [val]] + }, function (e, response) { + if (e || response.error) { + UI.warn(Messages.error); + console.error(e, response); + } + APP.updateStatus(function () { + setState(APP.instanceStatus.restrictRegistration); + refresh(); + flushCacheNotice(); + }); }); - }); - }, - }); + } + })(); + + var $sso = makeAdminCheckbox({ + key: 'registration-sso', + getState: function () { + return APP.instanceStatus.restrictSsoRegistration; + }, + query: function (val, setState) { + sFrameChan.query('Q_ADMIN_RPC', { + cmd: 'ADMIN_DECREE', + data: ['RESTRICT_SSO_REGISTRATION', [val]] + }, function (e, response) { + if (e || response.error) { + UI.warn(Messages.error); + console.error(e, response); + } + APP.updateStatus(function () { + setState(APP.instanceStatus.restrictSsoRegistration); + flushCacheNotice(); + }); + }); + } + })(); + var ssoEnabled = ApiConfig.sso && ApiConfig.sso.list && ApiConfig.sso.list.length; + if (ssoEnabled) { + $sso.find('#cp-admin-registration-sso').hide(); + $sso.find('> span.cp-sidebarlayout-description').hide(); + $div.append($sso); + } + + refresh = () => { + var closed = APP.instanceStatus.restrictRegistration; + if (closed) { + $sso.show(); + } else { + $sso.hide(); + } + }; + refresh(); + + + return $div; + }; Messages.admin_invitationCreate = "Create invitation link"; // XXX + Messages.admin_invitationHint = "Create invitation links to allow users to register even when registration is closed"; + Messages.admin_invitationTitle = "Invitation links"; create['invitation'] = function () { var key = 'invitation'; var $div = makeBlock(key); // Msg.admin_invitationHint, admin_invitationTitle @@ -1610,7 +1662,9 @@ Example return $div; }; - Messages.admin_usersAdd = "Add known user"; + Messages.admin_usersAdd = "Add known user"; // XXX + Messages.admin_userHint = "List of known users. You can add more using the form and select automated options"; + Messages.admin_userTitle = "Known users"; create['users'] = function () { var key = 'users'; var $div = makeBlock(key); // Msg.admin_usersHint, admin_usersTitle diff --git a/www/register/main.js b/www/register/main.js index 3af579459..cf7829a0a 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -34,6 +34,10 @@ define([ var hash = window.location.hash.slice(1); token = hash; $('body').removeClass('cp-register-closed'); + } else if (Config.sso && Config.restrictRegistration && !Config.restrictSsoRegistration) { + $('body').find('.cp-register-det').css('display', 'flex'); + $('body').find('#data').hide(); + $('body').find('#userForm').hide(); } // text and password input fields @@ -69,7 +73,6 @@ define([ var list = Config.sso.list.map(function (name) { var b = h('button.btn.btn-secondary', name); var $b = $(b).click(function () { - console.log('sso register click:', name); $b.prop('disabled', 'disabled'); Login.ssoAuth(name, function (err, data) { if (data.url) { diff --git a/www/ssoauth/main.js b/www/ssoauth/main.js index a32f6bad2..9535a72c7 100644 --- a/www/ssoauth/main.js +++ b/www/ssoauth/main.js @@ -33,19 +33,22 @@ define([ secretKey: Nacl.util.decodeBase64(b64Keys.s), publicKey: Nacl.util.decodeBase64(b64Keys.p) }; + var inviteToken = b64Keys.token; ServerCommand(keys, { command: 'SSO_AUTH_CB', url: window.location.href }, function (err, data) { delete localStorage.CP_sso_auth; document.cookie = 'ssotoken=; Max-Age=-99999999;'; + if (data) { data.inviteToken = inviteToken; } cb(err, data); }); }; - let ssoLoginRegister = function (seed, pw, jwt, name, isRegister) { + let ssoLoginRegister = function (seed, pw, jwt, name, isRegister, inviteToken) { Login.loginOrRegister({ uname: seed, passwd: pw, + token: inviteToken, isRegister: isRegister, onOTP: UI.getOTPScreen, ssoAuth: { @@ -97,7 +100,7 @@ define([ hideTips: true, }); setTimeout(function () { // Second timeout for the loading screen befofe Scrypt - ssoLoginRegister(seed, pw, jwt, name, data.register); + ssoLoginRegister(seed, pw, jwt, name, data.register, data.inviteToken); }, 100); }, 100); @@ -108,6 +111,10 @@ define([ return void next(''); } + if (data.register && ApiConfig.restrictSsoRegistration && !data.inviteToken) { + return void UI.errorLoadingScreen(Messages.register_registrationIsClosed); + } + // Registration and CP password disabled, continue if (data.register && !ApiConfig.sso.password) { return void next(''); @@ -130,14 +137,17 @@ define([ $button.click(() => { let pw = $pw.val(); + let warning = Messages._getKey('register_passwordTooShort', [ + Cred.MINIMUM_PASSWORD_LENGTH + ]); if (data.register && pw !== $pw2.val()) { return void UI.warn(Messages.register_passwordsDontMatch); } if (data.register && pw && !Cred.isLongEnoughPassword(pw)) { - return void UI.warn(Messages.register_passwordTooShort); + return void UI.warn(warning); } if (data.register && !pw && ApiConfig.sso.password === 2) { - return void UI.warn(Messages.register_passwordTooShort); + return void UI.warn(warning); } $button.prop('disabled', 'disabled');