Skip to content

Commit 9ee1cae

Browse files
Allow Teams to be used to assume Org Membership (#67)
Closes #66 This PR also includes some refactoring/cleanup work so that maintaining this logic stays "easy"
1 parent 7427e39 commit 9ee1cae

2 files changed

Lines changed: 120 additions & 87 deletions

File tree

src/services/githubSync.ts

Lines changed: 117 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -67,39 +67,7 @@ async function SynchronizeOrgMembers(installedGitHubClient: InstalledClient, tea
6767

6868
const orgName = installedGitHubClient.GetCurrentOrgName();
6969

70-
async function addOrgMember(gitHubId: GitHubId) {
71-
const isUserReal = await installedGitHubClient.DoesUserExist(gitHubId);
72-
73-
if (!isUserReal.successful || !isUserReal.data) {
74-
return {
75-
successful: false,
76-
user: gitHubId,
77-
message: `User '${gitHubId}' does not exist in GitHub.`
78-
}
79-
}
80-
81-
const isUserMemberAsync = await installedGitHubClient.IsUserMember(gitHubId)
82-
83-
if (!isUserMemberAsync.successful) {
84-
throw new Error("What");
85-
}
86-
87-
if (isUserMemberAsync.data) {
88-
return {
89-
successful: true,
90-
user: gitHubId
91-
}
92-
}
93-
94-
await installedGitHubClient.AddOrgMember(gitHubId)
95-
96-
return {
97-
successful: true,
98-
user: gitHubId
99-
}
100-
}
101-
102-
const orgMemberPromises = gitHubIds.map(g => addOrgMember(g));
70+
const orgMemberPromises = gitHubIds.map(g => addOrgMember(g, installedGitHubClient));
10371

10472
const responses = await Promise.all(orgMemberPromises);
10573

@@ -140,47 +108,15 @@ async function SynchronizeGitHubTeam(installedGitHubClient: InstalledClient, tea
140108

141109
const orgName = installedGitHubClient.GetCurrentOrgName();
142110

143-
async function checkValidOrgMember(gitHubId: GitHubId) {
144-
const isUserReal = await installedGitHubClient.DoesUserExist(gitHubId);
145-
146-
if (!isUserReal.successful || !isUserReal.data) {
147-
return {
148-
successful: false,
149-
gitHubId: gitHubId,
150-
message: `User '${gitHubId}' does not exist in GitHub.`
151-
};
152-
}
153-
154-
if (idsWithInvites.has(gitHubId)) {
155-
return {
156-
successful: false,
157-
gitHubId: gitHubId,
158-
message: `User '${gitHubId} has a Pending Invite to ${orgName}`
159-
};
160-
}
161-
162-
if (checkOrgMembers) {
163-
const isMember = existingMembers.filter(em => em == gitHubId);
111+
const validMemberCheckResults = await Promise.all(trueMembersList.map(tm => checkValidOrgMember({
112+
gitHubId: tm,
113+
checkOrgMembers: checkOrgMembers,
114+
existingMembers: existingMembers,
115+
idsWithInvites: idsWithInvites,
116+
installedGitHubClient: installedGitHubClient,
117+
orgName: orgName
118+
})));
164119

165-
if (!isMember) {
166-
return {
167-
successful: false,
168-
gitHubId: gitHubId,
169-
message: `User '${gitHubId} is not an Org Member of ${orgName}`
170-
};
171-
}
172-
}
173-
else {
174-
Log(`Skipping Org Membership check for ${gitHubId}`);
175-
}
176-
177-
return {
178-
successful: true,
179-
gitHubId: gitHubId
180-
};
181-
}
182-
183-
const validMemberCheckResults = await Promise.all(trueMembersList.map(tm => checkValidOrgMember(tm)));
184120
const trueValidTeamMembersList: string[] = validMemberCheckResults.filter(r => r.successful).map(r => r.gitHubId);
185121

186122
const listMembersResponse = await installedGitHubClient.ListCurrentMembersOfGitHubTeam(teamName);
@@ -191,20 +127,20 @@ async function SynchronizeGitHubTeam(installedGitHubClient: InstalledClient, tea
191127

192128
const currentTeamMembers = listMembersResponse.data;
193129

194-
const membersToRemove = currentTeamMembers.filter(m => !trueValidTeamMembersList.find((rm => rm == m)));
195-
const membersToAdd = trueValidTeamMembersList.filter(m => !currentTeamMembers.find((rm) => rm == m));
130+
const teamMembersToRemove = currentTeamMembers.filter(m => !trueValidTeamMembersList.find((rm => rm == m)));
131+
const teamMembersToAdd = trueValidTeamMembersList.filter(m => !currentTeamMembers.find((rm) => rm == m));
196132

197133
const teamSyncNotes = {
198134
teamSlug: `${orgName}/${teamName}`,
199-
toRemove: membersToRemove,
200-
toAdd: membersToAdd,
135+
toRemove: teamMembersToRemove,
136+
toAdd: teamMembersToAdd,
201137
issues: validMemberCheckResults.filter(r => !r.successful)
202138
}
203139

204140
Log(JSON.stringify(teamSyncNotes));
205141

206-
await Promise.all(membersToRemove.map(mtr => installedGitHubClient.RemoveTeamMemberAsync(teamName, mtr)));
207-
await Promise.all(membersToAdd.map(mta => installedGitHubClient.AddTeamMember(teamName, mta)));
142+
await Promise.all(teamMembersToRemove.map(mtr => installedGitHubClient.RemoveTeamMemberAsync(teamName, mtr)));
143+
await Promise.all(teamMembersToAdd.map(mta => installedGitHubClient.AddTeamMember(teamName, mta)));
208144

209145
return teamSyncNotes;
210146
}
@@ -237,7 +173,7 @@ async function SyncSecurityManagers(opts: {
237173
appConfig: AppConfig
238174
currentInvites: OrgInvite[]
239175
}): Promise<SuccessSync | FailedSecSync> {
240-
const { securityManagerTeams, setOfExistingTeams, shortLink, client:installedGitHubClient, appConfig, currentInvites } = opts;
176+
const { securityManagerTeams, setOfExistingTeams, shortLink, client: installedGitHubClient, appConfig, currentInvites } = opts;
241177

242178
const orgName = installedGitHubClient.GetCurrentOrgName();
243179

@@ -264,11 +200,11 @@ async function SyncSecurityManagers(opts: {
264200
const addResult = await installedGitHubClient.AddSecurityManagerTeam(t);
265201
if (addResult) {
266202
Log(`Added Security Manager Team for ${installedGitHubClient.GetCurrentOrgName()}: ${t}`)
267-
}
203+
}
268204
}
269205

270206
return {
271-
Success:true,
207+
Success: true,
272208
SyncedSecurityManagerTeams: securityManagerTeams
273209
}
274210
}
@@ -303,13 +239,13 @@ async function syncOrg(installedGitHubClient: InstalledClient, appConfig: AppCon
303239
}
304240
const setOfExistingTeams = new Set(existingTeamsResponse.data.map(t => t.Name.toUpperCase()));
305241

306-
const orgConfigResponse = await installedGitHubClient.GetConfigurationForInstallation();
242+
const orgConfigResponse = await installedGitHubClient.GetConfigurationForInstallation();
307243

308-
const orgConfig = orgConfigResponse.successful ? orgConfigResponse.data : undefined;
244+
const orgConfig = orgConfigResponse.successful ? orgConfigResponse.data : undefined;
309245

310246
const securityManagerTeams = [
311247
...appConfig.SecurityManagerTeams,
312-
...orgConfig?.AdditionalSecurityManagerGroups ?? []
248+
...orgConfig?.AdditionalSecurityManagerGroups ?? []
313249
];
314250

315251
if (securityManagerTeams.length > 0) {
@@ -322,7 +258,7 @@ async function syncOrg(installedGitHubClient: InstalledClient, appConfig: AppCon
322258
shortLink: appConfig.Description.ShortLink
323259
});
324260

325-
if(!syncManagersResponse.Success) {
261+
if (!syncManagersResponse.Success) {
326262
return {
327263
...response,
328264
message: "Cannot sync security managers",
@@ -368,6 +304,20 @@ async function syncOrg(installedGitHubClient: InstalledClient, appConfig: AppCon
368304
}
369305
}
370306

307+
async function syncOrgMembersByTeam(teamName: string, sourceTeamMap: Map<string, string>) {
308+
const sourceTeamName = sourceTeamMap.get(teamName) ?? teamName;
309+
Log(`Adding Org Members via ${sourceTeamName} membership in ${installedGitHubClient.GetCurrentOrgName()}`);
310+
await SynchronizeOrgMembers(installedGitHubClient, sourceTeamName, appConfig);
311+
}
312+
313+
if (orgConfig.AssumeMembershipViaTeams) {
314+
// TODO: this method is getting very busy, and most likely could benefit from a larger refactor.
315+
// Benefits most likely include performance gains.
316+
Log(`Syncing Members for ${installedGitHubClient.GetCurrentOrgName()} by individual teams.`);
317+
const orgMembershipPromises = gitHubTeams.map(t => syncOrgMembersByTeam(t, orgConfig.DisplayNameToSourceMap));
318+
await Promise.all(orgMembershipPromises);
319+
}
320+
371321
let currentMembers: GitHubId[] = [];
372322
if (membersGroupName != undefined || membersGroupName != null) {
373323
Log(`Syncing Members for ${installedGitHubClient.GetCurrentOrgName()}: ${membersGroupName}`)
@@ -402,7 +352,7 @@ async function syncOrg(installedGitHubClient: InstalledClient, appConfig: AppCon
402352
return response;
403353
}
404354

405-
async function syncTeam(teamName: string, orgConfig:OrgConfig) {
355+
async function syncTeam(teamName: string, orgConfig: OrgConfig) {
406356
Log(`Syncing Team Members for ${teamName} in ${installedGitHubClient.GetCurrentOrgName()}`)
407357
await SynchronizeGitHubTeam(installedGitHubClient, teamName, appConfig, currentMembers, currentInvites, orgConfig.DisplayNameToSourceMap);
408358
}
@@ -511,3 +461,83 @@ function RemoveTeamsToIgnore(TeamsToManage: string[], appConfig: AppConfig) {
511461
};
512462
}
513463

464+
async function addOrgMember(gitHubId: GitHubId, installedGitHubClient: InstalledClient) {
465+
const isUserReal = await installedGitHubClient.DoesUserExist(gitHubId);
466+
467+
if (!isUserReal.successful || !isUserReal.data) {
468+
return {
469+
successful: false,
470+
user: gitHubId,
471+
message: `User '${gitHubId}' does not exist in GitHub.`
472+
}
473+
}
474+
475+
const isUserMemberAsync = await installedGitHubClient.IsUserMember(gitHubId)
476+
477+
if (!isUserMemberAsync.successful) {
478+
throw new Error("What");
479+
}
480+
481+
if (isUserMemberAsync.data) {
482+
return {
483+
successful: true,
484+
user: gitHubId
485+
}
486+
}
487+
488+
await installedGitHubClient.AddOrgMember(gitHubId)
489+
490+
return {
491+
successful: true,
492+
user: gitHubId
493+
}
494+
}
495+
496+
async function checkValidOrgMember(opts: {
497+
gitHubId: GitHubId,
498+
installedGitHubClient: InstalledClient,
499+
checkOrgMembers: boolean,
500+
orgName: string,
501+
idsWithInvites: Set<string>,
502+
existingMembers: GitHubId[]
503+
}) {
504+
const { installedGitHubClient, gitHubId, checkOrgMembers, orgName, idsWithInvites, existingMembers } = opts;
505+
506+
const isUserReal = await installedGitHubClient.DoesUserExist(gitHubId);
507+
508+
if (!isUserReal.successful || !isUserReal.data) {
509+
return {
510+
successful: false,
511+
gitHubId: gitHubId,
512+
message: `User '${gitHubId}' does not exist in GitHub.`
513+
};
514+
}
515+
516+
if (idsWithInvites.has(gitHubId)) {
517+
return {
518+
successful: false,
519+
gitHubId: gitHubId,
520+
message: `User '${gitHubId} has a Pending Invite to ${orgName}`
521+
};
522+
}
523+
524+
if (checkOrgMembers) {
525+
const isMember = existingMembers.filter(em => em == gitHubId);
526+
527+
if (!isMember) {
528+
return {
529+
successful: false,
530+
gitHubId: gitHubId,
531+
message: `User '${gitHubId} is not an Org Member of ${orgName}`
532+
};
533+
}
534+
}
535+
else {
536+
Log(`Skipping Org Membership check for ${gitHubId}`);
537+
}
538+
539+
return {
540+
successful: true,
541+
gitHubId: gitHubId
542+
};
543+
}

src/services/orgConfig.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type OrgConfigurationOptions = {
1212
OrganizationMembersGroup?: GitHubTeamName | ManagedGitHubTeam
1313
OrganizationOwnersGroup?: GitHubTeamName | ManagedGitHubTeam
1414
AdditionalSecurityManagerGroups?: ManagedGitHubTeam[]
15+
AssumeMembershipViaTeams?: boolean
1516
}
1617

1718
export class OrgConfig {
@@ -23,6 +24,7 @@ export class OrgConfig {
2324
public TeamsToManage: string[];
2425
public DisplayNameToSourceMap: Map<string,string>;
2526
public CopilotTeams: string[];
27+
public AssumeMembershipViaTeams: boolean
2628

2729
constructor(options: OrgConfigurationOptions) {
2830
this.options = options;
@@ -32,6 +34,7 @@ export class OrgConfig {
3234
this.DisplayNameToSourceMap = this.GetSourceTeamMap();
3335
this.AdditionalSecurityManagerGroups = this.GetAdditionalSecurityManagerGroupNames();
3436
this.CopilotTeams = this.GetCopilotTeams();
37+
this.AssumeMembershipViaTeams = options.AssumeMembershipViaTeams ?? false;
3538
}
3639

3740
private GetCopilotTeams(): string[] {

0 commit comments

Comments
 (0)