153 vault secrets
This commit is contained in:
+4
-2
@@ -51,7 +51,7 @@
|
||||
|
||||
//variables.framework.docs={};
|
||||
variables.framework.docs.APIName="svc-api";
|
||||
variables.framework.docs.APIVersion="0.152"; /*$ git config --global --unset user.password*/
|
||||
variables.framework.docs.APIVersion="0.153"; /*$ git config --global --unset user.password*/
|
||||
|
||||
variables.framework.globalHeaders = structNew();
|
||||
variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location";
|
||||
@@ -69,7 +69,9 @@
|
||||
if (isNull(request.ORCHESTRATOR_AUTH)) {
|
||||
request.ORCHESTRATOR_AUTH = "Basic c3ZjX2xrdGVzdDoxMTgzZDIyNzc1OWExYjFkZmVjM2M3NTFiMzIyNzdiYTQ3";//*** времянка, дубль из deck/Application.cfm
|
||||
}
|
||||
request.vault_role_id=createObject("java", "java.lang.System").getEnv("VAULT_ROLE_ID");// *** дальше к ним обращаемся через this (а надо блокировать приложение при этом?)
|
||||
//request.vault_login_url=createObject("java", "java.lang.System").getEnv("VAULT_LOGIN_URL");
|
||||
request.vault_login_url="https://vault.lk.adl.nubes.ru/v1/auth/approle/login";
|
||||
request.vault_role_id=createObject("java", "java.lang.System").getEnv("VAULT_ROLE_ID");
|
||||
request.vault_secret_id=createObject("java", "java.lang.System").getEnv("VAULT_SECRET_ID");//*** тут в другом скоупе, не как deck, и вообще стоит ли это делать в псевдоконструкторе
|
||||
|
||||
|
||||
|
||||
@@ -1,879 +0,0 @@
|
||||
<cfcomponent extends="taffy.core.api">
|
||||
<!---<cfset this.name = "deck_api" />--->
|
||||
<cfset this.mappings["/resources"] = expandPath("./resources")/>
|
||||
<cfset this.mappings["/taffy"] = expandPath("./taffy")/>
|
||||
<cfset this.mappings["/lib"] = expandPath("./lib")/>
|
||||
|
||||
<cflock scope="application" type="exclusive" timeout="3"><!---*** не уверен, что это необходимо--->
|
||||
<cftry>
|
||||
<!---production environment--->
|
||||
<cfinclude template="../../conf/prod.cfm"/>
|
||||
<cfcatch type="ANY">
|
||||
<cftry>
|
||||
<!---staging environment--->
|
||||
<cfinclude template="../../conf/stage.cfm"/>
|
||||
<cfcatch type="ANY">
|
||||
<cftry>
|
||||
<!--- development environment --->
|
||||
<cfinclude template="../../conf/dev.cfm"/>
|
||||
<cfcatch type="ANY">
|
||||
<!---configure right here--->
|
||||
<!---для ясности--->
|
||||
<cfset this.config.environment = "dev-default" />
|
||||
<cfset this.config.datasource = "cmdb" />
|
||||
<cfset this.config.is_dev_default = "true" /><!---это виднее в сообщении об ошибке--->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cfset this.datasource = this.config.datasource />
|
||||
<cfset request.config = this.config />
|
||||
</cflock>
|
||||
|
||||
<cfscript>
|
||||
this.name = hash(getCurrentTemplatePath());
|
||||
//variables.framework = structNew(); //
|
||||
variables.framework.debugKey = "debug";
|
||||
variables.framework.reloadKey = "reload";
|
||||
variables.framework.reloadPassword = "true";
|
||||
variables.framework.reloadOnEveryRequest = true;
|
||||
variables.framework.serializer = "taffy.core.nativeJsonSerializer";
|
||||
//variables.framework.serializer = "JsonUtilSerializer";
|
||||
variables.framework.dashboardKey = "dashboard";
|
||||
variables.framework.disableDashboard = false;
|
||||
variables.framework.unhandledPaths = "/flex2gateway";
|
||||
variables.framework.allowCrossDomain = true; // соответствующие заголовки, включая Access-Control-Allow-Origin выставляются только при наличии заголовка origin в реквесте
|
||||
//variables.framework.allowCrossDomain = ""; // перепишем явно, возвращая Origin
|
||||
|
||||
//variables.framework.docs={};
|
||||
variables.framework.docs.APIName="svc-api";
|
||||
variables.framework.docs.APIVersion="0.150"; /*$ git config --global --unset user.password*/
|
||||
|
||||
variables.framework.globalHeaders = structNew();
|
||||
variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location";
|
||||
variables.framework.globalHeaders["Access-Control-Allow-Credentials"] = "true";
|
||||
variables.framework.globalHeaders["X-Application-Version"] = variables.framework.docs.APIVersion;
|
||||
//variables.framework.globalHeaders["Access-Control-Allow-Origin"] = "*";
|
||||
|
||||
request.UNDEFINED_USR_ID=-1;
|
||||
request.ANONYMOUS_USR_ID=2;
|
||||
request.GUEST_USR_ID=3;
|
||||
request.USER_AGENT="#variables.framework.docs.APIName# #variables.framework.docs.APIVersion# stand:#this.getStand()#";
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
request.ORCHESTRATOR_AUTH = createObject("java", "java.lang.System").getEnv("ORCHESTRATOR_AUTH");
|
||||
if (isNull(request.ORCHESTRATOR_AUTH)) {
|
||||
request.ORCHESTRATOR_AUTH = "Basic c3ZjX2xrdGVzdDoxMTgzZDIyNzc1OWExYjFkZmVjM2M3NTFiMzIyNzdiYTQ3";//*** времянка, дубль из deck/Application.cfm
|
||||
}
|
||||
request.vault_role_id=createObject("java", "java.lang.System").getEnv("VAULT_ROLE_ID");// *** дальше к ним обращаемся через this (а надо блокировать приложение при этом?)
|
||||
request.vault_secret_id=createObject("java", "java.lang.System").getEnv("VAULT_SECRET_ID");//*** тут в другом скоупе, не как deck, и вообще стоит ли это делать в псевдоконструкторе
|
||||
|
||||
|
||||
//*** почему-то scope this ведет себя совершенно не так, как request и variables
|
||||
// в нем, когда переменная получает значение Empty:null, можно выполнить writedump, а в других получаем ошибку, что такой переменной нет
|
||||
|
||||
request.auth_header="";
|
||||
request.locateIamService=locateIamService; //for possible debug
|
||||
//request.IAM_SERVICE_URL=this.iamServiceUrl;
|
||||
</cfscript>
|
||||
|
||||
|
||||
<cffunction name="rethrow" returntype="void">
|
||||
<!--- https://www.raymondcamden.com/2004/03/09/3089633C-9FA0-606B-3F540AE9642A795F --->
|
||||
<cftry>
|
||||
<cfcatch>
|
||||
<cfrethrow/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfthrow type="Context validation error" message="RETHROW() called outside TRY-CATCH"/>
|
||||
</cffunction>
|
||||
|
||||
<cffunction name="castToBool" returntype="any">
|
||||
<!--- приводит поле резалтсета к булевому типу, пригодному для сериализации в json --->
|
||||
<cfargument name="x"/>
|
||||
<cfif isNull(arguments.x) OR isEmpty(arguments.x)>
|
||||
<cfreturn arguments.x/><!--- при сериализации преобразуется в null --->
|
||||
<cfelse>
|
||||
<cfreturn (arguments.x NEQ 0)/><!--- при сериализации дает false/true --->
|
||||
</cfif>
|
||||
</cffunction>
|
||||
<cfset request.castToBool=#castToBool#/>
|
||||
|
||||
<cffunction name="castToBoolSimple" returntype="any">
|
||||
<!---
|
||||
приводит поле резалтсета к булевому типу, пригодному для сериализации в json,
|
||||
вместо null возвращает false
|
||||
--->
|
||||
<cfargument name="x"/>
|
||||
<cfreturn (arguments.x NEQ 0)/><!--- при сериализации дает false/true --->
|
||||
</cffunction>
|
||||
<cfset request.castToBoolSimple=#castToBoolSimple#/>
|
||||
|
||||
<!--- почему-то эта функция недоступна onApplicationStart --->
|
||||
<cffunction
|
||||
name="getStand"
|
||||
access="private"
|
||||
returntype="string"
|
||||
output="true">
|
||||
|
||||
<cftry>
|
||||
<cfquery name=qConfig>
|
||||
select value as stand from config
|
||||
where name='STAND'
|
||||
</cfquery>
|
||||
<cfreturn qConfig.stand/>
|
||||
|
||||
<cfcatch type="ANY">
|
||||
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
<cfreturn ""/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="onApplicationStart">
|
||||
<!--- <cfset application.dbUser = "username" />
|
||||
<cfset application.dbPass = "password" />--->
|
||||
<!---<cfset application._taffy.settings.allowCrossDomain = true/>--->
|
||||
<cfset this.iamServiceUrl=locateIamService()/> <!--- ***при недоступности будет падать все --->
|
||||
<cfreturn super.onApplicationStart() />
|
||||
</cffunction>
|
||||
|
||||
|
||||
|
||||
<cffunction name="locateIamService"><!--- *** архитектурный колхоз --->
|
||||
<!--- почему-то мы не видим других функций, когда нас вызывают onApplicationStart. С другой стороны, а почему вообще должны вызывать без перезагрузки приложения --->
|
||||
<cfset var stand=""/>
|
||||
<!--- дублируем код, потому что getStand отсюда почему-то не виден --->
|
||||
<cftry>
|
||||
<cfquery name=qConfig>
|
||||
select value as stand from config
|
||||
where name='STAND'
|
||||
</cfquery>
|
||||
<cfset stand = qConfig.stand/>
|
||||
|
||||
<cfswitch expression=#stand#>
|
||||
<cfcase value=",dev">
|
||||
<cfreturn "https://auth-api-dev.ngcloud.ru/api/v1/auth/user"/>
|
||||
</cfcase>
|
||||
<cfcase value="test">
|
||||
<cfreturn "https://auth-api-test.ngcloud.ru/api/v1/auth/user"/>
|
||||
</cfcase>
|
||||
<cfcase value="prod">
|
||||
<cfreturn "https://auth-api.ngcloud.ru/api/v1/auth/user"/>
|
||||
</cfcase>
|
||||
<cfdefaultcase><!--- proceed to the legacy locator ---></cfdefaultcase>
|
||||
</cfswitch>
|
||||
|
||||
<cfcatch type="ANY">
|
||||
<!--- do nothing, proceed to the legacy locator --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<!--- вот если ничего не прописано, попробуем по-старинке --->
|
||||
<cfloop list="https://auth-api-dev.ngcloud.ru/api/v1/auth/user,https://auth-api-test.ngcloud.ru/api/v1/auth/user,https://auth-api.ngcloud.ru/api/v1/auth/user" index="serviceUrl"><!--- *** начиная с девелоперского --->
|
||||
<cftry>
|
||||
<cfhttp url=#serviceUrl# method="OPTIONS" result="resp" timeout=1/>
|
||||
|
||||
<cfreturn serviceUrl/>
|
||||
<cfcatch type="any">
|
||||
<!--- <cfdump var=#serviceUrl#/>
|
||||
<cfdump var=#cfcatch#/><cfabort/> --->
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfloop>
|
||||
<cfthrow message="IAM service unavailable" detail="cannot find IAM"/>
|
||||
</cffunction>
|
||||
|
||||
<!--- <cffunction name="onRequest">
|
||||
<cfargument name="template" type="string" required="true"/>
|
||||
<cfset request.startTickCount=getTickCount()/>
|
||||
<!---<cfheader name="Access-Control-Allow-Origin" value="*"/>--->
|
||||
<cfreturn super.onRequest(template) />
|
||||
</cffunction> --->
|
||||
|
||||
|
||||
|
||||
<cfscript>
|
||||
|
||||
function onTaffyRequest(verb, cfc, requestArguments, mimeExt, headers){
|
||||
/* https://docs.taffy.io/#/3.5.0 */
|
||||
/*
|
||||
//allow white-listed requests through
|
||||
|
||||
if (cfc == "login"){
|
||||
return true;
|
||||
}
|
||||
|
||||
//otherwise require a device token
|
||||
if (!structKeyExists(requestArguments, "deviceToken")){
|
||||
return newRepresentation().noData().withStatus(401, "Authentication Required");
|
||||
|
||||
//and make sure it's valid
|
||||
}else if (!validateToken(requestArguments.deviceToken)){
|
||||
|
||||
return newRepresentation().noData().withStatus(403, "Not Authorized");
|
||||
}
|
||||
|
||||
//return representationOf(requestArguments);
|
||||
//if a token is included, and valid, allow the request to continue
|
||||
return true;
|
||||
*/
|
||||
////////////////////////////////////////////////
|
||||
// самым грубым образом получаем данные от IDP, не обрабатывая исключения
|
||||
|
||||
|
||||
//if (variables.framework.allowCrossDomain EQ "") {
|
||||
//corsHeaders(); // *** нужно, чтобы variables.framework.allowCrossDomain="" иначе задвоятся заголовки
|
||||
//}
|
||||
|
||||
//if (UCase(arguments.verb) EQ 'OPTIONS') return newRepresentation().noData().withStatus("204","No Data").withHeaders({"Content-Type":"application/json;charset=utf-8"});
|
||||
if (uCase(arguments.verb) EQ 'OPTIONS') return true;
|
||||
if (lCase(arguments.cfc) EQ 'err') return true;
|
||||
if (lCase(arguments.cfc) EQ 'throw') return true;
|
||||
|
||||
|
||||
if (structKeyExists(headers,"Authorization")) {
|
||||
request.auth_header=headers.Authorization;
|
||||
} else {
|
||||
return representationOf("Authorization header expected").withStatus(401);
|
||||
}
|
||||
var result="";
|
||||
try {
|
||||
var httpService = new http(method = "GET", charset = "utf-8", url = #this.iamServiceUrl#, timeout="5");
|
||||
httpService.addParam(type = "HEADER", name = "Accept", value = "application/json");
|
||||
httpService.addParam(type = "HEADER", name = "Authorization", value = "#request.auth_header#"); //passthrough
|
||||
//writedump(this.iamServiceUrl);abort;
|
||||
var resp = httpService.send();
|
||||
//if (resp.status_code NEQ 200) throw("IDP response not OK");
|
||||
//writedump(resp);abort;
|
||||
var prefix = resp.getPrefix();
|
||||
if (prefix.status_code NEQ 200) {
|
||||
//writedump(this.iamServiceUrl);
|
||||
//writedump(resp);
|
||||
var iamStatusCode = (isValid("integer", prefix.status_code)) ? val(prefix.status_code) : 500;
|
||||
return representationOf( {"iamServiceUrl"=this.iamServiceUrl, "idpResponse"=resp} ).withStatus(iamStatusCode);
|
||||
//abort;
|
||||
//throw("IDP response not OK");
|
||||
}
|
||||
result = prefix.filecontent;
|
||||
//writedump(result);abort;
|
||||
} catch (e) {
|
||||
/*if (fileExists("#GetDirectoryFromPath(GetCurrentTemplatePath())#/etc/local-debug")) { //true for local debug without IDP, etc/* does not go to repository
|
||||
var result='{"accounts":[{"login":"","type":"telegram"}],"avatar":["d2d2b7ac-50af-432b-b7c4-f7d6561e288a"],"company":"ООО «НУБЕС»","companyId":"8ec70ac0-546d-42a7-8cff-339c8fb51a23","contactId":"983967a3-58c2-4cdd-84a5-8b427ccfac82","email":"smishchuk@nubes.ru","externalUser":false,"fio":{"fullName":"Мищук Сергей ","name":"Сергей","secondName":"","surname":"Мищук"},"groupIds":["94bf5be7-52f4-5c13-87c3-513786934685","ab12b6b8-0265-4683-a5c2-5e1a74a55216","aff008c3-7443-50f6-86e1-ecff3cd3b04d","d6000da0-c9aa-55eb-9882-f118b432730b","d89a33dc-3177-5854-9580-f7f860a5ab7c","ddfe2555-9ef4-42cb-9a2b-7f24e61e7747","df12926f-ecb8-5479-857f-6d291464baad","fda5c295-230a-5025-9797-b8b4e99e08aa","2be9b4b6-94d1-59f9-a649-cab228d82169","64fe6f5c-a91b-5fea-a7ca-d9823701ebd4"],"integration":{"serviceId":""},"login":"smishchuk@nubes.ru","mobilePhone":[],"position":"","userId":"d72530e1-66a4-412f-a046-38153c4e5405"}';}
|
||||
*/
|
||||
//writedump(resp);
|
||||
//return representationOf( {"exception"=e} );
|
||||
return representationOf( {"exception"=e, "idpResponse"=result} ).withStatus(200);
|
||||
}
|
||||
|
||||
//writeDump(result); abort;
|
||||
try {
|
||||
var idpUserData=deserializeJson(result);
|
||||
arguments.requestArguments.companyUid=idpUserData.userInfo.companyId;//GUID!
|
||||
arguments.requestArguments.usrUid=idpUserData.userInfo.contactId;//userId; //GUID!
|
||||
} catch (e) {
|
||||
return representationOf( {"exception"=e, "idpResponse"=result} );
|
||||
}
|
||||
|
||||
var usrCustomerInfo=getUsrCustomerInfo(idpUserData.userInfo.contactId, idpUserData.userInfo.companyId);
|
||||
|
||||
if (lCase(arguments.cfc) EQ 'user') {
|
||||
if (structIsEmpty(usrCustomerInfo)) return representationOf("User information not found").withStatus(404);
|
||||
}
|
||||
|
||||
if (lCase(arguments.cfc) EQ 'notification_ls') { //*** костыль: для нотификаций можно адресоваться к пользователю без контракта
|
||||
if (structIsEmpty(usrCustomerInfo)) {
|
||||
arguments.requestArguments.usrId=-1; //Integer!
|
||||
arguments.requestArguments.contragentId=-1; //Integer
|
||||
arguments.requestArguments.contractId=-1; //Integer
|
||||
arguments.requestArguments.specificationId=-1; //Integer
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (structIsEmpty(usrCustomerInfo)) return representationOf("Cannot find default specification for current user #result#").withStatus(422); //это создает довольно много лишних движений при отладке
|
||||
|
||||
arguments.requestArguments.usrId=usrCustomerInfo.usrId; //Integer!
|
||||
arguments.requestArguments.contragentId=usrCustomerInfo.contragentId; //Integer
|
||||
arguments.requestArguments.contractId=usrCustomerInfo.contractId; //Integer
|
||||
arguments.requestArguments.specificationId=usrCustomerInfo.specificationId; //Integer
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//ensure https because of basic authentication
|
||||
if (!lcase(request.config.environment) == "development"){
|
||||
if (!cgi.server_port_secure) { return newRepresentation().noData().withStatus("403.4", "SSL Required");}
|
||||
}
|
||||
|
||||
request.usr_id=request.UNDEFINED_USR_ID;
|
||||
|
||||
if (checkAuth()) {return true;}
|
||||
|
||||
//return representationOf(arguments.headers);
|
||||
//return representationOf(arguments.cfc);
|
||||
var authToken="";
|
||||
if (structKeyExists(arguments.headers,"x-auth-token")) {authToken=structFind(arguments.headers,"x-auth-token");}
|
||||
var issuedFor="";
|
||||
if (structKeyExists(arguments.headers,"x-issued-for")) {issuedFor=structFind(arguments.headers,"x-issued-for");}
|
||||
if (validatePin(authToken,issuedFor, arguments.verb, arguments.cfc)) return true; //side effect: sets request.usr_id!
|
||||
|
||||
if (!validateCredentials(getBasicAuthCredentials())) {
|
||||
return newRepresentation().noData().withStatus("401", "Invalid Credentials").withHeaders({"WWW-Authenticate":"Basic realm=mms-api"});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//private function validateToken(token){ return false; }
|
||||
|
||||
//refactoring required
|
||||
private function validateCredentials(credentials) {//return true;
|
||||
if (arguments.credentials.username == "medved" /*AND credentials.password == "ghtdtlrhjcfdxtu!Y"*/) {
|
||||
request.usr_id=-3;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
</cfscript>
|
||||
|
||||
<!--- стоит ли сообщать отдельно об экспирации пина
|
||||
нужно блокировать перебор, можно делать пару pin-issuedFor
|
||||
вероятно, лучше сделать токен девайса
|
||||
--->
|
||||
|
||||
|
||||
|
||||
<cffunction name="checkAuth">
|
||||
<!---https://www.sjoerdlangkemper.nl/2018/09/12/authorization-header-and-cors/--->
|
||||
<cfset var jwtHelper=CreateObject("component","lib.jwt").init()/>
|
||||
<cfset var headers=#GetHttpRequestData().headers#/>
|
||||
<cftry>
|
||||
<cfset var rawToken=right(headers.Authorization,len(headers.Authorization)-len('bearer '))/>
|
||||
<cfset var token=jwtHelper.decode(token=rawToken, key=request.config.IDP_certificate.keys[1], algorithms='RS256')/>
|
||||
|
||||
<cfset var login=token.preferred_username/>
|
||||
<!---отрезаем первичный домен --->
|
||||
<cfset login=ReplaceNoCase(login,"#request.config.auth_domain_suffix#","")/>
|
||||
<cfquery name="local.qUsr">
|
||||
select usr_id from usr where login=<cfqueryparam cfsqltype="cf_sql_varchar" value="#login#"/>
|
||||
</cfquery>
|
||||
|
||||
<cfif #local.qUsr.recordCount# EQ 0>
|
||||
<cfheader statuscode="401" statustext="User not found in local database"/>
|
||||
<cfreturn false/>
|
||||
</cfif>
|
||||
|
||||
<cfset request.usr_id=local.qUsr.usr_id/>
|
||||
|
||||
<cfcatch type="database">
|
||||
<cfheader statuscode="500" statustext="Internal Server Error - Database Error"/>
|
||||
<cfoutput>#cfcatch.message# : #cfcatch.detail#</cfoutput>
|
||||
<cfreturn false/>
|
||||
</cfcatch>
|
||||
|
||||
<cfcatch type="ANY">
|
||||
<cfif !GetHttpRequestData().method EQ "OPTIONS">
|
||||
<cfoutput>#cfcatch.message# : #cfcatch.detail#</cfoutput>
|
||||
<cfreturn false/>
|
||||
</cfif>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cfreturn true/>
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="getUsrCustomerInfo">
|
||||
<cfargument name="usrUid"/>
|
||||
<cfargument name="contragentUid"/>
|
||||
|
||||
<!--- глупейшее определение спецификации по умолчанию (надо переделать, чтобы создавалась, или вообще просто записывать факты без спеки). Но помним, что при постановке на тестирование цены еше не фиксированы --->
|
||||
<!--- заметим, что у нас контрагент может быть определен параллельно, от IDP --->
|
||||
<cfset local={}/>
|
||||
|
||||
<!--- Во избежании потери времени при отладке временно сделаем создание дефолтового контракта и спецификации, если их нет --->
|
||||
<!--- *** хватается первый попавшийся контракт и спецификация --->
|
||||
|
||||
<cfquery name="local.qGetContragentInfo">
|
||||
select z.contragent_id, c.contragent_id, c.contract_id, s.specification_id
|
||||
from contragent z
|
||||
join contract c on (z.contragent_id=c.contragent_id)
|
||||
join specification s on (c.contract_id=s.contract_id)
|
||||
where z.external_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.contragentUid#" null=#!isValid('guid',arguments.contragentUid)#/>
|
||||
order by s.specification_id desc
|
||||
limit 1;
|
||||
</cfquery>
|
||||
|
||||
|
||||
<cfquery name="local.qGetUserInfo">
|
||||
select u.usr_id, c.contragent_id, c.contract_id, s.specification_id
|
||||
from usr u
|
||||
join contract c on (u.contragent_id=c.contragent_id)
|
||||
join specification s on (c.contract_id=s.contract_id)
|
||||
where u.idp_usr_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.usrUid#" null=#!isValid('guid',arguments.usrUid)#/>
|
||||
order by s.specification_id desc
|
||||
limit 1;
|
||||
</cfquery>
|
||||
|
||||
<!--- <cfdump var=#local.qGetCustomerInfo#/><cfabort/> --->
|
||||
<cfreturn {
|
||||
"usrId"=#local.qGetUserInfo.usr_id#,
|
||||
"contragentId"=#local.qGetContragentInfo.contragent_id#,
|
||||
"contractId"=#local.qGetContragentInfo.contract_id#,
|
||||
"specificationId"=#local.qGetContragentInfo.specification_id#
|
||||
}/>
|
||||
|
||||
</cffunction>
|
||||
|
||||
|
||||
<cffunction name="corsHeaders">
|
||||
<!--- *** фрагмент взят из taffy/core/api.cfc и немного переписан --->
|
||||
<cfset var _taffyRequest=request._taffyRequest/>
|
||||
<cfset local={}/>
|
||||
|
||||
<cfset local.allowVerbs = uCase(structKeyList(_taffyRequest.matchDetails.methods)) />
|
||||
<cfif true
|
||||
AND listFindNoCase('PUT,PATCH,DELETE,OPTIONS',_taffyRequest.verb)
|
||||
AND NOT listFind(local.allowVerbs,'OPTIONS')>
|
||||
<cfset local.allowVerbs = listAppend(local.allowVerbs,'OPTIONS') />
|
||||
</cfif>
|
||||
<cfif structKeyExists(_taffyRequest.headers, "origin") >
|
||||
|
||||
<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
|
||||
|
||||
<cfheader name="Access-Control-Allow-Methods" value="#local.allowVerbs#" />
|
||||
<!--- Why do we parrot back these headers? See: https://github.com/atuttle/Taffy/issues/144 --->
|
||||
<cfif not structKeyExists(_taffyRequest.headers, "Access-Control-Request-Headers")>
|
||||
<cfheader name="Access-Control-Allow-Headers" value="Origin, Authorization, X-CSRF-Token, X-Requested-With, Content-Type, X-HTTP-Method-Override, Accept, Referrer, User-Agent" />
|
||||
<cfelse>
|
||||
<!--- parrot back all of the request headers to allow the request to continue (can we improve on this?) --->
|
||||
<cfset local.allowedHeaders = {} />
|
||||
<cfloop list="Origin,Authorization,X-CSRF-Token,X-Requested-With,Content-Type,X-HTTP-Method-Override,Accept,Referrer,User-Agent" index="local.h">
|
||||
<cfset local.allowedHeaders[local.h] = 1 />
|
||||
</cfloop>
|
||||
<cfset local.requestedHeaders = _taffyRequest.headers['Access-Control-Request-Headers'] />
|
||||
<cfloop list="#local.requestedHeaders#" index="local.i">
|
||||
<cfset local.allowedHeaders[ local.i ] = 1 />
|
||||
</cfloop>
|
||||
<cfheader name="Access-Control-Allow-Headers" value="#structKeyList(local.allowedHeaders)#" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cffunction>
|
||||
|
||||
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
||||
<!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: --->
|
||||
|
||||
<!--- Overriding TAFFY short-circuit logic (taffy/core/api.cfc) --->
|
||||
<cffunction name="onRequest" output="true" returntype="boolean">
|
||||
<cfargument name="targetPage" type="string" required="true" />
|
||||
|
||||
<cfset request.startTickCount=getTickCount()/><!--- *** --->
|
||||
|
||||
<cfset var _taffyRequest = {} />
|
||||
<cfset var local = {} />
|
||||
<cfset var m = '' />
|
||||
<cfset request._taffyRequest = _taffyRequest />
|
||||
<cfset local.debug = false />
|
||||
|
||||
<cfset _taffyRequest.metrics = {} />
|
||||
<cfset m = _taffyRequest.metrics />
|
||||
<cfset m.init = getTickCount() />
|
||||
|
||||
<!--- enable/disable debug output per settings --->
|
||||
<cfif not structKeyExists(url, application._taffy.settings.debugKey)>
|
||||
<cfsetting showdebugoutput="false" />
|
||||
</cfif>
|
||||
|
||||
<!--- display api dashboard if requested --->
|
||||
<cfif
|
||||
NOT structKeyExists(url,application._taffy.settings.endpointURLParam)
|
||||
AND NOT structKeyExists(form,application._taffy.settings.endpointURLParam)
|
||||
AND len(cgi.path_info) lte 1
|
||||
AND listFindNoCase(cgi.script_name, "index.cfm", "/") EQ listLen(cgi.script_name, "/")>
|
||||
<cfif NOT application._taffy.settings.disableDashboard>
|
||||
<cfif StructKeyExists( URL, "docs" )>
|
||||
<cfinclude template="#application._taffy.settings.docsPath#" />
|
||||
<cfelse>
|
||||
<cfinclude template="../dashboard/dashboard.cfm" />
|
||||
</cfif>
|
||||
<cfabort />
|
||||
<cfelse>
|
||||
<cfif len(application._taffy.settings.disabledDashboardRedirect)>
|
||||
<cflocation url="#application._taffy.settings.disabledDashboardRedirect#" addtoken="false" />
|
||||
<cfabort />
|
||||
<cfelseif application._taffy.settings.showDocsWhenDashboardDisabled>
|
||||
<cfinclude template="#application._taffy.settings.docsPath#" />
|
||||
<cfabort />
|
||||
<cfelse>
|
||||
<cfset throwError(403, "Forbidden") />
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- get request details --->
|
||||
<cfset m.beforeParse = getTickCount() />
|
||||
<cfset local.parsed = parseRequest() />
|
||||
<cfset m.afterParse = getTickCount() />
|
||||
<cfset structAppend(_taffyRequest, local.parsed) />
|
||||
<cfset m.parseTime = m.afterParse - m.beforeParse />
|
||||
|
||||
<!--- CORS headers (so that CORS can pass even if the resource throws an exception) --->
|
||||
<cfset local.allowVerbs = uCase(structKeyList(_taffyRequest.matchDetails.methods)) />
|
||||
<cfif (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)
|
||||
AND listFindNoCase('PUT,PATCH,DELETE,OPTIONS',_taffyRequest.verb)
|
||||
AND NOT listFind(local.allowVerbs,'OPTIONS')>
|
||||
<cfset local.allowVerbs = listAppend(local.allowVerbs,'OPTIONS') />
|
||||
</cfif>
|
||||
<cfif structKeyExists(_taffyRequest.headers, "origin") AND (application._taffy.settings.allowCrossDomain eq true or len(application._taffy.settings.allowCrossDomain) gt 0)>
|
||||
<cfif application._taffy.settings.allowCrossDomain eq true>
|
||||
<!--- <cfheader name="Access-Control-Allow-Origin" value="*" /> ---><!--- *** dirty hack 2024-10-23 17:59:43--->
|
||||
<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
|
||||
<cfelse>
|
||||
<!---
|
||||
The Access-Control-Allow-Origin header can only have 1 value so we check to see if the Origin header is
|
||||
in the list of origins specified in the config setting and parrot back the Origin header if so.
|
||||
We also need to add the Access-Control-Allow-Credentials header and set it to true for those type requests
|
||||
--->
|
||||
<cfset local.domains = listToArray( application._taffy.settings.allowCrossDomain, ', ;' )>
|
||||
<cfif structKeyExists(_taffyRequest.headers, "origin")>
|
||||
<cfloop from="1" to="#arrayLen( local.domains )#" index="local.i">
|
||||
<cfif lcase( rereplace( _taffyRequest.headers.origin, "(http|https):\/\/", "", "all" ) ) EQ lcase( rereplace( local.domains[ local.i ], "(http|https):\/\/", "", "all" ) ) >
|
||||
<cfheader name="Access-Control-Allow-Origin" value="#_taffyRequest.headers.origin#" />
|
||||
<cfheader name="Access-Control-Allow-Credentials" value="true" />
|
||||
<cfbreak>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfheader name="Access-Control-Allow-Methods" value="#local.allowVerbs#" />
|
||||
<!--- Why do we parrot back these headers? See: https://github.com/atuttle/Taffy/issues/144 --->
|
||||
<cfif not structKeyExists(_taffyRequest.headers, "Access-Control-Request-Headers")>
|
||||
<cfheader name="Access-Control-Allow-Headers" value="Origin, Authorization, X-CSRF-Token, X-Requested-With, Content-Type, X-HTTP-Method-Override, Accept, Referrer, User-Agent" />
|
||||
<cfelse>
|
||||
<!--- parrot back all of the request headers to allow the request to continue (can we improve on this?) --->
|
||||
<cfset local.allowedHeaders = {} />
|
||||
<cfloop list="Origin,Authorization,X-CSRF-Token,X-Requested-With,Content-Type,X-HTTP-Method-Override,Accept,Referrer,User-Agent" index="local.h">
|
||||
<cfset local.allowedHeaders[local.h] = 1 />
|
||||
</cfloop>
|
||||
<cfset local.requestedHeaders = _taffyRequest.headers['Access-Control-Request-Headers'] />
|
||||
<cfloop list="#local.requestedHeaders#" index="local.i">
|
||||
<cfset local.allowedHeaders[ local.i ] = 1 />
|
||||
</cfloop>
|
||||
<cfheader name="Access-Control-Allow-Headers" value="#structKeyList(local.allowedHeaders)#" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- global headers --->
|
||||
<cfset addHeaders(getGlobalHeaders()) />
|
||||
|
||||
<!---
|
||||
Now we know everything we need to know to service the request. let's service it!
|
||||
--->
|
||||
|
||||
<!--- ...after we let the api developer know all of the request details first... --->
|
||||
<cfset m.beforeOnTaffyRequest = getTickCount() />
|
||||
<cfset _taffyRequest.continue = onTaffyRequest(
|
||||
_taffyRequest.verb
|
||||
,_taffyRequest.matchDetails.beanName
|
||||
,_taffyRequest.requestArguments
|
||||
,_taffyRequest.returnMimeExt
|
||||
,_taffyRequest.headers
|
||||
,_taffyRequest.methodMetadata
|
||||
,local.parsed.matchDetails.srcUri
|
||||
) />
|
||||
<cfset m.afterOnTaffyRequest = getTickCount() />
|
||||
<cfset m.otrTime = m.afterOnTaffyRequest - m.beforeOnTaffyRequest />
|
||||
|
||||
<cfif not structKeyExists(_taffyRequest, "continue")>
|
||||
<!--- developer forgot to return true --->
|
||||
<cfthrow
|
||||
message="Error in your onTaffyRequest method"
|
||||
detail="Your onTaffyRequest method returned no value. Expected: Return TRUE or call noData()/representationOf()."
|
||||
errorcode="400"
|
||||
/>
|
||||
</cfif>
|
||||
|
||||
<cfif isObject(_taffyRequest.continue)>
|
||||
<!--- inspection complete but request has been aborted by developer; return custom response --->
|
||||
<cfset _taffyRequest.result = duplicate(_taffyRequest.continue) />
|
||||
<cfset structDelete(_taffyRequest, "continue")/>
|
||||
<cfset m.resourceTime = 0 />
|
||||
<cfelse>
|
||||
<!--- inspection complete and request allowed by developer --->
|
||||
|
||||
<!--- handle requests for simulated responses --->
|
||||
<cfif structKeyExists(_taffyRequest.requestArguments, application._taffy.settings.simulateKey) and _taffyRequest.requestArguments[application._taffy.settings.simulateKey] eq application._taffy.settings.simulatePassword>
|
||||
<!--- is there a simulated response? --->
|
||||
<cfset sampler = 'sample#_taffyRequest.method#Response' />
|
||||
<cfif structKeyExists(_taffyRequest.matchDetails.metadata, sampler)>
|
||||
<!--- get simulated response --->
|
||||
<cfinvoke
|
||||
component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
|
||||
method="#sampler#"
|
||||
returnvariable="_taffyRequest.result"
|
||||
/>
|
||||
<cfset _taffyRequest.result = rep(_taffyRequest.result) />
|
||||
<cfelse>
|
||||
<!--- no method for simulated response, so return 400 --->
|
||||
<cfset _taffyRequest.result = noData().withStatus(400, "No Sample Response Available") />
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<!--- send request to service --->
|
||||
<cfif structKeyExists(_taffyRequest.matchDetails.methods, _taffyRequest.verb)>
|
||||
<!--- check the cache before we call the resource --->
|
||||
<cfset m.cacheCheckTime = getTickCount() />
|
||||
<cfset local.cacheKey = getCacheKey(
|
||||
_taffyRequest.matchDetails.beanName
|
||||
,_taffyRequest.requestArguments
|
||||
,local.parsed.matchDetails.srcUri
|
||||
) />
|
||||
<cfif ucase(_taffyRequest.verb) eq "GET" and validCacheExists(local.cacheKey)>
|
||||
<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
|
||||
<cfset m.cacheGetTime = getTickCount() />
|
||||
<cfset _taffyRequest.result = getCachedResponse(local.cacheKey) />
|
||||
<cfset m.cacheGetTime = m.cacheGetTime - getTickCount() />
|
||||
<cfelse>
|
||||
<cfif ucase(_taffyRequest.verb) eq "GET">
|
||||
<cfset m.cacheCheckTime = getTickCount() - m.cacheCheckTime />
|
||||
<cfelse>
|
||||
<cfset structDelete(m, "cacheCheckTime") />
|
||||
</cfif>
|
||||
<!--- returns a representation-object --->
|
||||
<cfset m.beforeResource = getTickCount() />
|
||||
<cfinvoke
|
||||
component="#application._taffy.factory.getBean(_taffyRequest.matchDetails.beanName)#"
|
||||
method="#_taffyRequest.method#"
|
||||
argumentcollection="#_taffyRequest.requestArguments#"
|
||||
returnvariable="_taffyRequest.result"
|
||||
/>
|
||||
<cfset m.afterResource = getTickCount() />
|
||||
<cfset m.resourceTime = m.afterResource - m.beforeResource />
|
||||
<cfif !isDefined("_taffyRequest.result")>
|
||||
<cfthrow
|
||||
message="Resource did not return a value"
|
||||
detail="The resource is expected to return a call to rep()/representationOf() or noData(). It appears there was no return at all."
|
||||
errorcode="taffy.resources.ResourceReturnsNothing"
|
||||
/>
|
||||
</cfif>
|
||||
<!--- If the type returned is not an instance of baseSerializer, wrap it with a call to rep().
|
||||
This way we can directly return the object instead of a serializer from resource actions. --->
|
||||
<cfif !isInstanceOf(_taffyRequest.result, "taffy.core.baseSerializer")>
|
||||
<cfset _taffyRequest.result = rep(_taffyRequest.result) />
|
||||
</cfif>
|
||||
<cfif ucase(_taffyRequest.verb) eq "GET" and structKeyExists(local, "cacheKey")>
|
||||
<cfset m.cacheSaveStart = getTickCount() />
|
||||
<cfset setCachedResponse(local.cacheKey, _taffyRequest.result) />
|
||||
<cfset m.cacheSaveTime = getTickCount() - m.cacheSaveStart />
|
||||
</cfif>
|
||||
</cfif>
|
||||
<cfelseif NOT listFind(local.allowVerbs,_taffyRequest.verb)>
|
||||
<!--- if the verb is not implemented, refuse the request --->
|
||||
<cfheader name="ALLOW" value="#local.allowVerbs#" />
|
||||
<cfset throwError(405, "Method Not Allowed") />
|
||||
<cfelse>
|
||||
<!--- create dummy response for cross domain OPTIONS request --->
|
||||
<cfset _taffyRequest.resultHeaders = structNew() />
|
||||
<cfset _taffyRequest.statusArgs = structNew() />
|
||||
<cfset _taffyRequest.statusArgs.statusCode = 200 />
|
||||
<cfset _taffyRequest.statusArgs.statusText = 'OK' />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
</cfif>
|
||||
<!--- make sure the requested mime type is available --->
|
||||
<cfif not mimeSupported(_taffyRequest.returnMimeExt)>
|
||||
<cfset throwError(400, "Requested format not available (#_taffyRequest.returnMimeExt#)") />
|
||||
</cfif>
|
||||
|
||||
<cfif structKeyExists(_taffyRequest,'result')>
|
||||
<!--- get status code --->
|
||||
<cfset _taffyRequest.statusArgs = structNew() />
|
||||
<cfset _taffyRequest.statusArgs.statusCode = _taffyRequest.result.getStatus() />
|
||||
<cfset _taffyRequest.statusArgs.statusText = _taffyRequest.result.getStatusText() />
|
||||
<!--- get custom headers --->
|
||||
<cfinvoke
|
||||
component="#_taffyRequest.result#"
|
||||
method="getHeaders"
|
||||
returnvariable="_taffyRequest.resultHeaders"
|
||||
/>
|
||||
</cfif>
|
||||
|
||||
<cfsetting enablecfoutputonly="true" />
|
||||
<cfcontent reset="true" type="#getReturnMimeAsHeader(_taffyRequest.returnMimeExt)#; charset=utf-8" />
|
||||
<cfheader statuscode="#_taffyRequest.statusArgs.statusCode#" statustext="#_taffyRequest.statusArgs.statusText#" />
|
||||
|
||||
<!--- headers --->
|
||||
<cfset addHeaders(_taffyRequest.resultHeaders) />
|
||||
|
||||
<!--- add ALLOW header for current resource, which describes available verbs --->
|
||||
<cfheader name="ALLOW" value="#local.allowVerbs#" />
|
||||
|
||||
<!--- metrics headers that should always apply --->
|
||||
<cfheader name="X-TIME-IN-PARSE" value="#m.parseTime#" />
|
||||
<cfheader name="X-TIME-IN-ONTAFFYREQUEST" value="#m.otrTime#" />
|
||||
<cfif structKeyExists(m, "resourceTime")>
|
||||
<cfheader name="X-TIME-IN-RESOURCE" value="#m.resourceTime#" />
|
||||
</cfif>
|
||||
<cfif structKeyExists(m, "cacheCheckTime")>
|
||||
<cfheader name="X-TIME-IN-CACHE-CHECK" value="#m.cacheCheckTime#" />
|
||||
</cfif>
|
||||
<cfif structKeyExists(m, "cacheGetTime")>
|
||||
<cfheader name="X-TIME-IN-CACHE-GET" value="#m.cacheGetTime#" />
|
||||
</cfif>
|
||||
<cfif structKeyExists(m, "cacheSaveTime")>
|
||||
<cfheader name="X-TIME-IN-CACHE-SAVE" value="#m.cacheSaveTime#" />
|
||||
</cfif>
|
||||
|
||||
<cfif application._taffy.settings.exposeHeaders>
|
||||
<cfset local.exposeHeaderList = structKeyList(_taffyRequest.resultHeaders) />
|
||||
<cfset local.exposeHeaderValue = "" />
|
||||
<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET" and _taffyRequest.result.getType() eq "textual">
|
||||
<cfset local.exposeHeaderList = listAppend(local.exposeHeaderList, "Etag") />
|
||||
</cfif>
|
||||
<cfloop list="#local.exposeHeaderList#" index="local.exposeHeader">
|
||||
<!--- filter out default simple response headers: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers --->
|
||||
<cfif not listFindNoCase("Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma", local.exposeHeader)>
|
||||
<cfset local.exposeHeaderValue = listAppend(local.exposeHeaderValue, local.exposeHeader) />
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfif listLen(local.exposeHeaderValue) gt 0>
|
||||
<cfheader name="Access-Control-Expose-Headers" value="#local.exposeHeaderValue#" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<!--- result data --->
|
||||
<cfif structKeyExists(_taffyRequest,'result')>
|
||||
<cfset _taffyRequest.resultType = _taffyRequest.result.getType() />
|
||||
<cfset local.resultSerialized = '' />
|
||||
|
||||
<cfif _taffyRequest.resultType eq "textual">
|
||||
<!--- serialize the representation's data into the requested mime type --->
|
||||
<cfset _taffyRequest.metrics.beforeSerialize = getTickCount() />
|
||||
<cfinvoke
|
||||
component="#_taffyRequest.result#"
|
||||
method="getAs#_taffyRequest.returnMimeExt#"
|
||||
returnvariable="_taffyRequest.resultSerialized"
|
||||
/>
|
||||
<cfset _taffyRequest.metrics.afterSerialize = getTickCount() />
|
||||
<cfset m.serializeTime = m.afterSerialize - m.beforeSerialize />
|
||||
<cfheader name="X-TIME-IN-SERIALIZE" value="#m.serializeTime#" />
|
||||
|
||||
<!--- apply jsonp wrapper if requested --->
|
||||
<cfif structKeyExists(_taffyRequest, "jsonpCallback")>
|
||||
<cfset _taffyRequest.resultSerialized = _taffyRequest.jsonpCallback & "(" & _taffyRequest.resultSerialized & ");" />
|
||||
</cfif>
|
||||
|
||||
<!--- don't return data if etags are enabled and the data hasn't changed --->
|
||||
<cfif application._taffy.settings.useEtags and _taffyRequest.verb eq "GET">
|
||||
<!--- etag values are quoted per: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag --->
|
||||
<cfif structKeyExists(server, "lucee")>
|
||||
<!--- hashCode() will not work for lucee, see issue #354 --->
|
||||
<cfset _taffyRequest.serverEtag = '"' & hash(_taffyRequest.resultSerialized) & '"' />
|
||||
<cfelse>
|
||||
<cfset _taffyRequest.serverEtag = '"' & _taffyRequest.result.getData().hashCode() & '"' />
|
||||
</cfif>
|
||||
<cfif structKeyExists(_taffyRequest.headers, "If-None-Match")>
|
||||
<cfset _taffyRequest.clientEtag = _taffyRequest.headers['If-None-Match'] />
|
||||
|
||||
<cfif len(_taffyRequest.clientEtag) gt 0 and _taffyRequest.clientEtag eq _taffyRequest.serverEtag>
|
||||
<cfheader statuscode="304" statustext="Not Modified" />
|
||||
<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
|
||||
<cfreturn true />
|
||||
<cfelse>
|
||||
<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
|
||||
</cfif>
|
||||
<cfelse>
|
||||
<cfheader name="Etag" value="#_taffyRequest.serverEtag#" />
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfset m.done = getTickCount() />
|
||||
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.serializeTime />
|
||||
<cfif structKeyExists(m, "resourceTime")>
|
||||
<cfset m.taffyTime -= m.resourceTime />
|
||||
</cfif>
|
||||
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
||||
|
||||
<cfcontent reset="true" type="#application._taffy.settings.mimeExtensions[_taffyRequest.returnMimeExt]#; charset=utf-8" />
|
||||
<cfif _taffyRequest.resultSerialized neq ('"' & '"')>
|
||||
<cfset local.resultSerialized = _taffyRequest.resultSerialized />
|
||||
</cfif>
|
||||
<!--- debug output --->
|
||||
<cfif structKeyExists(url, application._taffy.settings.debugKey)>
|
||||
<cfset local.debug = true />
|
||||
</cfif>
|
||||
|
||||
<cfelseif _taffyRequest.resultType eq "filename">
|
||||
<cfset m.done = getTickCount() />
|
||||
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
||||
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
||||
<cfcontent reset="true" file="#_taffyRequest.result.getFileName()#" type="#_taffyRequest.result.getFileMime()#" deletefile="#_taffyRequest.result.getDeleteFile()#" />
|
||||
|
||||
<cfelseif _taffyRequest.resultType eq "filedata">
|
||||
<cfset m.done = getTickCount() />
|
||||
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
||||
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
||||
<cfcontent reset="true" variable="#_taffyRequest.result.getFileData()#" type="#_taffyRequest.result.getFileMime()#" />
|
||||
|
||||
<cfelseif _taffyRequest.resultType eq "imagedata">
|
||||
<cfset m.done = getTickCount() />
|
||||
<cfset m.taffyTime = m.done - m.init - m.parseTime - m.otrTime - m.resourceTime />
|
||||
<cfheader name="X-TIME-IN-TAFFY" value="#m.taffyTime#" />
|
||||
<cfcontent reset="true" variable="#_taffyRequest.result.getImageData()#" type="#_taffyRequest.result.getFileMime()#" />
|
||||
|
||||
</cfif>
|
||||
</cfif>
|
||||
|
||||
<cfset local.resultSerialized = "" />
|
||||
<cfif structKeyExists( _taffyRequest, "resultSerialized" )>
|
||||
<cfset local.resultSerialized = _taffyRequest.resultSerialized />
|
||||
</cfif>
|
||||
|
||||
<cfset local.result = StructNew() />
|
||||
<cfif structKeyExists( _taffyRequest, "result" )>
|
||||
<cfset local.result = _taffyRequest.result.getData() />
|
||||
</cfif>
|
||||
|
||||
<!--- ...after the service has finished... --->
|
||||
<cfset m.beforeOnTaffyRequestEnd = getTickCount() />
|
||||
<cfset onTaffyRequestEnd(
|
||||
_taffyRequest.verb
|
||||
,_taffyRequest.matchDetails.beanName
|
||||
,_taffyRequest.requestArguments
|
||||
,_taffyRequest.returnMimeExt
|
||||
,_taffyRequest.headers
|
||||
,_taffyRequest.methodMetadata
|
||||
,local.parsed.matchDetails.srcUri
|
||||
,local.resultSerialized
|
||||
,local.result
|
||||
,_taffyRequest.statusArgs.statusCode
|
||||
) />
|
||||
<cfset m.otreTime = getTickCount() - m.beforeOnTaffyRequestEnd />
|
||||
<cfheader name="X-TIME-IN-ONTAFFYREQUESTEND" value="#m.otreTime#" />
|
||||
|
||||
<cfif len(trim(local.resultSerialized))>
|
||||
<cfoutput>#local.resultSerialized#</cfoutput>
|
||||
</cfif>
|
||||
<!--- debug output --->
|
||||
<cfif local.debug>
|
||||
<cfoutput><h3>Request Details:</h3><cfdump var="#_taffyRequest#"></cfoutput>
|
||||
</cfif>
|
||||
|
||||
<cfreturn true />
|
||||
</cffunction>
|
||||
|
||||
|
||||
<!--- <cffunction name="checkForLocalDebug">
|
||||
<cfreturn fileExists("#GetDirectoryFromPath(GetCurrentTemplatePath())#/etc/local-debug")>
|
||||
</cffunction> --->
|
||||
|
||||
|
||||
</cfcomponent>
|
||||
+15
-59
@@ -13,8 +13,9 @@
|
||||
<cfcatch type="invalidParamValue">
|
||||
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
</cftry>
|
||||
|
||||
<cfset var local={}/>
|
||||
|
||||
<cfquery name="local.qInstance" result="local.result">
|
||||
select
|
||||
@@ -188,56 +189,8 @@
|
||||
А есть смысл делать это в одну строну? наверно, есть - чтобы не забираться в операцию
|
||||
--->
|
||||
|
||||
<!--- видимо, достаточно нам параметров операции create... но можно и все параметры перебрать --->
|
||||
<!--- <cfquery name="local.qCfsParamRef" result="local.result">
|
||||
select
|
||||
p.svc_operation_cfs_param
|
||||
,p.label
|
||||
,p.descr
|
||||
,p.is_sensitive
|
||||
,r.svc_id as ref_svc_id
|
||||
,r.svc as ref_svc
|
||||
,r.code as ref_code
|
||||
from svc_operation_cfs_param p
|
||||
join svc_operation o on (p.svc_operation_id=o.svc_operation_id)
|
||||
join svc on (o.svc_id=svc.svc_id)
|
||||
join svc r on (p.ref_svc_id=r.svc_id)
|
||||
where svc.svc_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#qInstance.service_id#" null=#!isValid('integer',qInstance.service_id)#/>
|
||||
AND o.operation='create'
|
||||
</cfquery>
|
||||
|
||||
<cfset var dependencies=[]/>
|
||||
<!--- <cfset var dependencies=#local.qCfsParamRef#/> --->
|
||||
<cftry>
|
||||
<cfset var local.instanceData=deserializeJson(local.qCurrentState.instance_data)/>
|
||||
<cfloop query="local.qCfsParamRef">
|
||||
<cfset var paramName = local.qCfsParamRef.svc_operation_cfs_param/>
|
||||
<cfif structKeyExists(local.instanceData, "params") AND structKeyExists(local.instanceData.params, paramName)>
|
||||
<cfset var refKey = structFind(local.instanceData.params, paramName)/>
|
||||
<cfif isValid("guid",refKey)>
|
||||
<!--- *** нужно укрепить изоляцию и проконтролировать принадлежность инстанса данному тенанту --->
|
||||
<cfquery name="qDependency">
|
||||
select e.display_name, e.is_auxiliary
|
||||
from instance e
|
||||
where e.instance_uid = <cfqueryparam cfsqltype="cf_sql_other" value="#refKey#" null=#!isValid('guid',refKey)#/>
|
||||
</cfquery>
|
||||
<cfset var rec = {
|
||||
"param":"#svc_operation_cfs_param#",
|
||||
"descr":"#descr#",
|
||||
"svcId":#ref_svc_id#,
|
||||
"svc":"#ref_svc#",
|
||||
"code":"#ref_code#",
|
||||
"label":"#label#",
|
||||
"uid":"#refKey#",
|
||||
"displayName":"#qDependency.display_name#",
|
||||
"isAuxiliary": #request.castToBool(qDependency.is_auxiliary)#
|
||||
}/>
|
||||
<cfset arrayAppend(dependencies, rec)/>
|
||||
</cfif>
|
||||
</cfif>
|
||||
</cfloop>
|
||||
<cfcatch type="ANY"></cfcatch>
|
||||
</cftry> --->
|
||||
|
||||
|
||||
|
||||
<!--- *** здесь уместна параноидальная проверка прав --->
|
||||
<cfquery name="local.qDependency">
|
||||
@@ -329,7 +282,7 @@
|
||||
</cfquery>
|
||||
|
||||
<cftry>
|
||||
<cfset var local.instanceData=deserializeJson(local.qCurrentState.instance_data)/>
|
||||
<cfset local.instanceData=deserializeJson(local.qCurrentState.instance_data)/>
|
||||
<cfloop query="local.qCfsParam">
|
||||
<cfset var paramName = local.qCfsParam.svc_operation_cfs_param/>
|
||||
<cfif structKeyExists(local.instanceData, "params") AND structKeyExists(local.instanceData.params, paramName)>
|
||||
@@ -348,7 +301,10 @@
|
||||
|
||||
|
||||
<cfset var out=structNew("linked")/>
|
||||
<cfset var local.vaultData=structNew("linked")/>
|
||||
<cfset "local.vaultData"=structNew("linked")/>
|
||||
<cfif structKeyExists(instanceData,"vault") AND structKeyExists(instanceData.vault,"fields")>
|
||||
<cfset "local.vaultFields" = #instanceData.vault.fields#/>
|
||||
</cfif>
|
||||
|
||||
<cftry><!--- *** надо бы такие вещи делать асинхронно --->
|
||||
<cfset vaultUrl=""/>
|
||||
@@ -359,10 +315,8 @@
|
||||
<cfset vaultUrl=instanceData.vault.Url/><!--- v002 --->
|
||||
</cfif>
|
||||
|
||||
<!--- url="https://vault.adl.nubes.ru:8200/v1/auth/approle/login" --->
|
||||
|
||||
<cfhttp method="post"
|
||||
url="https://vault.lk.adl.nubes.ru/v1/auth/approle/login"
|
||||
url="#request.vault_login_url#"
|
||||
timeout="3"
|
||||
charset="utf-8"
|
||||
result="res"
|
||||
@@ -387,9 +341,11 @@
|
||||
</cfhttp>
|
||||
|
||||
<!--- *** костыль для перехода от формата v001 к v002 --->
|
||||
|
||||
<cfif structKeyExists(instanceData,"vault") AND structKeyExists(instanceData.vault,"fields")>
|
||||
<cfset local.tempVaultData = #deserializeJson(cred.filecontent).data.data#/>
|
||||
<cfset "local.tempVaultData" = #deserializeJson(cred.filecontent).data.data#/>
|
||||
<cfset "local.vaultData" = #structNew("linked")#/>
|
||||
|
||||
<!--- <cfset "out.vaultData" = #local.tempVaultData#/> --->
|
||||
<cfloop collection=#instanceData.vault.fields# index="i">
|
||||
<cfset var fld=instanceData.vault.fields[i]/>
|
||||
@@ -445,7 +401,7 @@
|
||||
<cfset "out.instance.vaultData"=local.vaultData/>
|
||||
|
||||
<cfset "out.instance.state" = (local.qCurrentState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.currentStateTitleMap, local.qCurrentState, this.helper.snake2camel) : {}/>
|
||||
<!--- <cfset "out.instance.operation"=this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, this.helper.snake2camel)/> --->
|
||||
|
||||
<cfset "out.instance.operations"=[]/>
|
||||
<cfloop query=#local.qOperation#>
|
||||
<cfset arrayAppend(out.instance.operations, this.helper.appendRecord(structNew("linked"), "", local.operationTitleMap, local.qOperation, this.helper.snake2camel))/>
|
||||
|
||||
@@ -126,11 +126,13 @@
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cftry><!--- *** времянка, пока глотается ошибка 500 --->
|
||||
|
||||
<!--- Такая бодяга. Кастомные сериализаторы не умеют вложенных структур (поразительно, или я не нашел как). Стандартный сериализатор не умеет выводить дату в ISO 8601. Приходится колхозить и форматировать на стороне БД (насилу подобрал формат). Также на стороне БД приходится конвертировать GUID, потому что драйвер pg jdbc его представляет как структуру из 2 чисел.
|
||||
И то сказать, jsonb как-то странно сериализуется, в 3 поля, вместо одного Value
|
||||
--->
|
||||
<cftry><!--- *** времянка, пока глотается ошибка 500 --->
|
||||
<!--- Такая бодяга. Кастомные сериализаторы не умеют вложенных структур (поразительно, или я не нашел как).
|
||||
Стандартный сериализатор не умеет выводить дату в ISO 8601.
|
||||
Приходится колхозить и форматировать на стороне БД (насилу подобрал формат).
|
||||
Также на стороне БД приходится конвертировать GUID, потому что драйвер pg jdbc его представляет как структуру из 2 чисел.
|
||||
Еще jsonb как-то странно сериализуется, в 3 поля, вместо одного Value (решается преобразованием ::text)
|
||||
--->
|
||||
|
||||
<m:field_set titleMapOut="local.titleMap"
|
||||
lengthOut="local.fieldCount"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<cfcomponent extends="taffy.core.resource" taffy:uri="/instances/{instanceUid}/vault/{secretName}" hint="getting secrets from the vault">
|
||||
|
||||
<cfsilent>
|
||||
<cfimport prefix="m" taglib="../lib"/>
|
||||
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
|
||||
</cfsilent>
|
||||
|
||||
<cffunction name="get" hint="Получить секрет">
|
||||
<cfargument name="instanceUid" type="string" required=true hint="type:UUID"/>
|
||||
<cfargument name="secretName" type="string" required=true hint="type:string"/>
|
||||
<!--- <cfargument name="usrId" type="string" hint="type:integer; no need to provide this argument in request, it is injected by IAM"/> --->
|
||||
<!--- arguments.usrId неявно вбрасывается из Application.cfm --->
|
||||
|
||||
<cftry>
|
||||
<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
|
||||
<cfcatch type="invalidParamValue">
|
||||
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cfset var local={}/>
|
||||
<cfset local.vaultData={}/>
|
||||
|
||||
<!--- тут необходимо проконтролировать, что мы смотрим не чужие данные --->
|
||||
<!--- в других местах мы молча возврашаем пустой резалтсет на запрос чужих данных --->
|
||||
<!--- варианты: 403, 404, пустой набор данных --->
|
||||
<cfquery name="local.qCurrentState" result="local.result">
|
||||
select
|
||||
<m:field_set titleMapOut="local.currentStateTitleMap" lengthOut="local.fieldCount">
|
||||
<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
|
||||
<m:field title="v.">st.version</m:field>
|
||||
<m:field title="creator_id">st.creator_id</m:field>
|
||||
<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
|
||||
<m:field formatter=#request.castToBool#>st.is_test</m:field>
|
||||
<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
|
||||
<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
|
||||
<m:field formatter=#function(x){return (x GT 0)}#>(st.instance_data->>'isDeleted')::boolean as is_deleted</m:field>
|
||||
</m:field_set>
|
||||
from instance_state st
|
||||
join instance e on (st.instance_uid=e.instance_uid)
|
||||
join specification_item i on (e.specification_item_id=i.specification_item_id)
|
||||
where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#"/>
|
||||
AND i.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/><!--- access protection --->
|
||||
order by st.version desc
|
||||
limit 1
|
||||
</cfquery>
|
||||
|
||||
<cftry>
|
||||
<cfset vaultUrl=""/>
|
||||
<cfset instanceData="#deserializeJson(local.qCurrentState.instance_data)#"/>
|
||||
<cfif structKeyExists(instanceData,"vaultUrl")>
|
||||
<cfset vaultUrl=instanceData.vaultUrl/> <!--- v001 --->
|
||||
<cfelseif structKeyExists(instanceData,"vault")>
|
||||
<cfset vaultUrl=instanceData.vault.Url/><!--- v002 --->
|
||||
</cfif>
|
||||
|
||||
<!--- *** вынести URL в конфиг --->
|
||||
<cfhttp method="post"
|
||||
url="#request.vault_login_url#"
|
||||
timeout="3"
|
||||
charset="utf-8"
|
||||
result="res"
|
||||
>
|
||||
<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
|
||||
<cfhttpparam type="BODY" value='{"role_id":"#request.vault_role_id#","secret_id":"#request.vault_secret_id#"}'/>
|
||||
</cfhttp>
|
||||
<cfset resData=#deserializeJson(res.filecontent)#/>
|
||||
<cfset client_token=#resData.auth.client_token#/>
|
||||
|
||||
<cftry>
|
||||
<cfhttp method="get"
|
||||
url="#vaultUrl#"
|
||||
timeout="3"
|
||||
charset="utf-8"
|
||||
result="cred">
|
||||
<cfhttpparam type="HEADER" name="X-Vault-Token" value="#client_token#">
|
||||
<cfhttpparam type="HEADER" name="User-Agent" value="#request.USER_AGENT#"/>
|
||||
</cfhttp>
|
||||
|
||||
<!--- *** костыль для перехода от формата v001 к v002 --->
|
||||
<cfif structKeyExists(instanceData,"vault")>
|
||||
<cfset "local.tempVaultData" = #deserializeJson(cred.filecontent).data.data#/>
|
||||
<cfset "local.vaultData" = local.tempVaultData[arguments.secretName]/>
|
||||
<!--- <cfelse>
|
||||
<cfset "local.vaultData" = #deserializeJson(cred.filecontent).data.data#/> --->
|
||||
</cfif>
|
||||
|
||||
<cfcatch type="ANY">
|
||||
<cfreturn representationOf(cfcatch).withStatus(500)/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
<cfcatch type="ANY">
|
||||
<cfreturn representationOf(cfcatch).withStatus(500)/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
|
||||
|
||||
<cfset var out = structNew("linked")/>
|
||||
<cfset "out.name" = #arguments.secretName#/>
|
||||
<cfset "out.value" = #local.vaultData#/>
|
||||
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
|
||||
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
|
||||
<cfreturn representationOf(out) />
|
||||
</cffunction>
|
||||
|
||||
</cfcomponent>
|
||||
Reference in New Issue
Block a user