Skip to content

Commit 2d9b5a3

Browse files
committed
workaround for token exchange
1 parent f105c13 commit 2d9b5a3

File tree

9 files changed

+300
-172
lines changed

9 files changed

+300
-172
lines changed

src/package-lock.json

Lines changed: 196 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"@chakra-ui/react": "^1.6.0",
6464
"@emotion/react": "^11.4.1",
6565
"@emotion/styled": "^11.3.0",
66-
"@keycloak/keycloak-admin-client": "^17.0.0-dev.26",
66+
"@keycloak/keycloak-admin-client": "^17.0.1",
6767
"@keystone-next/admin-ui": "^7.0.0",
6868
"@keystonejs/access-control": "^7.1.1",
6969
"@keystonejs/adapter-mongoose": "^11.2.2",

src/services/keycloak/client-registration-service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface ClientRegistration {
3333
clientId: string;
3434
clientSecret?: string;
3535
enabled?: boolean;
36+
attributes?: { [key: string]: string };
3637
}
3738

3839
export enum ClientAuthenticator {
Lines changed: 54 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,65 @@
11

22
export const clientTemplateClientCertificate = JSON.stringify({
3-
clientId: '',
4-
name: '',
5-
description: '',
6-
surrogateAuthRequired: false,
7-
enabled: false,
3+
access: { view: true, configure: true, manage: true },
84
alwaysDisplayInConsole: false,
9-
clientAuthenticatorType: 'client-x509',
10-
redirectUris: ['http://*', 'https://*'],
11-
webOrigins: ['*'],
12-
notBefore: 0,
13-
bearerOnly: false,
14-
consentRequired: false,
15-
standardFlowEnabled: true,
16-
implicitFlowEnabled: false,
17-
directAccessGrantsEnabled: false,
18-
serviceAccountsEnabled: true,
19-
publicClient: false,
20-
frontchannelLogout: false,
21-
protocol: 'openid-connect',
5+
authenticationFlowBindingOverrides: {},
226
attributes: {
23-
"request.object.signature.alg": "any",
24-
"saml.multivalued.roles": "false",
25-
"saml.force.post.binding": "false",
26-
"oauth2.device.authorization.grant.enabled": "false",
27-
"backchannel.logout.revoke.offline.tokens": "false",
28-
"saml.server.signature.keyinfo.ext": "false",
29-
"use.refresh.tokens": "true",
30-
"realm_client": "false",
31-
"oidc.ciba.grant.enabled": "false",
32-
"backchannel.logout.session.required": "true",
33-
"client_credentials.use_refresh_token": "false",
34-
"saml.client.signature": "false",
35-
"require.pushed.authorization.requests": "false",
36-
"request.object.encryption.enc": "any",
37-
"dpop.bound.access.tokens": "false",
38-
"saml.assertion.signature": "false",
39-
"x509.subjectdn": "",
7+
"acr.loa.map": "{}",
8+
"access.token.header.type.rfc9068": false,
9+
"backchannel.logout.revoke.offline.tokens": false,
10+
"backchannel.logout.session.required": true,
11+
"client.introspection.response.allow.jwt.claim.enabled": false,
12+
"client.use.lightweight.access.token.enabled": false,
13+
"client_credentials.use_refresh_token": false,
14+
"display.on.consent.screen": false,
15+
"dpop.bound.access.tokens": false,
16+
"exclude.session.state.from.auth.response": false,
17+
"oauth2.device.authorization.grant.enabled": false,
18+
"oidc.ciba.grant.enabled": false,
19+
"realm_client": false,
4020
"request.object.encryption.alg": "any",
41-
"client.introspection.response.allow.jwt.claim.enabled": "false",
42-
"saml.encrypt": "false",
43-
"standard.token.exchange.enabled": "true",
44-
"saml.server.signature": "false",
45-
"exclude.session.state.from.auth.response": "false",
46-
"client.use.lightweight.access.token.enabled": "false",
21+
"request.object.encryption.enc": "any",
4722
"request.object.required": "not required",
48-
"saml_force_name_id_format": "false",
49-
"access.token.header.type.rfc9068": "false",
50-
"acr.loa.map": "{}",
51-
"tls.client.certificate.bound.access.tokens": "true",
52-
"saml.authnstatement": "false",
53-
"display.on.consent.screen": "false",
54-
"x509.allow.regex.pattern.comparison": "false",
55-
"token.response.type.bearer.lower-case": "false",
56-
"saml.onetimeuse.condition": "false"
23+
"request.object.signature.alg": "any",
24+
"require.pushed.authorization.requests": false,
25+
"saml.client.signature": false,
26+
"saml.encrypt": false,
27+
"saml.assertion.signature": false,
28+
"saml.authnstatement": false,
29+
"saml.force.post.binding": false,
30+
"saml.multivalued.roles": false,
31+
"saml.onetimeuse.condition": false,
32+
"saml.server.signature": false,
33+
"saml.server.signature.keyinfo.ext": false,
34+
"saml_force_name_id_format": false,
35+
"standard.token.exchange.enabled": true,
36+
"tls.client.certificate.bound.access.tokens": true,
37+
"token.response.type.bearer.lower-case": false,
38+
"use.refresh.tokens": true,
39+
"x509.allow.regex.pattern.comparison": false,
40+
"x509.subjectdn": ""
5741
},
58-
authenticationFlowBindingOverrides: {},
42+
bearerOnly: false,
43+
clientAuthenticatorType: 'client-x509',
44+
clientId: '',
45+
consentRequired: false,
46+
defaultClientScopes: [] as string[],
47+
description: '',
48+
directAccessGrantsEnabled: false,
49+
enabled: false,
50+
frontchannelLogout: false,
5951
fullScopeAllowed: false,
52+
implicitFlowEnabled: false,
53+
name: '',
6054
nodeReRegistrationTimeout: -1,
61-
protocolMappers: [] as any[],
62-
defaultClientScopes: [] as string[],
55+
notBefore: 0,
6356
optionalClientScopes: [] as string[],
64-
access: { view: true, configure: true, manage: true },
57+
protocol: 'openid-connect',
58+
protocolMappers: [] as any[],
59+
publicClient: false,
60+
redirectUris: ['http://*', 'https://*'],
61+
serviceAccountsEnabled: false,
62+
standardFlowEnabled: true,
63+
surrogateAuthRequired: false,
64+
webOrigins: ['*'],
6565
});

src/services/keystone/access-request.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export async function getAccessRequest(context: any, id: string): Promise<Access
9090
name
9191
appId
9292
product {
93+
namespace
9394
openapiSpecs
9495
name
9596
}

src/services/workflow/apply.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ async function setupAuthorizationAndEnable(
346346
: issuerEnvConfig.initialAccessToken;
347347

348348
const controls: RequestControls = {
349-
...{ defaultClientScopes: [] },
349+
...{ defaultClientScopes: [], optionalClientScopes: [] },
350350
...setup.controls,
351351
};
352352

@@ -362,11 +362,14 @@ async function setupAuthorizationAndEnable(
362362
issuerEnvConfig.clientId,
363363
issuerEnvConfig.clientSecret
364364
);
365-
const clientScopes = controls.defaultClientScopes;
365+
366+
const optionalClientScopes = controls.optionalClientScopes;
367+
368+
const defaultClientScopes = controls.defaultClientScopes;
366369
if (controls.roles) {
367-
clientScopes.push('roles');
370+
defaultClientScopes.push('roles');
368371
}
369-
await kcClientService.syncAndApply(clientId, clientScopes, []);
372+
await kcClientService.syncAndApply(clientId, defaultClientScopes, optionalClientScopes);
370373

371374
if (controls.roles) {
372375
const clientRolesService = new KeycloakClientRolesService(

src/services/workflow/client-credentials.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ import {
1919
} from './types';
2020
import { ClientAuthenticator } from '../keycloak/client-registration-service';
2121
import { genClientId } from './client-shared-idp';
22+
import { Logger } from '../../logger';
23+
24+
const logger = Logger('wf.ClientCreds');
2225

2326
/**
2427
* Steps:
@@ -77,15 +80,17 @@ export async function registerClient(
7780

7881
// Find the Client ID for the ProductEnvironment - that will be used to associated the clientRoles
7982

80-
// lookup Application and use the ID to make sure a corresponding Consumer exists (1 -- 1)
81-
const client = await new KeycloakClientRegistrationService(
83+
const regService = new KeycloakClientRegistrationService(
8284
issuerEnvConfig.issuerUrl,
8385
openid.registration_endpoint,
8486
token
85-
).clientRegistration(
87+
);
88+
89+
// lookup Application and use the ID to make sure a corresponding Consumer exists (1 -- 1)
90+
const client = await regService.clientRegistration(
8691
<ClientAuthenticator>issuer.clientAuthenticator,
8792
newClientId,
88-
'',
93+
controls.clientName || "", // if no client name provided, use the clientId
8994
uuidv4(),
9095
controls.clientCertificate,
9196
controls.subjectDn,
@@ -95,6 +100,18 @@ export async function registerClient(
95100
);
96101
assert.strictEqual(client.clientId, newClientId);
97102

103+
if (issuer.clientAuthenticator === "client-certificate") {
104+
logger.warn("Workaround to set standard.token.exchange.enabled for client-certificate - not setting on creation");
105+
regService.updateClientRegistration(newClientId, {
106+
clientId: newClientId,
107+
attributes: {
108+
"standard.token.exchange.enabled": 'true',
109+
"tls.client.certificate.bound.access.tokens": 'true',
110+
//"dpop.bound.access.tokens": 'true',
111+
}
112+
})
113+
}
114+
98115
return {
99116
openid,
100117
client,

src/services/workflow/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ export interface SubjectIdentity {
4545
email?: string;
4646
}
4747
export interface RequestControls {
48+
clientName?: string;
4849
defaultClientScopes?: string[];
49-
defaultOptionalScopes?: string[];
50+
optionalClientScopes?: string[];
5051
roles?: string[];
5152
aclGroups?: string[];
5253
plugins?: ConsumerPlugin[];

src/test/integrated/keystonejs/accessRequest.ts

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ kubectl port-forward -n 1d4461-prod service/bcgov-aps-portal-feeder-generic-api
2424
2525
export FEEDER_URL=http://localhost:6767
2626
27+
2728
// userId is needed for Legal
2829
// namespace has to match requesting product if not published
2930
@@ -66,26 +67,28 @@ import {
6667
});
6768

6869
// o(await getOrganizations(ctx));
70+
const app = await createApplication(ctx, {
71+
name: 'App ' + new Date().toISOString(),
72+
description: 'App Desc',
73+
ownerId: userId,
74+
});
75+
76+
const controls = {
77+
clientName: app.name,
78+
subjectDn: 'CN=my-site',
79+
//defaultClientScopes: [],
80+
optionalClientScopes: ['user/Test1'],
81+
};
6982

7083
const accessRequestData = {
7184
acceptLegal: false,
7285
additionalDetails: 'here is some additional details',
73-
//applicationId: '5', // App2
74-
//controls: '{"clientGenCertificate":false,"jwksUrl":"","clientCertificate":""}',
75-
controls: JSON.stringify({ jwksUrl: '', subjectDn: 'CN=my-site' }),
86+
controls: JSON.stringify(controls),
7687
name: 'Sample API FOR Cope, Aidan CITZ:EX',
7788
productEnvironmentId: '13',
7889
requestor: userId,
7990
} as any;
8091

81-
// userId is needed for Legal
82-
83-
const app = await createApplication(ctx, {
84-
name: 'App ' + new Date().toISOString(),
85-
description: 'App Desc',
86-
ownerId: userId,
87-
});
88-
8992
accessRequestData.applicationId = app.id;
9093

9194
const result = await addAccessRequest(ctx, accessRequestData);
@@ -95,27 +98,15 @@ import {
9598
const credDetails = JSON.parse(creds.credential);
9699
o(credDetails);
97100

98-
// query
99-
// :
100-
// "\n mutation SaveConsumerLabels($consumerId: ID!, $labels: [JSON]) {\n saveConsumerLabels(consumerId: $consumerId, labels: $labels)\n }\n"
101-
// variables
102-
// :
103-
// {consumerId: "27",…}
104-
// consumerId
105-
// :
106-
// "27"
107-
// labels
108-
// :
109-
// [{labelGroup: "Priority", values: ["Mister"]}, {labelGroup: "", values: []}]
101+
const request = await getAccessRequest(ctx, result.id);
102+
o(request);
110103

111104
const labels = [
112105
{ labelGroup: 'sdx-member', values: ['/MIN/CITZ'] },
113106
{ labelGroup: 'sdx-res-locator', values: ['/LAB/MIN/CITZ/MYSVC-API'] },
107+
{ labelGroup: "application", values: [app.name] }
114108
];
115109

116-
const request = await getAccessRequest(ctx, result.id);
117-
o(request);
118-
119110
await saveConsumerLabels(ctx, ns, request.serviceAccess.consumer.id, labels);
120111

121112
// const revoke = await revokeAllConsumerAccess(ctx, ns, request.serviceAccess.id);
@@ -124,15 +115,6 @@ import {
124115
// const revoke = await deleteServiceAccess(ctx, request.serviceAccess.id);
125116
// o(revoke);
126117

127-
// flow: client-credentials
128-
// clientId: 50C1D755-945C1E80ABB
129-
// clientSecret: null
130-
// issuer: null
131-
// tokenEndpoint: >-
132-
// https://sdx-authz-apps-gov-bc-ca-lab.apps.gov.bc.ca/auth/realms/sdx/protocol/openid-connect/token
133-
// clientPublicKey: null
134-
// clientPrivateKey: null
135-
136118
// const serviceAccess = await getOpenAccessRequestsByConsumer(
137119
// ctx,
138120
// ns,

0 commit comments

Comments
 (0)