Security: FEATURE REMOVAL: Remove all plain text password logic and ui (#4178)

This will be a breaking change for some people.  

We removed all internal password control logic.  If this affects you, you have two options:

1. Use a plugin for authentication and use session based pad access (recommended).
1. Use a plugin for password setting.

The reasoning for removing this feature is to reduce the overall security footprint of Etherpad.  It is unnecessary and cumbersome to keep this feature and with the thousands of available authentication methods available in the world our focus should be on supporting those and allowing more granual access based on their implementations (instead of half assed baking our own).
This commit is contained in:
John McLear 2020-10-07 13:43:54 +01:00 committed by GitHub
parent 45bee54aa0
commit 66df0a572f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 23 additions and 246 deletions

View file

@ -684,7 +684,6 @@ exports.setPublicStatus = async function(padID, publicStatus)
publicStatus = (publicStatus.toLowerCase() === "true");
}
// set the password
await pad.setPublicStatus(publicStatus);
}
@ -706,44 +705,6 @@ exports.getPublicStatus = async function(padID)
return { publicStatus: pad.getPublicStatus() };
}
/**
setPassword(padID, password) returns ok or a error message
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
*/
exports.setPassword = async function(padID, password)
{
// ensure this is a group pad
checkGroupPad(padID, "password");
// get the pad
let pad = await getPadSafe(padID, true);
// set the password
await pad.setPassword(password === '' ? null : password);
}
/**
isPasswordProtected(padID) returns true or false
Example returns:
{code: 0, message:"ok", data: {passwordProtection: true}}
{code: 1, message:"padID does not exist", data: null}
*/
exports.isPasswordProtected = async function(padID)
{
// ensure this is a group pad
checkGroupPad(padID, "password");
// get the pad
let pad = await getPadSafe(padID, true);
return { isPasswordProtected: pad.isPasswordProtected() };
}
/**
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad

View file

@ -37,7 +37,6 @@ let Pad = function Pad(id) {
this.head = -1;
this.chatHead = -1;
this.publicStatus = false;
this.passwordHash = null;
this.id = id;
this.savedRevisions = [];
};
@ -595,19 +594,6 @@ Pad.prototype.setPublicStatus = async function setPublicStatus(publicStatus) {
await this.saveToDatabase();
};
Pad.prototype.setPassword = async function setPassword(password) {
this.passwordHash = password == null ? null : hash(password, generateSalt());
await this.saveToDatabase();
};
Pad.prototype.isCorrectPassword = function isCorrectPassword(password) {
return compare(this.passwordHash, password);
};
Pad.prototype.isPasswordProtected = function isPasswordProtected() {
return this.passwordHash != null;
};
Pad.prototype.addSavedRevision = async function addSavedRevision(revNum, savedById, label) {
// if this revision is already saved, return silently
for (var i in this.savedRevisions) {
@ -633,19 +619,3 @@ Pad.prototype.getSavedRevisions = function getSavedRevisions() {
return this.savedRevisions;
};
/* Crypto helper methods */
function hash(password, salt) {
var shasum = crypto.createHash('sha512');
shasum.update(password + salt);
return shasum.digest("hex") + "$" + salt;
}
function generateSalt() {
return randomString(86);
}
function compare(hashStr, password) {
return hash(password, hashStr.split("$")[1]) === hashStr;
}

View file

@ -28,8 +28,6 @@ var log4js = require('log4js');
var authLogger = log4js.getLogger("auth");
const DENY = Object.freeze({accessStatus: 'deny'});
const WRONG_PASSWORD = Object.freeze({accessStatus: 'wrongPassword'});
const NEED_PASSWORD = Object.freeze({accessStatus: 'needPassword'});
/**
* Determines whether the user can access a pad.
@ -42,16 +40,14 @@ const NEED_PASSWORD = Object.freeze({accessStatus: 'needPassword'});
* is false and the user is accessing a public pad. If there is not an author already associated
* with this token then a new author object is created (including generating an author ID) and
* associated with this token.
* @param password is the password the user has given to access this pad. It can be null.
* @param userSettings is the settings.users[username] object (or equivalent from an authn plugin).
* @return {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx}. The caller
* must use the author ID returned in this object when making any changes associated with the
* author.
* @return {accessStatus: grant|deny, authorID: a.xxxxxx}. The caller must use the author ID
* returned in this object when making any changes associated with the author.
*
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
*/
exports.checkAccess = async function(padID, sessionCookie, token, password, userSettings)
exports.checkAccess = async function(padID, sessionCookie, token, userSettings)
{
if (!padID) {
authLogger.debug('access denied: missing padID');
@ -84,7 +80,7 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
// allow plugins to deny access
const isFalse = (x) => x === false;
if (hooks.callAll('onAccessCheck', {padID, password, token, sessionCookie}).some(isFalse)) {
if (hooks.callAll('onAccessCheck', {padID, token, sessionCookie}).some(isFalse)) {
authLogger.debug('access denied: an onAccessCheck hook function returned false');
return DENY;
}
@ -112,8 +108,7 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
};
if (!padID.includes('$')) {
// Only group pads can be private or have passwords, so there is nothing more to check for this
// non-group pad.
// Only group pads can be private, so there is nothing more to check for this non-group pad.
return grant;
}
@ -122,7 +117,7 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
authLogger.debug('access denied: must have an HTTP API session to create a group pad');
return DENY;
}
// Creating a group pad, so there is no password or public status to check.
// Creating a group pad, so there is no public status to check.
return grant;
}
@ -133,18 +128,5 @@ exports.checkAccess = async function(padID, sessionCookie, token, password, user
return DENY;
}
const passwordExempt = settings.sessionNoPassword && sessionAuthorID != null;
const requirePassword = pad.isPasswordProtected() && !passwordExempt;
if (requirePassword) {
if (password == null) {
authLogger.debug('access denied: password required');
return NEED_PASSWORD;
}
if (!password || !pad.isCorrectPassword(password)) {
authLogger.debug('access denied: wrong password');
return WRONG_PASSWORD;
}
}
return grant;
};