Skip to content

Commit

Permalink
Merge branch 'develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
geekgonecrazy committed Apr 15, 2019
2 parents 1ac39f4 + dd76eca commit e03033b
Show file tree
Hide file tree
Showing 90 changed files with 652 additions and 203 deletions.
1 change: 1 addition & 0 deletions app/apps/server/bridges/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export class AppSettingBridge {
'Accounts_OAuth_Wordpress_secret', 'Push_apn_passphrase', 'Push_apn_key', 'Push_apn_cert', 'Push_apn_dev_passphrase',
'Push_apn_dev_key', 'Push_apn_dev_cert', 'Push_gcm_api_key', 'Push_gcm_project_number', 'SAML_Custom_Default_cert',
'SAML_Custom_Default_private_key', 'SlackBridge_APIToken', 'Smarsh_Email', 'SMS_Twilio_Account_SID', 'SMS_Twilio_authToken',
'SMS_Voxtelesys_authToken', 'SMS_Voxtelesys_URL',
];
}

Expand Down
248 changes: 156 additions & 92 deletions app/custom-oauth/server/custom_oauth_server.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Meteor } from 'meteor/meteor';
import { Match } from 'meteor/check';
import { Match, check } from 'meteor/check';
import { Accounts } from 'meteor/accounts-base';
import { OAuth } from 'meteor/oauth';
import { HTTP } from 'meteor/http';
import { ServiceConfiguration } from 'meteor/service-configuration';
import { Logger } from '../../logger';
import { Users } from '../../models';
import { mapRolesFromSSO, updateRolesFromSSO } from './oauth_helpers';
import _ from 'underscore';
import { isURL } from '../../utils/lib/isURL';
import { registerAccessTokenService } from '../../lib/server/oauth/oauth';

const logger = new Logger('CustomOAuth');

Expand Down Expand Up @@ -40,6 +42,7 @@ export class CustomOAuth {
Accounts.oauth.registerService(this.name);
this.registerService();
this.addHookToProcessUser();
this.registerAccessTokenService(this.name, this.accessTokenParam);
}

