From c9a7439cdd6db42d4f8ab2e9ef4207103397c00e Mon Sep 17 00:00:00 2001 From: Antonio Calanducci Date: Wed, 17 Apr 2024 18:36:25 +0200 Subject: [PATCH] Add support for legacy gcube_token --- src/config2.js | 32 ++ src/nginx.default.conf.template | 9 + src/pep.js | 539 ++++++++++++++++++-------------- 3 files changed, 350 insertions(+), 230 deletions(-) create mode 100644 src/config2.js diff --git a/src/config2.js b/src/config2.js new file mode 100644 index 0000000..3d83eec --- /dev/null +++ b/src/config2.js @@ -0,0 +1,32 @@ +export default { config }; + +var config = { + "pep_credentials" : "w1mXHx5agliwL2dc3Bf14jwCduG1NUgf", + "debug": true, + "accounting": { + "scope": "/d4science.research-infrastructures.eu/D4OS/EcologicalRestorationLab", + "service_name": "shinyproxy_bluecloud_erl", + "host": "shinyproxy-ecologicalrestorationlab-pep.d4science.org" + }, + "hosts": [ + { + "host": ["shinyproxy-ecologicalrestorationlab-pep.d4science.org"], + "audience": "shinyproxy_bluecloud-erl", + "allow-basic-auth": "false", + "paths": [ + { + "name": "Default Resource", + "path": "^/?.*$", + "methods": [ + { + "method": "GET" + }, + { + "method": "POST" + } + ] + } + ] + } + ] +} diff --git a/src/nginx.default.conf.template b/src/nginx.default.conf.template index a407ffb..eae7f47 100644 --- a/src/nginx.default.conf.template +++ b/src/nginx.default.conf.template @@ -104,4 +104,13 @@ server { proxy_set_header Content-Type "application/json"; proxy_pass "${ACCOUNTING_SERVICE_BASEURL}/record"; } + + location /_accounting_legacy { + internal; + proxy_method POST; + proxy_http_version 1.1; + proxy_set_header gcube-token "$auth_token"; + proxy_set_header Content-Type "application/json"; + proxy_pass https://accounting-service.d4science.org/accounting-service/record; + } } diff --git a/src/pep.js b/src/pep.js index d65d96f..6fd8119 100644 --- a/src/pep.js +++ b/src/pep.js @@ -1,30 +1,34 @@ export default { enforce_legacy }; -import defaultExport from './config.js'; +import defaultExport from './config2.js'; -function log(c, s){ +function log(c, s) { c.request.log(s) } -var _debug = defaultExport["debug"] -function debug(c, s){ - if(_debug === true){ +var _debug = defaultExport["config"]["debug"] +var _debug = true + +njs.dump(_debug); + +function debug(c, s) { + if (_debug === true) { log(c, s) } } function enforce(r) { - - var context = { - request: r , - config : defaultExport["config"], - backend : (defaultExport.backend ? defaultExport.backend : "/_backend") + + var context = { + request: r, + config: defaultExport["config"], + backend: (defaultExport.backend ? defaultExport.backend : "/_backend") } - + log(context, "Inside NJS enforce for " + r.method + " @ " + r.headersIn.host + r.uri) - + context = computeProtection(context) - + wkf.run(wkf.build(context), context) } @@ -33,45 +37,54 @@ function enforce(r) { function enforce_legacy(r) { var context = { - request: r + request: r, + config: defaultExport["config"] } - - var allowedcontexts = [defaultExport["accounting"]["scope"]] - - log(context, "Inside NJS enforce for " + r.method + " @ " + r.headersIn.host + "/" + r.uri) + + //log(context, JSON.stringify(context.config["accounting"], null, 2)); + var allowedcontexts = [context.config["accounting"]["scope"]] + + + log(context, "Inside NJS enforce for " + r.method + " @ " + r.headersIn.host + "/" + r.uri) + log(context, "debug is " + JSON.stringify(defaultExport["config"])) const token = getGCubeToken(context) - if(token != null){ + //log(context, JSON.stringify(context, null, 2)) + log(context, "gcube token" + token) + if (token != null) { debug(context, "[PEP] token is " + token) exportVariable(context, "auth_token", token) context.request.subrequest("/gcube_user_info") - .then(reply=>{ - if (reply.status === 200) { - debug(context, "[Social Service] got response " + reply.responseBody) - var response = JSON.parse(reply.responseBody); - if(allowedcontexts.indexOf(response.result.context) === -1){ - debug(context, "[PEP] Unathorized context " + response.result.context) + .then(reply => { + if (reply.status === 200) { + debug(context, "[Social Service] got response " + reply.responseBody) + var response = JSON.parse(reply.responseBody); + if (allowedcontexts.indexOf(response.result.context) === -1) { + debug(context, "[PEP] Unathorized context " + response.result.context) + throw new Error("Unauthorized") + } + return response + } else { + log(context, "[Social Service] failed " + reply.status + ":" + reply.responseBody) throw new Error("Unauthorized") } - return response - } else { - debug(context, "[Social Service] failed " + reply.status + ":" + reply.responseBody) - throw new Error("Unauthorized") - } - }).then(userinfo => { - debug(context, "[Social Service] username is " + userinfo.result.username) - context.userinfo = userinfo - context.record = buildAccountingRecord(context) - return context.request.subrequest("/_backend", { method : context.request.method, args : context.request.args, headers : context.request.headersIn}) - }).then(reply=>{ - debug(context, "[{{ sobigdata_ontotagme_service_name }}] response status: " + reply.status) - closeAccountingRecord(context.record, (reply.status === 200 || reply.status === 201 || reply.status === 204)) - context.request.subrequest("/_accounting", { detached : true, body : JSON.stringify([context.record]) }) - r.return(reply.status, reply.responseBody) - }).catch(e => { log(context, "Error .... " + njs.dump(e)); context.request.return(e.message === "Unauthorized" ? 403 : 500)} ) - + }).then(userinfo => { + debug(context, "[Social Service] username is " + userinfo.result.username) + //log(context, njs.dump(context)); + context.userinfo = userinfo + context.record = buildAccountingRecord_legacy(context) + + return context.request.subrequest("/_backend", { method: context.request.method, args: JSON.stringify(context.request.args), headers: context.request.headersIn }) + }).then(reply => { + debug(context, "[{{ sobigdata_ontotagme_service_name }}] response status: " + reply.status) + debug(context, reply.responseBody); + closeAccountingRecord_legacy(context.record, (reply.status === 200 || reply.status === 201 || reply.status === 204)) + context.request.subrequest("/_accounting_legacy", { detached: true, body: JSON.stringify([context.record]) }) + r.return(reply.status, reply.responseBody) + }).catch(e => { log(context, "Error .... " + njs.dump(e)); context.request.return(e.message === "Unauthorized" ? 403 : 500) }) + return - } + } r.return(401, "Authorization required") } @@ -81,17 +94,17 @@ function enforce_legacy(r) { // ######## WORKFLOW FUNCTIONS ############### var wkf = { - - build : (context)=>{ + + build: (context) => { var actions = [ "export_pep_credentials", "parse_authentication", "check_authentication", "export_authn_token", -// "pip", + // "pip", "pdp", -// "export_backend_headers", -// "pass" + // "export_backend_headers", + // "pass" "start_accounting", "pass_and_wait", "close_accounting", @@ -114,35 +127,35 @@ var wkf = { return actions }, - run : (actions, context) => { + run: (actions, context) => { context.request.log("Starting workflow with " + njs.dump(actions)) var w = actions.reduce( - (acc, f) => { return acc.then(typeof(f) === "function" ? f : wkf[f]) }, - Promise.resolve().then(()=>context) + (acc, f) => { return acc.then(typeof (f) === "function" ? f : wkf[f]) }, + Promise.resolve().then(() => context) ) - w.catch(e => { context.request.error(njs.dump(e)); context.request.return(401)} ) + w.catch(e => { context.request.error(njs.dump(e)); context.request.return(401) }) }, - - export_pep_credentials : exportPepCredentials, - export_authn_token : exportAuthToken, - export_backend_headers : exportBackendHeaders, - parse_authentication : parseAuthentication, - check_authentication : checkAuthentication, - verify_token : verifyToken, - request_token : requestToken, - pip : pipExecutor, - pdp : pdpExecutor, - pass : pass, - pass_and_wait : pass_and_wait, - respond_to_client : respondToClient, - start_accounting : buildAccountingRecord, - close_accounting : closeAccountingRecord, - send_accounting : sendAccountingRecord + + export_pep_credentials: exportPepCredentials, + export_authn_token: exportAuthToken, + export_backend_headers: exportBackendHeaders, + parse_authentication: parseAuthentication, + check_authentication: checkAuthentication, + verify_token: verifyToken, + request_token: requestToken, + pip: pipExecutor, + pdp: pdpExecutor, + pass: pass, + pass_and_wait: pass_and_wait, + respond_to_client: respondToClient, + start_accounting: buildAccountingRecord, + close_accounting: closeAccountingRecord, + send_accounting: sendAccountingRecord } -function getGCubeToken(context){ +function getGCubeToken(context) { if (context.request.args["gcube-token"]) { - return context.request["gcube-token"]; + return context.request.args["gcube-token"]; } else if (context.request.headersIn['gcube-token']) { return context.request.headersIn['gcube-token']; } @@ -150,68 +163,68 @@ function getGCubeToken(context){ } -function getTokenField(context, f){ +function getTokenField(context, f) { return context.authn.verified_token[f] } -function exportVariable(context, name, value){ +function exportVariable(context, name, value) { context.request.variables[name] = value return context } -function exportBackendHeaders(context){ +function exportBackendHeaders(context) { return context } -function exportPepCredentials(context){ - if(process.env["pep_credentials"] || process.env["PEP_CREDENTIALS"]){ +function exportPepCredentials(context) { + if (process.env["pep_credentials"] || process.env["PEP_CREDENTIALS"]) { return exportVariable(context, "pep_credentials", "Basic " + process.env["PEP_CREDENTIALS"]) - }else if(context.config["pep_credentials"]){ + } else if (context.config["pep_credentials"]) { return exportVariable(context, "pep_credentials", "Basic " + context.config["pep_credentials"]) - }else{ + } else { throw new Error("Need PEP credentials") } } -function exportAuthToken(context){ +function exportAuthToken(context) { return exportVariable(context, "auth_token", context.authn.token) } -function checkAuthentication(context){ +function checkAuthentication(context) { return context.authn.type === "bearer" ? wkf.verify_token(context) : wkf.request_token(context) } -function parseAuthentication(context){ +function parseAuthentication(context) { context.request.log("Inside parseAuthentication") context.request.log(JSON.stringify(context.request.args, null, 2)) var incomingauth = context.request.headersIn["Authorization"] - - if(!incomingauth) throw new Error("Authentication required"); - + + if (!incomingauth) throw new Error("Authentication required"); + var arr = incomingauth.trim().replace(/\s\s+/g, " ").split(" ") - if(arr.length != 2) throw new Error("Unknown authentication scheme"); - + if (arr.length != 2) throw new Error("Unknown authentication scheme"); + var type = arr[0].toLowerCase() - if(type === "basic" && context.authz.host && (context.authz.host["allow-basic-auth"] || context.authz.host["allow_basic_auth"])) { + if (type === "basic" && context.authz.host && (context.authz.host["allow-basic-auth"] || context.authz.host["allow_basic_auth"])) { var unamepass = Buffer.from(arr[1], 'base64').toString().split(":") - if(unamepass.length != 2) return null; - context.authn = { type : type, raw : arr[1], user : unamepass[0], password : unamepass[1]} + if (unamepass.length != 2) return null; + context.authn = { type: type, raw: arr[1], user: unamepass[0], password: unamepass[1] } return context - }else if(type === "bearer"){ - context.authn = { type : type, raw : arr[1], token : arr[1]} + } else if (type === "bearer") { + context.authn = { type: type, raw: arr[1], token: arr[1] } return context } throw new Error("Unknown authentication scheme"); } -function verifyToken(context){ +function verifyToken(context) { log(context, "Inside verifyToken") debug(context, "Token is " + context.authn.token) var options = { - "body" : "token=" + context.authn.token + "&token_type_hint=access_token" + "body": "token=" + context.authn.token + "&token_type_hint=access_token" } return context.request.subrequest("/jwt_verify_request", options) - .then(reply=>{ + .then(reply => { if (reply.status === 200) { var response = JSON.parse(reply.responseText); if (response.active === true) { @@ -223,39 +236,39 @@ function verifyToken(context){ throw new Error("Unauthorized: " + reply.responseText) } }).then(verified_token => { - context.authn.verified_token = + context.authn.verified_token = JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) return context }) } -function requestToken(context){ +function requestToken(context) { log(context, "Inside requestToken") var options = { - "body" : "grant_type=client_credentials&client_id="+context.authn.user+"&client_secret="+context.authn.password + "body": "grant_type=client_credentials&client_id=" + context.authn.user + "&client_secret=" + context.authn.password } return context.request.subrequest("/jwt_request", options) - .then(reply=>{ + .then(reply => { if (reply.status === 200) { var response = JSON.parse(reply.responseText); context.authn.token = response.access_token context.authn.verified_token = JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) return context - } else if (reply.status === 400 || reply.status === 401){ + } else if (reply.status === 400 || reply.status === 401) { var options = { - "body" : "grant_type=password&username="+context.authn.user+"&password="+context.authn.password + "body": "grant_type=password&username=" + context.authn.user + "&password=" + context.authn.password } return context.request.subrequest("/jwt_request", options) - .then( reply=>{ + .then(reply => { if (reply.status === 200) { - var response = JSON.parse(reply.responseText); - context.authn.token = response.access_token - context.authn.verified_token = - JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) - return context - } else{ - throw new Error("Unauthorized " + reply.status) + var response = JSON.parse(reply.responseText); + context.authn.token = response.access_token + context.authn.verified_token = + JSON.parse(Buffer.from(context.authn.token.split('.')[1], 'base64url').toString()) + return context + } else { + throw new Error("Unauthorized " + reply.status) } }) } else { @@ -264,18 +277,18 @@ function requestToken(context){ }) } -function pipExecutor(context){ +function pipExecutor(context) { log(context, "Inside extra claims PIP") - context.authz.pip.forEach(extra =>{ + context.authz.pip.forEach(extra => { //call extra claim pip function - try{ + try { var operator = extra.operator var result = wkf[operator](context, extra.args) //ensure array and add to extra_claims - if(!(result instanceof Array)) result = [result] - if(!context.extra_claims) context.extra_claims = {}; + if (!(result instanceof Array)) result = [result] + if (!context.extra_claims) context.extra_claims = {}; context.extra_claims[extra.claim] = result - } catch (error){ + } catch (error) { log(context, "Skipping invalid extra claim " + njs.dump(error)) } }) @@ -283,70 +296,70 @@ function pipExecutor(context){ return context } -function pdpExecutor(context){ +function pdpExecutor(context) { log(context, "Inside PDP") return context.authz.pdp(context) } -function umaCall(context){ +function umaCall(context) { log(context, "Inside UMA call") - var options = { "body" : computePermissionRequestBody(context) }; + var options = { "body": computePermissionRequestBody(context) }; return context.request.subrequest("/permission_request", options) - .then(reply =>{ - if(reply.status === 200){ + .then(reply => { + if (reply.status === 200) { debug(context, "UMA call reply is " + reply.status) return context - }else{ - throw new Error("Response for authorization request is not ok " + reply.status + " " + njs.dump(reply.responseText)) + } else { + throw new Error("Response for authorization request is not ok " + reply.status + " " + njs.dump(reply.responseText)) } }) } // Call backend and return reply to client directly -async function pass(context){ +async function pass(context) { log(context, "Inside pass and wait"); const r = context.request - const reply = await r.subrequest(context.backend, { method : r.method, args : r.variables.args, headers : r.headersIn}) + const reply = await r.subrequest(context.backend, { method: r.method, args: r.variables.args, headers: r.headersIn }) debug(context, "[BACKEND] response status: " + reply.status) context.backendresponse = reply return respondToClient(context) } // Pass to backend but instead of returning to client wait for the reply. Thi intercepts the possibility of performing extra action like closing and sending accounting record. -async function pass_and_wait(context){ - log(context, "Inside pass and wait"); - const r = context.request - const reply = await r.subrequest(context.backend, { method : r.method, args : r.variables.args, headers : r.headersIn}) - debug(context, "[BACKEND] response status: " + reply.status) - context.backendresponse = reply - return context +async function pass_and_wait(context) { + log(context, "Inside pass and wait"); + const r = context.request + const reply = await r.subrequest(context.backend, { method: r.method, args: r.variables.args, headers: r.headersIn }) + debug(context, "[BACKEND] response status: " + reply.status) + context.backendresponse = reply + return context } // Definetely return backend response -function respondToClient(context){ - log(context, "Inside respond to client" + njs.dump(context.backendresponse)); - for(let k in context.backendresponse.headersOut){ - context.request.headersOut[k] = context.backendresponse.headersOut[k] - } - context.request.return(context.backendresponse.status, context.backendresponse.responseText) - return context +function respondToClient(context) { + log(context, "Inside respond to client" + njs.dump(context.backendresponse)); + for (let k in context.backendresponse.headersOut) { + context.request.headersOut[k] = context.backendresponse.headersOut[k] + } + context.request.return(context.backendresponse.status, context.backendresponse.responseText) + return context } // ######## AUTHORIZATION PART ############### -function computePermissionRequestBody(context){ - - if(!context.authz.host || !context.authz.path ){ +function computePermissionRequestBody(context) { + + if (!context.authz.host || !context.authz.path) { throw new Error("Enforcemnt mode is always enforcing. Host or path not found...") } - + var audience = computeAudience(context) var grant = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket" var mode = "response_mode=decision" var permissions = computePermissions(context) var extra = "" - if(context.extra_claims){ - extra = - "claim_token_format=urn:ietf:params:oauth:token-type:jwt&claim_token=" + + if (context.extra_claims) { + extra = + "claim_token_format=urn:ietf:params:oauth:token-type:jwt&claim_token=" + JSON.stringify(context.extra_claims).toString("base64url") } var body = audience + "&" + grant + "&" + permissions + "&" + mode + "&" + extra @@ -354,67 +367,67 @@ function computePermissionRequestBody(context){ return body } -function computeAudience(context){ +function computeAudience(context) { var aud = context.request.headersIn.host - if(context.authz.host){ - aud = context.authz.host.audience||context.authz.host.host + if (context.authz.host) { + aud = context.authz.host.audience || context.authz.host.host } return "audience=" + aud } -function computePermissions(context){ +function computePermissions(context) { var resource = context.request.uri - if(context.authz.path){ - resource = context.authz.path.name||context.authz.path.path + if (context.authz.path) { + resource = context.authz.path.name || context.authz.path.path } var scopes = [] - if(context.authz.method && context.authz.method.scopes){ + if (context.authz.method && context.authz.method.scopes) { scopes = context.authz.method.scopes } - if(scopes.length > 0){ - return scopes.map(s=>"permission=" + resource + "#" + s).join("&") + if (scopes.length > 0) { + return scopes.map(s => "permission=" + resource + "#" + s).join("&") } return "permission=" + resource } -function getPath(hostconfig, incomingpath, incomingmethod){ +function getPath(hostconfig, incomingpath, incomingmethod) { var paths = hostconfig.paths || [] var matchingpaths = paths - .filter(p => {return incomingpath.match(p.path) != null}) - .reduce((acc, p) => { - if (!p.methods || p.methods.length === 0) acc.weak.push({ path: p}); - else{ - var matchingmethods = p.methods.filter(m=>m.method.toUpperCase() === incomingmethod) - if(matchingmethods.length > 0) acc.strong.push({ method : matchingmethods[0], path: p}); - } - return acc; - }, { strong: [], weak: []}) - return matchingpaths.strong.concat(matchingpaths.weak)[0] + .filter(p => { return incomingpath.match(p.path) != null }) + .reduce((acc, p) => { + if (!p.methods || p.methods.length === 0) acc.weak.push({ path: p }); + else { + var matchingmethods = p.methods.filter(m => m.method.toUpperCase() === incomingmethod) + if (matchingmethods.length > 0) acc.strong.push({ method: matchingmethods[0], path: p }); + } + return acc; + }, { strong: [], weak: [] }) + return matchingpaths.strong.concat(matchingpaths.weak)[0] } -function getHost(config, host){ - var matching = config.hosts.filter(h=>{ - //compare for both string and array of strings +function getHost(config, host) { + var matching = config.hosts.filter(h => { + //compare for both string and array of strings return ((h.host.filter && h.host.indexOf(host) !== -1) || h.host === host) }) return matching.length > 0 ? matching[0] : null } -function computeProtection(context){ +function computeProtection(context) { debug(context, "Getting by host " + context.request.headersIn.host) context.authz = {} context.authz.host = getHost(context.config, context.request.headersIn.host) - if(context.authz.host !== null){ + if (context.authz.host !== null) { log(context, "Host found: " + njs.dump(context.authz.host)) context.authz.pip = context.authz.host.pip ? context.authz.host.pip : []; context.authz.pdp = context.authz.host.pdp ? context.authz.host.pdp : umaCall; var pathandmethod = getPath(context.authz.host, context.request.uri, context.request.method); - if(pathandmethod){ + if (pathandmethod) { context.authz.path = pathandmethod.path; context.authz.pip = context.authz.path.pip ? context.authz.pip.concat(context.authz.path.pip) : context.authz.pip; context.authz.pdp = context.authz.path.pdp ? context.authz.path.pdp : context.authz.pdp; context.authz.method = pathandmethod.method; - if(context.authz.method){ + if (context.authz.method) { context.authz.pip = context.authz.method.pip ? context.authz.pip.concat(context.authz.method.pip) : context.authz.pip; context.authz.pdp = context.authz.method.pdp ? context.authz.method.pdp : context.authz.pdp; } @@ -425,65 +438,131 @@ function computeProtection(context){ } // ####### ACCOUNTING PART ##################### -function buildAccountingRecord(context){ - log(context, "Inside build accounting record"); - const t = (new Date()).getTime() - context.record = { - "recordType": "ServiceUsageRecord", - "operationCount": 1, - "creationTime": t, - "callerHost": context.request.headersIn["x-forwarded-for"], - "serviceClass": "Application", - "callerQualifier": "TOKEN", - "consumerId": getTokenField(context, "preferred_username"), - "aggregated": true, - "serviceName": defaultExport["accounting"]["service_name"], - "duration": 0, - "maxInvocationTime": 0, - "scope": defaultExport["accounting"]["scope"], - "host": defaultExport["accounting"]["host"], - "startTime": t, - "id": uuid(), - "calledMethod": context.request.method + " " + context.request.uri, - "endTime": 0, - "minInvocationTime": 0, - "operationResult": null - } - debug(context, "Record is " + JSON.stringify(context.record)) - return context - } - - function closeAccountingRecord(context, success){ - log(context, "Inside close accounting"); - const t = (new Date()).getTime() - context.record.duration = t - context.record.startTime - context.record.endTime = t - context.record.minInvocationTime = context.record.duration - context.record.operationResult = success ? "SUCCESS" : "FAILED"; - debug(context, "Record is " + JSON.stringify(context.record)) - return context - } - -async function sendAccountingRecord(context){ - log(context, "Inside send accounting"); - const detached = !_debug; - const subreq_params = { detached : detached, body : JSON.stringify([context.record]) }; - if (_debug) { - // debug(context, JSON.stringify(subreq_params)); - const acc_request = await context.request.subrequest("/accounting", subreq_params); - debug(context, "[ACCOUNTING] response status: " + JSON.stringify(acc_request)); - } else { - context.request.subrequest("/accounting", subreq_params); - } - // const acc_request = await context.request.subrequest("/accounting", { detached : false, body : JSON.stringify([context.record]) }) - // debug(context, "[ACCOUNTING] response status: " + acc_request.status) - // debug(context, "[ACCOUNTING] " + njs.dump(acc_request)); - return context +function buildAccountingRecord(context) { + log(context, "Inside build accounting record"); + const t = (new Date()).getTime() + context.record = { + "recordType": "ServiceUsageRecord", + "operationCount": 1, + "creationTime": t, + "callerHost": context.request.headersIn["x-forwarded-for"], + "serviceClass": "Application", + "callerQualifier": "TOKEN", + "consumerId": getTokenField(context, "preferred_username"), + "aggregated": true, + "serviceName": defaultExport["accounting"]["service_name"], + "duration": 0, + "maxInvocationTime": 0, + "scope": defaultExport["accounting"]["scope"], + "host": defaultExport["accounting"]["host"], + "startTime": t, + "id": uuid(), + "calledMethod": context.request.method + " " + context.request.uri, + "endTime": 0, + "minInvocationTime": 0, + "operationResult": null } + debug(context, "Record is " + JSON.stringify(context.record)) + return context +} - function uuid() { - return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { - const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } \ No newline at end of file +function __buildAccountingRecord_legacy(context) { + log(context, "Inside build accounting record"); + const t = (new Date()).getTime() + context.record = { + "recordType": "ServiceUsageRecord", + "operationCount": 1, + "creationTime": t, + "callerHost": context.request.headersIn["x-forwarded-for"], + "serviceClass": "Application", + "callerQualifier": "TOKEN", + "consumerId": context.userinfo.username, + "aggregated": true, + "serviceName": context.config["accounting"]["service_name"], + "duration": 0, + "maxInvocationTime": 0, + "scope": context.config["accounting"]["scope"], + "host": context.config["accounting"]["host"], + "startTime": t, + "id": uuid(), + "calledMethod": context.request.method + " " + context.request.uri, + "endTime": 0, + "minInvocationTime": 0, + "operationResult": null + } + log(context, "Record is " + JSON.stringify(context.record)) + return context +} + +function buildAccountingRecord_legacy(context) { + const t = (new Date()).getTime() + return { + "recordType": "ServiceUsageRecord", + "operationCount": 1, + "creationTime": t, + "callerHost": context.request.remoteAddress, + "serviceClass": "ShinyApp", + "callerQualifier": "TOKEN", + "consumerId": context.userinfo.username, + "aggregated": true, + "serviceName": context.request.uri.split("app/")[1], + "duration": 0, + "maxInvocationTime": 0, + "scope": context.userinfo.context, + "host": context.request.headersIn.host, + "startTime": t, + "id": uuid(), + "calledMethod": context.request.method + " " + context.request.uri, + "endTime": 0, + "minInvocationTime": 0, + "operationResult": null + } +} + +function closeAccountingRecord_legacy(record, success) { + const t = (new Date()).getTime() + record.duration = t - record.startTime + record.endTime = t + record.minInvocationTime = record.duration + record.operationResult = success ? "SUCCESS" : "FAILED"; +} + + + + + + +function closeAccountingRecord(context, success) { + log(context, "Inside close accounting"); + const t = (new Date()).getTime() + context.record.duration = t - context.record.startTime + context.record.endTime = t + context.record.minInvocationTime = context.record.duration + context.record.operationResult = success ? "SUCCESS" : "FAILED"; + log(context, "Record is " + njs.dump(context.record)) + return context +} + +async function sendAccountingRecord(context) { + log(context, "Inside send accounting"); + const detached = !_debug; + const subreq_params = { detached: detached, body: JSON.stringify([context.record]) }; + if (_debug) { + // debug(context, JSON.stringify(subreq_params)); + const acc_request = await context.request.subrequest("/accounting", subreq_params); + debug(context, "[ACCOUNTING] response status: " + JSON.stringify(acc_request)); + } else { + context.request.subrequest("/accounting", subreq_params); + } + // const acc_request = await context.request.subrequest("/accounting", { detached : false, body : JSON.stringify([context.record]) }) + // debug(context, "[ACCOUNTING] response status: " + acc_request.status) + // debug(context, "[ACCOUNTING] " + njs.dump(acc_request)); + return context +} + +function uuid() { + return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} \ No newline at end of file