Skip to content

Commit c3e7a2e

Browse files
authored
Merge pull request #534 from bheesham/use-aal-from-matched-authz-rule
fix(access): Use AAL from the matched authz rule
2 parents 44caede + 8cb73d5 commit c3e7a2e

3 files changed

Lines changed: 79 additions & 61 deletions

File tree

tf/actions/accessRules.js

Lines changed: 67 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -200,19 +200,32 @@ exports.onExecutePostLogin = async (event, api) => {
200200
};
201201
};
202202

203-
// Process the access cache decision
203+
// Process the access cache decision.
204+
// Note that applications may be defined multiple times in the access rules.
205+
//
206+
// When access is granted, the AAL from the rule is used, meaning different
207+
// groups can be subjected to different MFA requirements.
208+
//
209+
// The one exception is that: if any apps say _no_ users nor groups should
210+
// have access, then we bail early.
204211
const access_decision = (groups, access_rules, access_file_conf) => {
205212
// This is used for authorized user/groups
206213
let authorized = false;
207214

208215
// Defaut app requested aal to MEDIUM for all apps which do not have
209216
// this set in access file
210-
let required_aal = "MEDIUM";
217+
const default_aal = "MEDIUM";
211218

212-
const apps = access_rules.filter(
213-
(a) =>
214-
(a.application.client_id ?? "").indexOf(event.client.client_id) >= 0
215-
);
219+
// The AAL from the matched authorization rule is used.
220+
let required_aal;
221+
222+
// Only look at rules which match our client_id.
223+
const apps = access_rules
224+
.filter(
225+
(a) =>
226+
(a.application.client_id ?? "").indexOf(event.client.client_id) >= 0
227+
)
228+
.map((a) => a.application);
216229

217230
// Default deny for apps we don't define in
218231
// https://github.com/mozilla-iam/sso-dashboard-configuration/blob/master/apps.yml
@@ -221,68 +234,66 @@ exports.onExecutePostLogin = async (event, api) => {
221234
return deny("notingroup");
222235
}
223236

224-
// Check users and groups.
225-
for (let i = 0; i < apps.length; i++) {
226-
let app = apps[i].application;
237+
// XXX This needs to be fixed in the dashboard first. Empty users
238+
// or groups (length == 0) means no access in the dashboard
239+
// apps.yml world.
240+
const deny_all =
241+
apps.find(
242+
(a) =>
243+
a.authorized_users.length === 0 && a.authorized_groups.length === 0
244+
) !== undefined;
245+
if (deny_all) {
246+
console.log(
247+
`Access denied to ${event.client.client_id} for user ` +
248+
`${event.user.email} (${event.user.user_id})` +
249+
` - this app denies ALL users and ALL groups")`
250+
);
251+
return deny("notingroup");
252+
}
227253

254+
// Check users and groups.
255+
for (const app of apps) {
228256
//Handy for quick testing in dev (overrides access rules)
229257
//var app = {'client_id': 'pCGEHXW0VQNrQKURDcGi0tghh7NwWGhW', // This is testrp social-ldap-pwless
230258
// 'authorized_users': ['gdestuynder@mozilla.com'],
231259
// 'authorized_groups': ['okta_mfa'],
232260
// 'aal': 'LOW'
233261
// };
234262

235-
if (app.client_id && app.client_id.indexOf(event.client.client_id) >= 0) {
236-
// If there are multiple applications in apps.yml with the same
237-
// client_id then this expiration of access check will only run
238-
// against the first one encountered. This matters if there are
239-
// multiple applications, using the same client_id, and asserting
240-
// different expire_access_when_unused_after values.
241-
242-
// Set app AAL (AA level) if present
243-
required_aal = app.AAL || required_aal;
244-
245-
// AUTHORIZED_{GROUPS,USERS}
246-
//
247-
// XXX this authorized_users SHOULD BE REMOVED as it's unsafe (too
248-
// easy to make mistakes). USE GROUPS.
249-
//
250-
// XXX This needs to be fixed in the dashboard first. Empty users
251-
// or groups (length == 0) means no access in the dashboard
252-
// apps.yml world.
253-
if (
254-
app.authorized_users.length === 0 &&
255-
app.authorized_groups.length === 0
256-
) {
257-
const msg =
258-
`Access denied to ${event.client.client_id} for user ${event.user.email} (${event.user.user_id})` +
259-
` - this app denies ALL users and ALL groups")`;
260-
console.log(msg);
261-
return deny("notingroup");
262-
}
263+
// AUTHORIZED_{GROUPS,USERS}
264+
//
265+
// XXX this authorized_users SHOULD BE REMOVED as it's unsafe (too
266+
// easy to make mistakes). USE GROUPS.
263267

264-
// Check if the user is authorized to access
265-
// A user is authorized if they are a member of any authorized_groups or if they are one of the authorized_users
266-
if (
267-
app.authorized_users.length > 0 &&
268-
app.authorized_users.indexOf(event.user.email) >= 0
269-
) {
270-
authorized ||= true;
271-
// Same dance as above, but for groups
272-
} else if (
273-
app.authorized_groups.length > 0 &&
274-
hasCommonElements(app.authorized_groups, groups)
275-
) {
276-
authorized ||= true;
277-
}
278-
} // correct client id / we matched the current RP
268+
// Check if the user is authorized to access.
269+
// A user is authorized if they are a member of any authorized_groups
270+
// or if they are one of the authorized_users.
271+
if (
272+
app.authorized_users.length > 0 &&
273+
app.authorized_users.indexOf(event.user.email) >= 0
274+
) {
275+
console.log(`${event.user.user_id} was in authorized_users`);
276+
required_aal = app.AAL || default_aal;
277+
authorized = true;
278+
break;
279+
// Same dance as above, but for groups
280+
} else if (
281+
app.authorized_groups.length > 0 &&
282+
hasCommonElements(app.authorized_groups, groups)
283+
) {
284+
console.log(`${event.user.user_id} was in authorized_groups`);
285+
required_aal = app.AAL || default_aal;
286+
authorized = true;
287+
break;
288+
}
279289
} // for loop / next rule in apps.yml
280290

281291
if (!authorized) {
282-
const msg =
283-
`Access denied to ${event.client.client_id} for user ${event.user.email} (${event.user.user_id})` +
284-
` - not in authorized group or not an authorized user`;
285-
console.log(msg);
292+
console.log(
293+
`Access denied to ${event.client.client_id} for user ` +
294+
`${event.user.email} (${event.user.user_id}) - not in ` +
295+
"authorized group or not an authorized user"
296+
);
286297
return deny("notingroup");
287298
}
288299

tf/tests/accessRules.test.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -627,27 +627,32 @@ describe("Client is defined in apps.yml as client00000000000000000000000008", ()
627627
});
628628

629629
describe("Client is defined multiple times in apps.yml as client00000000000000000000000009", () => {
630-
test("User in restricted_group_1; expect allowed", async () => {
630+
test("User in restricted_group_1 without 2FA; expect allowed", async () => {
631+
// This entry in apps.yml does not require MFA.
631632
_event.client.client_id = "client00000000000000000000000009";
632633
_event.connection.name = "google-oauth2";
633634
_event.user.groups = ["restricted_group_1"];
634635
_event.user.ldap_groups = [];
635636
_event.user.app_metadata.groups = [];
636637
await onExecutePostLogin(_event, api);
638+
expect(api.multifactor.enable).not.toHaveBeenCalled();
637639
expect(_event.transaction.redirect_uri).toEqual(undefined);
638640
});
639-
test("User in restricted_group_2; expect allowed", async () => {
641+
test("User in restricted_group_2 with 2FA; expect allowed", async () => {
640642
_event.client.client_id = "client00000000000000000000000009";
641-
_event.connection.name = "google-oauth2";
643+
_event.connection.strategy = "ad";
644+
_event.user.multifactor = ["duo"];
642645
_event.user.groups = ["restricted_group_2"];
643646
_event.user.ldap_groups = [];
644647
_event.user.app_metadata.groups = [];
645648
await onExecutePostLogin(_event, api);
649+
expect(api.multifactor.enable).toHaveBeenCalled();
646650
expect(_event.transaction.redirect_uri).toEqual(undefined);
647651
});
648-
test("User in restricted_group_3; expect denied", async () => {
652+
test("User no allowed groups with 2FA; expect denied", async () => {
649653
_event.client.client_id = "client00000000000000000000000009";
650-
_event.connection.name = "google-oauth2";
654+
_event.connection.strategy = "ad";
655+
_event.user.multifactor = ["duo"];
651656
_event.user.groups = ["restricted_group_3"];
652657
_event.user.ldap_groups = [];
653658
_event.user.app_metadata.groups = [];

tf/tests/modules/apps.yml.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ apps:
6363
- team_mofo
6464
authorized_users: []
6565
- application:
66+
AAL: LOW
6667
authorized_groups:
6768
- restricted_group_1
6869
authorized_users: []
6970
client_id: client00000000000000000000000009
7071
- application:
72+
AAL: MEDIUM
7173
authorized_groups:
7274
- restricted_group_2
7375
authorized_users: []

0 commit comments

Comments
 (0)