UNPKG

k2hr3-api

Version:

K2HR3 REST API is K2hdkc based Resource and Roles and policy Rules

1,039 lines 127 kB
"use strict"; /* * K2HR3 REST API * * Copyright 2017 Yahoo Japan Corporation. * * K2HR3 is K2hdkc based Resource and Roles and policy Rules, gathers * common management information for the cloud. * K2HR3 can dynamically manage information as "who", "what", "operate". * These are stored as roles, resources, policies in K2hdkc, and the * client system can dynamically read and modify these information. * * For the full copyright and license information, please view * the license file that was distributed with this source code. * * AUTHOR: Takeshi Nakatani * CREATE: Wed Jun 8 2017 * REVISION: * */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.k2hr3tokens = void 0; const k2hr3dkc_1 = __importDefault(require("./k2hr3dkc")); const k2hr3apiutil_1 = __importDefault(require("./k2hr3apiutil")); const dbglogging_1 = __importDefault(require("./dbglogging")); const k2hr3keys_1 = require("./k2hr3keys"); const r3keys = k2hr3keys_1.getK2hr3Keys; const k2hr3config_1 = require("./k2hr3config"); const apiConf = new k2hr3config_1.r3ApiConfig(); // // Check type // const rawIsDkcTypeSourceUserHostInfo = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } if ((null !== val.user && !k2hr3apiutil_1.default.isString(val.user)) || (null !== val.hostname && !k2hr3apiutil_1.default.isString(val.hostname)) || (null !== val.ip && !k2hr3apiutil_1.default.isString(val.ip)) || !k2hr3apiutil_1.default.isSafeNumber(val.port) || (null !== val.cuk && !k2hr3apiutil_1.default.isString(val.cuk))) { return false; } return true; }; const rawIsValTypeUserTenantInfo = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } if ((undefined !== val.user && null !== val.user && !k2hr3apiutil_1.default.isString(val.user)) || (undefined !== val.tenant && null !== val.tenant && !k2hr3apiutil_1.default.isString(val.tenant)) || (undefined !== val.userid && null !== val.userid && !k2hr3apiutil_1.default.isString(val.userid)) || (undefined !== val.display && null !== val.display && !k2hr3apiutil_1.default.isString(val.display)) || (undefined !== val.id && null !== val.id && !k2hr3apiutil_1.default.isString(val.id)) || (undefined !== val.description && null !== val.description && !k2hr3apiutil_1.default.isString(val.description)) || (undefined !== val.region && null !== val.region && !k2hr3apiutil_1.default.isString(val.region)) || !rawIsDkcTypeSourceUserHostInfo(val)) { return false; } return true; }; const rawIsDkcTypeBaseRoleToken = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } if (!k2hr3apiutil_1.default.isString(val.date) || !k2hr3apiutil_1.default.isString(val.expire) || (undefined !== val.registerpath && !k2hr3apiutil_1.default.isString(val.registerpath))) { return false; } return true; }; const rawIsResTypeObjRoleTokens = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } for (const [, value] of Object.entries(val)) { if (!rawIsDkcTypeBaseRoleToken(value)) { return false; } } return true; }; const rawIsResTypeCheckKindToken = (val) => { if (!k2hr3apiutil_1.default.isPlainObject(val)) { return false; } if ((null !== val.role && !k2hr3apiutil_1.default.isString(val.role)) || (null !== val.extra && !k2hr3apiutil_1.default.isString(val.extra)) || !k2hr3apiutil_1.default.isBoolean(val.scoped) || !rawIsDkcTypeSourceUserHostInfo(val)) { return false; } return true; }; const rawIsResTypeCheckUserToken = (val) => { return rawIsResTypeCheckKindToken(val); }; const rawIsResTypeCheckRoleToken = (val) => { return rawIsResTypeCheckKindToken(val); }; //--------------------------------------------------------- // Configuration // * Keystone api wrapper // * Get expiration for role tokens //--------------------------------------------------------- // [NOTE] // We use config which has keystone.type value. // Default value is "keystone_v3". // let osapi = null; let expire_rtoken = 0; let expire_reg_rtoken = 0; (async () => { const keystone_type = './' + apiConf.getKeystoneType(); const osapiModule = await k2hr3apiutil_1.default.tryLoadModule(keystone_type); if (k2hr3apiutil_1.default.isSafeEntity(osapiModule)) { osapi = osapiModule; } expire_rtoken = apiConf.getExpireTimeRoleToken(); expire_reg_rtoken = apiConf.getExpireTimeRegRoleToken(); })(); //--------------------------------------------------------- // set user/tenant token //--------------------------------------------------------- // tenant undefined(or null) thus token must be unscoped. // expire UTC ISO 8601 format // // [NOTE] Must initialize User/Tenant before calling this function. // const rawSetUserToken = (user, tenant, token, expire, region, seed) => { if (!k2hr3apiutil_1.default.isSafeString(user) || !k2hr3apiutil_1.default.isSafeString(token) || !k2hr3apiutil_1.default.isSafeString(expire) || !k2hr3apiutil_1.default.isSafeString(region)) { // allow tenant/seed is null dbglogging_1.default.elog('some parameters are wrong : user=' + JSON.stringify(user) + ', tenant=' + JSON.stringify(tenant) + ', token=' + JSON.stringify(token) + ', expire=' + JSON.stringify(expire) + ', region=' + JSON.stringify(region)); return false; } const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean) const keys = r3keys(user, tenant); if (!k2hr3apiutil_1.default.isPlainObject(dkcobj)) { return false; } // // Check user key exists and create these. // const expire_limit = k2hr3apiutil_1.default.calcExpire(expire); // UTC ISO 8601 to unixtime const token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" const user_token_key = keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + '/' + token; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" const user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" // [NOTE] // If tenant is null, following keys have not tenant keyword in that key string. // [ not have tenant name ] vs [ have tenant name ] // keys.USER_TENANT_AMBIGUOUS_KEY ---> "yrn:yahoo::::user:<user>:tenant/" or "yrn:yahoo::::user:<user>:tenant/<tenant>" // keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY ---> "yrn:yahoo::::user:<user>:tenant//token" or "yrn:yahoo::::user:<user>:tenant/<tenant>/token" // // token top let subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(keys.USER_TENANT_AMBIGUOUS_KEY, true)); if (k2hr3apiutil_1.default.tryAddStringToArray(subkeylist, keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY)) { if (!dkcobj.setSubkeys(keys.USER_TENANT_AMBIGUOUS_KEY, subkeylist)) { // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token -> yrn:yahoo::::user:<user>:tenant/{<tenant>} dbglogging_1.default.elog('could not add ' + keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + ' subkey under ' + keys.USER_TENANT_AMBIGUOUS_KEY + ' key'); dkcobj.clean(); return false; } } // to token key subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY, true)); if (k2hr3apiutil_1.default.tryAddStringToArray(subkeylist, user_token_key)) { if (!dkcobj.setSubkeys(keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY, subkeylist)) { // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token dbglogging_1.default.elog('could not add ' + user_token_key + ' subkey under ' + keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + ' key'); dkcobj.clean(); return false; } } // get/set token value let old_value = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(user_token_key, null, true, null)); if (old_value != region) { if (!dkcobj.setValue(user_token_key, region, null, null, expire_limit)) { // update new token key(value with region and expire) -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> dbglogging_1.default.elog('could not set ' + k2hr3apiutil_1.default.getSafeString(region) + '(expire=' + k2hr3apiutil_1.default.getSafeString(expire) + ') to ' + user_token_key + ' key'); dkcobj.clean(); return false; } } // get/set seed value if (k2hr3apiutil_1.default.isSafeString(seed)) { old_value = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(user_token_seed_key, null, true, null)); if (old_value != seed) { if (!dkcobj.setValue(user_token_seed_key, seed, null, null, expire_limit)) { // update new token seed key(value with expire) -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed dbglogging_1.default.elog('could not set ' + seed + '(expire=' + expire + ') to ' + user_token_seed_key + ' key'); dkcobj.clean(); return false; } } subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(user_token_key, true)); if (k2hr3apiutil_1.default.tryAddStringToArray(subkeylist, user_token_seed_key)) { if (!dkcobj.setSubkeys(user_token_key, subkeylist)) { // add subkey yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed -> yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token> dbglogging_1.default.elog('could not add ' + user_token_seed_key + ' subkey under ' + user_token_key + ' key'); dkcobj.clean(); return false; } } } // create new token under token top key // // [NOTE] // This key is not set expire limit. if you need to check expire, // you look up key under user key. // if (!dkcobj.setValue(token_value_key, user_token_key)) { // create(over write) value(="yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>") without expire -> yrn:yahoo::::token/<token> dbglogging_1.default.elog('could not set ' + user_token_key + ' value without expire to ' + token_value_key + ' key'); dkcobj.clean(); return false; } subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(keys.TOKEN_USER_TOP_KEY, true)); if (k2hr3apiutil_1.default.tryAddStringToArray(subkeylist, token_value_key)) { if (!dkcobj.setSubkeys(keys.TOKEN_USER_TOP_KEY, subkeylist)) { // add subkey yrn:yahoo::::token:user/<token> -> yrn:yahoo::::token:user dbglogging_1.default.elog('could not add ' + token_value_key + ' subkey under ' + keys.TOKEN_USER_TOP_KEY + ' key'); dkcobj.clean(); return false; } } dkcobj.clean(); return true; }; //--------------------------------------------------------- // get user unscoped token //--------------------------------------------------------- // // Get user token from keystone // // result: null or resTypeUserToken // { // user: user name(if existed, from parameter) // userid: user id(if existed, from "yrn:yahoo::::user:<user name>:id") // scoped: false(always false) // token: token string(id) // expire: expire string(if existed, null) // region: region string // } // // [NOTE] // This function is wrapper for osapi.getUserUnscopedToken(). // This function checks existing unscoped token before call it. // const rawGetUserUnscopedTokenWrap = (user, passwd, callback) => { const _user = user; const _passwd = k2hr3apiutil_1.default.getSafeString(passwd); const _callback = callback; if (!k2hr3apiutil_1.default.isSafeEntity(osapi)) { const error = new Error('could not load osapi file(object)'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // get unscoped token osapi.getUserUnscopedToken(_user, _passwd, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get user access token by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // init user const resobj = k2hr3dkc_1.default.initUser(jsonres.user, jsonres.userid, jsonres.user, null); if (!resobj.result) { const error = new Error(resobj.message ?? ''); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // set unscoped token const token_seed = k2hr3apiutil_1.default.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if (!rawSetUserToken(jsonres.user, null, jsonres.token, jsonres.expire, jsonres.region, token_seed)) { const error = new Error('failed to set unscoped/scoped user token'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres); return; }); }; //--------------------------------------------------------- // Get Scoped User Token from keystone //--------------------------------------------------------- const rawGetScopedUserToken = (unscopedtoken, username, userid, tenant, callback) => { const _callback = callback; if (!k2hr3apiutil_1.default.isSafeEntity(osapi)) { const error = new Error('could not load osapi file(object)'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } if (!k2hr3apiutil_1.default.isSafeString(unscopedtoken) || !k2hr3apiutil_1.default.isSafeString(username) || !k2hr3apiutil_1.default.isSafeString(userid) || !k2hr3apiutil_1.default.isSafeString(tenant)) { const error = new Error('unscopedtoken or username or userid or tenant parameters are wrong'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } const _unscopedtoken = unscopedtoken; const _username = username; const _userid = userid; const _tenant = tenant.toLowerCase(); // get tenant list for check osapi.getUserTenantList(_unscopedtoken, _userid, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get tenant list for user ' + _username + '(token=' + _unscopedtoken + ') by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } //r3logger.dlog('get user tenant list jsonres=\n' + JSON.stringify(jsonres)); // check tenants(and initialize tenants) let _tenant_name = null; //let _tenant_id: string | null = null; //let _tenant_desc: string | null = null; //let _tenant_display: string | null = null; const _tenant_list = new Array(0); for (let cnt = 0; cnt < jsonres.length; ++cnt) { if (!k2hr3apiutil_1.default.isSafeEntity(jsonres[cnt])) { continue; } // over write const resobj = k2hr3dkc_1.default.initUserTenant(_username, _userid, _username, jsonres[cnt].name, jsonres[cnt].id, jsonres[cnt].description, jsonres[cnt].display); if (!resobj.result) { const error = new Error(resobj.message ?? ''); dbglogging_1.default.elog(error.message); _callback(error, null); return; } if (k2hr3apiutil_1.default.compareCaseString(jsonres[cnt].name, _tenant)) { // find target tenant _tenant_name = jsonres[cnt].name; //_tenant_id = jsonres[cnt].id; //_tenant_desc = jsonres[cnt].description; //_tenant_display = jsonres[cnt].display; } _tenant_list.push(jsonres[cnt].name); } // get and add local tenants const tmpresobj = k2hr3dkc_1.default.listLocalTenant(_username, true); if (!k2hr3apiutil_1.default.isPlainObject(tmpresobj) || !k2hr3apiutil_1.default.isBoolean(tmpresobj.result) || false === tmpresobj.result || !k2hr3apiutil_1.default.isSafeEntity(tmpresobj.tenants)) { if (k2hr3apiutil_1.default.isPlainObject(tmpresobj) && k2hr3apiutil_1.default.isSafeString(tmpresobj.message)) { dbglogging_1.default.wlog('failed to get local tenant list by ' + tmpresobj.message); } else { dbglogging_1.default.wlog('failed to get local tenant list.'); } } else { if (k2hr3dkc_1.default.isDkcTypeTenantInfoList(tmpresobj.tenants)) { for (let cnt2 = 0; cnt2 < tmpresobj.tenants.length; ++cnt2) { if (!k2hr3dkc_1.default.isDkcTypeTenantInfo(tmpresobj.tenants[cnt2])) { continue; } if (k2hr3apiutil_1.default.compareCaseString(tmpresobj.tenants[cnt2].name, _tenant)) { // find target tenant _tenant_name = tmpresobj.tenants[cnt2].name; //_tenant_id = tmpresobj.tenants[cnt2].id; //_tenant_desc = tmpresobj.tenants[cnt2].desc; //_tenant_display = tmpresobj.tenants[cnt2].display; } _tenant_list.push(tmpresobj.tenants[cnt2].name); } } } // check and remove old tenant for user if (!k2hr3dkc_1.default.removeComprehensionByNewTenants(_username, _tenant_list)) { dbglogging_1.default.elog('failed to remove some tenant for user, but continue...'); } if (!k2hr3apiutil_1.default.isSafeEntity(osapi)) { const error = new Error('could not load osapi file(object)'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // get scoped token osapi.getUserScopedToken(_unscopedtoken, _tenant_name, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get scoped user token for user ' + _username + ' by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } //r3logger.dlog('get user scoped token jsonres=\n' + JSON.stringify(jsonres)); const token_seed = k2hr3apiutil_1.default.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if (!rawSetUserToken(_username, _tenant_name, jsonres.token, jsonres.expire, jsonres.region, token_seed)) { const error = new Error('failed to set unscoped/scoped user token'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres.token); return; }); }); }; //--------------------------------------------------------- // Get User Token //--------------------------------------------------------- // // Get scoped/unscoped user token from keystone // const rawGetUserToken = (user, passwd, tenant, callback) => { const _callback = callback; if (!k2hr3apiutil_1.default.isSafeString(user)) { const error = new Error('user parameter is wrong'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } const _tenant = k2hr3apiutil_1.default.getSafeString(tenant).toLowerCase(); let _user = user; const _passwd = k2hr3apiutil_1.default.getSafeString(passwd); // get unscoped token rawGetUserUnscopedTokenWrap(_user, _passwd, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get user access token by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // save to local val _user = jsonres.user; // over write const _userid = jsonres.userid; const _username = jsonres.user; const _unscopedtoken = jsonres.token; //const _tokenexpire = jsonres.expire; //const _region = jsonres.region; // break when unscoped token if (!k2hr3apiutil_1.default.isSafeString(_tenant)) { _callback(null, _unscopedtoken); return; } // get scoped user token return rawGetScopedUserToken(_unscopedtoken, _username, _userid, _tenant, _callback); }); }; //--------------------------------------------------------- // get user unscoped token from token issued by another authentication system //--------------------------------------------------------- // // Get user token from token issued by another authentication system // // result: null or resTypeUserToken // { // user: user name(if existed, from parameter) // userid: user id(if existed, from "yrn:yahoo::::user:<user name>:id") // scoped: false(always false) // token: token string(id) // expire: expire string(if existed, null) // region: region string // } // // [NOTE] // This function is wrapper for osapi.getUserUnscopedTokenByToken(). // This function checks existing unscoped token before call it. // const rawGetUserUnscopedTokenbyTokenWrap = (token, callback) => { const _orgtoken = token; const _callback = callback; if (!k2hr3apiutil_1.default.isSafeEntity(osapi)) { const error = new Error('could not load osapi file(object)'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // get unscoped token osapi.getUserUnscopedTokenByToken(_orgtoken, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get user access token by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // init user const resobj = k2hr3dkc_1.default.initUser(jsonres.user, jsonres.userid, jsonres.user, null); if (!resobj.result) { const error = new Error(resobj.message ?? ''); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // set unscoped token const token_seed = k2hr3apiutil_1.default.isSafeString(jsonres.token_seed) ? jsonres.token_seed : null; if (!rawSetUserToken(jsonres.user, null, jsonres.token, jsonres.expire, jsonres.region, token_seed)) { const error = new Error('failed to set unscoped/scoped user token'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // succeed _callback(null, jsonres); return; }); }; //--------------------------------------------------------- // Get User Token from token issued by another authentication system //--------------------------------------------------------- // // Get scoped/unscoped user token from token issued by another authentication system // (ex. openstack identity token which is not registered in k2hr3 yet.) // const rawGetUserTokenByToken = (token, tenant, callback) => { const _callback = callback; if (!k2hr3apiutil_1.default.isSafeString(token)) { const error = new Error('token parameter is wrong'); dbglogging_1.default.elog(error.message); _callback(error, null); return; } const _tenant = k2hr3apiutil_1.default.getSafeString(tenant).toLowerCase(); const _orgtoken = token; // get unscoped token rawGetUserUnscopedTokenbyTokenWrap(_orgtoken, (err, jsonres) => { if (null !== err || null === jsonres) { const error = new Error('could not get user access token by ' + (err?.message ?? 'unknown')); dbglogging_1.default.elog(error.message); _callback(error, null); return; } // save to local val const _userid = jsonres.userid; const _username = jsonres.user; const _unscopedtoken = jsonres.token; //const _tokenexpire = jsonres.expire; // eslint-disable-line no-unused-vars //const _region = jsonres.region; // eslint-disable-line no-unused-vars // break when unscoped token if (!k2hr3apiutil_1.default.isSafeString(_tenant)) { _callback(null, _unscopedtoken); return; } // get scoped user token return rawGetScopedUserToken(_unscopedtoken, _username, _userid, _tenant, _callback); }); }; // // Get scoped user token from unscoped user token // const rawGetScopedUserTokenByUnscoped = (unscopedtoken, username, tenant, callback) => { if (!k2hr3apiutil_1.default.isSafeString(unscopedtoken) || !k2hr3apiutil_1.default.isSafeString(username) || !k2hr3apiutil_1.default.isSafeString(tenant)) { const error = new Error('unscopedtoken or username or tenant parameters are wrong'); dbglogging_1.default.elog(error.message); callback(error, null); return; } // user id from user name const _tenant = tenant.toLowerCase(); const user_info = k2hr3dkc_1.default.getUserId(username); // user id from user name if (null === user_info || !k2hr3apiutil_1.default.isSafeEntity(user_info.name) || !k2hr3apiutil_1.default.isSafeEntity(user_info.id)) { const error = new Error('could not find username(' + username + ') from unscoped token in k2hdkc.'); dbglogging_1.default.elog(error.message); callback(error, null); return; } // get scoped user token return rawGetScopedUserToken(unscopedtoken, user_info.name, user_info.id, _tenant, callback); }; //--------------------------------------------------------- // cleanup user/tenant by token //--------------------------------------------------------- // utility local function // // [NOTE] // This process can be time consuming when subkey list is too many. // In that case, there is a possibility that the consistency of the subkey will be lost. // If a new token is added to the subkey list during processing with this function, that // token subkey will be overwritten(lost) when this function completes processing. // Originally, this subkey list of "yrn:yahoo::::token:user" key has no direct use, so this // is not a problem. Because it normally reads the sub key(token key) directly. // Please do not read subkey(token key) from the subkey list of "yrn:yahoo::::token:user" key. // This subkeys are only useful for "notes". // const rawCleanupUserToken = (callback) => { const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean) if (!k2hr3apiutil_1.default.isPlainObject(dkcobj)) { callback(false); return; } const keys = r3keys(); let subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(keys.TOKEN_USER_TOP_KEY, true)); // get subkeys under "yrn:yahoo::::token:user" const retrive_skeys = new Array(0); for (let cnt = 0; cnt < subkeylist.length; ++cnt) { const user_token_key = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(subkeylist[cnt], null, true, null)); if (!k2hr3apiutil_1.default.isSafeString(user_token_key)) { // value is not existed, so this key should be removing retrive_skeys.push(subkeylist[cnt]); } else { // // user_token_key => "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" // const value = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(user_token_key, null, true, null)); if (!k2hr3apiutil_1.default.isSafeString(value)) { // user_token_key is not existed or expired. retrive_skeys.push(subkeylist[cnt]); // try remove user_token_key from "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token" subkey list const parent_user_key = k2hr3apiutil_1.default.getParentPath(user_token_key); if (k2hr3apiutil_1.default.isSafeString(parent_user_key)) { const user_skeys = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(parent_user_key, true)); // get subkeys under "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token" if (k2hr3apiutil_1.default.removeStringFromArray(user_skeys, user_token_key)) { if (!dkcobj.setSubkeys(parent_user_key, user_skeys)) { // update subkey -> "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token" dbglogging_1.default.wlog('could not update subkey under ' + parent_user_key + ' key, but continue...'); } } } else { dbglogging_1.default.wlog('could not get parent key from ' + user_token_key + ' key, but skip it and continue...'); } } } } if (0 < retrive_skeys.length) { // need to remove keys from subkey list let is_update = false; subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(keys.TOKEN_USER_TOP_KEY, true)); // re-get subkeys under "yrn:yahoo::::token:user" for (let cnt = 0; cnt < retrive_skeys.length; ++cnt) { if (k2hr3apiutil_1.default.removeStringFromArray(subkeylist, retrive_skeys[cnt])) { is_update = true; } } if (is_update) { if (!dkcobj.setSubkeys(keys.TOKEN_USER_TOP_KEY, subkeylist)) { // update subkey -> "yrn:yahoo::::token:user" dbglogging_1.default.elog('could not update subkey under ' + keys.TOKEN_USER_TOP_KEY + ' key, but continue...'); dkcobj.clean(); callback(false); return; } } } dkcobj.clean(); callback(true); }; //--------------------------------------------------------- // get tenant list by token //--------------------------------------------------------- // result: resTypeUserTenantInfo = { // user: xxx, // tenant: xxxx, // region: xxxxx // } // // [NOTE] Must initialize User/Tenant before calling this function. // const rawGetUserTenantByToken = (token) => { if (!k2hr3apiutil_1.default.isSafeString(token)) { dbglogging_1.default.elog('token parameters are wrong : token=' + JSON.stringify(token)); return null; } const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean) if (!k2hr3apiutil_1.default.isPlainObject(dkcobj)) { return null; } // // Get subkeys under token top key // const keys = r3keys(); const token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" // get token key under user key const user_token_key = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(token_value_key, null, true, null)); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" if (!k2hr3apiutil_1.default.isSafeString(user_token_key)) { dbglogging_1.default.dlog('token key(' + token_value_key + ') for token(' + token + ') is not existed.'); dkcobj.clean(); // // check and remove old token under token top key("yrn:yahoo::::token:user" and "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>") // if old token is expired // // [NOTE] // This processing is taking time, so it runs asynchronously. // And for notes on this processing, refer to NOTE of rawCleanupUserToken function. // rawCleanupUserToken((result) => { if (!result) { dbglogging_1.default.wlog('Failed to cleanup expired user tokens under ' + keys.TOKEN_USER_TOP_KEY + ' key, but continue...'); } }); return null; } // get user token key's value which is region const region = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(user_token_key, null, true, null)); // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" value is region if (!k2hr3apiutil_1.default.isSafeString(region)) { dbglogging_1.default.dlog('token key(' + user_token_key + ') for token(' + token + ') is not existed.'); dkcobj.clean(); // // check and remove old token under token top key("yrn:yahoo::::token:user" and "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>") // if old token is expired // // [NOTE] look forwards // rawCleanupUserToken((result) => { if (!result) { dbglogging_1.default.wlog('Failed to cleanup expired user tokens under ' + keys.TOKEN_USER_TOP_KEY + ' key, but continue...'); } }); return null; } // user_token_key format is "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" const pattern = new RegExp('^' + keys.MATCH_ANY_USER_TOKEN); // regex = /^yrn:yahoo::::user:(.*):tenant/(.*)/token/(.*)/ const matches = user_token_key.match(pattern); // reverse to user/tenant names if (!k2hr3apiutil_1.default.isNotEmptyArray(matches) || matches.length < 4 || '' === k2hr3apiutil_1.default.getSafeString(matches[1])) { dbglogging_1.default.elog('token key(' + token_value_key + ') for token(' + token + ') has wrong format value(' + user_token_key + ')'); dkcobj.clean(); return null; } const user_name = k2hr3apiutil_1.default.getSafeString(matches[1]); let tenant_name = k2hr3apiutil_1.default.getSafeString(matches[2]); let tenant_display = null; let tenant_id = null; let tenant_desc = null; if ('' === tenant_name) { tenant_name = null; } else { const tenant_keys = r3keys(user_name, tenant_name); tenant_display = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(tenant_keys.TENANT_DISP_KEY, null, true, null)); tenant_id = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(tenant_keys.TENANT_ID_KEY, null, true, null)); tenant_desc = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(tenant_keys.TENANT_DESC_KEY, null, true, null)); } // if token has seed, need to check seed const user_token_seed_key = user_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" const token_seed = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(user_token_seed_key, null, true, null)); if (k2hr3apiutil_1.default.isSafeString(token_seed)) { // token has seed, then we need to check manually. // //r3logger.dlog('token key(' + user_token_key + ') has seed.'); if (!k2hr3apiutil_1.default.isSafeEntity(osapi)) { dbglogging_1.default.elog('could not load osapi file(object)'); dkcobj.clean(); return null; } const vres = osapi.verifyUserToken(dkcobj, user_name, tenant_name, token, token_seed); if (!vres.result) { dbglogging_1.default.elog('failed to verify token(' + token + ') with seed by ' + (vres?.message ?? '')); dkcobj.clean(); return null; } } dkcobj.clean(); const result = { user: user_name, tenant: tenant_name, display: tenant_display, id: tenant_id, description: tenant_desc, region: region }; return result; }; //--------------------------------------------------------- // Check User Token //--------------------------------------------------------- // // Check User Token // // result : null or token information // { // role: role name // user: user name // hostname: always null // ip: always null // port: always 0 // cuk: always null // extra: always null // tenant: tenant name // display: display alias name for tenant // id: tenant id string // description: description for tenant // scoped: role token is always scoped(true) // region: when user token, the creator region name of the token // } // const rawCheckUserToken = (token) => { if (!k2hr3apiutil_1.default.isSafeString(token)) { dbglogging_1.default.elog('token parameter is wrong'); return null; } // get user/tenant from token const tenant_info = rawGetUserTenantByToken(token); if (null === tenant_info || !k2hr3apiutil_1.default.isSafeEntity(tenant_info.user)) { dbglogging_1.default.elog('token is not any user/tenant(expired or wrong token)'); return null; } // check scoped flag let scoped = false; if (k2hr3apiutil_1.default.isSafeEntity(tenant_info.tenant)) { scoped = true; } const token_info = { role: null, user: tenant_info.user ?? null, hostname: null, ip: null, port: 0, cuk: null, extra: null, tenant: tenant_info.tenant ?? null, display: tenant_info.display ?? null, id: tenant_info.id ?? null, description: tenant_info.description ?? null, region: tenant_info.region ?? null, scoped: scoped }; return token_info; }; //--------------------------------------------------------- // remove scoped user token //--------------------------------------------------------- // // token scoped user token // // [NOTE] This removes(force expire) scoped user token for // using ACR API. // The token used by ACR must be removed after checking // it, because this case allows using token one time. // const rawRemoveScopedUserToken = (token) => { if (!k2hr3apiutil_1.default.isSafeString(token)) { dbglogging_1.default.elog('parameter is wrong : token=' + JSON.stringify(token)); return false; } // // Check token // if (0 === token.indexOf('R=')) { dbglogging_1.default.elog('token(' + JSON.stringify(token) + ') is role token.'); return false; } else if (0 === token.indexOf('U=')) { token = token.substr(2); // cut 'U=' } const token_info = rawCheckUserToken(token); if (null === token_info || !k2hr3apiutil_1.default.isSafeString(token_info.user) || !k2hr3apiutil_1.default.isSafeString(token_info.tenant) || !k2hr3apiutil_1.default.isSafeEntity(token_info.scoped) || !k2hr3apiutil_1.default.isBoolean(token_info.scoped) || true !== token_info.scoped) { dbglogging_1.default.elog('token(' + JSON.stringify(token) + ') is something wrong.'); return false; } // // Remove token // const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean) let errmsg = ''; if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) { return false; } // // Keys // const keys = r3keys(token_info.user, token_info.tenant); const token_top_key = keys.TOKEN_USER_TOP_KEY; // "yrn:yahoo::::token:user" const token_value_key = keys.TOKEN_USER_TOP_KEY + '/' + token; // "yrn:yahoo::::token:user/<token>" const utoken_top_key = keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY; // "yrn:yahoo::::user:<user>:tenant/<tenant>/token" const utoken_token_key = keys.USER_TENANT_AMBIGUOUS_TOKEN_KEY + '/' + token; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" const utoken_seed_key = utoken_token_key + '/' + keys.SEED_KW; // "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" // // check under token top // let subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(token_top_key, true)); if (k2hr3apiutil_1.default.removeStringFromArray(subkeylist, token_value_key)) { // remove subkeys "yrn:yahoo::::token:user/<token>" -> "yrn:yahoo::::token:user" if (!dkcobj.setSubkeys(token_top_key, subkeylist)) { errmsg += 'could not remove ' + token_value_key + ' subkey under ' + token_top_key + ' key, '; } } if (!dkcobj.remove(token_value_key, false)) { // remove key "yrn:yahoo::::token:user/<token>" errmsg += 'could not remove ' + token_value_key + 'key, probably it is not existed, '; } // // check under user top // subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(utoken_top_key, true)); if (k2hr3apiutil_1.default.removeStringFromArray(subkeylist, utoken_token_key)) { // remove subkeys "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" -> "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token" if (!dkcobj.setSubkeys(utoken_top_key, subkeylist)) { errmsg += 'could not remove ' + utoken_token_key + ' subkey under ' + utoken_top_key + ' key, '; } } subkeylist = k2hr3apiutil_1.default.getSafeStringArray(dkcobj.getSubkeys(utoken_token_key, true)); if (k2hr3apiutil_1.default.removeStringFromArray(subkeylist, utoken_seed_key)) { // remove subkeys "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" -> "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" if (!dkcobj.setSubkeys(utoken_token_key, subkeylist)) { errmsg += 'could not remove ' + utoken_seed_key + ' subkey under ' + utoken_token_key + ' key, '; } } if (!dkcobj.remove(utoken_seed_key, false)) { // remove key "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>/seed" errmsg += 'could not remove ' + utoken_seed_key + 'key, probably it is not existed, '; } if (!dkcobj.remove(utoken_token_key, false)) { // remove key "yrn:yahoo::::user:<user>:tenant/{<tenant>}/token/<token>" errmsg += 'could not remove ' + utoken_token_key + 'key, probably it is not existed, '; } if (k2hr3apiutil_1.default.isSafeString(errmsg)) { dbglogging_1.default.elog(errmsg); } dkcobj.clean(); return true; // Returns true even if there is an error in deletion processing }; //--------------------------------------------------------- // [Role Token] //--------------------------------------------------------- // // Token: Token Id(################) // X-Auth-Token: R=Token Id // Token Id: The "Token Id" is a unique hex number string for 128bit. // "Token Id" = "(<base id(64bit:8byte)> ^ <crypt id(64bit:8byte)>)" + "(<role id(64bit:8byte)> ^ <crypt id(64bit:8byte)>)" // Role Token Key: "yrn:yahoo::::token:role/<Token Id>" // Role Token Value: = dkcTypeRoleTokenValue { // role: "role full yrn" // date: "UTC ISO 8601 time at create" // expire: "UTC ISO 8601 time at expire" // creator: "Host full yrn" or "User full yrn" // base: "id from host" or "last 64bit string in UserToken" // user: "user name", if creator is user, this value is user name.(if not, this is null) // ip: "ip address", if creator is host, this value is ip address.(if not, this is null) // hostname: "hostname", if creator is host, this value is hostname.(if not, this is null) // port: "port number", if creator is host, this value is port number.(if not, this is 0) // cuk: "cuk", if creator is host on iaas, this value is cuk.(allowed null) // extra: "extra", if creator is host on iaas, this value is extra.(allowed null) // tenant: "tenant name" for this role token(this mean token is scoped) // verify: "random 64bit id for verify token" // } // // [NOTE] // "role id" which is in "Token Id" is included from role key(yrn:yahoo:<Tenant>:::role:<role name>/id). // This value is secret, any API could not get this value directly. // //--------------------------------------------------------- // Get Role Token From user(token) //--------------------------------------------------------- // // user : this value is parsed from user token // tenant : this value is parsed from user token // role : target role name(full yrn or only role name) // expire_limit : specify expire second(default 24H = 24 * 60 * 60 sec, 0 means no expire time.) // // result : resTypeGetRoleToken = { // result: true/false // message: null or error message string // token: undefined(error) or role token string // } // // [NOTE] // set role token value is following: // dkcTypeRoleTokenValue = { // role: token's role yrn("yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}") // date: create date(UTC ISO 8601) // expire: expire date(UTC ISO 8601) // creator: creator yrn("yrn:yahoo::::user:<user>" or "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/hosts/ip/<ip:port>") // user: if creator is user, this value is user name. // hostname: always null // ip: always null // port: if creator is host, this value is port number.(if not, this is 0) // cuk: if creator is host on iaas, this value is cuk.(allowed null) // extra: if creator is host on iaas, this value is extra.(allowed null) // tenant: tenant name for this role token(this mean token is scoped) // base: 32bytes hex string // } // const rawGetRoleTokenByUser = (user, tenant, role, expire_limit) => { const resobj = { result: true, message: null }; if (!k2hr3apiutil_1.default.isSafeString(user) || !k2hr3apiutil_1.default.isSafeString(tenant) || !k2hr3apiutil_1.default.isSafeString(role)) { // allow other argument is empty resobj.result = false; resobj.message = 'some parameters are wrong : user=' + JSON.stringify(user) + ', tenant=' + JSON.stringify(tenant) + ', role=' + JSON.stringify(role); dbglogging_1.default.elog(resobj.message); return resobj; } if (!k2hr3apiutil_1.default.isSafeNumber(expire_limit)) { // expire_limit must be number or null(undefined) expire_limit = expire_rtoken; // default 24H } else { if (0 == expire_limit) { // [NOTE] // If 0, set the maximum value to 10 years. // Disable expire by setting a period of time within which this // application is guaranteed not to survive, as permitted by ISO8601. // expire_limit = expire_reg_rtoken; } } // check role name is only name or full yrn path const keys = r3keys(user, tenant); role = role.toLowerCase(); let roleptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/ const rolematchs = role.match(roleptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(rolematchs) || rolematchs.length < 4) { // role is not matched role(maybe not full yrn), then we need check it is another yrn path roleptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/ if (role.match(roleptn)) { resobj.result = false; resobj.message = 'role(' + role + ') is not role yrn path)'; dbglogging_1.default.elog(resobj.message); return resobj; } // role is only role name, then we do not modify it. } else { // check tenant name if (tenant !== rolematchs[2]) { resobj.result = false; resobj.message = 'role(' + role + ') yrn has tenant(' + rolematchs[2] + '), but it is not specified tenant(' + tenant + ')'; dbglogging_1.default.elog(resobj.message); return resobj; } // role is set only role name role = rolematchs[3]; } const dkcobj = k2hr3dkc_1.default.getK2hdkc(true, false); // use permanent object(need to clean) let subkeylist; if (!k2hr3apiutil_1.default.isSafeEntity(dkcobj)) { resobj.result = false; resobj.message = 'Not initialize yet.'; dbglogging_1.default.elog(resobj.message); return resobj; } // // keys // const role_key = keys.ROLE_TOP_KEY + ':' + role; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}" const role_id_key = role_key + '/' + keys.ID_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/id" const role_tokens_key = role_key + '/' + keys.ROLE_TOKEN_KW; // "yrn:yahoo:<service>::<tenant>:role:<role>{/<role>{...}}/tokens" // user id const user_id = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(keys.USER_ID_KEY, null, true, null)); // get user id from "yrn:yahoo::::user:<user name>:id" if (!k2hr3apiutil_1.default.isSafeString(user_id)) { resobj.result = false; resobj.message = 'could not get user id(' + keys.USER_ID_KEY + ') value, or it is wrong value(' + JSON.stringify(user_id) + ').'; dbglogging_1.default.elog(resobj.message); dkcobj.clean(); return resobj; } // role id let role_id = k2hr3apiutil_1.default.getSafeString(dkcobj.getValue(role_id_key, null, true, null)); if (!k2hr3apiutil_1.default.isSafeStrUuid4(role_id)) { let isError = false; const old_role_id = k2hr3apiutil_1.default.parseJSON(role_id); if (k2hr3apiutil_1.default.isNotEmptyArray(old_role_id) && 4 == old_role_id.length) { // old version id, so reset new valus(UUID4) here. role_id = k2hr3apiutil_1.default.getStrUuid4(); if (!dkcobj.setValue(r