Skip to content

Commit

Permalink
chore: ms graph
Browse files Browse the repository at this point in the history
change from bceid to ms graph
  • Loading branch information
jlangy committed Jun 27, 2024
1 parent e3e1711 commit 86ed6da
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 125 deletions.
17 changes: 12 additions & 5 deletions docker/kc-cron-job/.env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
BCEID_SERVICE_BASIC_AUTH=
BCEID_REQUESTER_IDIR_GUID=
BCEID_SERVICE_ID_DEV=
BCEID_SERVICE_ID_TEST=
BCEID_SERVICE_ID_PROD=
DEV_KEYCLOAK_CLIENT_ID=
DEV_KEYCLOAK_CLIENT_SECRET=
DEV_KEYCLOAK_URL=https://dev.loginproxy.gov.bc.ca
Expand All @@ -23,3 +18,15 @@ RC_WEBHOOK=
VC_USERS_RETENTION_DAYS=
INACTIVE_IDIR_USERS_RETENTION_DAYS=
NAMESPACE=

MS_GRAPH_API_AUTHORITY_DEV=
MS_GRAPH_API_CLIENT_ID_DEV=
MS_GRAPH_API_CLIENT_SECRET_DEV=

MS_GRAPH_API_AUTHORITY_TEST=
MS_GRAPH_API_CLIENT_ID_TEST=
MS_GRAPH_API_CLIENT_SECRET_TEST=

MS_GRAPH_API_AUTHORITY_PROD=
MS_GRAPH_API_CLIENT_ID_PROD=
MS_GRAPH_API_CLIENT_SECRET_PROD=
2 changes: 2 additions & 0 deletions docker/kc-cron-job/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"test": "jest"
},
"dependencies": {
"@azure/msal-node": "^2.9.2",
"archiver": "^5.3.0",
"async": "^3.2.4",
"axios": "^1.4.0",
"easy-soap-request": "^5.3.0",
"jsonwebtoken": "^9.0.2",
"jws": "^4.0.0",
"keycloak-admin": "^1.14.22",
"lodash": "^4.17.21",
Expand Down
199 changes: 94 additions & 105 deletions docker/kc-cron-job/remove-inactive-idir-users.js
Original file line number Diff line number Diff line change
@@ -1,126 +1,111 @@
const _ = require('lodash');
const { promisify } = require('util');
const { parseString } = require('xml2js');
const async = require('async');
const axios = require('axios');
const { getAdminClient, log, getPgClient, sendRcNotification, handleError, deleteLegacyData } = require('./helpers');
const jwt = require('jsonwebtoken');
const { ConfidentialClientApplication } = require('@azure/msal-node');

require('dotenv').config();

const parseStringSync = promisify(parseString);

function getWebServiceInfo({ env = 'dev' }) {
const requestHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
authorization: `Basic ${process.env.BCEID_SERVICE_BASIC_AUTH}`
};
const MS_GRAPH_URL = 'https://graph.microsoft.com';
const MS_GRAPH_IDIR_GUID_ATTRIBUTE = 'onPremisesExtensionAttributes/extensionAttribute12';

const requesterIdirGuid = process.env.BCEID_REQUESTER_IDIR_GUID || '';
require('dotenv').config();

let serviceUrl = '';
let serviceId = '';
if (env === 'dev') {
serviceUrl = 'https://gws2.development.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_DEV || '';
} else if (env === 'test') {
serviceUrl = 'https://gws2.test.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_TEST || '';
} else if (env === 'prod') {
serviceUrl = 'https://gws2.bceid.ca';
serviceId = process.env.BCEID_SERVICE_ID_PROD || '';
}
let devMsalInstance;
let testMsalInstance;
let prodMsalInstance;

return { requestHeaders, requesterIdirGuid, serviceUrl, serviceId };
}

