Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:RocketChat/Rocket.Chat into feat…
Browse files Browse the repository at this point in the history
…/admin-root

* 'develop' of github.com:RocketChat/Rocket.Chat:
  [IMPROVE] Increase decoupling between React components and Blaze templates (#16642)
  Fix self DM (#17239)
  [CHORE] Use REST API for sending audio messages (#17237)
  [FIX] Random errors on SAML logout (#17227)
  Collect metrics about meteor facts (#17216)
  Removed the invalid and unnecessary parameter clientAction. (#17224)
  [FIX] Wrong SAML Response Signature Validation (#16922)
  [FIX] SAML login errors not showing on UI (#17219)
  Add statistics and metrics about push queue (#17208)
  Regression: Fix users raw model (#17204)
  • Loading branch information
ggazzo committed Apr 10, 2020
2 parents 5e862f6 + 7a46c7d commit c0f4f75
Show file tree
Hide file tree
Showing 16 changed files with 416 additions and 376 deletions.
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,4 @@ [email protected]
rocketchat:oauth2-server
rocketchat:i18n
dandv:caret-position
facts-base
1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ [email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
Expand Down
4 changes: 3 additions & 1 deletion app/api/server/lib/users.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import s from 'underscore.string';

import { Users } from '../../../models/server/raw';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';

Expand All @@ -19,7 +21,7 @@ export async function findUsersToAutocomplete({ uid, selector }) {
limit: 10,
};

const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(selector.term, exceptions, conditions, options).toArray();
const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(new RegExp(s.escapeRegExp(selector.term), 'i'), exceptions, conditions, options).toArray();

return {
items: users,
Expand Down
4 changes: 2 additions & 2 deletions app/lib/server/functions/createDirectRoom.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ export const createDirectRoom = function(members, roomExtraData = {}, options =
const uids = members.map(({ _id }) => _id).sort();

// Deprecated: using users' _id to compose the room _id is deprecated
const room = uids.length <= 2
const room = uids.length === 2
? Rooms.findOneById(uids.join(''), { fields: { _id: 1 } })
: Rooms.findOneDirectRoomContainingAllUserIDs(uids, { fields: { _id: 1 } });

const isNewRoom = !room;

const rid = room?._id || Rooms.insert({
...uids.length <= 2 && { _id: uids.join('') }, // Deprecated: using users' _id to compose the room _id is deprecated
...uids.length === 2 && { _id: uids.join('') }, // Deprecated: using users' _id to compose the room _id is deprecated
t: 'd',
usernames,
usersCount: members.length,
Expand Down
6 changes: 5 additions & 1 deletion app/meteor-accounts-saml/client/saml_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ Meteor.logoutWithSaml = function(options/* , callback*/) {
MeteorLogout.apply(Meteor);
return;
}

// Remove the userId from the client to prevent calls to the server while the logout is processed.
// If the logout fails, the userId will be reloaded on the resume call
Meteor._localStorage.removeItem(Accounts.USER_ID_KEY);

// A nasty bounce: 'result' has the SAML LogoutRequest but we need a proper 302 to redirected from the server.
// window.location.replace(Meteor.absoluteUrl('_saml/sloRedirect/' + options.provider + '/?redirect='+result));
window.location.replace(Meteor.absoluteUrl(`_saml/sloRedirect/${ options.provider }/?redirect=${ encodeURIComponent(result) }`));
});
};
263 changes: 138 additions & 125 deletions app/meteor-accounts-saml/server/saml_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ const guessNameFromUsername = (username) =>
.replace(/^(.)/, (u) => u.toLowerCase())
.replace(/^\w/, (u) => u.toUpperCase());

const findUser = (username, emailRegex) => {
if (Accounts.saml.settings.immutableProperty === 'Username') {
if (username) {
return Meteor.users.findOne({
username,
});
}

return null;
}

return Meteor.users.findOne({
'emails.address': emailRegex,
});
};

Accounts.registerLoginHandler(function(loginRequest) {
if (!loginRequest.saml || !loginRequest.credentialToken) {
return undefined;
Expand All @@ -243,170 +259,167 @@ Accounts.registerLoginHandler(function(loginRequest) {
error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'No matching login attempt found'),
};
}

const { emailField, usernameField, nameField, userDataFieldMap, regexes } = getUserDataMapping();
const { defaultUserRole = 'user', roleAttributeName, roleAttributeSync } = Accounts.saml.settings;

if (loginResult && loginResult.profile && loginResult.profile[emailField]) {
const emailList = Array.isArray(loginResult.profile[emailField]) ? loginResult.profile[emailField] : [loginResult.profile[emailField]];
const emailRegex = new RegExp(emailList.map((email) => `^${ RegExp.escape(email) }$`).join('|'), 'i');
try {
const emailList = Array.isArray(loginResult.profile[emailField]) ? loginResult.profile[emailField] : [loginResult.profile[emailField]];
const emailRegex = new RegExp(emailList.map((email) => `^${ RegExp.escape(email) }$`).join('|'), 'i');

const eduPersonPrincipalName = loginResult.profile.eppn;
const profileFullName = getProfileValue(loginResult.profile, nameField, regexes.name);
const fullName = profileFullName || loginResult.profile.displayName || loginResult.profile.username;
const eduPersonPrincipalName = loginResult.profile.eppn;
const profileFullName = getProfileValue(loginResult.profile, nameField, regexes.name);
const fullName = profileFullName || loginResult.profile.displayName || loginResult.profile.username;

let eppnMatch = false;
let user = null;
let eppnMatch = false;
let user = null;

// Check eppn
if (eduPersonPrincipalName) {
user = Meteor.users.findOne({
eppn: eduPersonPrincipalName,
});
// Check eppn
if (eduPersonPrincipalName) {
user = Meteor.users.findOne({
eppn: eduPersonPrincipalName,
});

if (user) {
eppnMatch = true;
if (user) {
eppnMatch = true;
}
}
}

let username;
if (loginResult.profile[usernameField]) {
const profileUsername = getProfileValue(loginResult.profile, usernameField, regexes.username);
if (profileUsername) {
username = Accounts.normalizeUsername(profileUsername);
let username;
if (loginResult.profile[usernameField]) {
const profileUsername = getProfileValue(loginResult.profile, usernameField, regexes.username);
if (profileUsername) {
username = Accounts.normalizeUsername(profileUsername);
}
}
}

// If eppn is not exist
if (!user) {
if (Accounts.saml.settings.immutableProperty === 'Username') {
if (username) {
user = Meteor.users.findOne({
username,
});
}
// If eppn is not exist
if (!user) {
user = findUser(username, emailRegex);
}

const emails = emailList.map((email) => ({
address: email,
verified: settings.get('Accounts_Verify_Email_For_External_Accounts'),
}));

let globalRoles;
if (roleAttributeName && loginResult.profile[roleAttributeName]) {
globalRoles = [].concat(loginResult.profile[roleAttributeName]);
} else {
user = Meteor.users.findOne({
'emails.address': emailRegex,
});
globalRoles = [].concat(defaultUserRole.split(','));
}
}

const emails = emailList.map((email) => ({
address: email,
verified: settings.get('Accounts_Verify_Email_For_External_Accounts'),
}));
if (!user) {
const newUser = {
name: fullName,
active: true,
eppn: eduPersonPrincipalName,
globalRoles,
emails,
services: {},
};

let globalRoles;
if (roleAttributeName && loginResult.profile[roleAttributeName]) {
globalRoles = [].concat(loginResult.profile[roleAttributeName]);
} else {
globalRoles = [].concat(defaultUserRole.split(','));
}
if (Accounts.saml.settings.generateUsername === true) {
username = generateUsernameSuggestion(newUser);
}

if (!user) {
const newUser = {
name: fullName,
active: true,
eppn: eduPersonPrincipalName,
globalRoles,
emails,
services: {},
};
if (username) {
newUser.username = username;
newUser.name = newUser.name || guessNameFromUsername(username);
}

if (Accounts.saml.settings.generateUsername === true) {
username = generateUsernameSuggestion(newUser);
}
const languages = TAPi18n.getLanguages();
if (languages[loginResult.profile.language]) {
newUser.language = loginResult.profile.language;
}

if (username) {
newUser.username = username;
newUser.name = newUser.name || guessNameFromUsername(username);
}
const userId = Accounts.insertUserDoc({}, newUser);
user = Meteor.users.findOne(userId);

const languages = TAPi18n.getLanguages();
if (languages[loginResult.profile.language]) {
newUser.language = loginResult.profile.language;
if (loginResult.profile.channels) {
const channels = loginResult.profile.channels.split(',');
Accounts.saml.subscribeToSAMLChannels(channels, user);
}
}

const userId = Accounts.insertUserDoc({}, newUser);
user = Meteor.users.findOne(userId);

if (loginResult.profile.channels) {
const channels = loginResult.profile.channels.split(',');
Accounts.saml.subscribeToSAMLChannels(channels, user);
// If eppn is not exist then update
if (eppnMatch === false) {
Meteor.users.update({
_id: user._id,
}, {
$set: {
eppn: eduPersonPrincipalName,
},
});
}
}

// If eppn is not exist then update
if (eppnMatch === false) {
Meteor.users.update({
_id: user._id,
}, {
$set: {
eppn: eduPersonPrincipalName,
// creating the token and adding to the user
const stampedToken = Accounts._generateStampedLoginToken();
Meteor.users.update(user, {
$push: {
'services.resume.loginTokens': stampedToken,
},
});
}

// creating the token and adding to the user
const stampedToken = Accounts._generateStampedLoginToken();
Meteor.users.update(user, {
$push: {
'services.resume.loginTokens': stampedToken,
},
});
const samlLogin = {
provider: Accounts.saml.RelayState,
idp: loginResult.profile.issuer,
idpSession: loginResult.profile.sessionIndex,
nameID: loginResult.profile.nameID,
};

const samlLogin = {
provider: Accounts.saml.RelayState,
idp: loginResult.profile.issuer,
idpSession: loginResult.profile.sessionIndex,
nameID: loginResult.profile.nameID,
};
const updateData = {
// TBD this should be pushed, otherwise we're only able to SSO into a single IDP at a time
'services.saml': samlLogin,
};

const updateData = {
// TBD this should be pushed, otherwise we're only able to SSO into a single IDP at a time
'services.saml': samlLogin,
};
for (const field in userDataFieldMap) {
if (!userDataFieldMap.hasOwnProperty(field)) {
continue;
}

for (const field in userDataFieldMap) {
if (!userDataFieldMap.hasOwnProperty(field)) {
continue;
if (loginResult.profile[field]) {
const rcField = userDataFieldMap[field];
const value = getProfileValue(loginResult.profile, field, regexes[rcField]);
updateData[`customFields.${ rcField }`] = value;
}
}

if (loginResult.profile[field]) {
const rcField = userDataFieldMap[field];
const value = getProfileValue(loginResult.profile, field, regexes[rcField]);
updateData[`customFields.${ rcField }`] = value;
if (Accounts.saml.settings.immutableProperty !== 'EMail') {
updateData.emails = emails;
}
}

if (Accounts.saml.settings.immutableProperty !== 'EMail') {
updateData.emails = emails;
}

if (roleAttributeSync) {
updateData.roles = globalRoles;
}
if (roleAttributeSync) {
updateData.roles = globalRoles;
}

Meteor.users.update({
_id: user._id,
}, {
$set: updateData,
});
Meteor.users.update({
_id: user._id,
}, {
$set: updateData,
});

if (username) {
_setUsername(user._id, username);
}
if (username) {
_setUsername(user._id, username);
}

overwriteData(user, fullName, eppnMatch, emailList);
overwriteData(user, fullName, eppnMatch, emailList);

// sending token along with the userId
const result = {
userId: user._id,
token: stampedToken.token,
};
// sending token along with the userId
const result = {
userId: user._id,
token: stampedToken.token,
};

return result;
return result;
} catch (error) {
console.error(error);
return {
type: 'saml',
error,
};
}
}
throw new Error('SAML Profile did not contain an email address');
});
Expand Down
Loading

0 comments on commit c0f4f75

Please sign in to comment.