UNPKG

scimgateway

Version:

Using SCIM protocol as a gateway for user provisioning to other endpoints

290 lines (265 loc) 12.2 kB
// ================================================================================= // File: plugin-saphana.js // // Author: Jarle Elshaug // // Purpose: SAP Hana user-provisioning for saml enabled users // // Prereq: SAP Hana endpoint is up and running // // Supported attributes: // // GlobalUser Template Scim Endpoint // ------------------------------------------------------ // User name %AC% userName USER_NAME // Suspended (auto included) active ACTIVATE/DEACTIVATE // // Currently no other attributes needed for maintaining saml users // ================================================================================= import hdb from 'hdb' // prereq: bun install hdb // for supporting nodejs running scimgateway package directly, using dynamic import instead of: import { ScimGateway } from 'scimgateway' // scimgateway also inclues HelperRest: import { ScimGateway, HelperRest } from 'scimgateway' // start - mandatory plugin initialization const ScimGateway: typeof import('scimgateway').ScimGateway = await (async () => { try { return (await import('scimgateway')).ScimGateway } catch (err) { const source = './scimgateway.ts' return (await import(source)).ScimGateway } })() const scimgateway = new ScimGateway() const config = scimgateway.getConfig() scimgateway.authPassThroughAllowed = false // end - mandatory plugin initialization const endpointHost = config.host const endpointPort = config.port const endpointUsername = config.username const endpointPassword = scimgateway.getSecret('endpoint.password') const endpointSamlProvider = config.saml_provider const hdbClient = hdb.createClient({ host: endpointHost, port: endpointPort, user: endpointUsername, password: endpointPassword, }) // ================================================= // getUsers // ================================================= scimgateway.getUsers = async (baseEntity, getObj, attributes) => { const action = 'getUsers' scimgateway.logDebug(baseEntity, `handling ${action} getObj=${getObj ? JSON.stringify(getObj) : ''} attributes=${attributes}`) let sqlQuery // mandatory if-else logic - start if (getObj.operator) { if (getObj.operator === 'eq' && ['id', 'userName', 'externalId'].includes(getObj.attribute)) { // mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x sqlQuery = `select USER_NAME, USER_DEACTIVATED from SYS.USERS where USER_NAME like '${getObj.value}'` } else if (getObj.operator === 'eq' && getObj.attribute === 'group.value') { // optional - only used when groups are member of users, not default behavior - correspond to getGroupUsers() in versions < 4.x.x throw new Error(`${action} error: not supporting groups member of user filtering: ${getObj.rawFilter}`) } else { // optional - simpel filtering throw new Error(`${action} error: not supporting simpel filtering: ${getObj.rawFilter}`) } } else if (getObj.rawFilter) { // optional - advanced filtering having and/or/not - use getObj.rawFilter throw new Error(`${action} error: not supporting advanced filtering: ${getObj.rawFilter}`) } else { // mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all users to be returned - correspond to exploreUsers() in versions < 4.x.x sqlQuery = 'select USER_NAME, USER_DEACTIVATED from SYS.USERS where IS_SAML_ENABLED like \'TRUE\'' } // mandatory if-else logic - end if (!sqlQuery) throw new Error(`${action} error: mandatory if-else logic not fully implemented`) try { return await new Promise((resolve, reject) => { const ret: any = { // itemsPerPage will be set by scimgateway Resources: [], totalResults: null, } hdbClient.connect(function (err: any) { if (err) { const newErr = new Error('exploreUsers hdbcClient.connect: SAP Hana client connect error: ' + err.message) return reject(newErr) } // Find all SAML_ENABLED users hdbClient.exec(sqlQuery, function (err: any, rows: any) { hdbClient.end() if (err) { const newErr = new Error('exploreUsers hdbcClient.exec: SAP Hana client execute error: ' + err.message + ' sqlQuery = ' + sqlQuery) return reject(newErr) } for (const row in rows) { const scimUser = { // returning userName and id userName: rows[row].USER_NAME, id: rows[row].USER_NAME, active: !JSON.parse((rows[0].USER_DEACTIVATED).toLowerCase()), } ret.Resources.push(scimUser) } resolve(ret) }) // exec }) // connect }) // Promise } catch (err: any) { throw new Error(`${action} error: ${err.message}`) } } // ================================================= // createUser // ================================================= scimgateway.createUser = async (baseEntity, userObj) => { const action = 'createUser' scimgateway.logDebug(baseEntity, `handling ${action} userObj=${JSON.stringify(userObj)}`) try { return await new Promise((resolve, reject) => { hdbClient.connect(function (err: any) { if (err) { const newErr = new Error('createUser hdbcClient.connect: SAP Hana client connect error: ' + err.message) return reject(newErr) } // SAPHana create user do not need any additional provisioning attributes to be included // let sqlQuery = 'CREATE USER ' + userObj.userName + ' WITH IDENTITY ANY FOR SAML PROVIDER ' + endpointSamlProvider; // let sqlQuery = 'CREATE USER ' + userObj.userName + ' WITH IDENTITY ' + "'" + userObj.userName + "'" + ' FOR SAML PROVIDER ' + endpointSamlProvider; let sqlQuery = 'CREATE USER ' + userObj.userName + ' WITH IDENTITY ' + '\'' + userObj.userName + '\'' + ' FOR SAML PROVIDER ' + endpointSamlProvider + ' SET PARAMETER CLIENT = ' + '\'103\'' hdbClient.exec(sqlQuery, function (err: any) { hdbClient.end() if (err) { const newErr = new Error('createUser hdbcClient.exec: SAP Hana client execute error: ' + err.message + ' sqlQuery = ' + sqlQuery) return reject(newErr) } sqlQuery = 'GRANT NG_REPORTING_ROLE TO ' + userObj.userName hdbClient.exec(sqlQuery, function (err: any) { hdbClient.end() if (err) { const newErr = new Error('createUser hdbcClient.exec: SAP Hana client execute error: ' + err.message + ' sqlQuery = ' + sqlQuery) return reject(newErr) } resolve(null) // user created }) // exec }) // exec }) // connect }) // Promise } catch (err: any) { throw new Error(`${action} error: ${err.message}`) } } // ================================================= // deleteUser // ================================================= scimgateway.deleteUser = async (baseEntity, id) => { const action = 'deleteUser' scimgateway.logDebug(baseEntity, `handling ${action} id=${id}`) try { return await new Promise((resolve, reject) => { hdbClient.connect(function (err: any) { if (err) { const newErr = new Error('deleteUser hdbcClient.connect: SAP Hana client connect error: ' + err.message) return reject(newErr) } const sqlQuery = 'DROP USER ' + id hdbClient.exec(sqlQuery, function (err: any) { hdbClient.end() if (err) { const newErr = new Error('deleteUser hdbcClient.exec: SAP Hana client execute error: ' + err.message + ' sqlQuery = ' + sqlQuery) return reject(newErr) } resolve(null) // successfully deleted }) // exec }) // connect }) // Promise } catch (err: any) { throw new Error(`${action} error: ${err.message}`) } } // ================================================= // modifyUser // ================================================= scimgateway.modifyUser = async (baseEntity, id, attrObj) => { const action = 'modifyUser' scimgateway.logDebug(baseEntity, `handling ${action} id=${id} attrObj=${JSON.stringify(attrObj)}`) try { return await new Promise((resolve, reject) => { let sqlAction = '' if (attrObj.active !== undefined) { if (sqlAction.length === 0) sqlAction = (attrObj.active === true) ? 'ACTIVATE' : 'DEACTIVATE' else sqlAction += (attrObj.active === true) ? ' ACTIVATE' : ' DEACTIVATE' } // Add more attribute checks here according supported endpoint attributes hdbClient.connect(function (err: any) { if (err) { const newErr = new Error('modifyUser hdbcClient.connect: SAP Hana client connect error: ' + err.message) return reject(newErr) } const sqlQuery = 'ALTER USER ' + id + ' ' + sqlAction hdbClient.exec(sqlQuery, function (err: any) { hdbClient.end() if (err) { const newErr = new Error('modifyUser hdbcClient.exec: SAP Hana client execute error: ' + err.message + ' sqlQuery = ' + sqlQuery) return reject(newErr) } resolve(null) // user successfully updated }) // execute }) // connect }) // Promise } catch (err: any) { throw new Error(`${action} error: ${err.message}`) } } // ================================================= // getGroups // ================================================= scimgateway.getGroups = async (baseEntity, getObj, attributes) => { const action = 'getGroups' scimgateway.logDebug(baseEntity, `handling ${action} getObj=${getObj ? JSON.stringify(getObj) : ''} attributes=${attributes}`) // mandatory if-else logic - start if (getObj.operator) { if (getObj.operator === 'eq' && ['id', 'displayName', 'externalId'].includes(getObj.attribute)) { // mandatory - unique filtering - single unique user to be returned - correspond to getUser() in versions < 4.x.x } else if (getObj.operator === 'eq' && getObj.attribute === 'members.value') { // mandatory - return all groups the user 'id' (getObj.value) is member of - correspond to getGroupMembers() in versions < 4.x.x // Resources = [{ id: <id-group>> , displayName: <displayName-group>, members [{value: <id-user>}] }] } else { // optional - simpel filtering } } else if (getObj.rawFilter) { // optional - advanced filtering having and/or/not - use getObj.rawFilter } else { // mandatory - no filtering (!getObj.operator && !getObj.rawFilter) - all groups to be returned - correspond to exploreGroups() in versions < 4.x.x } // mandatory if-else logic - end return { Resources: [] } // groups not supported - returning empty Resources } // ================================================= // createGroup // ================================================= scimgateway.createGroup = async (baseEntity, groupObj) => { const action = 'createGroup' scimgateway.logDebug(baseEntity, `handling ${action} groupObj=${JSON.stringify(groupObj)}`) throw new Error(`${action} error: ${action} is not supported`) } // ================================================= // deleteGroup // ================================================= scimgateway.deleteGroup = async (baseEntity, id) => { const action = 'deleteGroup' scimgateway.logDebug(baseEntity, `handling ${action} id=${id}`) throw new Error(`${action} error: ${action} is not supported`) } // ================================================= // modifyGroup // ================================================= scimgateway.modifyGroup = async (baseEntity, id, attrObj) => { const action = 'modifyGroup' scimgateway.logDebug(baseEntity, `handling ${action} id=${id} attrObj=${JSON.stringify(attrObj)}`) throw new Error(`${action} error: ${action} is not supported`) } // ================================================= // helpers // ================================================= // // Cleanup on exit // process.on('SIGTERM', () => { // kill }) process.on('SIGINT', () => { // Ctrl+C })