k2hr3-api
Version:
K2HR3 REST API is K2hdkc based Resource and Roles and policy Rules
1,039 lines • 127 kB
JavaScript
"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