Skip to content

Commit

Permalink
Merge pull request #348 from vscheuber/main
Browse files Browse the repository at this point in the history
add support for trusted issuer management
  • Loading branch information
vscheuber authored Nov 22, 2023
2 parents c815ab7 + 2f85db1 commit 09f842a
Show file tree
Hide file tree
Showing 7 changed files with 943 additions and 7 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@
"name": "Ali Falahi",
"email": "ali@falahi.com"
},
{
"name": "Huston Franklin",
"email": "hfranklin@trivir.com"
},
{
"name": "Preston Hales",
"email": "phales@trivir.com"
},
{
"name": "Nick Hangartner",
"email": "nhangartner@gmail.com"
Expand Down
2 changes: 1 addition & 1 deletion src/api/ApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type Readable<Type> = Type;

export type Writable<Type> = {
inherited: boolean;
value: Type;
value?: Type;
};

export type QueryResult<Type> = {
Expand Down
1 change: 1 addition & 0 deletions src/api/OAuth2OIDCApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const getApiConfig = () => ({

export type AccessTokenResponseType = {
access_token: string;
id_token?: string;
scope: string;
token_type: string;
expires_in: number;
Expand Down
153 changes: 153 additions & 0 deletions src/api/OAuth2TrustedJwtIssuerApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import util from 'util';

import { State } from '../shared/State';
import { getCurrentRealmPath } from '../utils/ForgeRockUtils';
import { deleteDeepByKey } from '../utils/JsonUtils';
import {
type IdObjectSkeletonInterface,
type NoIdObjectSkeletonInterface,
type PagedResult,
type Readable,
type Writable,
} from './ApiTypes';
import { generateAmApi } from './BaseApi';

const oauth2TrustedJwtIssuerURLTemplate =
'%s/json%s/realm-config/agents/TrustedJwtIssuer/%s';
const oauth2TrustedJwtIssuerListURLTemplate =
'%s/json%s/realm-config/agents/OAuth2Client?_queryFilter=true';
const apiVersion = 'protocol=2.0,resource=1.0';
const getApiConfig = () => {
return {
apiVersion,
};
};

export type OAuth2TrustedJwtIssuerSkeleton = IdObjectSkeletonInterface & {
allowedSubjects?: Readable<string[]> | Writable<string[]>;
jwksCacheTimeout?: Readable<number> | Writable<number>;
jwkSet?: Readable<string> | Writable<string>;
consentedScopesClaim?: Readable<string> | Writable<string>;
issuer: Readable<string> | Writable<string>;
jwkStoreCacheMissCacheTime?: Readable<number> | Writable<number>;
resourceOwnerIdentityClaim?: Readable<string> | Writable<string>;
jwksUri?: Readable<string> | Writable<string>;
_type: {
_id: 'TrustedJwtIssuer';
name: 'OAuth2 Trusted JWT Issuer';
collection: true;
};
};

/**
* Get OAuth2 Trusted JWT Issuers
* @returns {Promise<PagedResult>} a promise that resolves to a PagedResults object containing an array of oauth2client objects
*/
export async function getOAuth2TrustedJwtIssuers({
state,
}: {
state: State;
}): Promise<PagedResult<OAuth2TrustedJwtIssuerSkeleton>> {
const urlString = util.format(
oauth2TrustedJwtIssuerListURLTemplate,
state.getHost(),
getCurrentRealmPath(state)
);
const { data } = await generateAmApi({ resource: getApiConfig(), state }).get(
urlString,
{
withCredentials: true,
}
);
return data;
}

/**
* Get OAuth2 Trusted JWT Issuer
* @param {string} id client id
* @returns {Promise<OAuth2TrustedJwtIssuerSkeleton>} a promise that resolves to an oauth2 client object
*/
export async function getOAuth2TrustedJwtIssuer({
id,
state,
}: {
id: string;
state: State;
}): Promise<OAuth2TrustedJwtIssuerSkeleton> {
const urlString = util.format(
oauth2TrustedJwtIssuerURLTemplate,
state.getHost(),
getCurrentRealmPath(state),
id
);
const { data } = await generateAmApi({ resource: getApiConfig(), state }).get(
urlString,
{
withCredentials: true,
}
);
return data;
}

/**
* Put OAuth2 Trusted JWT Issuer
* @param {string} id issuer id
* @param {OAuth2TrustedJwtIssuerSkeleton} issuerData TrustedJwtIssuer object
* @returns {Promise<OAuth2TrustedJwtIssuerSkeleton>} a promise that resolves to an oauth2 client object
*/
export async function putOAuth2TrustedJwtIssuer({
id,
issuerData,
state,
}: {
id: string;
issuerData: OAuth2TrustedJwtIssuerSkeleton | NoIdObjectSkeletonInterface;
state: State;
}): Promise<OAuth2TrustedJwtIssuerSkeleton> {
// until we figure out a way to use transport keys in Frodo,
// we'll have to drop those encrypted attributes.
const client = deleteDeepByKey(issuerData, '-encrypted');
delete client._provider;
delete client._rev;
const urlString = util.format(
oauth2TrustedJwtIssuerURLTemplate,
state.getHost(),
getCurrentRealmPath(state),
id
);
const { data } = await generateAmApi({ resource: getApiConfig(), state }).put(
urlString,
client,
{
withCredentials: true,
}
);
return data;
}

/**
* Delete OAuth2 Trusted JWT Issuer
* @param {string} id OAuth2 issuer id
* @returns {Promise<OAuth2TrustedJwtIssuerSkeleton>} a promise that resolves to a trusted jwt issuer object
*/
export async function deleteOAuth2TrustedJwtIssuer({
id,
state,
}: {
id: string;
state: State;
}): Promise<OAuth2TrustedJwtIssuerSkeleton> {
const urlString = util.format(
oauth2TrustedJwtIssuerURLTemplate,
state.getHost(),
getCurrentRealmPath(state),
id
);
const { data } = await generateAmApi({
resource: getApiConfig(),
state,
}).delete(urlString, {
withCredentials: true,
});
return data;
}
53 changes: 47 additions & 6 deletions src/ops/OAuth2OidcOps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export type OAuth2Oidc = {
data: any,
config: AxiosRequestConfig
): Promise<AccessTokenMetaType>;
accessTokenRfc7523AuthZGrant(
clientId: string,
jwt: string,
scope: string[],
config?: AxiosRequestConfig
): Promise<AccessTokenMetaType>;
getTokenInfo(
amBaseUrl: string,
config: AxiosRequestConfig
Expand All @@ -52,17 +58,27 @@ export default (state: State): OAuth2Oidc => {
state,
});
},

async accessToken(
amBaseUrl: string,
data: any,
config: AxiosRequestConfig
): Promise<AccessTokenMetaType> {
const response = await accessToken({ amBaseUrl, config, data, state });
response['expires'] = new Date().getTime() + response.expires_in;
return response as AccessTokenMetaType;
return accessToken({ amBaseUrl, config, data, state });
},
async accessTokenRfc7523AuthZGrant(
clientId: string,
jwt: string,
scope: string[],
config?: AxiosRequestConfig
): Promise<AccessTokenMetaType> {
return accessTokenRfc7523AuthZGrant({
clientId,
jwt,
scope,
config,
state,
});
},

async getTokenInfo(
amBaseUrl: string,
config: AxiosRequestConfig
Expand All @@ -73,7 +89,6 @@ export default (state: State): OAuth2Oidc => {
state,
});
},

async clientCredentialsGrant(
amBaseUrl: string,
clientId: string,
Expand Down Expand Up @@ -126,6 +141,32 @@ export async function accessToken({
return response as AccessTokenMetaType;
}

export async function accessTokenRfc7523AuthZGrant({
clientId,
jwt,
scope,
config = {},
state,
}: {
clientId: string;
jwt: string;
scope: string[];
config?: AxiosRequestConfig;
state: State;
}): Promise<AccessTokenMetaType> {
const data = `grant_type=${encodeURIComponent(
'urn:ietf:params:oauth:grant-type:jwt-bearer'
)}&assertion=${jwt}&scope=${encodeURIComponent(
scope.join(' ')
)}&client_id=${encodeURIComponent(clientId)}`;
return accessToken({
amBaseUrl: state.getHost(),
config,
data,
state,
});
}

export async function getTokenInfo({
amBaseUrl,
config,
Expand Down
Loading

0 comments on commit 09f842a

Please sign in to comment.