Merge pull request #4392 from Viehlieb/feature/empower_sso_oicd_data_propagation

Feature/empower sso oicd data propagation
This commit is contained in:
Lauri Ojansivu 2022-03-05 09:06:45 +02:00 committed by GitHub
commit d52e0bcb6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 266 additions and 79 deletions

View file

@ -148,30 +148,30 @@ template(name="newUserRow")
template(name="orgRow")
tr
if orgData.orgIsActive
td <s>{{ orgData.orgDisplayName }}</s>
else
td {{ orgData.orgDisplayName }}
if orgData.orgIsActive
td <s>{{ orgData.orgDesc }}</s>
else
td <s>{{ orgData.orgDisplayName }}</s>
if orgData.orgIsActive
td {{ orgData.orgDesc }}
if orgData.orgIsActive
td <s>{{ orgData.orgShortName }}</s>
else
td <s>{{ orgData.orgDesc }}</s>
if orgData.orgIsActive
td {{ orgData.orgShortName }}
if orgData.orgIsActive
td <s>{{ orgData.orgWebsite }}</s>
else
td <s>{{ orgData.orgShortName }}</s>
if orgData.orgIsActive
td {{ orgData.orgWebsite }}
if orgData.orgIsActive
td <s>{{ moment orgData.createdAt 'LLL' }}</s>
else
td <s>{{ orgData.orgWebsite }}</s>
if orgData.orgIsActive
td {{ moment orgData.createdAt 'LLL' }}
else
td <s>{{ moment orgData.createdAt 'LLL' }}</s>
td
if orgData.orgIsActive
| {{_ 'no'}}
else
| {{_ 'yes'}}
else
| {{_ 'no'}}
td
a.edit-org
i.fa.fa-edit
@ -182,30 +182,30 @@ template(name="orgRow")
template(name="teamRow")
tr
if teamData.teamIsActive
td <s>{{ teamData.teamDisplayName }}</s>
else
td {{ teamData.teamDisplayName }}
if teamData.teamIsActive
td <s>{{ teamData.teamDesc }}</s>
else
td <s>{{ teamData.teamDisplayName }}</s>
if teamData.teamIsActive
td {{ teamData.teamDesc }}
if teamData.teamIsActive
td <s>{{ teamData.teamShortName }}</s>
else
td <s>{{ teamData.teamDesc }}</s>
if teamData.teamIsActive
td {{ teamData.teamShortName }}
if teamData.teamIsActive
td <s>{{ teamData.teamWebsite }}</s>
else
td <s>{{ teamData.teamShortName }}</s>
if teamData.teamIsActive
td {{ teamData.teamWebsite }}
if teamData.teamIsActive
td <s>{{ moment teamData.createdAt 'LLL' }}</s>
else
td <s>{{ teamData.teamWebsite }}</s>
if teamData.teamIsActive
td {{ moment teamData.createdAt 'LLL' }}
else
td <s>{{ moment teamData.createdAt 'LLL' }}</s>
td
if teamData.teamIsActive
| {{_ 'no'}}
else
| {{_ 'yes'}}
else
| {{_ 'no'}}
td
a.edit-team
i.fa.fa-edit
@ -313,8 +313,8 @@ template(name="editOrgPopup")
label
| {{_ 'active'}}
select.select-active.js-org-isactive
option(value="false") {{_ 'yes'}}
option(value="true" selected="{{org.orgIsActive}}") {{_ 'no'}}
option(value="false") {{_ 'no'}}
option(value="true" selected="{{org.orgIsActive}}") {{_ 'yes'}}
hr
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
@ -339,8 +339,8 @@ template(name="editTeamPopup")
label
| {{_ 'active'}}
select.select-active.js-team-isactive
option(value="false") {{_ 'yes'}}
option(value="true" selected="{{team.teamIsActive}}") {{_ 'no'}}
option(value="false") {{_ 'no'}}
option(value="true" selected="{{team.teamIsActive}}") {{_ 'yes'}}
hr
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
@ -442,8 +442,8 @@ template(name="newOrgPopup")
label
| {{_ 'active'}}
select.select-active.js-org-isactive
option(value="false" selected="selected") {{_ 'yes'}}
option(value="true") {{_ 'no'}}
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
hr
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")
@ -466,8 +466,8 @@ template(name="newTeamPopup")
label
| {{_ 'active'}}
select.select-active.js-team-isactive
option(value="false" selected="selected") {{_ 'yes'}}
option(value="true") {{_ 'no'}}
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
hr
div.buttonsContainer
input.primary.wide(type="submit" value="{{_ 'save'}}")

