diff --git a/models/org.js b/models/org.js index 93631e06b..e4c3a8524 100644 --- a/models/org.js +++ b/models/org.js @@ -209,7 +209,31 @@ if (Meteor.isServer) { }); } }, - + setOrgAllFieldsFromOidc( + org, + orgDisplayName, + orgDesc, + orgShortName, + orgWebsite, + orgIsActive, + ) { + check(org, Object); + check(orgDisplayName, String); + check(orgDesc, String); + check(orgShortName, String); + check(orgWebsite, String); + check(orgIsActive, Boolean); + Org.update(org, { + $set: { + orgDisplayName: orgDisplayName, + orgDesc: orgDesc, + orgShortName: orgShortName, + orgWebsite: orgWebsite, + orgIsActive: orgIsActive, + }, + }); + Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); + }, setOrgAllFields( org, orgDisplayName, diff --git a/models/team.js b/models/team.js index 5d568bc9b..a53515081 100644 --- a/models/team.js +++ b/models/team.js @@ -206,7 +206,31 @@ if (Meteor.isServer) { }); } }, - + setTeamAllFieldsFromOidc( + team, + teamDisplayName, + teamDesc, + teamShortName, + teamWebsite, + teamIsActive, + ) { + check(team, Object); + check(teamDisplayName, String); + check(teamDesc, String); + check(teamShortName, String); + check(teamWebsite, String); + check(teamIsActive, Boolean); + Team.update(team, { + $set: { + teamDisplayName: teamDisplayName, + teamDesc: teamDesc, + teamShortName: teamShortName, + teamWebsite: teamWebsite, + teamIsActive: teamIsActive, + }, + }); + Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); + }, setTeamAllFields( team, teamDisplayName, diff --git a/packages/wekan-accounts-lockout/src/knownUser.js b/packages/wekan-accounts-lockout/src/knownUser.js index 81558e1b8..9365274f8 100644 --- a/packages/wekan-accounts-lockout/src/knownUser.js +++ b/packages/wekan-accounts-lockout/src/knownUser.js @@ -214,6 +214,11 @@ class KnownUser { } static onLogin(loginInfo) { + //get the data from oidc login and remove again? + if(loginInfo.type ==='oidc'){ + Meteor.call('groupRoutineOnLogin', loginInfo.user.services.oidc, loginInfo.user._id); + return; + } if (loginInfo.type !== 'password') { return; } diff --git a/packages/wekan-oidc/loginHandler.js b/packages/wekan-oidc/loginHandler.js index 8526a4e14..3063d0bc9 100644 --- a/packages/wekan-oidc/loginHandler.js +++ b/packages/wekan-oidc/loginHandler.js @@ -13,6 +13,18 @@ function createObject(initArr, objString) initArr[4]//xxxisActive ); } +function updateObject(initArr, objString) +{ + functionName = objString === "Org" ? 'setOrgAllFieldsFromOidc' : 'setTeamAllFieldsFromOidc'; + return Meteor.call(functionName, + initArr[0],//team || org Object + initArr[1],//displayName + initArr[2],//desc + initArr[3],//shortName + initArr[4],//website + initArr[5]//xxxisActive + ); +} //checks whether obj is in collection of userObjs //params //e.g. userObjs = user.teams @@ -22,7 +34,7 @@ function contains(userObjs, obj, collection) { id = collection+'Id'; - if(!userObjs.length) + if(typeof userObjs == "undefined" || !userObjs.length) { return false; } @@ -36,30 +48,6 @@ function contains(userObjs, obj, collection) return false; } module.exports = { - // Soft version of adding teams to user via Oidc - // teams won't be created if nonexistent - // groups are treated as teams in the general case - addGroups: function (user, groups){ - teamArray=[]; - teams = user.teams; - orgArray=[]; - for (group of groups){ - team = Team.findOne({"teamDisplayName": group}); - if(team) - { - if (contains(teams,team,"team")) - { - continue; - } - else - { - teamArray.push({'teamId': Team.findOne({'teamDisplayName': group})._id, 'teamDisplayName': group}); - } - } - } - teams = {'teams': { '$each': teamArray}}; - users.update({ _id: user._id }, { $push: teams}); -}, // This function adds groups as organizations or teams to users and // creates them if not already existing @@ -72,12 +60,20 @@ module.exports = { addGroupsWithAttributes: function (user, groups){ teamArray=[]; orgArray=[]; + isAdmin = []; teams = user.teams; orgs = user.orgs; for (group of groups) { + initAttributes = [ + group.displayName, + group.desc || group.displayName, + group.shortName ||group.displayName, + group.website || group.displayName, group.isActive || false]; + isOrg = group.isOrganisation || false; forceCreate = group.forceCreate|| false; + isAdmin.push(group.isAdmin || false); if (isOrg) { org = Org.findOne({"orgDisplayName": group.displayName}); @@ -85,16 +81,13 @@ addGroupsWithAttributes: function (user, groups){ { if(contains(orgs, org, "org")) { + initAttributes.unshift(org); + updateObject(initAttributes, "Org"); continue; } } else if(forceCreate) { - initAttributes = [ - group.displayName, - group.desc || group.displayName, - group.shortName ||group.displayName, - group.website || group.displayName, group.isActive || false] createObject(initAttributes, "Org"); org = Org.findOne({'orgDisplayName': group.displayName}); } @@ -114,17 +107,13 @@ addGroupsWithAttributes: function (user, groups){ { if(contains(teams, team, "team")) { + initAttributes.unshift(team); + updateObject(initAttributes, "Team"); continue; } } else if(forceCreate) { - initAttributes = [ - group.displayName, - group.desc || group.displayName, - group.shortName ||group.displayName, - group.website || group.displayName, - group.isActive || false] createObject(initAttributes, "Team"); team = Team.findOne({'teamDisplayName': group.displayName}); } @@ -135,16 +124,19 @@ addGroupsWithAttributes: function (user, groups){ teamHash = {'teamId': team._id, 'teamDisplayName': group.displayName}; teamArray.push(teamHash); } - // user is assigned to group which has set isAdmin: true in oidc data - // hence user will get admin privileges in wekan - if(group.isAdmin){ - users.update({ _id: user._id }, { $set: {isAdmin: true}}); - } } + // user is assigned to team/org which has set isAdmin: true in oidc data + // hence user will get admin privileges in wekan + // E.g. Admin rights will be withdrawn if no group in oidc provider has isAdmin set to true + + users.update({ _id: user._id }, { $set: {isAdmin: isAdmin.some(i => (i === true))}}); teams = {'teams': {'$each': teamArray}}; orgs = {'orgs': {'$each': orgArray}}; users.update({ _id: user._id }, { $push: teams}); users.update({ _id: user._id }, { $push: orgs}); + // remove temporary oidc data from user collection + users.update({ _id: user._id }, { $unset: {"services.oidc.groups": []}}); + return; }, diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index 23984b4ac..9f0a9e190 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -1,4 +1,4 @@ -import {addGroups, addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; +import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; Oidc = {}; httpCa = false; @@ -14,11 +14,13 @@ if (process.env.OAUTH2_CA_CERT !== undefined) { console.log(e); } } +var profile = {}; +var serviceData = {}; +var userinfo = {}; OAuth.registerService('oidc', 2, null, function (query) { var debug = process.env.DEBUG || false; - var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false; var token = getToken(query); if (debug) console.log('XXX: register token:', token); @@ -28,7 +30,6 @@ OAuth.registerService('oidc', 2, null, function (query) { var claimsInAccessToken = (process.env.OAUTH2_ADFS_ENABLED === 'true' || process.env.OAUTH2_ADFS_ENABLED === true) || false; - var userinfo; if(claimsInAccessToken) { // hack when using custom claims in the accessToken. On premise ADFS @@ -44,13 +45,13 @@ OAuth.registerService('oidc', 2, null, function (query) { if (userinfo.metadata) userinfo = userinfo.metadata // Openshift hack if (debug) console.log('XXX: userinfo:', userinfo); - var serviceData = {}; serviceData.id = userinfo[process.env.OAUTH2_ID_MAP]; // || userinfo["id"]; serviceData.username = userinfo[process.env.OAUTH2_USERNAME_MAP]; // || userinfo["uid"]; serviceData.fullname = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; serviceData.accessToken = accessToken; serviceData.expiresAt = expiresAt; + // If on Oracle OIM email is empty or null, get info from username if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { if (userinfo[process.env.OAUTH2_EMAIL_MAP]) { @@ -74,24 +75,37 @@ OAuth.registerService('oidc', 2, null, function (query) { serviceData.refreshToken = token.refresh_token; if (debug) console.log('XXX: serviceData:', serviceData); - var profile = {}; profile.name = userinfo[process.env.OAUTH2_FULLNAME_MAP]; // || userinfo["displayName"]; profile.email = userinfo[process.env.OAUTH2_EMAIL_MAP]; // || userinfo["email"]; - if (propagateOidcData) - { - - users= Meteor.users; - user = users.findOne({'services.oidc.id': serviceData.id}); - if(user) - { - (!userinfo?.["wekanGroups"]?.length) ? addGroups(user, userinfo["groups"]): addGroupsWithAttributes(user, userinfo["wekanGroups"]); - if(profile.email) addEmail(user, profile.email); - if(profile.name) changeFullname(user, profile.name); - if(profile.username) changeUsername(user, profile.username); - } - } if (debug) console.log('XXX: profile:', profile); + + //temporarily store data from oidc in user.services.oidc.groups to update groups + serviceData.groups = (userinfo["groups"] && userinfo["wekanGroups"]) ? userinfo["wekanGroups"] : userinfo["groups"]; + + // groups arriving as array of strings indicate there is no scope set in oidc privider + // to assign teams and keep admin privileges + // data needs to be treated differently. + // use case: in oidc provider no scope is set, hence no group attributes. + // therefore: keep admin privileges for wekan as before + if(typeof serviceData.groups[0] === "string" ) + { + user = Meteor.users.findOne({'_id': serviceData.id}); + + serviceData.groups.forEach(function(groupName, i) + { + if(user?.isAdmin && i == 0) + { + // keep information of user.isAdmin since in loginHandler the user will // be updated regarding group admin privileges provided via oidc + serviceData.groups[i] = {"isAdmin": true}; + serviceData.groups[i]["displayName"]= groupName; + } + else + { + serviceData.groups[i] = {"displayName": groupName}; + } + }); + } return { serviceData: serviceData, options: { profile: profile } @@ -208,6 +222,7 @@ if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED }; } + var getUserInfo = function (accessToken) { var debug = process.env.DEBUG || false; var config = getConfiguration(); @@ -263,6 +278,28 @@ var getTokenContent = function (token) { } return content; } +Meteor.methods({ + 'groupRoutineOnLogin': function(info, userId) + { + check(info, Object); + check(userId, String); + var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false; + if (propagateOidcData) + { + + users= Meteor.users; + user = users.findOne({'_id': userId}); + if(user) + { + //updates/creates Groups and user admin privileges accordingly + addGroupsWithAttributes(user, info.groups); + if(info.email) addEmail(user, info.email); + if(info.fullname) changeFullname(user, info.fullname); + if(info.username) changeUsername(user, info.username); + } + } + } +}); Oidc.retrieveCredential = function (credentialToken, credentialSecret) { return OAuth.retrieveCredential(credentialToken, credentialSecret);