
One of the methods I use to share data with SharePoint online is to create a web service using ColdFusion, which will provide data from internal data sources via JSON. However, I’ve recently become increasingly concerned over the security of these web services. By default, you can create a CFC component and make one of the methods remote accessible, and you have a easy way to share data. Over the past few days, I’ve been working on a way to secure the web wervices in a way that ColdFusion won’t just hand over the data without some kind of validation.
One of the first challenges I needed to overcome, is that ColdFusion doesn’t provide access to the session scope for web service users; or if it does, it’s not easy to access via the code. Historically, I haven’t had much success using the onCFCRequest method in the Application.cfc; and in order to make this work, I need the onCFCRequest method.
The scenario for this article is that Synergy Interaction Corporation (SiCO) records all workstation logins (username, loginDateTime, Workstation) to a database, and they are wanting to display a list of recent logins on a SharePoint Online Page. In this article, I am going to create a ColdFusion Webpart and an Application.cfc to provide the data, and a SharePoint SPFx Webpart to display the data to the user.
First let’s discuss the userlogins.cfc file.
component displayname=”userLogins” hint=”returns user login information stored in the database” output=”true” {
remote any function getData() returnformat=”JSON” {
queryResults = queryExecute(
“SELECT username, loginDateTime, workstation FROM workstationLogins WHERE loginDateTime between :yesterday AND :today ORDER BY loginDateTime”,
{
yesterday={value=”#DateFormat(DateAdd(‘d’,-1,Now()),’yyyy-mm-dd’#”,cfsqltype=”cf_sql_date”},
today={value=”#DateFormat(Now(),’yyyy-mm-dd’)#”,cfsqltype=”cf_sql_date”}
}, {
datasource=”WorkstationLoginsDSN”
}
);
return #SerializeJSON(queryResults)#
}
}
Assuming the file, “userlogin.cfc” is in a folder called “webservices” in the root of the website, a user could access the JSON data provided in this web part by navigating to https:///webservices/userlogins.cfc?method=getData in a browser. This isn’t the most ideal situation in most cases.
Next, I created a database called, “TokenServices” and added a table called, “Tokens.” The token table has the following three fields: “AppKey”, “AppToken”, and “App.” The “AppKey”, “AppToken” fields are both varchar(512) type fileds, while the “App” field is varchar(255).
The values for the database are:
AppKey: 3c93a253d3f604300a4b9191daaffa492c2a99cc5e6ad05a299af62f4b4ddc654bd8ea5011678a1e89e043d1ed1dfbe62ee4358140404d4b23b1aa80129f342080148cc3fc94e4beb4d9f5fcbdb22de40e6e3381605e2c15fb38b9456b024d46fd9e071f6d58d2b279401615c73455edfeb75b674b4f20ca7e3d8c2f6d95c65e27e573079cc62e3804e130e030e99fa6cd34ea0ecbbca319464f35baff041ecde8ba5d1531424db06222632319edff0ace6b80115784a7cc447ab328759debb6a5066206c70370f0af3d504aa9191ac3abd90a01e8801af8ef696ee1515e6b9e7e6c6ea75ed8f6bb2f3a07c9a576887aeacfe92b75d09930ef636c59d127ef6
AppToken: eef4c56c8c727f973e79baf34d2b65551fb811366b95525c8d31049d83e9ef1f00707cf9fd7753a526381e38f858ff6cebd994fed9a8e64bfdefcb21428efadc54ec76701cd0b6a7055f6c3b2b7ae488da7757b1c94a6cf6ab3ea6c5cf06f87bfa458647914d8a401b57775d6f6b84515651cb102cd62b0f8a167fc245bc2da934ad329080053f8a29ef6808bb66be52fc1ed59f60d487046856a5ac4f93babbf73c27bf3babc7e4fe84487f4243005202199b666adfe6237ecbbb5e91777a15c0db0a9373e0ff2afa5b407a73da5599b7fcfa9b9673376412f6164aa4e9a9c0fe2447a3bf195037c924922c0f46166f9c22396260785c65f42236ebe9d1c13
App: webservices.userlogins
I created a stock Application.cfc. I modified the onCFCRequest function as followed:
public any function onCFCRequest(required string cfc, required string method, required struct args) {
var RequestParams = deserializeJSON(toString(getHttpRequestData().content));
var APPKEY = toString(RequestParams.AppKey);
var APPTOKEN = toString(RequestParams.AppToken);
var USER = toString(RequestParams.User);
var IP_ADDRESS = getClientIp();
queryResults = queryExecute("SELECT AppKey, AppToken FROM tokens WHERE App=:cfc",{cfc=#cfc#},{datasource = "TokenServices"});
if ((APPKEY eq queryResults.AppKey) and (APPTOKEN eq queryResults.AppToken)) {
try {
var o = createObject(arguments.cfc);
var metadata = getMetadata(o[method])
if (structKeyExists(metadata, "access") && metadata.access == "remote"){
return invoke(o, method, args);
} else {
returnValue = StructNew();
returnValue.status = "ERROR";
returnValue.message = "The method #method# does not exists or is inaccessible remotely";
writeOutput(serializeJSON(returnValue));
}
} catch (any err) {
returnValue = StructNew();
returnValue.status = "ERROR";
returnValue.message = "The method #method# does not exists or is inaccessible remotely.";
returnValue.err = #err#;
writeOutput(serializeJSON(returnValue));
}
} else {
returnValue = StructNew();
returnValue.status = "ERROR";
returnValue.message = "Authorization Failed.";
writeOutput(serializeJSON(returnValue));
}
}
The onError method was modified to:
public void function onError(required any exception, required string eventName) {
returnValue = StructNew();
returnValue.status = "ERROR";
returnValue.message = exception.message;
returnValue.Detail = exception.detail;
writeOutput(serializeJSON(returnValue));
return;
}
To access the data in our SPFx webpart, I created the following method:
private getData = (): Promise => {
let restQuery = “https:///webservices/userlogins.cfc?method=getData”;
return axios ({
method: ‘POST”,
url: restQuery,
data: {
AppKey: “3c93a253d3f604300a4b9191daaffa492c2a99cc5e6ad05a299af62f4b4ddc654bd8ea5011678a1e89e043d1ed1dfbe62ee4358140404d4b23b1aa80129f342080148cc3fc94e4beb4d9f5fcbdb22de40e6e3381605e2c15fb38b9456b024d46fd9e071f6d58d2b279401615c73455edfeb75b674b4f20ca7e3d8c2f6d95c65e27e573079cc62e3804e130e030e99fa6cd34ea0ecbbca319464f35baff041ecde8ba5d1531424db06222632319edff0ace6b80115784a7cc447ab328759debb6a5066206c70370f0af3d504aa9191ac3abd90a01e8801af8ef696ee1515e6b9e7e6c6ea75ed8f6bb2f3a07c9a576887aeacfe92b75d09930ef636c59d127ef6”
AppToken: “eef4c56c8c727f973e79baf34d2b65551fb811366b95525c8d31049d83e9ef1f00707cf9fd7753a526381e38f858ff6cebd994fed9a8e64bfdefcb21428efadc54ec76701cd0b6a7055f6c3b2b7ae488da7757b1c94a6cf6ab3ea6c5cf06f87bfa458647914d8a401b57775d6f6b84515651cb102cd62b0f8a167fc245bc2da934ad329080053f8a29ef6808bb66be52fc1ed59f60d487046856a5ac4f93babbf73c27bf3babc7e4fe84487f4243005202199b666adfe6237ecbbb5e91777a15c0db0a9373e0ff2afa5b407a73da5599b7fcfa9b9673376412f6164aa4e9a9c0fe2447a3bf195037c924922c0f46166f9c22396260785c65f42236ebe9d1c13”
},
responseType: ‘JSON’})
.then(response => {
return response.data;}
});
}