View file

@ -143,7 +143,32 @@ if (Meteor.isServer) {
}
}
},
setCreateOrgFromOidc(
orgDisplayName,
orgDesc,
orgShortName,
orgWebsite,
orgIsActive,
) {
check(orgDisplayName, String);
check(orgDesc, String);
check(orgShortName, String);
check(orgWebsite, String);
check(orgIsActive, Boolean);
const nOrgNames = Org.find({ orgShortName }).count();
if (nOrgNames > 0) {
throw new Meteor.Error('orgname-already-taken');
} else {
Org.insert({
orgDisplayName,
orgDesc,
orgShortName,
orgWebsite,
orgIsActive,
});
}
},
setOrgDisplayName(org, orgDisplayName) {
if (Meteor.user() && Meteor.user().isAdmin) {
check(org, Object);

View file

@ -141,7 +141,31 @@ if (Meteor.isServer) {
}
}
},
setCreateTeamFromOidc(
teamDisplayName,
teamDesc,
teamShortName,
teamWebsite,
teamIsActive,
) {
check(teamDisplayName, String);
check(teamDesc, String);
check(teamShortName, String);
check(teamWebsite, String);
check(teamIsActive, Boolean);
const nTeamNames = Team.find({ teamShortName }).count();
if (nTeamNames > 0) {
throw new Meteor.Error('teamname-already-taken');
} else {
Team.insert({
teamDisplayName,
teamDesc,
teamShortName,
teamWebsite,
teamIsActive,
});
}
},
setTeamDisplayName(team, teamDisplayName) {
if (Meteor.user() && Meteor.user().isAdmin) {
check(team, Object);

View file

@ -5,3 +5,44 @@ A Meteor implementation of OpenID Connect Login flow
## Usage and Documentation
Look at the `salleman:accounts-oidc` package for the documentation about using OpenID Connect with Meteor.
## Usage with e.g. authentik for updating users via oidc
To use the following features set:
'export PROPAGATE_OIDC_DATA=true'
SIMPLE: If user is assigned to 'group in authentik' it will be automatically assigned to corresponding team in wekan if exists
ADVANCED: Users can be assigned to teams or organisations via oidc on login. Teams and organisations that do not exist in wekan, yet, will be created, when specified. Admin privileges for wekan through a specific group can be set via Oidc.
See example below:
1. Specify scope in authentik for what will be delivered via userinfo["wekanGroups"]
Possible configuration for *yourScope*:
'
groupsDict = {"wekanGroups": []}
for group in request.user.ak_groups.all():
groupDict = {"displayName": group.name}
groupAdmin = {"isAdmin": group.isAdmin}
groupAttributes = group.attributes
tmp_dict= groupDict | groupAttributes | groupAdmin
groupsDict["wekanGroups"].append(tmp_dict)
return groupsDict
'
2. Tell provider to include *yourScope* and set
OAUTH2_REQUEST_PERMISSIONS="openid profile email *yourScope*"
3. In your group settings in authentik add attributes:
desc: groupDesc // default group.name
isAdmin: true // default false
website: groupWebsite // default group.name
isActive: true // default false
shortName: groupShortname // default group.name
forceCreate: true // default false
isOrganisation: true // default false
4. On next login user will be added to either newly created group/organization or to already existing
NOTE: orgs & teams won't be updated if they already exist.

View file

@ -1,53 +1,153 @@
module.exports = {
addGroups: function (user, groups){
teamArray=[]
teams = user.teams
if (!teams)
// creates Object if not present in collection
// initArr = [displayName, shortName, website, isActive]
// objString = ["Org","Team"] for method mapping
function createObject(initArr, objString)
{
functionName = objString === "Org" ? 'setCreateOrgFromOidc' : 'setCreateTeamFromOidc';
creationString = 'setCreate'+ objString + 'FromOidc';
return Meteor.call(functionName,
initArr[0],//displayName
initArr[1],//desc
initArr[2],//shortName
initArr[3],//website
initArr[4]//xxxisActive
);
}
//checks whether obj is in collection of userObjs
//params
//e.g. userObjs = user.teams
//e.g. obj = Team.findOne...
//e.g. collection = "team"
function contains(userObjs, obj, collection)
{
id = collection+'Id';
if(!userObjs.length)
{
for (group of groups){
team = Team.findOne({"teamDisplayName": group});
if (team)
return false;
}
for (const [count, hash] of Object.entries(userObjs))
{
if (hash[id] === obj._id)
{
team_hash = {'teamId': team._id, 'teamDisplayName': group}
teamArray.push(team_hash);
return true;
}
}
teams = {'teams': teamArray}
users.update({ _id: user._id }, { $set: teams});
return;
}
else{
for (group of groups){
team = Team.findOne({"teamDisplayName": group})
team_contained= false;
if (team)
{
team_hash = {'teamId': team._id, 'teamDisplayName': group}
for (const [count,teams_hash] of Object.entries(teams))
{
if (teams_hash["teamId"] === team._id)
{
team_contained=true;
break;
}
}
if (team_contained)
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
{
console.log("TEAM to be added:", team);
teams.push({'teamId': Team.findOne({'teamDisplayName': group})._id, 'teamDisplayName': group});
teamArray.push({'teamId': Team.findOne({'teamDisplayName': group})._id, 'teamDisplayName': group});
}
}
}
console.log("XXXXXXXXXXX Team Array: ", teams);
teams = {'teams': teams}
users.update({ _id: user._id }, { $set: teams});
}
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
// DEFAULT after creation orgIsActive & teamIsActive: true
// PODC provider needs to send group data within "wekanGroup" scope
// PARAMS to be set for groups within your Oidc provider:
// isAdmin: [true, false] -> admin group becomes admin in wekan
// isOrganization: [true, false] -> creates org and adds to user
// displayName: "string"
addGroupsWithAttributes: function (user, groups){
teamArray=[];
orgArray=[];
teams = user.teams;
orgs = user.orgs;
for (group of groups)
{
isOrg = group.isOrganisation || false;
forceCreate = group.forceCreate|| false;
if (isOrg)
{
org = Org.findOne({"orgDisplayName": group.displayName});
if(org)
{
if(contains(orgs, org, "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});
}
else
{
continue;
}
orgHash = {'orgId': org._id, 'orgDisplayName': group.displayName};
orgArray.push(orgHash);
}
else
{
//start team routine
team = Team.findOne({"teamDisplayName": group.displayName});
if (team)
{
if(contains(teams, team, "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});
}
else
{
continue;
}
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}});
}
}
teams = {'teams': {'$each': teamArray}};
orgs = {'orgs': {'$each': orgArray}};
users.update({ _id: user._id }, { $push: teams});
users.update({ _id: user._id }, { $push: orgs});
return;
},
changeUsername: function(user, name)
{
username = {'username': name};
@ -81,7 +181,6 @@ addEmail: function(user, email)
{
user_email.unshift({'address': email, 'verified': true});
user_email = {'emails': user_email};
console.log(user_email);
users.update({ _id: user._id }, { $set: user_email});
}
}

View file

@ -1,4 +1,4 @@
import {addGroups, addEmail,changeFullname, changeUsername} from './loginHandler';
import {addGroups, addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler';
Oidc = {};
httpCa = false;
@ -18,7 +18,6 @@ if (process.env.OAUTH2_CA_CERT !== undefined) {
OAuth.registerService('oidc', 2, null, function (query) {
var debug = process.env.DEBUG || false;
console.log(process.env);
var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false;
var token = getToken(query);
@ -80,16 +79,15 @@ OAuth.registerService('oidc', 2, null, function (query) {
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)
{
serviceData.groups = profile.groups
profile.groups = userinfo["groups"];
if(userinfo["groups"]) addGroups(user, userinfo["groups"]);
if(profile.email) addEmail(user, profile.email)
if(profile.name) changeFullname(user, profile.name)
if(profile.username) changeUsername(user, profile.username)
(!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);