Skip to content

Commit

Permalink
Revert "chore: microsoft graph (#357)"
Browse files Browse the repository at this point in the history
This reverts commit 8b522cb.
  • Loading branch information
jlangy authored Jun 28, 2024
1 parent 8b522cb commit ef1af2c
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 302 deletions.
29 changes: 11 additions & 18 deletions docker/kc-cron-job/.env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
DEV_KEYCLOAK_PASSWORD=
DEV_KEYCLOAK_USERNAME=
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
TEST_KEYCLOAK_PASSWORD=
TEST_KEYCLOAK_USERNAME=
TEST_KEYCLOAK_CLIENT_ID=
TEST_KEYCLOAK_CLIENT_SECRET=
TEST_KEYCLOAK_URL=https://test.loginproxy.gov.bc.ca
PROD_KEYCLOAK_PASSWORD=
PROD_KEYCLOAK_USERNAME=
PROD_KEYCLOAK_CLIENT_ID=
PROD_KEYCLOAK_CLIENT_SECRET=
PROD_KEYCLOAK_URL=https://loginproxy.gov.bc.ca
PGHOST=
PGPORT=
Expand All @@ -18,15 +23,3 @@ 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: 0 additions & 2 deletions docker/kc-cron-job/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
"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
198 changes: 104 additions & 94 deletions docker/kc-cron-job/remove-inactive-idir-users.js
Original file line number Diff line number Diff line change
@@ -1,112 +1,126 @@
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');

const MS_GRAPH_URL = 'https://graph.microsoft.com';
const MS_GRAPH_IDIR_GUID_ATTRIBUTE = 'onPremisesExtensionAttributes/extensionAttribute12';

require('dotenv').config();

let devMsalInstance;
let testMsalInstance;
let prodMsalInstance;
const parseStringSync = promisify(parseString);

let msTokenCache = {
dev: {
token: '',
decoded: null
},
test: {
token: '',
decoded: null
function getWebServiceInfo({ env = 'dev' }) {
const requestHeaders = {
'Content-Type': 'text/xml;charset=UTF-8',
authorization: `Basic ${process.env.BCEID_SERVICE_BASIC_AUTH}`
};

const requesterIdirGuid = process.env.BCEID_REQUESTER_IDIR_GUID || '';

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 || '';
}

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

const generateXML = (
{
property = 'userId',
matchKey = '',
matchType = 'Exact',
serviceId = '',
requesterIdirGuid = '',
page = 1,
limit = 1
},
prod: {
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>`;
}
};

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

try {
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 response = await axios.post(`${serviceUrl}/webservices/client/V10/BCeIDService.asmx?WSDL`, xml, {
headers: requestHeaders,
timeout: 10000
});

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 { data: body } = response;

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 result = await parseStringSync(body);
const data = _.get(result, 'soap:Envelope.soap:Body.0.getAccountDetailResponse.0.getAccountDetailResult.0');
if (!data) throw Error('no data');

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';
}
if (result && result.data?.value?.length > 0) {
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') {
return 'notexists';
} else {
log(`${env}: [${status}][${failureCode}] ${property}: ${matchKey}: ${String(failMessage)})`);
}
console.error(`unexpected response from ms graph: ${result}`);
return 'error';
} catch (error) {
console.log(error?.response?.data || error);
throw new Error(error);
}
}
Expand Down Expand Up @@ -181,11 +195,7 @@ 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: MS_GRAPH_IDIR_GUID_ATTRIBUTE,
matchKey: idirUserGuid,
env
});
const userExistsAtWb = await checkUserExistsAtIDIM({ property: 'userGuid', matchKey: idirUserGuid, env });
if (userExistsAtWb === 'notexists') {
const { realmRoles, clientRoles } = await getUserRolesMappings(adminClient, id);
await removeUserFromKc(adminClient, id);
Expand Down
Loading

0 comments on commit ef1af2c

Please sign in to comment.