Question

I have a CFC file with remote access enabled, I am using this to perform various admin tasks such as adding / removing image records from a database. The calls to this CFC are made through AJAX via custom JavaScript on my admin pages. I placed the CFC in what I thought was a secured directory but having had some issues with images disappearing of their own accord and I have since found it is not secure at all.

I would like to secure the CFC. There is already a session based security CFC that I use for the admin pages, and the protect method of that gets called each time one of the admin pages is requested, this redirects a user if the authentication fails. Can I use something this on my CFC? IF so, what is the best way to implement it? If not, how should I implement security on it?

Below is my example CFC:

<cfcomponent
  name="test"
  displayname="test"
  output="false"
  hint="test"
>

<!--- pseudo constructor --->
<cfscript>
    variables.propertyImageDAO = CreateObject("component","cfcs.dataobjects.property_imageDAO").init(APPLICATION.dsn);
    variables.propertyImageGateway = CreateObject("component","cfcs.dataobjects.property_imageGateway").init(APPLICATION.dsn);
</cfscript>

<!--- constructor --->
<cffunction name="init" access="public" output="false" returntype="any"
        hint="Constructor for this CFC">

    <!--- return this CFC --->
    <cfreturn this />
</cffunction>

<!--- CRUD methods (create, read, update, delete) --->
<!--- CREATE: inserts a new property_image into the database --->
<cffunction name="createRecord" access="remote" output="true" 
        hint="Creates a new property_image record and returns a struct containing a boolean (success) indicating the success or
        failure of the operation, an id (id), and a string (message) containing a message"
        >

    <cfargument name="name" type="any" required="false" default="" />
    <cfargument name="alt" type="any" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- create property bean --->
    <cfscript>
        var propertyImageBean = CreateObject("component","cfcs.beans.property_image").init(
            '',
            arguments.name,
            arguments.alt
        );
        results = propertyImageDAO.createRecord(propertyImageBean);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>
<!--- READ: reads a property_image from the database and populates the property_image object --->
<cffunction name="readRecord" access="remote" output="true" returntype="void"
   hint="Reads property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="id" type="numeric" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- create property bean --->
    <cfscript>
        propertyImageBean = CreateObject("component","cfcs.beans.property_image");
        propertyImageBean.setid(arguments.id);
        propertyImageDAO.readRecord(propertyImageBean);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(propertyImageBean)#</cfoutput>
</cffunction>
<!--- DELETE: reads a property_image from the database and populates the property_image object --->
<cffunction name="deleteRecord" access="remote" output="true" returntype="void"
   hint="Reads property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="id" type="numeric" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- create property bean --->
    <cfscript>
        results = propertyImageDAO.deleteRecordById(arguments.id);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>   
<!--- DELETERECORDS: deletes a property_image from the database --->
<cffunction name="deleteRecords" access="remote" output="true" returntype="void"
   hint="Deletes property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="imageIdList" type="string" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- delete DB records --->
    <cfscript>
        results = propertyImageDAO.deleteRecordsByIdList(arguments.imageIdList);
    </cfscript>
    <!--- delete files --->

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>   
<!--- DELETERECORDS: reads a property_image from the database and populates the property_image object --->
<cffunction name="deleteRecordById" access="remote" output="true" returntype="void"
   hint="Deletes property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="id" type="numeric" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- delete DB records --->
    <cfscript>
        results = propertyImageDAO.deleteRecordById(arguments.id);
    </cfscript>
    <!--- delete files --->

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction> 
<!--- DELETERECORDSBYIDLIST: reads a property_image from the database and populates the property_image object --->
<cffunction name="deleteRecordsByIdList" access="remote" output="true" returntype="void"
   hint="Deletes property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="imageIdList" type="string" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- delete DB records --->
    <cfscript>
        results = propertyImageDAO.deleteRecordsByIdList(arguments.imageIdList);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>   

<cffunction name="deleteImagesByNameList" access="remote" output="true" returntype="void"
   hint="Deletes property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="imageNameList" type="string" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- delete DB records --->
    <cfscript>
        results = propertyImageDAO.deleteImagesByNameList(arguments.imageNameList);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>   

<!--- READ: reads a property_image from the database and populates the property_image object --->
<cffunction name="getByIdList" access="remote" output="true" returntype="void"
   hint="Reads property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="imageIdList" type="string" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- create property bean --->
    <cfscript>
        qGetByIdList = propertyImageGateway.getByIdList(arguments.imageIdList);
    </cfscript>

    <!--- convert into JSON friendly format --->
    <cfif qGetByIdList.recordCount GT 0>
      <cfset images = ArrayNew(1)>
      <cfloop query="qGetByIdList" startRow="1" endRow="#qGetByIdList.recordCount#">
          <cfscript>
              // create image struct and assign values
              image = StructNew();
              image.id = id;
              image.name = name;
              image.alt = alt;
              // append to JSON response
              ArrayAppend(images,image);
          </cfscript>
      </cfloop>
      <cfset results.images = images>
    </cfif>
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>
<!--- READ: reads a property_image from the database and populates the property_image object --->
<cffunction name="updateRecord" access="remote" output="true" returntype="void"
   hint="Reads property_image data from the database and returns a JSON">

    <!--- take property_image bean as argument --->
    <cfargument name="id" type="numeric" required="true" />
    <cfargument name="name" type="any" required="true" />
    <cfargument name="alt" type="any" required="true" />

    <!--- initialize variables --->
    <cfset var results = StructNew() />

    <!--- create property bean --->
    <cfscript>
        propertyImageBean = CreateObject("component","cfcs.beans.property_image").init(
            arguments.id,
            arguments.name,
            arguments.alt
        );
        results = propertyImageDAO.updateRecord(propertyImageBean);
    </cfscript>

    <!--- return the struct --->
    <cfoutput>#SerializeJSON(results)#</cfoutput>
</cffunction>

Was it helpful?

Solution

To enforce your authentication logic, you should wrap all remote CFC calls in Application.cfc with this logic.

Unfortunately you're using CF8, so you can't use the onCFCRequest method of Application.cfc to easily wrap all your remote requests. But you can do the same thing in onRequestStart by checking if the target page ends in '.cfc'.

<cffunction name="onRequestStart">
    <cfargment name="targetPage">
    <cfif right(targetPage, 4) eq '.cfc'>
        <!--- Perform authentication check --->
        <cfif not loggedIn>
            <!--- Return "unauthorized" to the client --->
            <cfheader statuscode="401"> 
            <cfabort>
        </cfif>
    </cfif>
</cffunction>

Then, in your Ajax fail handler, check for a 401 status code and display a message to the user indicating the need for logging in.

OTHER TIPS

Why don't you just use a session token every time you make ajax call.

Place your authentication logic (validate session) into a remote facade, or if you are using any MVC framework, put the authentication logic onto the controller layer.

Return the appropriate HTTP status code (e.g. 403) if session validation fails so the frontend code can react appropriately.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top