k2hr3-api
Version:
K2HR3 REST API is K2hdkc based Resource and Roles and policy Rules
1,479 lines (1,359 loc) • 90.3 kB
JavaScript
/*
* 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:
*
*/
'use strict';
var express = require('express');
var router = express.Router();
var r3token = require('../lib/k2hr3tokens');
var apiutil = require('../lib/k2hr3apiutil');
var resutil = require('../lib/k2hr3resutil');
var r3userdata = require('../lib/k2hr3userdata');
var k2hr3 = require('../lib/k2hr3dkc');
var r3keys = require('../lib/k2hr3keys').getK2hr3Keys;
// Debug logging objects
var r3logger = require('../lib/dbglogging');
//---------------------------------------------------------
// Configuration
// * Get role full path which is allowed to remove ip address
// * Get expiration for role tokens
//---------------------------------------------------------
var delhost_role_yrn = null;
var expire_rtoken = 0;
var expire_reg_rtoken = 0;
(function()
{
var r3Conf = require('../lib/k2hr3config').r3ApiConfig;
var apiConf = new r3Conf();
var admincfgobj = apiConf.getK2hr3AdminConfig();
if(apiutil.isSafeEntity(admincfgobj) && apiutil.isSafeString(admincfgobj.tenant) && apiutil.isSafeString(admincfgobj.delhostrole)){
var keys = r3keys(null, admincfgobj.tenant.trim());
delhost_role_yrn= keys.ROLE_TOP_KEY + ':' + admincfgobj.delhostrole.trim();
}else{
r3logger.elog('Could not find tenant/role in configuration for deleting host by cuk.');
delhost_role_yrn= null;
}
expire_rtoken = apiConf.getExpireTimeRoleToken();
expire_reg_rtoken = apiConf.getExpireTimeRegRoleToken();
}());
//---------------------------------------------------------
// Router POST
//---------------------------------------------------------
//
// Mountpath : '/v1/role'
// POST '/v1/role{/<role{/...}>}' : post role on version 1
// HEADER : X-Auth-Token => User token or Role token
// response body : result => true/false
// message => messages
//
// This mount point is for creating(update) role or creating(update) host in role.
//
router.post('/', function(req, res, next)
{
r3logger.dlog('CALL:', req.method, req.url);
res.type('application/json; charset=utf-8');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.baseUrl) )
{
result = {
result: false,
message: 'POST request or url or query is wrong'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// check api type
if('/v1/role' === decodeURI(req.baseUrl)){
//------------------------------
// create role type
//------------------------------
postRole(req, res, next);
}else{
// check host api
var keys = r3keys();
var requestptn = new RegExp(keys.MATCH_URI_GET_ROLE_DATA); // regex = /^\/v1\/role\/(.*)/
var reqmatchs = decodeURI(req.baseUrl).match(requestptn);
if(apiutil.isEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === apiutil.getSafeString(reqmatchs[1])){
result = {
result: false,
message: 'POST request url does not have role name'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// role name
var name = reqmatchs[1];
name = name.toLowerCase();
//------------------------------
// create host type
//------------------------------
postRoleHost(name, req, res, next);
}
});
//---------------------------------------------------------
// Router PUT
//---------------------------------------------------------
// Mountpath : '/v1/role'
// PUT '/v1/role{/<role{/...}>}': put role on version 1
// HEADER : X-Auth-Token => User token or Role token
// response body : result => true/false
// message => messages
//
// This mount point is for creating(update) role and creating(update) host in role.
//
router.put('/', function(req, res, next)
{
r3logger.dlog('CALL:', req.method, req.url);
res.type('application/json; charset=utf-8');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.baseUrl) )
{
result = {
result: false,
message: 'PUT request or url or query is wrong'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// check api type
if('/v1/role' === decodeURI(req.baseUrl)){
//------------------------------
// create role type
//------------------------------
putRole(req, res, next);
}else{
// check host api
var keys = r3keys();
var requestptn = new RegExp(keys.MATCH_URI_GET_ROLE_DATA); // regex = /^\/v1\/role\/(.*)/
var reqmatchs = decodeURI(req.baseUrl).match(requestptn);
if(apiutil.isEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === apiutil.getSafeString(reqmatchs[1])){
result = {
result: false,
message: 'POST request url does not have role name'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// role name
var name = reqmatchs[1];
name = name.toLowerCase();
//------------------------------
// create host type
//------------------------------
putRoleHost(name, req, res, next);
}
});
//
// Sub router function for POST CREATE ROLE
//
// Mountpath : '/v1/role'
// POST '/v1/role' : post role on version 1
// HEADER : X-Auth-Token => User token
// response body : result => true/false
// message => messages
// body :
// {
// "role": {
// "name": <role name> => key is "yrn:yahoo:<service>::<tenant>:role:<role>"
// <role> can include '/' for hierarchical path
// "policies": [<policy yrn full path>, ...] => key is "yrn:yahoo:<service>::<tenant>:role:<role>/policies"
// specify policy as "yrn:yahoo:<service>::<tenant>:policy:<policy>"
// if null or undefined is specified, not update this member in role when this role exists.
// if '' or zero array, this member in role is set empty array.
// "alias": [<role yrn full path>, ...] => key is "yrn:yahoo:<service>::<tenant>:role:<role>/@"
// specify another role as "yrn:yahoo:<service>::<tenant>:role:<role>"
// if null or undefined is specified, not update this member in role when this role exists.
// if '' or zero array, this member in role is set empty array.
// }
// }
//
// [NOTE]
// This API does not set host into roles as initial. You can add host to role
// by another API which is an API dedicated to adding host.
//
function postRole(req, res, next) // eslint-disable-line no-unused-vars
{
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.body) ||
!apiutil.isSafeEntity(req.body.role) )
{
result = {
result: false,
message: 'POST body does not have role data'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
//------------------------------
// check token
//------------------------------
var token_result = r3token.checkToken(req, true, true); // scoped, user token
if(!token_result.result){
r3logger.elog(token_result.message);
var _status = token_result.status;
delete token_result.status;
resutil.errResponse(req, res, _status, token_result);
return;
}
var token_info = token_result.token_info;
//------------------------------
// check arguments
//------------------------------
// name
if(!apiutil.isSafeString(req.body.role.name)){
result = {
result: false,
message: 'role:name field is wrong : ' + JSON.stringify(req.body.role.name)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
var keys = r3keys(token_info.user, token_info.tenant);
var name = apiutil.getSafeString(req.body.role.name);
name = name.toLowerCase();
// role name is only name or full yrn path
var nameptn = new RegExp('^' + keys.ROLE_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:role:(.*)/
var namematchs = name.match(nameptn);
if(!apiutil.isEmptyArray(namematchs) && 2 <= namematchs.length){
name = namematchs[1];
}
// check name which is not full yrn
nameptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/
if(name.match(nameptn)){
r3logger.elog('POST request query has wrong yrn full path to role');
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// policies
var policiesptn = new RegExp('^' + keys.POLICY_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:policy:(.*)/
var policiespram= apiutil.getNormalizeParameter(req.body.role.policies, policiesptn, null);
if(false === policiespram.result){
result = {
result: false,
message: 'role:policies field is wrong : ' + JSON.stringify(req.body.role.policies)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
var policies = policiespram.parameter;
// alias
var aliasptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/
var aliaspram = apiutil.getNormalizeParameter(req.body.role.alias, aliasptn, null);
if(false === aliaspram.result){
result = {
result: false,
message: 'role:alias field is wrong : ' + JSON.stringify(req.body.role.alias)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
var aliases = aliaspram.parameter;
//------------------------------
// set all field to role
//------------------------------
result = k2hr3.setRoleAll(token_info.user, token_info.tenant, name, policies, aliases, null, false, null, false);
if(!apiutil.isSafeEntity(result) || !apiutil.isSafeEntity(result.result) || false === result.result){
if(!apiutil.isSafeEntity(result)){
result = {
result: false,
message: 'Could not get response from setRoleAll'
};
}else{
if(!apiutil.isSafeEntity(result.result)){
result.result = false;
}
if(!apiutil.isSafeEntity(result.message)){
result.message = 'Could not get error message in response from setRoleAll';
}
}
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
r3logger.dlog('succeed : ' + result.message);
res.status(201); // 201: Created
res.send(JSON.stringify(result));
}
//
// Sub router function for PUT CREATE ROLE
//
// Mountpath : '/v1/role'
// PUT '/v1/role{/<role{/...}>}' : put role on version 1
// HEADER : X-Auth-Token => User token
// response body : result => true/false
// message => messages
// url argument
// "name": <role name> => key is "yrn:yahoo:<service>::<tenant>:role:<role>"
// <role> can include '/' for hierarchical path
// "policies": [<policy yrn full path>, ...] => key is "yrn:yahoo:<service>::<tenant>:role:<role>/policies"
// specify policy as "yrn:yahoo:<service>::<tenant>:policy:<policy>", it is formatted by JSON.
// if null or undefined is specified, not update this member in role when this role exists.
// if '' or zero array, this member in role is set empty array.
// "alias": [<role yrn full path>, ...] => key is "yrn:yahoo:<service>::<tenant>:role:<role>/@"
// specify another role as "yrn:yahoo:<service>::<tenant>:role:<role>", it is formatted by JSON.
// if null or undefined is specified, not update this member in role when this role exists.
// if '' or zero array, this member in role is set empty array.
//
// [NOTE]
// This API does not set host into roles as initial. You can add host to role
// by another API which is an API dedicated to adding host.
//
function putRole(req, res, next) // eslint-disable-line no-unused-vars
{
r3logger.dlog('CALL:', req.method, req.url);
res.type('application/json; charset=utf-8');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.query) )
{
result = {
result: false,
message: 'PUT argument does not have any data'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
//------------------------------
// check token
//------------------------------
var token_result = r3token.checkToken(req, true, true); // scoped, user token
if(!token_result.result){
r3logger.elog(token_result.message);
var _status = token_result.status;
delete token_result.status;
resutil.errResponse(req, res, _status, token_result);
return;
}
var token_info = token_result.token_info;
//------------------------------
// check arguments
//------------------------------
// name
if(!apiutil.isSafeString(req.query.name)){
result = {
result: false,
message: 'role:name field is wrong : ' + JSON.stringify(req.query.name)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
var keys = r3keys(token_info.user, token_info.tenant);
var name = apiutil.getSafeString(req.query.name);
name = name.toLowerCase();
// role name is only name or full yrn path
var nameptn = new RegExp('^' + keys.ROLE_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:role:(.*)/
var namematchs = name.match(nameptn);
if(!apiutil.isEmptyArray(namematchs) && 2 <= namematchs.length){
name = namematchs[1];
}
// check name which is not full yrn
nameptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/
if(name.match(nameptn)){
result = {
result: false,
message: 'PUT request query has wrong yrn full path to role'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// policies
var policies = null;
if('' === req.query.policies){
policies = '';
}else if(apiutil.isSafeString(req.query.policies)){
// policies is encoded by JSON, this value is array.
//
var tmppolicies = apiutil.getSafeString(req.query.policies);
if(apiutil.checkSimpleJSON(tmppolicies)){
tmppolicies = JSON.parse(tmppolicies);
}
var policiesptn = new RegExp('^' + keys.POLICY_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:policy:(.*)/
var policiespram= apiutil.getNormalizeParameter(tmppolicies, policiesptn, null);
if(false === policiespram.result){
result = {
result: false,
message: 'role:policies field is wrong : ' + req.query.policies
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
policies = policiespram.parameter;
}
// alias
var aliases = null;
if('' === req.query.alias){
aliases = '';
}else if(apiutil.isSafeString(req.query.alias)){
// alias is encoded by JSON, this value is array.
//
var tmpaliases = apiutil.getSafeString(req.query.alias);
if(apiutil.checkSimpleJSON(tmpaliases)){
tmpaliases = JSON.parse(tmpaliases);
}
var aliasptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/
var aliaspram = apiutil.getNormalizeParameter(tmpaliases, aliasptn, null);
if(false === aliaspram.result){
result = {
result: false,
message: 'role:alias field is wrong : ' + req.query.alias
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
aliases = aliaspram.parameter;
}
//------------------------------
// set all field to role
//------------------------------
result = k2hr3.setRoleAll(token_info.user, token_info.tenant, name, policies, aliases, null, false, null, false);
if(!apiutil.isSafeEntity(result) || !apiutil.isSafeEntity(result.result) || false === result.result){
if(!apiutil.isSafeEntity(result)){
result = {
result: false,
message: 'Could not get response from setRoleAll'
};
}else{
if(!apiutil.isSafeEntity(result.result)){
result.result = false;
}
if(!apiutil.isSafeEntity(result.message)){
result.message = 'Could not get error message in response from setRoleAll';
}
}
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
r3logger.dlog('succeed : ' + result.message);
res.status(201); // 201: Created
res.send(JSON.stringify(result));
}
//
// Sub router function for POST CREATE HOST
//
// Mountpath : '/v1/role'
// POST '/v1/role/<role{/...}>' : post role on version 1
// HEADER : X-Auth-Token => User token or Role token
// response body : result => true/false
// message => messages
//
// [UserToken] body :
// {
// "host": { => specified single host
// "host": <hostname / ip address> => key is for "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/{name, ip}/<hostname port cuk>"
// "port": <port number> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is number string(0-), allowed null and '' for this value.
// if this value is '0', it means any port.
// "cuk": <container unique key> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is string. if this value is undefined/null/empty string, it means any.
// "extra": <extra string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// extra is any string including Control code, allowed null and '' for this value.
// "tag": <string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// tag is any string including Control code, allowed null and '' for this value.
// "inboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// inboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// "outboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// outboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// }
// "clear_hostname": <true/false>
// "clear_ips": <true/false>
// }
// or
// {
// "host": [ => specified host as Array(only POST request has this type)
// {
// "host": <hostname / ip address>
// "port": <port number>
// "cuk": <container unique key>
// "extra": <extra string data>
// "tag": <string data>
// "inboundip": <ip address>
// "outboundip": <ip address>
// }
// ...
// ]
// "clear_hostname": <true/false>
// "clear_ips": <true/false>
// }
//
// [RoleToken] body :
// {
// "host": {
// "port": <port number> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/ip/<ip port cuk>"
// this value is number string(0-), allowed null and '' for this value.
// if this value is '0', it means any port.
// "cuk": <container unique key> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is string. if this value is undefined/null/empty string, it means any.
// "extra": <extra string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// extra is any string including Control code, allowed null and '' for this value.
// "tag": <string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// tag is any string including Control code, allowed null and '' for this value.
// "inboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// inboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// "outboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// outboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// }
// }
//
// [NOTE]
// This API only set(add/create) host into role. The host is specified hostname.
// The hostname is any string as like hostname.(ex. "x.yahoo.co.jp", "x[0-9].yahoo.co.jp", "*.yahoo.co.jp", "*", "(.*)", etc)
// If port number is 0, it means any port.
// If cuk is undefined/null/empty string, it means any.
// Extra data can include control-code(CR, etc).
//
function postRoleHost(role, req, res, next) // eslint-disable-line no-unused-vars
{
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.body) ||
!apiutil.isSafeEntity(req.body.host) )
{
result = {
result: false,
message: 'POST body does not have host data'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
//------------------------------
// check token
//------------------------------
var token_result = r3token.checkToken(req, true); // scoped, both token
if(!token_result.result){
r3logger.elog(token_result.message);
var _status = token_result.status;
delete token_result.status;
resutil.errResponse(req, res, _status, token_result);
return;
}
var is_host_req = (!apiutil.isArray(req.body.host) && !apiutil.isSafeString(req.body.host.host));
var token_info = token_result.token_info;
var keys = r3keys(token_info.user, token_info.tenant);
//------------------------------
// check arguments
//------------------------------
// role name check
var name = apiutil.getSafeString(role);
name = name.toLowerCase();
var nameptn = new RegExp('^' + keys.ROLE_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:role:(.*)/
var namematchs = name.match(nameptn);
if(!apiutil.isEmptyArray(namematchs) && 2 <= namematchs.length){
// name is full yrn, then reset only name.
name = namematchs[1];
}else{
// role name is not full yrn, then check other yrn path
nameptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/
if(name.match(nameptn)){
result = {
result: false,
message: 'POST request url has wrong yrn full path to role'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
//------------------------------
// build parameters
//------------------------------
var port;
var cuk;
var extra;
var tag;
var host_info;
if(!is_host_req){
//
// request from user token
//
var hostArray;
if(apiutil.isArray(req.body.host)){
hostArray = req.body.host;
}else{
hostArray = [ req.body.host ];
}
// check array and make ip array
var hostnameArray = [];
var ipArray = [];
for(var cnt = 0; cnt < hostArray.length; ++cnt){
if(!apiutil.isSafeString(hostArray[cnt].host)){
result = {
result: false,
message: 'host is not specified.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// hostname or ip address
var tg_host = apiutil.getSafeString(hostArray[cnt].host);
var tg_ip = null;
if(apiutil.isIpAddressString(tg_host)){
tg_ip = tg_host.toLowerCase();
tg_host = null;
}else{
tg_host = tg_host.toLowerCase();
tg_ip = null;
}
// port
port = 0; // default any
if(apiutil.isSafeEntity(hostArray[cnt].port)){
if(isNaN(hostArray[cnt].port)){
result = {
result: false,
message: 'POST request has port which is not number: ' + JSON.stringify(hostArray[cnt].port)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
port = parseInt(hostArray[cnt].port);
}
// cuk
cuk = null; // default any
if(apiutil.isSafeString(hostArray[cnt].cuk) && apiutil.isSafeString(hostArray[cnt].cuk.trim())){
cuk = apiutil.getSafeString(hostArray[cnt].cuk).trim();
}
// extra
extra = null;
if(apiutil.isSafeString(hostArray[cnt].extra)){
extra = apiutil.getSafeString(hostArray[cnt].extra);
}
// tag
tag = null;
if(apiutil.isSafeString(hostArray[cnt].tag)){
tag = apiutil.getSafeString(hostArray[cnt].tag);
}
// set base host information
if(null !== tg_host){
host_info = {
ip: null,
hostname: tg_host,
port: port,
cuk: cuk,
extra: extra,
tag: tag
};
}else{ // null !== tg_ip
host_info = {
ip: tg_ip,
hostname: null,
port: port,
cuk: cuk,
extra: extra,
tag: tag
};
}
// set optional keys
if(apiutil.isSafeString(hostArray[cnt].inboundip)){
if(!apiutil.isIpAddressString(hostArray[cnt].inboundip)){
result = {
result: false,
message: 'POST request has inbound ip address which is not ignore ip address string: ' + JSON.stringify(hostArray[cnt].inboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
host_info.inboundip = apiutil.getSafeString(hostArray[cnt].inboundip);
}
if(apiutil.isSafeString(hostArray[cnt].outboundip)){
if(!apiutil.isIpAddressString(hostArray[cnt].outboundip)){
result = {
result: false,
message: 'POST request has outbound ip address which is not ignore ip address string: ' + JSON.stringify(hostArray[cnt].outboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
host_info.outboundip = apiutil.getSafeString(hostArray[cnt].outboundip);
}
// push array
if(null !== tg_host){
hostnameArray.push(host_info);
}else{ // null !== tg_ip
ipArray.push(host_info);
}
}
if(apiutil.isEmptyArray(hostnameArray)){
hostnameArray = null;
}
if(apiutil.isEmptyArray(ipArray)){
ipArray = null;
}
var clear_hostname = false;
var clear_ips = false;
if(apiutil.isSafeEntity(req.body.clear_hostname) && 'boolean' === typeof req.body.clear_hostname){
clear_hostname = req.body.clear_hostname;
}
if(apiutil.isSafeEntity(req.body.clear_ips) && 'boolean' === typeof req.body.clear_ips){
clear_ips = req.body.clear_ips;
}
//
// Add hostnames and ips ---> Need User Token
//
result = k2hr3.updateRoleHosts(token_info.user, token_info.tenant, name, hostnameArray, clear_hostname, ipArray, clear_ips);
}else{
//
// request from host(token)
//
// get ip address
var ip = apiutil.getClientIpAddress(req);
if(!apiutil.isSafeString(ip)){
result = {
result: false,
message: 'Could not get ip address from request.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// port
port = 0; // default any
if(apiutil.isSafeEntity(req.body.host.port)){
if(isNaN(req.body.host.port)){
result = {
result: false,
message: 'POST request has port which is not number: ' + JSON.stringify(req.body.host.port)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
port = parseInt(req.body.host.port);
}
// cuk
cuk = null; // default any
if(apiutil.isSafeString(req.body.host.cuk) && apiutil.isSafeString(req.body.host.cuk.trim())){
cuk = apiutil.getSafeString(req.body.host.cuk).trim();
}
// extra
extra = null;
if(apiutil.isSafeString(req.body.host.extra)){
extra = apiutil.getSafeString(req.body.host.extra);
if(apiutil.checkSimpleJSON(extra)){
extra = JSON.parse(extra);
}
}
// tag
tag = null;
if(apiutil.isSafeString(req.body.host.tag)){
tag = apiutil.getSafeString(req.body.host.tag);
if(apiutil.checkSimpleJSON(tag)){
tag = JSON.parse(tag);
}
}
// inboundip(optional)
var inboundip = null;
if(apiutil.isSafeString(req.body.host.inboundip)){
if(!apiutil.isIpAddressString(req.body.host.inboundip)){
result = {
result: false,
message: 'POST request has inbound ip address which is not ignore ip address string: ' + JSON.stringify(req.body.host.inboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
inboundip = apiutil.getSafeString(req.body.host.inboundip);
}
// outboundip(optional)
var outboundip = null;
if(apiutil.isSafeString(req.body.host.outboundip)){
if(!apiutil.isIpAddressString(req.body.host.outboundip)){
result = {
result: false,
message: 'POST request has outbound ip address which is not ignore ip address string: ' + JSON.stringify(req.body.host.outboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
outboundip = apiutil.getSafeString(req.body.host.outboundip);
}
//
// Add ip address ---> Role Token or User Token
//
result = k2hr3.addHost(token_info.tenant, name, null, ip, port, cuk, extra, tag, inboundip, outboundip);
}
//------------------------------
// check result
//------------------------------
if(!apiutil.isSafeEntity(result) || !apiutil.isSafeEntity(result.result) || false === result.result){
if(!apiutil.isSafeEntity(result)){
result = {
result: false,
message: 'Could not get response from addHost'
};
}else{
if(!apiutil.isSafeEntity(result.result)){
result.result = false;
}
if(!apiutil.isSafeEntity(result.message)){
result.message = 'Could not get error message in response from addHost';
}
}
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
r3logger.dlog('succeed : ' + result.message);
res.status(201); // 201: Created
res.send(JSON.stringify(result));
}
//
// Sub router function for PUT CREATE HOST
//
// Mountpath : '/v1/role'
// PUT '/v1/role/<role{/...}>' : put role on version 1
// HEADER : X-Auth-Token => User token or Role token
// response body : result => true/false
// message => messages
//
// [UserToken] url argument
// "host": <hostname or ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/{name, ip}/<hostname port cuk>"
// "port": <port number> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is number string(0-), allowed null and '' for this value.
// if this value is '0', it means any port.
// "cuk": <container unique key> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is string. if this value is undefined/null/empty string, it means any.
// "extra": <extra string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// This value must be encoded by JSON.
// extra is any string including Control code, allowed null and '' for this value.
// "tag": <string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// This value must be encoded by JSON.
// tag is any string including Control code, allowed null and '' for this value.
// "inboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// inboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// "outboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// outboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
//
// [RoleToken] url argument
// "port": <port number> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/ip/<ip port cuk>"
// this value is number string(0-), allowed null and '' for this value.
// if this value is '0', it means any port.
// "cuk": <container unique key> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/name/<hostname port cuk>"
// this value is string. if this value is undefined/null/empty string, it means any.
// "extra": <extra string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// This value must be encoded by JSON.
// extra is any string including Control code, allowed null and '' for this value.
// "tag": <string data> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// This value must be encoded by JSON.
// tag is any string including Control code, allowed null and '' for this value.
// "inboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// inboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
// "outboundip": <ip address> => key is "yrn:yahoo:<service>::<tenant>:role:<role>/hosts/..."
// outboundip is set ip address string. if you do not use proxy/gateway/bridge/etc, you do not need to set this key.
//
// [NOTE]
// This API only set(add/create) host into role. Ether hostname or ip address must be specified.
// If port number is 0, it means any port.
// If cuk is undefined/null/empty string, it means any.
// Extra data can include control-code(CR, etc).
//
function putRoleHost(role, req, res, next) // eslint-disable-line no-unused-vars
{
r3logger.dlog('CALL:', req.method, req.url);
res.type('application/json; charset=utf-8');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.query) )
{
result = {
result: false,
message: 'PUT argument does not have any data'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
//------------------------------
// check token
//------------------------------
var token_result = r3token.checkToken(req, true); // scoped, both token
if(!token_result.result){
r3logger.elog(token_result.message);
var _status = token_result.status;
delete token_result.status;
resutil.errResponse(req, res, _status, token_result);
return;
}
var is_host_req = !apiutil.isSafeString(req.query.host);
var token_info = token_result.token_info;
var keys = r3keys(token_info.user, token_info.tenant);
//------------------------------
// check arguments
//------------------------------
// role name check
var name = apiutil.getSafeString(role);
name = name.toLowerCase();
var nameptn = new RegExp('^' + keys.ROLE_TOP_KEY + ':(.*)'); // regex = /^yrn:yahoo:<service>::<tenant>:role:(.*)/
var namematchs = name.match(nameptn);
if(!apiutil.isEmptyArray(namematchs) && 2 <= namematchs.length){
// name is full yrn, then reset only name.
name = namematchs[1];
}else{
// role name is not full yrn, then check other yrn path
nameptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/
if(name.match(nameptn)){
result = {
result: false,
message: 'POST request url has wrong yrn full path to role'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
// hostname
var hostname= null;
var ip = null;
if(!is_host_req){
if(!apiutil.isSafeString(req.query.host)){
result = {
result: false,
message: 'host is not specified.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
var tg_host = apiutil.getSafeString(req.query.host);
if(apiutil.isIpAddressString(tg_host)){
ip = tg_host.toLowerCase();
}else{
hostname= tg_host.toLowerCase();
}
}else{
// get ip address
ip = apiutil.getClientIpAddress(req);
if(!apiutil.isSafeString(ip)){
result = {
result: false,
message: 'Could not get ip address from request.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
// port
var port;
if(apiutil.isSafeString(req.query.port)){
if(isNaN(req.query.port)){
result = {
result: false,
message: 'PUT request has port which is not number: ' + JSON.stringify(req.query.port)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
port = parseInt(req.query.port);
}else{
port = 0; // default any
}
// cuk
var cuk;
if(apiutil.isSafeString(req.query.cuk) && apiutil.isSafeString(req.query.cuk.trim())){
cuk = apiutil.getSafeString(req.query.cuk).trim();
}else{
cuk = null;
}
// extra
var extra;
if(apiutil.isSafeString(req.query.extra)){
extra = apiutil.getSafeString(req.query.extra);
if(apiutil.checkSimpleJSON(extra)){
extra = JSON.parse(extra); // extra encoded JSON
}
}else{
extra = null;
}
// tag
var tag;
if(apiutil.isSafeString(req.query.tag)){
tag = apiutil.getSafeString(req.query.tag);
if(apiutil.checkSimpleJSON(tag)){
tag = JSON.parse(tag); // tag encoded JSON
}
}else{
tag = null;
}
// make base host information
var host_info = { ip: ip, hostname: hostname, port: port, cuk: cuk, extra: extra, tag: tag };
// set inboundip(optional)
var inboundip = null;
if(apiutil.isSafeString(req.query.inboundip)){
if(!apiutil.isIpAddressString(req.query.inboundip)){
result = {
result: false,
message: 'PUT request has inbound ip address which is not ignore ip address string: ' + JSON.stringify(req.query.inboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
inboundip = apiutil.getSafeString(req.query.inboundip);
host_info.inboundip = inboundip;
}
// set outboundip(optional)
var outboundip = null;
if(apiutil.isSafeString(req.query.outboundip)){
if(!apiutil.isIpAddressString(req.query.outboundip)){
result = {
result: false,
message: 'PUT request has outbound ip address which is not ignore ip address string: ' + JSON.stringify(req.query.outboundip)
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
outboundip = apiutil.getSafeString(req.query.outboundip);
host_info.outboundip= outboundip;
}
//------------------------------
// add host to role
//------------------------------
if(!is_host_req){
// Add hostname ---> Need User Token
if(null === ip){
result = k2hr3.updateRoleHosts(token_info.user, token_info.tenant, name, host_info);
}else{
result = k2hr3.updateRoleHosts(token_info.user, token_info.tenant, name, null, false, host_info);
}
}else{
// Add ip address ---> Role Token or User Token
result = k2hr3.addHost(token_info.tenant, name, null, ip, port, cuk, extra, tag, inboundip, outboundip);
}
if(!apiutil.isSafeEntity(result) || !apiutil.isSafeEntity(result.result) || false === result.result){
if(!apiutil.isSafeEntity(result)){
result = {
result: false,
message: 'Could not get response from addHost'
};
}else{
if(!apiutil.isSafeEntity(result.result)){
result.result = false;
}
if(!apiutil.isSafeEntity(result.message)){
result.message = 'Could not get error message in response from addHost';
}
}
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
r3logger.dlog('succeed : ' + result.message);
res.status(201); // 201: Created
res.send(JSON.stringify(result));
}
//---------------------------------------------------------
// Router GET
//---------------------------------------------------------
//
// Mountpath : '/v1/role/<role{/...}>'
//
// GET '/v1/role/<role{/...}>' : get role on version 1
// HEADER : X-Auth-Token => User token
// URL arguments : expand => "true"(default) or "false"
// response : {
// "result": true or false
// "message": error message
// "role": {
// policies: array,
// aliases: array <--- only not expand
// hosts: { <--- only not expand
// 'hostnames': [ hostname array or empty array
// <hostname> <port> <cuk> <extra> <tag>, (if any port, port is *)
// ...
// ],
// 'ips': [ ip address array or empty array
// <ip address> <port> <cuk> <extra> <tag>,(if any port, port is *)
// ...
// ]
// }
// }
// }
//
// GET '/v1/role/token/<role{/...}>' : get role token on version 1
// HEADER : X-Auth-Token => User token or Role token
// URL arguments : expire => "expire time(unix time value)" or undefined(default 24H)
// response : {
// "result": true or false
// "message": error message
// "token": "role token"
// "registerpath": "path for registering"
// }
//
// GET '/v1/role/token/list/<role{/...}>': get list of role tokens on version 1
// HEADER : X-Auth-Token => User token
// URL arguments : expand => "true"(default) or "false"
// response : {
// result: true/false
// message: null or error message string
// tokens: {
// "token": {
// date: create date(UTC ISO 8601)
// expire: expire date(UTC ISO 8601)
// user: user name if user created this token
// hostname: hostname if this token was created by host(name)
// ip: ip address if this token was created by ip
// port: port number, if specified port when created token
// cuk: cuk, if specified cuk when created token
// },
// ...
// }
// }
// or
// {
// result: true/false
// message: null or error message string
// tokens: [
// "role token",
// ....
// ]
// }
//
// This mount point is for creating(update) role or creating(update) host in role.
// And get role token by host(ip address) or user(user token), update role token by
// role token.
//
router.get('/', function(req, res, next)
{
r3logger.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');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.baseUrl) )
{
result = {
result: false,
message: 'GET request or url is wrong'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
//------------------------------
// check token for API mode
//------------------------------
var token_str = null;
var token_type = null;
var token_info = null;
var keys = r3keys();
if(r3token.hasAuthTokenHeader(req)){
var token_result = r3token.checkToken(req, true); // scoped, both token
if(!token_result.result){
r3logger.elog(token_result.message);
var _status = token_result.status;
delete token_result.status;
resutil.errResponse(req, res, _status, token_result);
return;
}
token_str = token_result.token;
token_type = token_result.token_type;
token_info = token_result.token_info;
keys = r3keys(token_info.user, token_info.tenant);
}
//------------------------------
// get role name
//------------------------------
// check get token type and parse role name
var is_get_token= false;
var is_get_list = false;
var requestptn = new RegExp(keys.MATCH_URI_GET_RTOKEN_LIST); // regex = /^\/v1\/role\/token\/list\/(.*)/
var reqmatchs = decodeURI(req.baseUrl).match(requestptn);
if(!apiutil.isEmptyArray(reqmatchs) && 2 <= reqmatchs.length && '' !== apiutil.getSafeString(reqmatchs[1])){
// get list of tokens
is_get_list = true;
}else{
// recheck
requestptn = new RegExp(keys.MATCH_URI_GET_RTOKEN); // regex = /^\/v1\/role\/token\/(.*)/
reqmatchs = decodeURI(req.baseUrl).match(requestptn);
if(!apiutil.isEmptyArray(reqmatchs) && 2 <= reqmatchs.length && '' !== apiutil.getSafeString(reqmatchs[1])){
// get token
is_get_token= true;
}else{
// retry parse role name
requestptn = new RegExp(keys.MATCH_URI_GET_ROLE_DATA); // regex = /^\/v1\/role\/(.*)/
reqmatchs = decodeURI(req.baseUrl).match(requestptn);
if(apiutil.isEmptyArray(reqmatchs) || reqmatchs.length < 2 || '' === apiutil.getSafeString(reqmatchs[1])){
result = {
result: false,
message: 'GET request url does not have role name'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
}
// check role name is only name or full yrn path
var name = reqmatchs[1];
name = name.toLowerCase();
var nameptn = new RegExp('^' + keys.MATCH_ANY_TENANT_ROLE); // regex = /^yrn:yahoo:(.*)::(.*):role:(.*)/
var namematchs = name.match(nameptn);
if(apiutil.isEmptyArray(namematchs) || namematchs.length < 4){
//
// name is not full yrn to role, then check wrong role name
//
nameptn = new RegExp('^' + keys.NO_TENANT_KEY); // regex = /^yrn:yahoo:/
if(name.match(nameptn)){
result = {
result: false,
message: 'GET request query has wrong yrn full path to role'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// role name is not full yrn, we need tenant name
if(!apiutil.isSafeEntity(keys.ROLE_TOP_KEY)){
result = {
result: false,
message: 'GET request role name which is not full yrn, and not token. role name must be full yrn, if token is not specified.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
// make full yrn for role name
name = keys.ROLE_TOP_KEY + ':' + name;
}else{
//
// name is full yrn to role.
// need to check tenant name when token is specified.
//
if(null !== token_type && (!apiutil.isSafeEntity(token_info) || !apiutil.isSafeString(token_info.tenant) || !apiutil.compareCaseString(namematchs[2], token_info.tenant))){
result = {
result: false,
message: 'GET request query has wrong tenant yrn full path(tenant=' + namematchs[2] + ') or not specify tenant.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
// Run
if(is_get_token){
//------------------------------
// GET ROLE TOKEN
//------------------------------
// token_info: null(undefined) => not specify token, put token by host ip address
// user token => put token by user
// role token => update token by role
//
getRoleToken(name, token_info, token_type, token_str, req, res);
}else if(is_get_list){
//------------------------------
// GET LIST OF ROLE TOKENS
//------------------------------
if('user' === apiutil.getSafeString(token_type)){
getListRoleTokens(name, token_info, req, res);
}else{
result = {
result: false,
message: 'GET request without UserToken for getting list of role(' + name + ') tokens, need User Token.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}else{
//------------------------------
// GET ROLE DATA
//------------------------------
if('user' === apiutil.getSafeString(token_type)){
getRole(name, token_info, req, res);
}else{
result = {
result: false,
message: 'GET request without UserToken for getting role(' + name + '), need User Token.'
};
r3logger.elog(result.message);
resutil.errResponse(req, res, 400, result); // 400: Bad Request
return;
}
}
});
//
// Sub router function for GET ROLE DATA
//
// Mountpath : '/v1/role/<role{/...}>'
//
// GET '/v1/role/<role{/...}>' : get role on version 1
// HEADER : X-Auth-Token => User token
// URL arguments : expand => "true"(default) or "false"
// response : {
// "result": true or false
// "message": error message
// "role": {
// policies: array,
// aliases: array <--- only not expand
// hosts: { <--- only not expand
// 'hostnames': [ hostname array or empty array
// <hostname> <port> <cuk> <extra> <tag>, (if any port, port is *)
// ...
// ],
// 'ips': [ ip address array or empty array
// <ip address> <port> <cuk> <extra> <tag>,(if any port, port is *)
// ...
// ]
// }
// }
// }
//
// This mount point is for creating(update) role or creating(update) host in role.
//
function getRole(role, token_info, req, res)
{
r3logger.dlog('CALL:', req.method, req.url);
res.type('application/json; charset=utf-8');
var result;
if( !apiutil.isSafeEntity(req) ||
!apiutil.isSafeEntity(req.query) )
{
result = {
result: false,
message: 'GET request query is wrong'
};
r3logger.elo