Skip to content

PIN-6662 Auth Server M2M token generation #1824

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 47 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9e4c277
PIN-6329 - refactor authorization & context AuthData - pt 1 (#1717)
ecamellini Apr 7, 2025
eca022c
Merge branch 'develop' into refactor/authorization
ecamellini Apr 7, 2025
79c179f
Merge branch 'develop' into refactor/authorization
ecamellini Apr 9, 2025
7907e07
PIN-6329 - Refactor/authorization agreement process (#1746)
ecamellini Apr 9, 2025
923658f
PIN-6329 - Refactor/authorization unit tests (#1741)
ecamellini Apr 9, 2025
6022ee7
PIN-6329 - Refactor/authorization purpose process (#1747)
ecamellini Apr 9, 2025
d81188e
PIN-6329 - Refactor/authorization auth process (#1750)
ecamellini Apr 9, 2025
1977b45
PIN-6329 - Refactor/authorization BFF & API GW (#1753)
ecamellini Apr 10, 2025
a9f1d58
PIN-6329 - Refactor/authorization e-service template process (#1756)
ecamellini Apr 10, 2025
24c4265
PIN-6329 - Refactor/authorization delegation process (#1755)
ecamellini Apr 10, 2025
1d42b8b
PIN-6329 - Refactor/authorization tenant process (#1748)
ecamellini Apr 10, 2025
9548881
Merge branch 'develop' into refactor/authorization
ecamellini Apr 10, 2025
7dc89e9
Fixing tests
ecamellini Apr 11, 2025
529d055
Adding selfcareId to existing comments
ecamellini Apr 11, 2025
7ce93d6
Commenting example
ecamellini Apr 11, 2025
51e3faf
Using emptyErrorMapper everywhere
ecamellini Apr 11, 2025
5f8c198
Merge branch 'develop' into refactor/authorization
ecamellini Apr 14, 2025
1929abe
Improving example comment
ecamellini Apr 14, 2025
a50bb91
Merge branch 'develop' into refactor/authorization
ecamellini Apr 14, 2025
62505b1
Fixing tests after merge from develop
ecamellini Apr 14, 2025
dbfd2a4
Merge branch 'develop' into refactor/authorization
ecamellini Apr 14, 2025
90d93e5
update client model
rGregnanin Apr 15, 2025
99e395f
wip
rGregnanin Apr 15, 2025
25dcbf2
Merge branch 'develop' into PIN-6663_update-client-model
rGregnanin Apr 15, 2025
ab3c0d1
Merge branch 'develop' into PIN-6663_update-client-model
rGregnanin Apr 16, 2025
e07f4af
fixed as suggested
rGregnanin Apr 16, 2025
5abd763
Merge branch 'develop' into PIN-6663_update-client-model
rGregnanin Apr 17, 2025
e6e02e3
refactor
rGregnanin Apr 17, 2025
05990fb
fixed as suggested
rGregnanin Apr 17, 2025
5f14fe4
Merge branch 'develop' into PIN-6663_update-client-model
rGregnanin Apr 17, 2025
5f413fc
added adminId on tokenGenerationStates
rGregnanin Apr 17, 2025
620daf8
Merge branch 'develop' into PIN-6663_update-client-model
rGregnanin Apr 18, 2025
9b18511
added adminId to tokengenerationStates.json
rGregnanin Apr 18, 2025
c41a157
added adminId on platform-states
AsterITA Apr 22, 2025
e9185cf
added adminId handling in token generation and platform client functions
AsterITA Apr 22, 2025
c355f21
Merge branch 'develop' into PIN-6663_update-client-model
AsterITA Apr 22, 2025
efd8e92
Rename adminId to clientAdminId in platform client entries and relate…
AsterITA Apr 22, 2025
714af93
Merge branch 'PIN-6663_update-client-model' of https://github.com/pag…
AsterITA Apr 22, 2025
8b43f20
Auth server generate M2M Admin token
Viktor-K Apr 22, 2025
e08cdc8
Add auth server test
Viktor-K Apr 23, 2025
61a2e4d
Add unit test to auth server
Viktor-K Apr 23, 2025
98ba001
fix linting
Viktor-K Apr 23, 2025
ce07ec3
Apply minor suggestions
Viktor-K Apr 24, 2025
588025b
Apply suggestion and minor refactor
Viktor-K Apr 28, 2025
285e6b5
Merge branch 'develop' into PIN-6662-auth-server-m2m-token-generation
Viktor-K Apr 29, 2025
81c07b3
Fix wrong imports in tests
Viktor-K Apr 29, 2025
e886248
Fix tests
Viktor-K Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { afterEach, beforeAll, describe, expect, it, vi, vitest } from "vitest";
import {
InteropToken,
InteropInternalToken,
InteropTokenGenerator,
ReadModelRepository,
RefreshableInteropToken,
Expand Down Expand Up @@ -40,7 +40,7 @@ describe("ANAC Certified Attributes Importer", () => {
const readModelClient = {} as ReadModelRepository;
const readModelQueriesMock = new ReadModelQueries(readModelClient);

const interopToken: InteropToken = {
const interopInternalToken: InteropInternalToken = {
header: {
alg: "algorithm",
use: "use",
Expand All @@ -59,8 +59,8 @@ describe("ANAC Certified Attributes Importer", () => {
},
serialized: "the-token",
};
const generateInternalTokenMock = (): Promise<InteropToken> =>
Promise.resolve(interopToken);
const generateInternalTokenMock = (): Promise<InteropInternalToken> =>
Promise.resolve(interopInternalToken);

const run = () =>
importAttributes(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export function tokenServiceBuilder({
const token = await tokenGenerator.generateInteropApiToken({
sub: jwt.payload.sub,
consumerId: key.consumerId,
clientAdminId: key.adminId,
});

logTokenGenerationInfo({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import {
TokenGenerationStatesApiClient,
TokenGenerationStatesConsumerClient,
unsafeBrandId,
UserId,
} from "pagopa-interop-models";
import {
formatDateyyyyMMdd,
genericLogger,
secondsToMilliseconds,
systemRole,
} from "pagopa-interop-commons";
import { authorizationServerApi } from "pagopa-interop-api-clients";
import {
Expand Down Expand Up @@ -767,7 +769,7 @@ describe("authorization server tests", () => {
expect(parsedAuditSent).toEqual(expectedMessageBody);
});

it("should succeed - api key - no audit", async () => {
it("should succeed - api key - no audit - M2M role", async () => {
vi.spyOn(fileManager, "storeBytes");

const clientId = generateId<ClientId>();
Expand Down Expand Up @@ -815,8 +817,76 @@ describe("authorization server tests", () => {
expect(fileListAfter).toHaveLength(0);
expect(fileManager.storeBytes).not.toHaveBeenCalled();

expect(response.limitReached).toBe(false);
expect(response.token?.payload).toMatchObject({
role: systemRole.M2M_ROLE,
});
expect(response.token?.payload).not.toMatchObject({
adminId: expect.any(String),
});
expect(response.rateLimiterStatus).toEqual({
maxRequests: config.rateLimiterMaxRequests,
rateInterval: config.rateLimiterRateInterval,
remainingRequests: config.rateLimiterMaxRequests - 1,
});
});

it("should succeed - api key - no audit - M2M_ADMIN role", async () => {
vi.spyOn(fileManager, "storeBytes");

const clientId = generateId<ClientId>();
const clientAdminId = generateId<UserId>();

const { jws, clientAssertion, publicKeyEncodedPem } =
await getMockClientAssertion({
standardClaimsOverride: { sub: clientId },
});

const request: authorizationServerApi.AccessTokenRequest = {
...(await getMockAccessTokenRequest()),
client_assertion: jws,
client_id: clientId,
};

const tokenClientKidK = makeTokenGenerationStatesClientKidPK({
clientId,
kid: clientAssertion.header.kid!,
});

const tokenClientKidEntry: TokenGenerationStatesApiClient = {
...getMockTokenGenStatesApiClient(tokenClientKidK),
clientKind: clientKindTokenGenStates.api,
publicKey: publicKeyEncodedPem,
adminId: clientAdminId,
};

await writeTokenGenStatesApiClient(tokenClientKidEntry, dynamoDBClient);

const fileListBefore = await fileManager.listFiles(
config.s3Bucket,
genericLogger
);
expect(fileListBefore).toHaveLength(0);

const response = await tokenService.generateToken(
request,
generateId(),
genericLogger
);

const fileListAfter = await fileManager.listFiles(
config.s3Bucket,
genericLogger
);
expect(fileListAfter).toHaveLength(0);
expect(fileManager.storeBytes).not.toHaveBeenCalled();

expect(response.limitReached).toBe(false);
expect(response.token).toBeDefined();
expect(response.token?.payload).toMatchObject({
role: systemRole.M2M_ADMIN_ROLE,
adminId: tokenClientKidEntry.adminId,
});
expect(response.rateLimiterStatus).toEqual({
maxRequests: config.rateLimiterMaxRequests,
rateInterval: config.rateLimiterRateInterval,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
PurposeId,
TokenGenerationStatesApiClient,
TokenGenerationStatesConsumerClient,
UserId,
} from "pagopa-interop-models";
import {} from "pagopa-interop-client-assertion-validation";
import { genericLogger } from "pagopa-interop-commons";
Expand Down Expand Up @@ -209,6 +210,31 @@ describe("unit tests", () => {

expect(key).toEqual(tokenClientEntry);
});

it("should succeed - clientKid entry - api key - adminId", async () => {
const clientId = generateId<ClientId>();
const clientAdminId = generateId<UserId>();
const kid = "kid";

const tokenClientKidPK = makeTokenGenerationStatesClientKidPK({
clientId,
kid,
});

const tokenClientEntry: TokenGenerationStatesApiClient = {
...getMockTokenGenStatesApiClient(tokenClientKidPK),
clientKind: clientKindTokenGenStates.api,
adminId: clientAdminId,
};

await writeTokenGenStatesApiClient(tokenClientEntry, dynamoDBClient);
const key = await retrieveKey(dynamoDBClient, tokenClientKidPK);

expect(key).toEqual(tokenClientEntry);
expect(key).toMatchObject({
adminId: clientAdminId,
});
});
});

describe("fallbackAudit", () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/commons-test/src/tokenGenerationReadmodelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export const writeTokenGenStatesApiClient = async (
GSIPK_clientId_kid: {
S: tokenGenStatesEntry.GSIPK_clientId_kid,
},
...(tokenGenStatesEntry.adminId
? {
adminId: { S: tokenGenStatesEntry.adminId },
}
: {}),
},
TableName: "token-generation-states",
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ export const AuthorizationServerTokenGenerationConfig = z
.object({
GENERATED_INTEROP_TOKEN_KID: z.string(),
GENERATED_INTEROP_TOKEN_ISSUER: z.string(),

/*
AUDIENCE and DURATION_SECONDS are used
to generate both M2M and M2M_ADMIN token.
*/
GENERATED_INTEROP_TOKEN_M2M_AUDIENCE: z.string(),
GENERATED_INTEROP_TOKEN_M2M_DURATION_SECONDS: z.string(),
})
Expand Down
37 changes: 27 additions & 10 deletions packages/commons/src/interop-token/interopTokenService.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import crypto from "crypto";
import { KMSClient, SignCommand, SignCommandInput } from "@aws-sdk/client-kms";
import {
ClientAssertionDigest,
ClientId,
generateId,
PurposeId,
TenantId,
ClientAssertionDigest,
UserId,
} from "pagopa-interop-models";
import { systemRole } from "../auth/authData.js";
import { AuthorizationServerTokenGenerationConfig } from "../config/authorizationServerTokenGenerationConfig.js";
import { SessionTokenGenerationConfig } from "../config/sessionTokenGenerationConfig.js";
import { TokenGenerationConfig } from "../config/tokenGenerationConfig.js";
import { AuthorizationServerTokenGenerationConfig } from "../config/authorizationServerTokenGenerationConfig.js";
import { dateToSeconds } from "../utils/date.js";
import {
CustomClaims,
GENERATED_INTEROP_TOKEN_M2M_ROLE,
InteropApiToken,
InteropConsumerToken,
InteropInternalToken,
InteropJwtApiCommonPayload,
InteropJwtApiPayload,
InteropJwtConsumerPayload,
InteropJwtHeader,
InteropJwtPayload,
InteropToken,
InteropJwtInternalPayload,
ORGANIZATION_ID_CLAIM,
ROLE_CLAIM,
SessionClaims,
Expand All @@ -46,7 +48,7 @@ export class InteropTokenGenerator {
this.kmsClient = kmsClient || new KMSClient();
}

public async generateInternalToken(): Promise<InteropToken> {
public async generateInternalToken(): Promise<InteropInternalToken> {
const currentTimestamp = dateToSeconds(new Date());

if (
Expand All @@ -66,7 +68,7 @@ export class InteropTokenGenerator {
kid: this.config.kid,
};

const payload: InteropJwtPayload = {
const payload: InteropJwtInternalPayload = {
jti: crypto.randomUUID(),
iss: this.config.issuer,
aud: this.config.audience,
Expand Down Expand Up @@ -140,9 +142,11 @@ export class InteropTokenGenerator {
public async generateInteropApiToken({
sub,
consumerId,
clientAdminId,
}: {
sub: ClientId;
consumerId: TenantId;
clientAdminId: UserId | undefined;
}): Promise<InteropApiToken> {
if (
!this.config.generatedInteropTokenKid ||
Expand All @@ -164,7 +168,7 @@ export class InteropTokenGenerator {
kid: this.config.generatedInteropTokenKid,
};

const payload: InteropJwtApiPayload = {
const userDataPayload: InteropJwtApiCommonPayload = {
jti: generateId(),
iss: this.config.generatedInteropTokenIssuer,
aud: this.toJwtAudience(this.config.generatedInteropTokenM2MAudience),
Expand All @@ -175,7 +179,20 @@ export class InteropTokenGenerator {
exp:
currentTimestamp + this.config.generatedInteropTokenM2MDurationSeconds,
[ORGANIZATION_ID_CLAIM]: consumerId,
[ROLE_CLAIM]: GENERATED_INTEROP_TOKEN_M2M_ROLE,
};

const systemRolePayload = clientAdminId
? {
[ROLE_CLAIM]: systemRole.M2M_ADMIN_ROLE,
adminId: clientAdminId,
}
: {
[ROLE_CLAIM]: systemRole.M2M_ROLE,
};

const payload: InteropJwtApiPayload = {
...userDataPayload,
...systemRolePayload,
};

const serializedToken = await this.createAndSignToken({
Expand Down Expand Up @@ -256,7 +273,7 @@ export class InteropTokenGenerator {
}: {
header: InteropJwtHeader;
payload:
| InteropJwtPayload
| InteropJwtInternalPayload
| SessionJwtPayload
| InteropJwtConsumerPayload
| InteropJwtApiPayload;
Expand Down
Loading