const generateXML = (
{
property = 'userId',
matchKey = '',
matchType = 'Exact',
serviceId = '',
requesterIdirGuid = '',
page = 1,
limit = 1
let msTokenCache = {
dev: {
token: '',
decoded: null
},
test: {
token: '',
decoded: null
},
requestType = 'searchInternalAccount'
) => {
if (requestType === 'getAccountDetail') {
return `<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:V10="http://www.bceid.ca/webservices/Client/V10/">
<soapenv:Header />
<soapenv:Body>
<V10:getAccountDetail>
<V10:accountDetailRequest>
<V10:onlineServiceId>${serviceId}</V10:onlineServiceId>
<V10:requesterAccountTypeCode>Internal</V10:requesterAccountTypeCode>
<V10:requesterUserGuid>${requesterIdirGuid}</V10:requesterUserGuid>
<V10:${property}>${matchKey}</V10:${property}>
<V10:accountTypeCode>Internal</V10:accountTypeCode>
</V10:accountDetailRequest>
</V10:getAccountDetail>
</soapenv:Body>
</soapenv:Envelope>`;
} else {
return `<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:V10="http://www.bceid.ca/webservices/Client/V10/">
<soapenv:Header />
<soapenv:Body>
<V10:searchInternalAccount>
<V10:internalAccountSearchRequest>
<V10:onlineServiceId>${serviceId}</V10:onlineServiceId>
<V10:requesterAccountTypeCode>Internal</V10:requesterAccountTypeCode>
<V10:requesterUserGuid>${requesterIdirGuid}</V10:requesterUserGuid>
<requesterAccountTypeCode>Internal</requesterAccountTypeCode>
<V10:pagination>
<V10:pageSizeMaximum>${String(limit || 100)}</V10:pageSizeMaximum>
<V10:pageIndex>${String(page || 1)}</V10:pageIndex>
</V10:pagination>
<V10:sort>
<V10:direction>Ascending</V10:direction>
<V10:onProperty>UserId</V10:onProperty>
</V10:sort>
<V10:accountMatch>
<V10:${property}>
<V10:value>${matchKey}</V10:value>
<V10:matchPropertyUsing>${matchType}</V10:matchPropertyUsing>
</V10:${property}>
</V10:accountMatch>
</V10:internalAccountSearchRequest>
</V10:searchInternalAccount>
</soapenv:Body>
</soapenv:Envelope>`;
prod: {
token: '',
decoded: null
}
};

async function checkUserExistsAtIDIM({ property = 'userGuid', matchKey = '', env = 'prod' }) {
const { requestHeaders, requesterIdirGuid, serviceUrl, serviceId } = getWebServiceInfo({ env });
const xml = generateXML({ property, matchKey, serviceId, requesterIdirGuid }, 'getAccountDetail');

async function getAzureAccessToken(env) {
try {
const response = await axios.post(`${serviceUrl}/webservices/client/V10/BCeIDService.asmx?WSDL`, xml, {
headers: requestHeaders,
timeout: 10000
});
const currentTime = Math.floor(Date.now() / 1000);
if (msTokenCache[env].decoded && msTokenCache[env].decoded?.exp > currentTime) {
return msTokenCache[env].token;
}
const request = {
scopes: [`${MS_GRAPH_URL}/.default`]
};

const { data: body } = response;
let msalInstance;
switch (env) {
case 'dev':
msalInstance =
devMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_DEV || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_DEV || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_DEV || ''
}
});
break;
case 'test':
msalInstance =
testMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_TEST || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_TEST || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_TEST || ''
}
});
break;
case 'prod':
msalInstance =
prodMsalInstance ||
new ConfidentialClientApplication({
auth: {
authority: process.env.MS_GRAPH_API_AUTHORITY_PROD || '',
clientId: process.env.MS_GRAPH_API_CLIENT_ID_PROD || '',
clientSecret: process.env.MS_GRAPH_API_CLIENT_SECRET_PROD || ''
}
});
break;
}
const response = await msalInstance.acquireTokenByClientCredential(request);
msTokenCache[env].token = response.accessToken;
msTokenCache[env].decoded = jwt.decode(response.accessToken);
return response.accessToken;
} catch (error) {
console.error(error);
throw new Error('Error acquiring access token');
}
}

