UNPKG

k2hr3-api

Version:

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

705 lines (704 loc) 30.1 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: Thu Nov 2 2017 * REVISION: * */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const k2hr3apiutil_1 = __importDefault(require("../lib/k2hr3apiutil")); const k2hr3resutil_1 = __importDefault(require("../lib/k2hr3resutil")); const k2hr3tokens_1 = __importDefault(require("../lib/k2hr3tokens")); const k2hr3dkc_1 = __importDefault(require("../lib/k2hr3dkc")); const dbglogging_1 = __importDefault(require("../lib/dbglogging")); const express_1 = __importDefault(require("express")); const router = express_1.default.Router(); // // Common utility function // // Create or update service // // Result null(succeed) or error message(failed) // const rawCreateServiceTenant = (token_info, token, tenantname, servicename, callback) => { if (!k2hr3apiutil_1.default.isFunction(callback)) { const error = new Error('callback parameter is wrong : callback=' + JSON.stringify(callback)); dbglogging_1.default.elog(error.message); return; } // // Check service name // if (!k2hr3apiutil_1.default.isSafeString(servicename)) { const error = new Error('service name is wrong.'); dbglogging_1.default.elog(error.message); callback(error); return; } // // Check token // if (!k2hr3apiutil_1.default.isSafeString(token) || !k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info) || !k2hr3apiutil_1.default.isString(token_info.user)) { const error = new Error('specified wrong token or it is not scoped or no tenant.'); dbglogging_1.default.elog(error.message); callback(error); return; } // // Create service + tenant // if (token_info.scoped) { // [NOTE] // The token is scoped to tenant, but if the user exists, we use this token as unscoped. // By using as an unscoped token, the following function creates a scoped token inside it. // Then this logic will not be affected by deletion (one time) of token. // k2hr3dkc_1.default.createServiceTenantByScopedToken(tenantname, servicename, token, callback); } else { k2hr3dkc_1.default.createServiceTenantByUnscopedToken(tenantname, servicename, token, k2hr3apiutil_1.default.getSafeString(token_info.user), callback); } }; // // Mountpath : '/v1/acr' // // POST '/v1/acr/<service>' : post service/tenant on version 1 // HEADER : X-Auth-Token => Unscoped/Scoped User token or Role Token // body : { // tenant: => tenant name(when unscoped user token) // } // response body : result => true/false // message => messages // router.post('/', (req, res, _) => { dbglogging_1.default.dlog('CALL:', req.method, req.url, req.baseUrl); res.type('application/json; charset=utf-8'); if (!k2hr3apiutil_1.default.isPlainObject(req) || !k2hr3apiutil_1.default.isSafeString(req.baseUrl)) { const result = { result: false, message: 'POST request is wrong' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } //------------------------------ // check token //------------------------------ const token_result = k2hr3tokens_1.default.checkToken(req, false, true); // (un)scoped user token if (!token_result.result) { dbglogging_1.default.elog(k2hr3apiutil_1.default.getSafeString(token_result.message)); const result = { result: token_result.result, message: k2hr3apiutil_1.default.getSafeString(token_result.message), }; k2hr3resutil_1.default.errResponse(req, res, token_result.status, result); return; } const token_info = token_result.token_info; if (!k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info)) { const result = { result: false, message: 'specified wrong token or it is not scoped user token' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } let tenantname; if (false === token_info.scoped) { // // Unscoped user token : need tenant parameter // if (!k2hr3apiutil_1.default.isPlainObject(req.body) || !k2hr3apiutil_1.default.isSafeString(req.body.tenant)) { const result = { result: false, message: 'Specified unscoped user token, but there is not tenant in body data.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } // get user's tenant list const tenant_list = k2hr3tokens_1.default.getTenantList(token_info.user); if (!k2hr3apiutil_1.default.isArray(tenant_list) || !k2hr3apiutil_1.default.isNotEmptyArray(tenant_list)) { const result = { result: false, message: 'token(' + k2hr3apiutil_1.default.getSafeString(token_result.token) + ') for user (' + k2hr3apiutil_1.default.getSafeString(token_info.user) + ') does not have any tenant.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } // check tenant if (!k2hr3tokens_1.default.checkTenantInTenantList(tenant_list, req.body.tenant.toLowerCase())) { const result = { result: false, message: 'user (' + k2hr3apiutil_1.default.getSafeString(token_info.user) + ') is not member of tenant(' + k2hr3apiutil_1.default.getSafeString(req.body.tenant) + ').' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } tenantname = req.body.tenant.toLowerCase(); } else { // // Scoped user token : not need tenant parameter // if (k2hr3apiutil_1.default.isPlainObject(req.body) && k2hr3apiutil_1.default.isSafeString(req.body.tenant)) { if (!k2hr3apiutil_1.default.compareCaseString(token_info.tenant, req.body.tenant)) { const result = { result: false, message: 'Specified scoped user token and tenant in body data, but these are not same tenant name.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } } tenantname = k2hr3apiutil_1.default.getSafeString(token_info.tenant); } //------------------------------ // check service path in url //------------------------------ const requestptn = new RegExp('^/v1/acr/(.*)'); // regex = /^\/v1\/acr\/(.*)/ const reqmatchs = decodeURI(req.baseUrl).match(requestptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) { const result = { result: false, message: 'POST request url does not have service name' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const servicename = reqmatchs[1].toLowerCase(); //------------------------------ // create service + tenant //------------------------------ rawCreateServiceTenant(token_info, (token_result.token ?? null), tenantname, servicename, (error) => { if (null !== error) { const result = { result: false, message: error.message }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } else { const result = { result: true, message: null }; res.status(201); // 201: Created res.send(JSON.stringify(result)); } }); }); // // Mountpath : '/v1/acr' // // PUT '/v1/acr/<service>' : post service/tenant on version 1 // HEADER : X-Auth-Token => Unscoped/Scoped User token or Role Token // URL arguments : tenant => tenant name(when unscoped user token) // response body : result => true/false // message => messages // router.put('/', (req, res, _) => { dbglogging_1.default.dlog('CALL:', req.method, req.url, req.baseUrl); res.type('application/json; charset=utf-8'); if (!k2hr3apiutil_1.default.isPlainObject(req) || !k2hr3apiutil_1.default.isSafeString(req.baseUrl)) { const result = { result: false, message: 'PUT request is wrong' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } //------------------------------ // check token //------------------------------ const token_result = k2hr3tokens_1.default.checkToken(req, false, true); // (un)scoped user token if (!token_result.result) { dbglogging_1.default.elog(k2hr3apiutil_1.default.getSafeString(token_result.message)); const result = { result: token_result.result, message: k2hr3apiutil_1.default.getSafeString(token_result.message), }; k2hr3resutil_1.default.errResponse(req, res, token_result.status, result); return; } const token_info = token_result.token_info; if (!k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info)) { const result = { result: false, message: 'specified wrong token or it is not scoped user token' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } let tenantname; if (false === token_info.scoped) { // // Unscoped user token : need tenant parameter // if (!k2hr3apiutil_1.default.isPlainObject(req.query) || !k2hr3apiutil_1.default.isSafeString(req.query.tenant)) { const result = { result: false, message: 'Specified unscoped user token, but there is not tenant in argument.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } // get user's tenant list const tenant_list = k2hr3tokens_1.default.getTenantList(token_info.user); if (!k2hr3apiutil_1.default.isArray(tenant_list) || !k2hr3apiutil_1.default.isNotEmptyArray(tenant_list)) { const result = { result: false, message: 'token(' + k2hr3apiutil_1.default.getSafeString(token_result.token) + ') for user (' + k2hr3apiutil_1.default.getSafeString(token_info.user) + ') does not have any tenant.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } // check tenant if (!k2hr3tokens_1.default.checkTenantInTenantList(tenant_list, req.query.tenant.toLowerCase())) { const result = { result: false, message: 'user (' + k2hr3apiutil_1.default.getSafeString(token_info.user) + ') is not member of tenant(' + k2hr3apiutil_1.default.getSafeString(req.query.tenant) + ').' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } tenantname = req.query.tenant.toLowerCase(); } else { // // Scoped user token : not need tenant parameter // if (k2hr3apiutil_1.default.isPlainObject(req.body) && k2hr3apiutil_1.default.isSafeString(req.body.tenant)) { if (!k2hr3apiutil_1.default.compareCaseString(token_info.tenant, req.body.tenant)) { const result = { result: false, message: 'Specified scoped user token and tenant in body data, but these are not same tenant name.' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } } tenantname = k2hr3apiutil_1.default.getSafeString(token_info.tenant); } //------------------------------ // check service path in url //------------------------------ const requestptn = new RegExp('^/v1/acr/(.*)'); // regex = /^\/v1\/acr\/(.*)/ const reqmatchs = decodeURI(req.baseUrl).match(requestptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) { const result = { result: false, message: 'PUT request url does not have service name' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const servicename = reqmatchs[1].toLowerCase(); //------------------------------ // create service + tenant //------------------------------ rawCreateServiceTenant(token_info, (token_result.token ?? null), tenantname, servicename, (error) => { if (null !== error) { const result = { result: false, message: error.message }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } else { const result = { result: true, message: null }; res.status(201); // 201: Created res.send(JSON.stringify(result)); } }); }); // // Utility function for getting Service/Tenant Names // const rawGetServiceTenantNames = (req, res) => { if (!k2hr3apiutil_1.default.isPlainObject(req) || !k2hr3apiutil_1.default.isSafeString(req.baseUrl)) { const result = { result: false, message: 'GET request or url is wrong' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } //------------------------------ // check token //------------------------------ const token_result = k2hr3tokens_1.default.checkToken(req, true, true); // scoped user token if (!token_result.result) { dbglogging_1.default.elog(k2hr3apiutil_1.default.getSafeString(token_result.message)); const result = { result: token_result.result, message: k2hr3apiutil_1.default.getSafeString(token_result.message), }; k2hr3resutil_1.default.errResponse(req, res, token_result.status, result); return; } const token_info = token_result.token_info; if (!k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info)) { const result = { result: false, message: 'specified wrong token or it is not scoped user token' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } // // force remove user token(this is one shot!) // k2hr3tokens_1.default.removeScopedUserToken(token_result.token ?? undefined); //------------------------------ // check service path in url //------------------------------ const requestptn = new RegExp('^/v1/acr/(.*)'); // regex = /^\/v1\/acr\/(.*)/ const reqmatchs = decodeURI(req.baseUrl).match(requestptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) { const result = { result: false, message: 'PUT request url does not have service name' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const servicename = reqmatchs[1].toLowerCase(); //------------------------------ // check tenant in service's tenant list //------------------------------ const resobj = k2hr3dkc_1.default.checkTenantInService(servicename, token_info.tenant); if (!k2hr3apiutil_1.default.isPlainObject(resobj) || !k2hr3apiutil_1.default.isBoolean(resobj.result) || false === resobj.result) { if (!k2hr3apiutil_1.default.isSafeEntity(resobj)) { const result = { result: false, message: 'Could not get service data from checkTenantInService' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } else { const result = { result: resobj.result, message: k2hr3apiutil_1.default.isString(resobj.message) ? resobj.message : 'Could not get error message in response from checkTenantInService' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } return; } //------------------------------ // make result //------------------------------ const tokeninfo = { user: k2hr3apiutil_1.default.getSafeString(token_info.user), tenant: k2hr3apiutil_1.default.getSafeString(token_info.tenant), service: servicename }; const success_result = { result: true, message: null, tokeninfo: tokeninfo }; dbglogging_1.default.dlog('succeed'); res.status(200); // 200: OK res.send(JSON.stringify(success_result)); }; // // Utility function for getting Service/Tenant Resources // const rawGetServiceTenantResources = (req, res) => { //------------------------------ // check request //------------------------------ const sip = k2hr3apiutil_1.default.getClientIpAddress(req); if (!k2hr3apiutil_1.default.isPlainObject(req) || !k2hr3apiutil_1.default.isSafeString(req.baseUrl) || !k2hr3apiutil_1.default.isPlainObject(req.query) || !k2hr3apiutil_1.default.isSafeString(req.query.cip) || !k2hr3apiutil_1.default.isSafeString(sip) || !k2hr3apiutil_1.default.isSafeString(req.query.crole) || !k2hr3apiutil_1.default.isSafeString(req.query.srole)) { const result = { result: false, message: 'GET request or parameters are wrong' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } //------------------------------ // port parameters //------------------------------ const cport = k2hr3apiutil_1.default.cvtToNumber(req.query.cport); const sport = k2hr3apiutil_1.default.cvtToNumber(req.query.sport); //------------------------------ // cuk parameters //------------------------------ const ccuk = k2hr3apiutil_1.default.isString(req.query.ccuk) ? req.query.ccuk : null; const scuk = k2hr3apiutil_1.default.isString(req.query.scuk) ? req.query.scuk : null; //------------------------------ // check service path in url //------------------------------ const requestptn = new RegExp('^/v1/acr/(.*)'); // regex = /^\/v1\/acr\/(.*)/ const reqmatchs = decodeURI(req.baseUrl).match(requestptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) { const result = { result: false, message: 'PUT request url does not have service name' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const servicename = reqmatchs[1].toLowerCase(); //------------------------------ // Get ACR resources //------------------------------ const resource_result = k2hr3dkc_1.default.getServiceTenantResources(servicename, sip, sport, scuk, req.query.srole, req.query.cip, cport, ccuk, req.query.crole); if (!k2hr3apiutil_1.default.isPlainObject(resource_result) || !k2hr3apiutil_1.default.isBoolean(resource_result.result) || false === resource_result.result) { if (!k2hr3apiutil_1.default.isSafeEntity(resource_result)) { const result = { result: false, message: 'Could not get ACR resources from getServiceTenantResources' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } else { const result = { result: resource_result.result, message: k2hr3apiutil_1.default.isString(resource_result.message) ? resource_result.message : 'Could not get error message in response from getServiceTenantResources' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403, result); // 403: Forbidden(is this status OK?) } return; } dbglogging_1.default.dlog('succeed : ' + JSON.stringify(resource_result)); res.status(200); // 200: OK res.send(JSON.stringify(resource_result)); }; // // Mountpath : '/v1/acr' // // GET '/v1/acr/<service>' : get service/tenant names on version 1 // HEADER : X-Auth-Token => Scoped User token // response body : result => true/false // message => messages // tokeninfo => user/tenant information for verify // // tokeninfo = { // user: : <user name> => user name string // tenant: : <tenant name> => tenant name string // service: : <service name> => service name string // } // // // GET '/v1/acr/<service>' : get resource proxying service on version 1 // URL argument : // "cip" : <IP address> => client IP address(client peer address to service) gave by service // "cport" : <port> => client port number specified by client(proxied by service) // this value is optional // "ccuk" : <cuk string> => client container unique key // this value is optional // "sport" : <port> => service port number specified by service // this value is optional // "scuk" : <cuk string> => service container unique key // this value is optional // "crole" : <role yrn> => client role full yrn specified by client(proxied by service) // "srole" : <role yrn> => service role full yrn // // response body : result => true/false // message => messages // resource => following format // // response = [ => allowed null/undefined, this object is the result of verify from service at creating service/tenant // { // name : <resource name> => resource name which is key name(path) for resource // expire : <expire> => undefined/null or integer // type : <resource type> => resource data type(string or object), if date is null or '', this value must be string. // data : <resource data> => resource data which must be string or object or null/undefined. // keys = { : <resource keys> => resource has keys(associative array), or null/undefined. // 'foo': bar,: => any value is allowed // ... // } // }, // ... // ] // router.get('/', (req, res, next) => { dbglogging_1.default.dlog('CALL:', req.method, req.url); if ('GET' !== req.method) { // HEAD request comes here, so it should be routed to head function. next(); return; } res.type('application/json; charset=utf-8'); //------------------------------ // check request type //------------------------------ if (k2hr3tokens_1.default.hasAuthTokenHeader(req)) { // // Get service/tenant name // rawGetServiceTenantNames(req, res); } else { // // Get resources for service/tenant // rawGetServiceTenantResources(req, res); } }); // // Mountpath : '/v1/acr/*' // // DELETE '/v1/acr/<service>' : delete service tenant on version 1 // HEADER : X-Auth-Token => Scoped User token // response body : result => true/false // router.delete('/', (req, res, _) => { dbglogging_1.default.dlog('CALL:', req.method, req.url, req.baseUrl); res.type('application/json; charset=utf-8'); if (!k2hr3apiutil_1.default.isPlainObject(req) || !k2hr3apiutil_1.default.isSafeString(req.baseUrl)) { const result = { result: false, message: 'DELETE request or url is wrong' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400); // 400: Bad Request return; } //------------------------------ // check token //------------------------------ const token_result = k2hr3tokens_1.default.checkToken(req, true, true); // scoped user token if (!token_result.result) { dbglogging_1.default.elog(k2hr3apiutil_1.default.getSafeString(token_result.message)); const result = { result: token_result.result, message: k2hr3apiutil_1.default.getSafeString(token_result.message), }; k2hr3resutil_1.default.errResponse(req, res, token_result.status, result); return; } const token_info = token_result.token_info; if (!k2hr3tokens_1.default.isResTypeCheckRoleToken(token_info) || !k2hr3apiutil_1.default.isSafeString(token_info.tenant) || !k2hr3apiutil_1.default.isSafeString(token_info.user)) { const result = { result: false, message: 'specified wrong token or it is not scoped user token' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const tenantname = token_info.tenant; const user = token_info.user; //------------------------------ // check service path in url //------------------------------ const requestptn = new RegExp('^/v1/acr/(.*)'); // regex = /^\/v1\/acr\/(.*)/ const reqmatchs = decodeURI(req.baseUrl).match(requestptn); if (!k2hr3apiutil_1.default.isNotEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === k2hr3apiutil_1.default.getSafeString(reqmatchs[1])) { const result = { result: false, message: 'DELETE request url does not have service name' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 400, result); // 400: Bad Request return; } const servicename = reqmatchs[1].toLowerCase(); //------------------------------ // remove service + tenant //------------------------------ const rm_result = k2hr3dkc_1.default.removeServiceTenant(user, tenantname, servicename); if (!k2hr3apiutil_1.default.isPlainObject(rm_result) || !k2hr3apiutil_1.default.isBoolean(rm_result.result) || false === rm_result.result) { if (!k2hr3apiutil_1.default.isSafeEntity(rm_result)) { const result = { result: false, message: 'Could not get response from removeServiceTenant' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403); // 403: Forbidden(is this status OK?) } else { const result = { result: rm_result.result, message: k2hr3apiutil_1.default.isString(rm_result.message) ? rm_result.message : 'Could not get error message in response from removeServiceTenant' }; dbglogging_1.default.elog(result.message); k2hr3resutil_1.default.errResponse(req, res, 403); // 403: Forbidden(is this status OK?) } return; } dbglogging_1.default.dlog('succeed : ' + rm_result.message); res.status(204); // 204: No Content res.send(); }); //--------------------------------------------------------- // Exports //--------------------------------------------------------- // // Functions // exports.default = router; /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noexpandtab sw=4 ts=4 fdm=marker * vim<600: noexpandtab sw=4 ts=4 */