294 lines
14 KiB
Plaintext
294 lines
14 KiB
Plaintext
<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" /><!---это виднее в сообщении об ошибке--->
|
|
<cfset this.config.auth_domain_suffix = "" /><!---эта строка - например, @nubes.ru - вырезается из имени пользователя, полученного с токеном. Case Insensitive--->
|
|
<!---https://kk-dev.nubes.ru/auth/realms/mms/protocol/openid-connect/certs--->
|
|
<cfset this.config.IDP_certificate = {
|
|
"keys": [
|
|
{
|
|
"kid": "daZqOfZpc_vz7RLAVDWux6jshjD2lFAr5Z2ZNn1dwLA",
|
|
"kty": "RSA",
|
|
"alg": "RS256",
|
|
"use": "sig",
|
|
"n": "lQpd1QPTcKr84DLwhW6fOg3KlzvOQUzmrW6AlNFM0Iuiv2RZ5dbHtVaIZMQKTpVy1FbDU70p33rxHgeC_I_7VqzixpX4m2oJrIYkz-hr7hEw93iLRju9XYr5V82jkIL1xLvVASFk7nlnJcMBnGoWXv6j8jhJBD8M5EWNT2hWs6HTpXczYdFCwaVLsGvrUTxnY2iESKEGbahEOfG4LicD7POnMIp-vuEKG-G8aIC7K290uEU8QKSCzKQAQ2viptdO275KAkZUs0r_DDK2msI5SM7JBOTQxAbXyVizsi6idx9yVOoaXCfCb4xYeTg9DVy-oJ2Ukyvs570mEdag6hyURQ",
|
|
"e": "AQAB",
|
|
"x5c": [
|
|
"MIIClTCCAX0CBgF9IxLgpDANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANtbXMwHhcNMjExMTE1MTAwNzU5WhcNMzExMTE1MTAwOTM5WjAOMQwwCgYDVQQDDANtbXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVCl3VA9NwqvzgMvCFbp86DcqXO85BTOatboCU0UzQi6K/ZFnl1se1VohkxApOlXLUVsNTvSnfevEeB4L8j/tWrOLGlfibagmshiTP6GvuETD3eItGO71divlXzaOQgvXEu9UBIWTueWclwwGcahZe/qPyOEkEPwzkRY1PaFazodOldzNh0ULBpUuwa+tRPGdjaIRIoQZtqEQ58bguJwPs86cwin6+4Qob4bxogLsrb3S4RTxApILMpABDa+Km107bvkoCRlSzSv8MMraawjlIzskE5NDEBtfJWLOyLqJ3H3JU6hpcJ8JvjFh5OD0NXL6gnZSTK+znvSYR1qDqHJRFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFIVMTudYLL/zyPqlZlU5iQz50KTaZGRH9Pl989TR3ECVOGyCuFGSNu0jWw9qIQME/GHYIZhP06XjCnfJNPeQbyZLet+xDw1BcPY4H0otSCTKXqiFL7ToTyrBWSH7/yzco0yWQ1iN4xPZYpltKOYVmNwBc3bSqcJZ7A3apGs6YjtEkkyTGRtRDFypbsnLYcTa/PWKDzei76cXuM8z7+j8TjIiiQRuxsfobAa0zL9DPzIFSuC6EhVOLVep9clafIxpxMMOT2ktA3cpPxkSC1FaAG5msvigHeXVp+KhWZsjHDyj0HloVnOPEOR8CmY/kyO4iHfalN01nTAx3jHda4o/KA="
|
|
],
|
|
"x5t": "1l71ENtrLzy7ff8vekF7F-9zRgk",
|
|
"x5t##S256": "Mupmx2VbSPMyU9VH_MPFKHm7fAMZouJzHjTiixqT91s"
|
|
}
|
|
]
|
|
}
|
|
/>
|
|
</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.globalHeaders = structNew();
|
|
variables.framework.globalHeaders["Access-Control-Expose-Headers"] = "Location";
|
|
//variables.framework.globalHeaders["Access-Control-Allow-Origin"] = "*"; //*** не знаю, нужно ли - но просили этот заголовок
|
|
//variables.framework.globalHeaders["X-MY-HEADER"] = "my header value for example";
|
|
|
|
//variables.framework.docs={};
|
|
variables.framework.docs.APIName="MMS API";
|
|
variables.framework.docs.APIVersion="3.26";
|
|
|
|
request.UNDEFINED_USR_ID=-1;
|
|
request.ANONYMOUS_USR_ID=2;
|
|
request.GUEST_USR_ID=3;
|
|
////////////////////////////////////////////////////////
|
|
request.ORCHESTRATOR_AUTH="Basic c3ZjX2xrdGVzdDoxMTgzZDIyNzc1OWExYjFkZmVjM2M3NTFiMzIyNzdiYTQ3"; //*** времянка, дубль из корневого Application.cfm
|
|
</cfscript>
|
|
|
|
|
|
<cffunction name="onApplicationStart">
|
|
<!--- <cfset application.dbUser = "username" />
|
|
<cfset application.dbPass = "password" />--->
|
|
<!---<cfset application._taffy.settings.allowCrossDomain = true/>--->
|
|
<cfreturn super.onApplicationStart() />
|
|
</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 (UCase(arguments.verb) EQ 'OPTIONS') return true;
|
|
|
|
var auth="";
|
|
if (structKeyExists(headers,"Authorization")) {
|
|
auth=headers.Authorization;
|
|
} else {
|
|
return representationOf("Authorization header expected").withStatus(401);
|
|
}
|
|
|
|
var httpService = new http(method = "GET", charset = "utf-8", url = "https://personal-cabinet.adl.nubes.ru/api/v1/user", timeout="3");
|
|
httpService.addParam(type = "HEADER", name = "Accept", value = "application/json");
|
|
httpService.addParam(type = "HEADER", name = "Authorization", value = "#auth#"); //passthrough
|
|
var resp = httpService.send();
|
|
var result = resp.getPrefix().filecontent;
|
|
//writeDump(result); abort;
|
|
|
|
var idpUserData=deserializeJson(result);
|
|
arguments.requestArguments.companyUid=idpUserData.companyId;
|
|
arguments.requestArguments.usrUid=idpUserData.contactId;//userId; //GUID!
|
|
|
|
var usrCustomerInfo=getUsrCustomerInfo(idpUserData.contactId);
|
|
if (structIsEmpty(usrCustomerInfo)) return representationOf("Cannot find default specification for current user #result#").withStatus(422);
|
|
arguments.requestArguments.usrId=usrCustomerInfo.usrId; //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="validatePin" access="private">
|
|
<cfargument name="authToken"/>
|
|
<cfargument name="issuedFor"/>
|
|
<cfargument name="verb"/>
|
|
<cfargument name="cfc"/> <!---для всех запросов, кроме GET,OPTIONS проверяем разрешенные CFC--->
|
|
|
|
<!---catch db issue--->
|
|
<cfquery name="qCheckToken">
|
|
select creator_id
|
|
from auth_token
|
|
where auth_token=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.authToken#" />
|
|
AND issued_for=<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.issuedFor#" />
|
|
AND dt_valid_till > <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#" />
|
|
</cfquery>
|
|
<!---тут можно добавить защиту от перебора с блокировкой попыток (можно добавить поле-счетчик ошибок)--->
|
|
|
|
<!--- <cfdump var=#qCheckToken#/>--->
|
|
|
|
<cfif qCheckToken.recordCount GT 0>
|
|
<cfset request.usr_id=qCheckToken.creator_id /> <!---impersonate pin issuer--->
|
|
<cfif uCase(arguments.verb) EQ "GET" OR uCase(arguments.verb) EQ "OPTIONS">
|
|
<cfreturn true/>
|
|
<cfelseif listFind("work_order,maint,maint_ls,maint_op,maint_op_ls,work_order_engineer_signature,work_order_spare_ls,work_order_spare,work_order_doc,maint_start,maint_doc_ls,maint_op_spare,maint_op_spare_ls,maint_doc", lCase(arguments.cfc))>
|
|
<cfreturn true/>
|
|
</cfif>
|
|
</cfif>
|
|
<cfreturn false/>
|
|
</cffunction>
|
|
|
|
<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"/>
|
|
<!--- глупейшее определение спецификации по умолчанию (надо переделать, чтобы создавалась, или вообще просто записывать факты без спеки). Но помним, что при постановке на тестирование цены еше не фиксированы --->
|
|
<!--- заметим, что у нас контрагент может быть определен параллельно, от IDP --->
|
|
<cfset local={}/>
|
|
|
|
<!--- Во избежании потери времени при отладке временно сделаем создание дефолтового контракта и спецификации, если их нет --->
|
|
|
|
<cfquery name="local.qGetCustomerInfo">
|
|
select u.usr_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 idp_usr_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.usrUid#" null=#!isValid('guid',arguments.usrUid)#/>
|
|
order by specification_id desc limit 1;
|
|
</cfquery>
|
|
<!--- <cfdump var=#local.qGetCustomerInfo#/><cfabort/> --->
|
|
<cfif local.qGetCustomerInfo.recordCount GT 0>
|
|
<cfreturn {"usrId"=#local.qGetCustomerInfo.usr_id#, "specificationId"=#local.qGetCustomerInfo.specification_id#}/>
|
|
<cfelse>
|
|
<cfreturn {}/> <!--- неуклюжий способ обозначить ошибку --->
|
|
</cfif>
|
|
</cffunction>
|
|
|
|
</cfcomponent>
|