Skip to content

Commit

Permalink
Merge pull request #932 from US-Trustee-Program/CAMS-446-adgroups-bug
Browse files Browse the repository at this point in the history
CAMS-446 - Refactor ad_groups concatenation
  • Loading branch information
btposey authored Sep 24, 2024
2 parents 1dad6f1 + 1f97c05 commit e39ae54
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,8 @@ export async function verifyAccessToken(
const oktaJwtVerifier = new OktaJwtVerifier({ issuer });
const oktaJwt: Jwt = await oktaJwtVerifier.verifyAccessToken(token, audience);

// DOJ Login Okta instances return a custom `AD_Groups` attribute on claims that does not
// appear on standard Okta claims. This line checks to see if it exists and if not
// appends an empty array for groups that will carry no permissions for the user.
const groups: string[] = oktaJwt.claims.AD_Groups ? (oktaJwt.claims.AD_Groups as string[]) : [];

return {
claims: { ...oktaJwt.claims, groups },
claims: { ...oktaJwt.claims },
header: { ...oktaJwt.header },
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ describe('Okta gateway tests', () => {
const actual = await gateway.getUser(token);
expect(actual).toEqual({
user: { id: undefined, name: userInfo.name },
groups: ['groupA', 'groupB', 'groupC'],
jwt,
jwt: {
...jwt,
claims: { ...jwt.claims, groups: expect.arrayContaining(['groupA', 'groupB', 'groupC']) },
},
});
});

Expand Down
25 changes: 14 additions & 11 deletions backend/functions/lib/adapters/gateways/okta/okta-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ async function verifyToken(token: string): Promise<CamsJwt> {
}
}

async function getUser(
accessToken: string,
): Promise<{ user: CamsUser; groups: string[]; jwt: CamsJwt }> {
async function getUser(accessToken: string): Promise<{ user: CamsUser; jwt: CamsJwt }> {
const { userInfoUri } = getAuthorizationConfig();

try {
Expand All @@ -69,15 +67,20 @@ async function getUser(
name: oktaUser.name,
};

type DojLoginUnifiedGroupClaims = {
ad_groups?: string[];
groups?: string[];
};

const claims = jwt.claims as unknown as DojLoginUnifiedGroupClaims;
const groups = Array.from(new Set<string>([].concat(claims.ad_groups, claims.groups)));
// DOJ Login Okta instances return a custom `AD_Groups` attribute on claims that does not
// appear on standard Okta claims. This line checks to see if it exists and if not
// appends an empty array for groups that will carry no permissions for the user.
jwt.claims.groups = Array.from(
new Set<string>(
[].concat(
jwt.claims.groups ?? [],
jwt.claims.AD_Groups ?? [],
jwt.claims.ad_groups ?? [],
),
),
);

return { user, groups, jwt };
return { user, jwt };
} else {
throw new Error('Failed to retrieve user info from Okta.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function getRoleMapping(): Map<string, CamsRole> {
if (!roleMapping) {
const roleArray = ROLE_MAPPING.split('\n');
roleMapping = roleArray.reduce((roleMap, roleString, idx) => {
if (idx === 0) return roleMap;
if (idx === 0 || !roleString.length) return roleMap;
const roleInfo = roleString.split(',');

roleMap.set(roleInfo[1], CamsRole[roleInfo[2]]);
Expand Down
2 changes: 1 addition & 1 deletion backend/functions/lib/adapters/types/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type AuthorizationConfig = {
};

export interface OpenIdConnectGateway {
getUser: (accessToken: string) => Promise<{ user: CamsUser; groups: string[]; jwt: CamsJwt }>;
getUser: (accessToken: string) => Promise<{ user: CamsUser; jwt: CamsJwt }>;
}

export interface UserGroupGateway {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('user-session.gateway test', () => {
jest.spyOn(Verifier, 'verifyAccessToken').mockResolvedValue(camsJwt);
jest
.spyOn(MockOpenIdConnectGateway, 'getUser')
.mockResolvedValue({ user: mockUser, groups: [], jwt: camsJwt });
.mockResolvedValue({ user: mockUser, jwt: camsJwt });
});

afterEach(() => {
Expand Down Expand Up @@ -115,7 +115,6 @@ describe('user-session.gateway test', () => {
});
jest.spyOn(MockOpenIdConnectGateway, 'getUser').mockResolvedValue({
user: mockUser,
groups: [],
jwt: null,
});
await expect(gateway.lookup(context, jwtString, provider)).rejects.toThrow(UnauthorizedError);
Expand All @@ -127,7 +126,6 @@ describe('user-session.gateway test', () => {
});
jest.spyOn(MockOpenIdConnectGateway, 'getUser').mockResolvedValue({
user: mockUser,
groups: [],
jwt: undefined,
});
await expect(gateway.lookup(context, jwtString, provider)).rejects.toThrow(UnauthorizedError);
Expand Down
6 changes: 3 additions & 3 deletions backend/functions/lib/use-cases/user-session/user-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ export class UserSessionUseCase {
});
}

const { user, groups, jwt } = await authGateway.getUser(token);
user.roles = getRoles(groups);
user.offices = await getOffices(context, groups);
const { user, jwt } = await authGateway.getUser(token);
user.roles = getRoles(jwt.claims.groups);
user.offices = await getOffices(context, jwt.claims.groups);

// Simulate the legacy behavior by appending roles and Manhattan office to the user
// if the 'restrict-case-assignment' feature flag is not set.
Expand Down
2 changes: 1 addition & 1 deletion common/src/cams/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type CamsJwtClaims = {
nbf?: number;
iat?: number;
jti?: string;
groups: string[];
groups?: string[];
[key: string]: unknown;
};

Expand Down

0 comments on commit e39ae54

Please sign in to comment.