const result = await parseStringSync(body);
const data = _.get(result, 'soap:Envelope.soap:Body.0.getAccountDetailResponse.0.getAccountDetailResult.0');
if (!data) throw Error('no data');
async function checkUserExistsAtIDIM({ property = MS_GRAPH_IDIR_GUID_ATTRIBUTE, matchKey = '', env }) {
try {
const accessToken = await getAzureAccessToken(env);
const options = {
headers: {
Authorization: `Bearer ${accessToken}`,
ConsistencyLevel: 'eventual'
}
};

const status = _.get(data, 'code.0');
const failureCode = _.get(data, 'failureCode.0');
const failMessage = _.get(data, 'message.0');
if (status === 'Success' && failureCode === 'Void') {
return 'exists';
} else if (status === 'Failed' && failureCode === 'NoResults') {
const url = `${MS_GRAPH_URL}/v1.0/users?$filter=${property} eq '${matchKey}'&$count=true`;
const result = await axios.get(url, options);
if (result && result.data?.value?.length === 0) {
return 'notexists';
} else {
log(`${env}: [${status}][${failureCode}] ${property}: ${matchKey}: ${String(failMessage)})`);
}
return 'error';
if (result && result.data?.value?.length > 0) {
return 'exists';
}
throw new Error('unexpected response from ms graph');
} catch (error) {
console.log(error?.response?.data || error);
throw new Error(error);
}
}
Expand Down Expand Up @@ -195,7 +180,11 @@ async function removeStaleUsersByEnv(env = 'dev', pgClient, runnerName, startFro
if (displayName && displayName.startsWith('hold -')) continue;
log(`[${runnerName}] processing user ${username}`);
if (username.includes('@idir')) {
const userExistsAtWb = await checkUserExistsAtIDIM({ property: 'userGuid', matchKey: idirUserGuid, env });
const userExistsAtWb = await checkUserExistsAtIDIM({
property: MS_GRAPH_IDIR_GUID_ATTRIBUTE,
matchKey: idirUserGuid,
env
});
if (userExistsAtWb === 'notexists') {
const { realmRoles, clientRoles } = await getUserRolesMappings(adminClient, id);
await removeUserFromKc(adminClient, id);
Expand Down
82 changes: 82 additions & 0 deletions docker/kc-cron-job/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"

"@azure/[email protected]":
version "14.12.0"
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-14.12.0.tgz#844abe269b071f8fa8949dadc2a7b65bbb147588"
integrity sha512-IDDXmzfdwmDkv4SSmMEyAniJf6fDu3FJ7ncOjlxkDuT85uSnLEhZi3fGZpoR7T4XZpOMx9teM9GXBgrfJgyeBw==

"@azure/msal-node@^2.9.2":
version "2.9.2"
resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-2.9.2.tgz#e6d3c1661012c1bd0ef68e328f73a2fdede52931"
integrity sha512-8tvi6Cos3m+0KmRbPjgkySXi+UQU/QiuVRFnrxIwt5xZlEEFa69O04RTaNESGgImyBBlYbo2mfE8/U8Bbdk1WQ==
dependencies:
"@azure/msal-common" "14.12.0"
jsonwebtoken "^9.0.0"
uuid "^8.3.0"

"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
Expand Down Expand Up @@ -2816,6 +2830,31 @@ json5@^2.2.3:
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==

jsonwebtoken@^9.0.0, jsonwebtoken@^9.0.2:
version "9.0.2"
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
dependencies:
jws "^3.2.2"
lodash.includes "^4.3.0"
lodash.isboolean "^3.0.3"
lodash.isinteger "^4.0.4"
lodash.isnumber "^3.0.3"
lodash.isplainobject "^4.0.6"
lodash.isstring "^4.0.1"
lodash.once "^4.0.0"
ms "^2.1.1"
semver "^7.5.4"

jwa@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"

jwa@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
Expand All @@ -2825,6 +2864,14 @@ jwa@^2.0.0:
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"

jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
dependencies:
jwa "^1.4.1"
safe-buffer "^5.0.1"

jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
Expand Down Expand Up @@ -2920,16 +2967,46 @@ lodash.flatten@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=

lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==

lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==

lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==

lodash.isnumber@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==

lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=

lodash.isstring@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==

lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==

lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==

lodash.union@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
Expand Down Expand Up @@ -3961,6 +4038,11 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=

uuid@^8.3.0:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==

v8-to-istanbul@^9.0.1:
version "9.1.3"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz#ea456604101cd18005ac2cae3cdd1aa058a6306b"
Expand Down
Loading

0 comments on commit 86ed6da

Please sign in to comment.