configure(options) {
Expand All @@ -59,13 +62,20 @@ export class CustomOAuth {
options.identityPath = '/me';
}

if (!Match.test(options.accessTokenParam, String)) {
options.accessTokenParam = 'access_token';
}

this.serverURL = options.serverURL;
this.tokenPath = options.tokenPath;
this.identityPath = options.identityPath;
this.tokenSentVia = options.tokenSentVia;
this.identityTokenSentVia = options.identityTokenSentVia;
this.usernameField = (options.usernameField || '').trim();
this.mergeUsers = options.mergeUsers;
this.mergeRoles = options.mergeRoles || false;
this.rolesClaim = options.rolesClaim || 'roles';
this.accessTokenParam = options.accessTokenParam;

if (this.identityTokenSentVia == null || this.identityTokenSentVia === 'default') {
this.identityTokenSentVia = this.tokenSentVia;
Expand Down Expand Up @@ -134,7 +144,7 @@ export class CustomOAuth {
}
}

getIdentity(accessToken) {
getIdentity(accessToken, accessTokenParam) {
const params = {};
const headers = {
'User-Agent': this.userAgent, // http://doc.gitlab.com/ce/api/users.html#Current-user
Expand All @@ -143,7 +153,7 @@ export class CustomOAuth {
if (this.identityTokenSentVia === 'header') {
headers.Authorization = `Bearer ${ accessToken }`;
} else {
params.access_token = accessToken;
params[accessTokenParam] = accessToken;
}

try {
Expand All @@ -162,7 +172,7 @@ export class CustomOAuth {

logger.debug('Identity response', JSON.stringify(data, null, 2));

return data;
return this.normalizeIdentity(data);
} catch (err) {
const error = new Error(`Failed to fetch identity from ${ this.name } at ${ this.identityPath }. ${ err.message }`);
throw _.extend(error, { response: err.response });
Expand All @@ -172,94 +182,19 @@ export class CustomOAuth {
registerService() {
const self = this;
OAuth.registerService(this.name, 2, null, (query) => {
const response = self.getAccessToken(query);
// console.log('app/custom-oauth/server/custom_oauth_server.js: self.getAccessToken()=', response);

let identity = self.getIdentity(response.access_token);

if (identity) {
// Set 'id' to '_id' for any sources that provide it
if (identity._id && !identity.id) {
identity.id = identity._id;
}

// Fix for Reddit
if (identity.result) {
identity = identity.result;
}

// Fix WordPress-like identities having 'ID' instead of 'id'
if (identity.ID && !identity.id) {
identity.id = identity.ID;
}

// Fix Auth0-like identities having 'user_id' instead of 'id'
if (identity.user_id && !identity.id) {
identity.id = identity.user_id;
}

if (identity.CharacterID && !identity.id) {
identity.id = identity.CharacterID;
}

// Fix Dataporten having 'user.userid' instead of 'id'
if (identity.user && identity.user.userid && !identity.id) {
if (identity.user.userid_sec && identity.user.userid_sec[0]) {
identity.id = identity.user.userid_sec[0];
} else {
identity.id = identity.user.userid;
}
identity.email = identity.user.email;
}
// Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
if (identity.user && identity.user.user_id && !identity.id) {
identity.id = identity.user.user_id;
identity.email = identity.user.user_email;
}
// Fix general 'phid' instead of 'id' from phabricator
if (identity.phid && !identity.id) {
identity.id = identity.phid;
}

// Fix Keycloak-like identities having 'sub' instead of 'id'
if (identity.sub && !identity.id) {
identity.id = identity.sub;
}

// Fix OpenShift identities where id is in 'metadata' object
if (!identity.id && identity.metadata && identity.metadata.uid) {
identity.id = identity.metadata.uid;
identity.name = identity.fullName;
}

// Fix general 'userid' instead of 'id' from provider
if (identity.userid && !identity.id) {
identity.id = identity.userid;
}

// Fix Nextcloud provider
if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
identity.id = identity.ocs.data.id;
identity.name = identity.ocs.data.displayname;
identity.email = identity.ocs.data.email;
}

// Fix when authenticating from a meteor app with 'emails' field
if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
}
}
const response = self.getAccessToken(query);

// console.log 'id:', JSON.stringify identity, null, ' '
const identity = self.getIdentity(response.accessToken, this.accessTokenParam);

const serviceData = {
_OAuthCustom: true,
accessToken: response.access_token,
idToken: response.id_token,
idToken: response.id_token,
expiresAt: (+new Date) + (1000 * parseInt(response.expires_in, 10)),
};

// only set the token in serviceData if it's there. this ensures
// only set the token in serviceData if it's there. this ensures
// that we don't lose old ones (since we only get this on the first
// log in attempt)
if (response.refresh_token) {
Expand All @@ -272,17 +207,98 @@ export class CustomOAuth {
serviceData,
options: {
profile: {
name: identity.name || identity.username || identity.nickname || identity.CharacterName || identity.userName || identity.preferred_username || (identity.user && identity.user.name),
name: identity.name,
},
},
};

// console.log data

return data;
});
}

normalizeIdentity(identity) {
if (identity) {
// Set 'id' to '_id' for any sources that provide it
if (identity._id && !identity.id) {
identity.id = identity._id;
}

// Fix for Reddit
if (identity.result) {
identity = identity.result;
}

// Fix WordPress-like identities having 'ID' instead of 'id'
if (identity.ID && !identity.id) {
identity.id = identity.ID;
}

// Fix Auth0-like identities having 'user_id' instead of 'id'
if (identity.user_id && !identity.id) {
identity.id = identity.user_id;
}

if (identity.CharacterID && !identity.id) {
identity.id = identity.CharacterID;
}

// Fix Dataporten having 'user.userid' instead of 'id'
if (identity.user && identity.user.userid && !identity.id) {
if (identity.user.userid_sec && identity.user.userid_sec[0]) {
identity.id = identity.user.userid_sec[0];
} else {
identity.id = identity.user.userid;
}
identity.email = identity.user.email;
}
// Fix for Xenforo [BD]API plugin for 'user.user_id; instead of 'id'
if (identity.user && identity.user.user_id && !identity.id) {
identity.id = identity.user.user_id;
identity.email = identity.user.user_email;
}
// Fix general 'phid' instead of 'id' from phabricator
if (identity.phid && !identity.id) {
identity.id = identity.phid;
}

// Fix Keycloak-like identities having 'sub' instead of 'id'
if (identity.sub && !identity.id) {
identity.id = identity.sub;
}

// Fix OpenShift identities where id is in 'metadata' object
if (!identity.id && identity.metadata && identity.metadata.uid) {
identity.id = identity.metadata.uid;
identity.name = identity.fullName;
}

// Fix general 'userid' instead of 'id' from provider
if (identity.userid && !identity.id) {
identity.id = identity.userid;
}

// Fix Nextcloud provider
if (!identity.id && identity.ocs && identity.ocs.data && identity.ocs.data.id) {
identity.id = identity.ocs.data.id;
identity.name = identity.ocs.data.displayname;
identity.email = identity.ocs.data.email;
}

// Fix when authenticating from a meteor app with 'emails' field
if (!identity.email && (identity.emails && Array.isArray(identity.emails) && identity.emails.length >= 1)) {
identity.email = identity.emails[0].address ? identity.emails[0].address : undefined;
}
}

if (this.usernameField) {
identity.username = this.getUsername(identity);
}

identity.name = this.getName(identity);

return identity;
}

retrieveCredential(credentialToken, credentialSecret) {
return OAuth.retrieveCredential(credentialToken, credentialSecret);
}
Expand All @@ -299,20 +315,27 @@ export class CustomOAuth {
return username;
}

getName(identity) {
const name = identity.name || identity.username || identity.nickname || identity.CharacterName || identity.userName || identity.preferred_username || (identity.user && identity.user.name);
return name;
}

addHookToProcessUser() {
BeforeUpdateOrCreateUserFromExternalService.push((serviceName, serviceData/* , options*/) => {
if (serviceName !== this.name) {
return;
}

if (this.usernameField) {
const username = this.getUsername(serviceData);

const user = Users.findOneByUsername(username);
if (serviceData.username) {
const user = Users.findOneByUsername(serviceData.username);
if (!user) {
return;
}

if (this.mergeRoles) {
updateRolesFromSSO(user, serviceData, this.rolesClaim);
}

// User already created or merged and has identical name as before
if (user.services && user.services[serviceName] && user.services[serviceName].id === serviceData.id && user.name === serviceData.name) {
return;
Expand Down Expand Up @@ -343,12 +366,53 @@ export class CustomOAuth {
user.username = this.getUsername(user.services[this.name]);
}

if (this.mergeRoles) {
user.roles = mapRolesFromSSO(user.services[this.name], this.rolesClaim);
}

return true;
});

}
}

registerAccessTokenService(name, accessTokenParam) {
const self = this;
const whitelisted = [
'id',
'email',
'username',
'name',
this.rolesClaim,
];

registerAccessTokenService(name, function(options) {
check(options, Match.ObjectIncluding({
accessToken: String,
expiresIn: Match.Integer,
identity: Match.Maybe(Object),
}));

const identity = options.identity || self.getIdentity(options.accessToken, accessTokenParam);

const serviceData = {
accessToken: options.accessToken,
expiresAt: (+new Date) + (1000 * parseInt(options.expiresIn, 10)),
};

const fields = _.pick(identity, whitelisted);
_.extend(serviceData, fields);

return {
serviceData,
options: {
profile: {
name: identity.name,
},
},
};
});
}
}

const { updateOrCreateUserFromExternalService } = Accounts;
Accounts.updateOrCreateUserFromExternalService = function(...args /* serviceName, serviceData, options*/) {
Expand Down
Loading

0 comments on commit e03033b

Please sign in to comment.