Skip to content

Commit 52fdd11

Browse files
committed
Allow for some groups to access all GitHub applications
Members of the infrastructure org, who admin GitHub Enterprise; and the security managers team, who do security audits, may require broad access to GitHub infra. This commit allows for inviting them to their respective groups (`ghe_admins` and `ghe_security-managers`), which will grant them access to any GitHub applications we have defined. Instead of accepting `2 x <GitHub Organization>` invites they'll only need to accept `1 + <GitHub Organization>` invites, where that `1 +` happens when they're onboarded. Jira: IAM-1021
1 parent 2c7a4ac commit 52fdd11

2 files changed

Lines changed: 99 additions & 37 deletions

File tree

tf/actions/gheGroups.js

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
exports.onExecutePostLogin = async (event, api) => {
22
console.log("Running action:", "gheGroups");
33

4+
// There's a way for us to store these configs in a "friendlier" way, but the
5+
// method we use may change.
6+
//
7+
// For now it's easier to hard-code these values until we resolve IAM-1499,
8+
// "Change the way apps.yaml is accessed".
9+
//
10+
// TODO(bhee) If in a ~1yr (2026-04-07) we haven't made progress on IAM-1499
11+
// or there's too much toil, we should consider stashing a yml file somewhere
12+
// and `fetch`ing it here.
13+
const GHE_ADMINS_GROUP = "mozilliansorg_ghe_admins";
14+
15+
// Used in tandem with `securityManagersAllowedApplications`.
16+
const GHE_SECURITY_MANAGERS_GROUP = "mozilliansorg_ghe_security-managers";
17+
418
// Object of applications with a 1:1 mapping of clientID and related group
519
const applicationGroupMapping = {
620
// Dev applications
@@ -80,6 +94,15 @@ exports.onExecutePostLogin = async (event, api) => {
8094
JDiNCQVrXzw2ILureegz1T8c3OrUZCUb: "mozilliansorg_ghe_mozilla-firefox_users",
8195
};
8296

97+
// The applications to allow a member of `GHE_SECURITY_MANAGERS_GROUP` access
98+
// to.
99+
//
100+
// Right now, defined as all applications. This may change at some point in
101+
// the future.
102+
const securityManagersAllowedApplications = Object.keys(
103+
applicationGroupMapping
104+
);
105+
83106
// ClientID isn't mapped here, return callback() and proceed rules processing
84107
if (applicationGroupMapping[event.client.client_id] === undefined) {
85108
console.log("Not mapped");
@@ -169,52 +192,71 @@ exports.onExecutePostLogin = async (event, api) => {
169192
}
170193
};
171194

172-
const processProfile = (profile) => {
173-
// Create a new URL object
195+
const bail = (errorCode) => {
174196
const gheWikiUrl = new URL("https://wiki.mozilla.org/GitHub/SAML_issues");
175-
176-
// Set the tenant searchParam
177197
gheWikiUrl.searchParams.set("auth", event.tenant.id);
198+
gheWikiUrl.searchParams.set("dbg", errorCode);
199+
api.redirect.sendUserTo(gheWikiUrl.href);
200+
return api.access.deny(`Access denied: See ${gheWikiUrl.href}`);
201+
};
178202

179-
let errorCode;
180-
181-
// Confirm the user has the group defined from mozillians matching the application id
203+
// Confirm the user has a githubUsername stored in mozillians and they have
204+
// the correct access groups.
205+
const processProfile = (profile) => {
206+
// Get githubUsername from person api, otherwise we'll redirect
207+
let githubUsername;
208+
try {
209+
githubUsername = profile.usernames.values["HACK#GITHUB"];
210+
} catch (err) {
211+
console.log("Unable to do the githubUsername lookup: " + err);
212+
return bail("ghul");
213+
}
214+
// If profile does not hold key/value for githubUsername
215+
if (githubUsername === undefined) {
216+
console.log("githubUsername is undefined");
217+
return bail("ghnd");
218+
} else if (githubUsername.length === 0) {
219+
// If somehow dinopark allows a user to store an empty value
220+
console.log("empty HACK#GITHUB");
221+
return bail("ghnd");
222+
}
223+
// Confirm the user has the group defined from mozillians matching the
224+
// application's client id.
182225
if (
183-
!event.user.app_metadata.groups?.includes(
226+
event.user.app_metadata.groups?.includes(
184227
applicationGroupMapping[event.client.client_id]
185228
)
186229
) {
187-
errorCode = "ghgr";
188-
} else {
189-
// Get githubUsername from person api, otherwise we'll redirect
190-
let githubUsername;
191-
192-
try {
193-
githubUsername = profile.usernames.values["HACK#GITHUB"];
194-
// If profile does not hold key/value for githubUsername
195-
if (githubUsername === undefined) {
196-
console.log("githubUsername is undefined");
197-
errorCode = "ghnd";
198-
} else if (githubUsername.length === 0) {
199-
// If somehow dinopark allows a user to store an empty value
200-
console.log("empty HACK#GITHUB");
201-
errorCode = "ghnd";
202-
}
203-
} catch (err) {
204-
console.log("Unable to do the githubUsername lookup: " + err);
205-
errorCode = "ghul";
206-
}
230+
console.log(
231+
`Granting access for ${githubUsername} (member of the mozillians GHE group)`
232+
);
233+
return;
207234
}
208-
209-
// confirm the user has a githubUsername stored in mozillians, otherwise redirect
210-
if (errorCode) {
211-
// Set the search parameter error code
212-
gheWikiUrl.searchParams.set("dbg", errorCode);
213-
// Redirect the user
214-
api.redirect.sendUserTo(gheWikiUrl.href);
215-
return api.access.deny(`Access denied: See ${gheWikiUrl.href}`);
235+
// Or, the member is a part of the security managers group, with specific
236+
// access to certain groups.
237+
//
238+
// See comments for `GHE_SECURITY_MANAGERS_GROUP` and
239+
// `securityManagersAllowedApplications`.
240+
if (
241+
event.user.app_metadata.groups?.includes(GHE_SECURITY_MANAGERS_GROUP) &&
242+
securityManagersAllowedApplications.includes(event.client.client_id)
243+
) {
244+
console.log(
245+
`Granting access for ${githubUsername} (member of the security managers group)`
246+
);
247+
return;
216248
}
217-
return;
249+
// Or, the member is a part of the admins group.
250+
if (event.user.app_metadata.groups?.includes(GHE_ADMINS_GROUP)) {
251+
console.log(
252+
`Granting access for ${githubUsername} (member of the admins group)`
253+
);
254+
return;
255+
}
256+
console.log(
257+
`Denying access to GHE, not a member; not in security; and not an admin`
258+
);
259+
return bail("ghgr");
218260
};
219261

220262
// Main

tf/tests/gheGroups.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,23 @@ test.each(applicationGroupEntries)(
388388
expect(api.access.deny).not.toHaveBeenCalled();
389389
}
390390
);
391+
392+
test("Member should be allowed if a part of mozilliansorg_ghe_admins", async () => {
393+
personResp = { usernames: { values: { "HACK#GITHUB": "jdoegithub" } } };
394+
tokenResp = { access_token: "faketoken" };
395+
_event.client.client_id = "9MR2UMAftbs6758Rmbs8yZ9Dj5AjeT0P";
396+
_event.user.app_metadata.groups = ["mozilliansorg_ghe_admins"];
397+
await onExecutePostLogin(_event, api);
398+
expect(_event.transaction.redirect_uri).toBe(undefined);
399+
expect(api.access.deny).not.toHaveBeenCalled();
400+
});
401+
402+
test("Member should be allowed if a part of mozilliansorg_ghe_security-managers", async () => {
403+
personResp = { usernames: { values: { "HACK#GITHUB": "jdoegithub" } } };
404+
tokenResp = { access_token: "faketoken" };
405+
_event.client.client_id = "9MR2UMAftbs6758Rmbs8yZ9Dj5AjeT0P";
406+
_event.user.app_metadata.groups = ["mozilliansorg_ghe_security-managers"];
407+
await onExecutePostLogin(_event, api);
408+
expect(_event.transaction.redirect_uri).toBe(undefined);
409+
expect(api.access.deny).not.toHaveBeenCalled();
410+
});

0 commit comments

Comments
 (0)