The amazing adventures of Doug Hughes

Archive for September, 2008

Creating a Web Service in ColdFusion

This is a quick blog entry to answer a question I received by email. Here’s the question:

I am a final year Computer Science and Engineering student. I have planned to implement and deploy a Web Service for my final year project. I couldn’t find a good tutorial for creating a Web Service with ColdFusion. It would be very kind of you if you can suggest me what to do, or if you could refer me to some tutorial which is available for download.

Honestly, I debated whether or not to blog this answer. This is the kind of topic that bloggers were writing about when ColdFusion MX came out many years ago. That said, sometimes old topics need to be rehashed for those newer to ColdFusion. (In other news, there are programmers who are new to ColdFusion!) Anyhow, to actually answer the question, creating web services in ColdFusion is painfully simple. All you do is start out by writing a CFC. For example, this hello world CFC:

<cfcomponent>
    <cffunction hint="I say hi!" name="sayHello" returntype="string">
        <cfargument default="world" hint="Whom to say hi to." name="to" required="false" type="string"/>
        <cfreturn "Hello, #arguments.to#"/>
    </cffunction>
</cfcomponent>

This component, should you instantiate it and call the sayHello method will return a string saying hello to whatever you tell it to. The question of the moment is how to make a web service that will remotely expose this method? By default, all functions in ColdFusion components are public. This means any code in your application can instantiate the component and call the method. Other options are private, protected, and remote. I’m going to ignore private and public and hone in on remote. If you set the access to remote you are allowing anyone in the word to execute that method as a web service. Here’s the updated code:

<cfcomponent>
    <cffunction access="remote" hint="I say hi!" name="sayHello" returntype="string">
        <cfargument default="world" hint="Whom to say hi to." name="to" required="false" type="string"/>
        <cfreturn "Hello, #arguments.to#"/>
    </cffunction>
</cfcomponent>

That’s it! That’s all it takes. To actually use this web service simply browse to the path to the CFC over the web and append “?wsdl” to the end of the request. For example: http://somesite.com/helloWorld.cfc?wsdl. At this point you have a WSDL file and can use that for remote requests.

The First Reactor Meeting

As I stated a few days ago, I’m handing the reigns of Reactor project management to the esteemed Mark Drew.  As a part of this hand off to the community, Mark and I have decided to hold bi-weekly meetings to discuss the state of Reactor and steer the project.  Today was the first Reactor project management meeting and I think it went well, if I do say so myself. We started out with some confusion over whether or not we should use the voip features in the Connect room generously provided by Andy Allan.  The short answer is no, it’s terrible.  So we’re going to try to work out a bridge between Skype a US based conference line.  That way we can all dial in and talk for free.  On the other hand, once we gave up and chatted in Connect the meeting actually went quite well.  So, we’ll get that worked out somehow. In the meeting we discussed a number of topics and came up with a list of action items which we assigned to various people.  The outcome of the meeting is documented here.  We also came up with a few points to discuss on October 8th which are outlined here.  At this point things are rolling again.  Those who are in on the action will take our steps and in two weeks we’ll figure out what to do next.  Please join in and help us make this process a success!

Alagad Needs a Part Time Programmer

I’m in need of finding a junior programmer who is willing to work part time for Alagad. Essentially, what I’m looking for is someone who is either currently a student and who wants experience or a junior programmer who wants to make some extra cash on the side. If this person does well, then I’d hope to hire them full time to work with the Alagad team next year.

This person’s time will be split between answering tech support requests and working on new Alagad products and internal projects. Tech support is currently a very small fraction of this time. This person should know ColdFusion and have a reasonably good understanding of CFCs, as this will be the primary language and technology they’ll be working in.

I’m also looking for some knowledge of HTML, CSS, JavaScript and other web-related technologies. Again, this position would be a part time contract position. I’d rather have someone who can work during the day, if possible, but I’m also happy to consider those who can only work nights and weekends.

If you’re interested in this position please send me an email at dhughes@alagad.com explaining why you’re the person for the job. In the subject of your email please be sure to use the word “aardvark”. Seriously. Extra points for creativity. (I use this to filter emails and get a sense of your creativity.) Don’t forget to attach your resume too, but be aware that I give a lot more focus to cover letters than resumes. I hope to hear from you soon!

When is coupling OK? Frameworks & Services & Applications… oh my!

The ModelGlue list had an interesting question come up this week. “Interesting questions” on lists are the primary source of blog fodder for me, mostly because I find myself writing responses that are worth reformatting a bit and posting to the blog… and such, honestly, is the case here. The individual in question was asking whether using the new beans=”” attribute in ModelGlue:Gesture controllers would couple your application too tightly to the framework and therefore shouldn’t be used. Before I get into the answer, however, a brief aside:

For those not-in-the-know, adding the beans=”” attribute to the cfcomponent tag of an application controller in MG:G, and providing a comma-delimited list of bean IDs from the ColdSpring file (you are using ColdSpring, right?) will automatically pull those beans from ColdSpring and compose them at variables.beans.beanName.

Example:

<cfcomponent extends="modelglue.gesture.controller.Controller" beans="UserService">
	<cffunction name="getUser">
		<cfargument name="event" />
		<cfset event.setValue("User",variables.beans.UserService.getUser(event.getValue("UserID"),0)>
	</cffunction>
<cfcomponent>

So the question was basically, “Does using the beans injection functionality in MG 3 couple your application to the framework?” and by extension, “Does using a framework’s features inextricably marry your application to the framework?” and applies to about any application framework out there. The short answer is, well, really… yes and no.

The use of anything that extends modelglue.gesture.controller.Controller definitively binds your application to the framework in question. However, since you’re only going to be extending the MG core controller class when you’re building your own controller in a ModelGlue app, it’s a moot point… using the built-in bean injection is just using a feature of the framework when you’re building an application that uses the framework.

In other words, yes because you’re building a ModelGlue application at all, and no because using bean injection doesn’t couple you to the framework any more than the rest of the application does. Where you wouldn’t want any reference to the framework is actually one tier below the controller layer… your service layer. At least in theory, your controller talks to services and services talk to everything else.

Let’s look at a psuedo-codish method you might find in an MG controller:

<cffunction name="saveUser">
	<cfargument name="event" /></p>
	<cfset var UserID = event.getValue("UserID",0)>
	<cfset var userName = event.getValue("userName","")>
	<cfset var emailAddress = event.getValue("emailAddress","")>
	<cfset var password = event.getValue("password,"")>

	<cfset var result = variables.instance.userService.saveUserProfile(UserID,userName,emailAddress,password)>

	<cfif result>
		<cfset event.addResult("saveSucceeded")>
	<cfelse>
		<cfset event.addResult("saveFailed")>
	</cfif>
</cffunction>

From this we can extract a few observations and parse them into a couple rules of thumb that I use to keep my own mind pointed in the right direction as I develop applications (not counting laziness, client expectations, and/or anything else that may cause me to randomly break my own thumbs):

  1. What goes on in the service layer is effectively hidden from the controller and therefore the service methods can be used to very simply service a Flex application, an application using a different framework, or ad-hoc code. Using ColdSpring to manage your service-layer allows you to use AOP and provide your service layer to Flex, AIR and AJAX applications very easily and passing discrete arguments into service methods keeps things nice an speedy and prevents things like “infinite composition” in objects… an ugly thing that I’ve seen cripple Flex development.
  2. Controller methods accept an event object as their sole argument. It contains all the data from the form and URL scopes as well as anything injected into it via onRequestStart events (often application constants get stuck in the event object for convenience).
  3. Service-layer functions take discrete arguments relevant to whatever objects they’re dealing with… so while in this example, saveUserProfile(userId,username,emailAddress,password) is called with discrete arguments extracted from the event object you could (were you to be lazy and like to take chances with your apps) call saveUserProfile(argumentCollection=event.getAllValues()) and have done with it.

On the other hand, though, passing Transfer (or Reactor, or your own Model) objects from the service thru the controller into the view makes a good many things a great deal easier. So having userService.getUser(userID) return a User object that’s then stuffed into the event object is fine… so your controllers can then use things like makeEventBean() and stuff which simplifies somethings and makes others more difficult.

In the end, though, the goal is to make your service layer be totally independent of any other facility, and as long as you’re doing that then using specific features of the application framework of your choice when you’re creating the application does nothing to couple the service and persistence layers (or the model) to the framework. Do that and you’ve truly achieved a decoupled service layer and model and when you get around to releasing the full-featured Flex version of the application you’ve already done at least 75% of the work!

The Future of Reactor

Wow, when was the last time I blogged anything about Reactor? According to my blog archives that would be almost a year ago. So, the natural question arises, whats happened in that time? Sadly, not much. Which is exactly what this blog entry will begin addressing. Last week I sent an email to the reactor mailing list regarding the future of Reactor. In this email I essentially pointed out what the community has known for quite some time. Namely, that Reactor has been quite stagnant for quite a while. In fact, Reactor stalled right before it reached 1.0 and has been labeled a Release Candidate for a very, very, long time. So, whats the cause of this? In a nutshell: me.

The rise and stall of Reactor corresponded with the Rise and (thus far) success of Alagad. I can not manage both the demands of developing, documenting and supporting Reactor while also growing Alagad. Now, before I make my big announcement, I would like to defend Reactor. We have been using Reactor here at Alagad for years. Furthermore, there are nearly a thousand people on the Reactor mailing list. I personally believe Reactor is 1.0, except for the documentation that is seriously lacking.

Some people have postulated or assumed that the success of Transfer has meant the death of Reactor. This is untrue. There are many people who feel that Reactor brings something different to the table that they prefer. Then, theres the question of Hibernate integration in ColdFusion 9. Should anyone dedicate cycles to developing Reactor when Hibernate will be integrated into ColdFusion 9? In my opinion, yes, for a few reasons.

First off, as much as we all love Adobe, how many times have you been burnt by bugs or incompleteness in new ColdFusion features? Adobe does a great job with ColdFusion, but they only seem to complete 80% of what I need in new features. Thus, I dont trust that Hibernate integration in version 9 will ready for prime time at first. Beyond all that, Ive been told that Reactor is the only ColdFusion ORM framework that works on Railo, though I dont know how true that is.

I think there is a bright future for Reactor, if we could just get the project moving again.

And so, over the last few months Ive been pestering Mark Drew of CFEclipse fame to take the mantle of Reactor Project Manager. Finally, last week he acquiesced and agreed to take the help of the Reactor project. We vetted this decision through the Reactor mailing list and most people with an opinion supported the decision.

I will still be involved with Reactor but more as a proud parent helping his child enter the world on its own. Now, the question is, not only how to get Reactor moving again, but where to take it. To answer that question, weve have decided to hold public planning meetings every Wednesday from 12 noon to 1pm EST time. The meetings will be held in Andy Allans Connect room at http://connect.reactorframework.com.

Anyone who wishes may attend these meetings to chip in and help steer the project and volunteer to help. The first meeting will be held this week, September 24th and will primarily be organizational. Well probably spend some time attempting to figure out where to take Reactor and creating some tasks we need to accomplish and, hopefully volunteers to help with those tasks.The next week well meet, check our progress and establish more goals. Rinse, repeat. Im not sure where this will take Reactor, but, with a little perseverance and a little luck, hopefully it will be someplace good. I hope to see you Wednesday.

What is a DSL?

Peter Bell has been talking about domain specific languages for some time now, but I expect they are still a pretty foreign concept to many people, myself included not too long ago. However, if you have used any of the popular ColdFusion frameworks like Model-Glue, Mach-II, ColdSpring, etc., then you have actually used a DSL in their XML configuration files.

To my current understanding, a DSL allows you to take a generic set of application code and configure it to a specific use – hence the words domain specific. That configuration may be done via XML, a text file that is parsed, or even code. The big thing here is that you are writing configuration for a specific problem area or domain.

On a recent project, we were faced with 50+ simple CRUD style lookup tables that all had to have the same style list, create, and update form user interfaces. So, instead of creating individual little DAO, business, and service objects as well as the same cfm views over and over, we are adopting a generic “object” approach where there will be a single DAO, business object and service object, which when given an “object definition” via an XML document, will become smart objects and all of the sudden know that they represent a certain data element.

By doing this, the actual code we have to maintain is limited and making fixes and tweaks that much easier. The UI design is also much simpler in that there are only one set of views, so changing styles etc. can be done in one place and be applied across the board. It is also much simpler to make changes to the different objects managed by the system, whether that be adding a new object definition or changing the properties of an existing object definition.

So, are people using DSLs out there without really knowing it? What are some problem areas that you have addressed with a DSL? I know Peter Bell has discussion building whole applications before and while I am not to that point yet, I am very interested.

Delete Almost Everything From Your MSSQL Database

Ok, this script is dangerous. Use it only when you intend to. If you delete stuff you don’t want to, it’s your problem!

Anyhow, now that we’re past the disclaimer, I’m posting this blog entry as a follow up to one Layne Vincent posted earlier this week on how to truncate most of your tables. As it were, we are currently working on a project where pretty much everything related to the database is scripted. We have a script we generate that will completely rebuild our database including all of it’s tables, stored procedures, functions, basic lookup data etc.

The problem is, although the script has a chunk of code at the beginning to drop tables, the script takes no account of foreign key constraints and these drops simply don’t work. Besides, we also have a lot more than just tables we may want to drop. So, we were talking about this problem the other day and it got me thinking about a way to drop virtually everything from your database without actually dropping and recreating your database. (Why? Well, what if you don’t have rights to drop the database or create a new database?)

What I came up with was a simple script that will select most types of objects from the sys.objects view into a cursor. I then loop over the cursor and call the correct drop statement to drop the object. The query is sorted so that I can first drop foreign keys, then tables, then other objects. Without further ado, here’s the script:

/*** drop (pretty much) everything before rebuilding the database ***/
DECLARE
	OBJECTS CURSOR FOR SELECT
		so.name,
		so.type,
		so.type_desc,
		p.name AS parentName
	FROM
		sys.objects AS so
	LEFT JOIN sys.objects AS p ON so.parent_object_id = p.object_id
	WHERE
		so.schema_id = 1
	ORDER BY
		CASE
	WHEN so.type = 'F' THEN
		0
	WHEN so.type = 'TR' THEN
		1
	WHEN so.type = 'U' THEN
		2
	WHEN so.type = 'F' THEN
		3
	ELSE
		4
	END OPEN OBJECTS DECLARE
		@name AS nvarchar (MAX) DECLARE
			@type AS nvarchar (2) DECLARE
				@type_desc AS nvarchar DECLARE
					@parentName AS nvarchar (MAX) DECLARE
						@statement AS nvarchar (MAX) FETCH NEXT
					FROM
						OBJECTS INTO @name,
						@type,
						@type_desc,
						@parentName
					WHILE @@FETCH_STATUS = 0
					BEGIN

					SET @statement = '  IF(@type = ' F ')
BEGIN
PRINT ' DROPING FK : ' + @name + ' OF type ' + @type + ' (' + @type_desc + ') '
SET @statement = ' ALTER TABLE ' + @parentName + ' DROP CONSTRAINT ' + @name
EXECUTE(@statement)
END
ELSE IF (@type = ' TR ')
BEGIN
PRINT ' DROPING TRIGGER : ' + @name + ' OF type ' + @type + ' (' + @type_desc + ') '
SET @statement = ' DROP TRIGGER ' + @name
EXECUTE(@statement)
END
ELSE IF (@type = ' U ')
BEGIN
PRINT ' DROPING TABLE : ' + @name + ' OF type ' + @type + ' (' + @type_desc + ') '
SET @statement = ' DROP TABLE ' + @name
EXECUTE(@statement)
END
ELSE IF (@type = ' FN ')
BEGIN
PRINT ' DROPING FUNCTION : ' + @name + ' OF type ' + @type + ' (' + @type_desc + ') '
SET @statement = ' DROP FUNCTION ' + @name
EXECUTE(@statement)
END
ELSE
PRINT ' Didn 't drop object ' + @name + ' of type ' + @type + ' (' + @type_desc + ')'  FETCH NEXT
					FROM
						OBJECTS INTO @name,
						@type,
						@type_desc,
						@parentName
					END CLOSE OBJECTS DEALLOCATE OBJECTS

MSSQL Driver Issue with CF 8.0.1

This is one of those cases where being on the leading edge of technology causes issues. I just got a new workstation that is running the 64-bit version of Windows Vista. I installed the 64-bit version of ColdFusion 8.0.1 with the latest hotfixes etc. and everything was going well. That is, until I tried setting up an MSSQL datasource. Apparently there is something wrong with the drivers that ship with CF 8.0.1 as I was not able to connect to a local MSSQL server or a remote MSSQL server. Any time I tried creating a new data source, JRun would crash.

So, after a bit of digging, Jared Rypka-Hauer and Jason Delmore both suggested trying the latest JDBC drivers from Microsoft. Microsoft just released a CTP of version 2.0 in July which can be found here …

http://www.microsoft.com/downloads/details.aspx?FamilyId=F914793A-6FB4-475F-9537-B8FCB776BEFD

For those that have not installed a JDBC driver ( I have not before for MSSQL ), here are the steps you will need to follow.

  1. Expand the zip that you get from Microsoft and grab the sqljdbc4.jar file.
  2. Copy that file to the JRunserverslib folder in your multi-server CF install
  3. Restart ColdFusion
  4. Create a new data source, selecting ‘other’ as the typeJDBC URL = jdbc:sqlserver:/localhost:1433;databaseName=[Your Database Name]
    Driver Class = com.microsoft.sqlserver.jdbc.SQLServerDriver
    Driver Name = MSSQL

Submit that along with your database username and password and hopefully you get a green Ok result. I will update if I get any new information from Jason.

Update: In less than 24 hours, Jason from Adobe has confirmed that they can reproduce this and will be addressing the issue. So, thanks Jason and Adobe for quickly looking into this!

Truncate Most of the Tables

Recently I was in need of a script for SQL Server 2005 that would go in and truncate all of my tables in a given database. Not too tall an order one would think. Just write the darn thing and off you go. Not so fast, Bucko. What about those pesky foreign-keys? What about tables I don’t want truncated? What about the tedium in putting this thing together?

Well, after Googling and Googling and Googling some more I found this little jewel written by pvsramu in a forum entry over at SQLTeam.com. I thought I had found the answer…and I did, almost.

It did everything I wanted except I wanted to be able to exclude tables from the truncation. So mods were in order. I started looking at the best way to create a variable that I could use to list the tables to be excluded. All I found was frustration. SQL Server is not very kind when it comes to using comma separated list variables in an “IN” clause. After looking around for a while and, frankly, running out of time, I decided the best course of action was brute force. Not pretty but it gets the job done. With the whole brute force thing in mind, all it takes to remove tables from the truncation process is to add a list of them to each of the four queries in the script. Like I said, it’s not to elegant.

I certainly welcome any thoughts you all might have as to how to get SQL Server to deal with a list of values in a variable so that I could have just one list in the script.

As for this script, we use it daily. It’s been very handy and I hope some of you might find it handy as well.

Until later…

set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
-- =============================================
-- Author:		<Author,,Name>
-- Create date: <Create Date,,>
-- Description:	<Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[truncate_tables]
	-- Add the parameters for the stored procedure here

AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

/*

	This batch t-sql deletes data from all the tables in the database.

	Here is what it does:
	1) Disable all the constraints/triggers for all the tables
	2) Delete the data for each child table & stand-alone table
	3) Delete the data for all the parent tables
	4) Reseed the identities of all tables to its initial value.
	5) Enable all the constraints/triggers for all the tables.

	Note: This is a batch t-sql code which does not create any object in database.
	If any error occurs, re-run the code again. It does not use TRUNCATE statement to delete
	the data and instead it uses DELETE statement. Using DELETE statement can increase the
	size of the log file and hence used the CHECKPOINT statement to clear the log file after
	every DELETE statement.

	Imp: You may want to skip CHECKIDENT statement for all tables and manually do it yourself. To skip the CHECKIDENT,
	set the variable @skipident to "YES" (By default, its set to "NO")

	Usage: replace #database_name# with the database name (that you wanted to truncate) and just execute the script in query analyzer.

	*/

	-- use [#database_name#]

	SET NOCOUNT ON

	DECLARE @tableName varchar(200)
	DECLARE @tableOwner varchar(100)
	DECLARE @skipident varchar(3)
	DECLARE @identInitValue int
	DECLARE @tableExclusions varchar(200)

	SET @tableName = '
	SET @tableOwner = '
	SET @skipident = 'NO'
	SET @identInitValue=1
	/*
	Step 1: Disable all constraints
	*/

	EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'
	EXEC sp_MSforeachtable 'ALTER TABLE ? DISABLE TRIGGER ALL'

	/*
	Step 2: Delete the data for all child tables & those with no relationships
	*/

	WHILE EXISTS
		(
		SELECT	T.table_name
		FROM	INFORMATION_SCHEMA.TABLES T
				LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
				ON T.table_name=TC.table_name
		WHERE	(TC.constraint_Type ='Foreign Key'
				OR TC.constraint_Type IS NULL)
				AND	T.table_name NOT IN ('dtproperties','sysconstraints','syssegments')
				-- add the other tables we want to exclude
				AND T.table_name NOT IN (--add values here--)
				AND Table_type='BASE TABLE'
				AND T.table_name > @TableName
		)

	BEGIN
		SELECT TOP 1 @tableOwner=T.table_schema,
				@tableName=T.table_name
		FROM	INFORMATION_SCHEMA.TABLES T
				LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
				ON T.table_name=TC.table_name
		WHERE	(TC.constraint_Type ='Foreign Key'
				OR TC.constraint_Type IS NULL)
				AND	T.table_name NOT IN ('dtproperties','sysconstraints','syssegments')
				-- add the other tables we want to exclude
				AND T.table_name NOT IN (--add values here--)
				AND Table_type='BASE TABLE'
				AND T.table_name > @TableName
		ORDER BY t.table_name

		--Delete the table
		EXEC('DELETE FROM '+ @tableOwner + '.' + @tableName)

		--Reset identity column
		IF @skipident = 'NO'
		IF EXISTS
			(
			SELECT * FROM information_schema.columns
			WHERE COLUMNPROPERTY(OBJECT_ID(
			QUOTENAME(table_schema)+'.'+QUOTENAME(@tableName)),
			column_name,'IsIdentity')=1
			)
		BEGIN
			SET @identInitValue=1
			SET @identInitValue=IDENT_SEED(@tableOwner + '.' + @tableName)
			DBCC CHECKIDENT (@tableName, RESEED, @identInitValue)
		END

		CHECKPOINT
	END

	/*
	Step 3: Delete the data for all Parent tables
	*/

	SET @TableName='
	SET @tableOwner='

	WHILE EXISTS
		(
		SELECT	T.table_name
		FROM	INFORMATION_SCHEMA.TABLES T
				LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
				ON T.table_name=TC.table_name
		WHERE	TC.constraint_Type ='Primary Key'
				AND T.table_name <>'dtproperties'
				-- add the other tables we want to exclude
				AND T.table_name NOT IN (--add values here--)
				AND Table_type='BASE TABLE'
				AND T.table_name > @TableName
		)

	BEGIN
		SELECT TOP 1 @tableOwner=T.table_schema,
				@tableName=T.table_name
		FROM	INFORMATION_SCHEMA.TABLES T
				LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
				ON T.table_name=TC.table_name
		WHERE	TC.constraint_Type ='Primary Key'
				AND T.table_name <>'dtproperties'
				-- add the other tables we want to exclude
				AND T.table_name NOT IN (--add values here--)
				AND Table_type='BASE TABLE'
				AND T.table_name > @TableName
		ORDER BY t.table_name

	--Delete the table
	EXEC('DELETE FROM '+ @tableOwner + '.' + @tableName)

	--Reset identity column
	IF @skipident = 'NO'
	IF EXISTS
		(
		SELECT	*
		FROM	information_schema.columns
		WHERE	COLUMNPROPERTY(OBJECT_ID(QUOTENAME(table_schema)+'.'+QUOTENAME(@tableName)),
				column_name,'IsIdentity')=1
		)
	BEGIN
		SET @identInitValue=1
		SET @identInitValue=IDENT_SEED(@tableOwner + '.' + @tableName)
		DBCC CHECKIDENT (@tableName, RESEED, @identInitValue)
	END

	CHECKPOINT

	END

	/*
	Step 4: Enable all constraints
	*/

	EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'
	EXEC sp_MSforeachtable 'ALTER TABLE ? ENABLE TRIGGER ALL'

	SET NOCOUNT OFF
END

Tinkering with CF Admin: Make your own login system!

A while back, a colleague emailed us asking if anyone knew how to get CF Admin to use LDAP for authentication instead of it’s own private data store. It wasn’t a question any of us had any experience trying out, and I was curious, so I did a bit of digging and found something interesting. I’ve been meaning to blog about it for ages, so I’m happy to finally have the chance.

Here’s the deal: the ColdFusion Administrator (at this point) is restricted to using its own datastore, it’s own login code, and it’s own security subsystem. Kind of like the Model-T’s color scheme, you can use any login mechanism you want, as long as it’s the one that comes baked-in. This can be a problem, especially if you want your team to be able to access the administrator via a login scheme like LDAP or a SQL DB full of user/group information and divide up access to different parts of the administrator. Now that the Admin has multi-user capability this is even more important, but the restriction still applies: There’s no built-in mechanism to swap out the datastore that CF Admin uses for users, roles and activities.

That doesn’t mean, however, that you can’t make this work… The key files in the Administrator that have to do with gaining access and setting up the environment are login.cfm and enter.cfm. Login is the form (dur) and it posts to enter.cfm which sets up the environment (including instantiating the whole AdminAPI). After IMing with Ben Forta about it a bit, I was able to come up with something that was, if not perfect, at least a workable solution. You see, while both of those pages are encrypted using cfencrypt, there are no rules against replacing the built-in files with files of your own creation. Well, there’s actually a caveat there: it seems that enter.cfm does a great deal of “stuff” to set up the Administrator’s environment and I just wouldn’t want to replace it without knowing more about what it does first.

The simplest login.cfm code required to gain access to the CF Admin is:

<cfset createObject("component","CFIDE.adminapi.administrator").login("pwd","username")
<cflocation url="http://localhost/CFIDE/administrator/index.cfm" addtoken="false" />

Since an uncredentialled user is always forwarded to login.cfm, and since enter.cfm (or whatever file sets up CF Admin’s environment) is run on every request, putting that in login.cfm will essentially remove any sort of login process at all. If you hit any page in the Administrator, you’ll always see it. Try it… change the name of login.cfm to login.cfm.old and put a new login.cfm file containing the above code (with a valid username/password combination, obviously) and hit a page in the Administrator. BOOM! Right on thru. Obviously this isn’t a great idea, but it illustrates the fact that you can use login.cfm as a hook into the front end of logging into the Administrator and replace it with functionality of your design, so long as it ends with adminapi.administrator.login() and forwards back to index.cfm.

Incidentally, this all has to happen within the context of the Admin’s folders… apparently the application scope is involved in figuring out who’s logged in and who isn’t and, therefore, if you call administrator.login() from another application it will NOT gain you access to CF Admin’s UI. So be sure that you’re working with login.cfm and enter.cfm to make this work.

There are a few primary ways I’ve thought of that this could be accomplished that I thought I’d share here:

  • Fetch the credentials from your data store along with role/privilege information anduse the AdminAPI to programmatically create an account for them. The advantage here is that since they’re logging via your data store, you can disable their account before they ever get to the admin without having to use CF Admin to do anything.
  • Create a set accounts with specific sets of roles and privileges, have your users credentials verified against your datastore and then call adminapi.administrator.login() using the appropriate account for that user’s login.
  • Have login.cfm post to itself, analyze the user’s state in the DB, and display various content based on various switches. If, for example, you have a DB with all sorts of users, some of which are allowed into the administrator, a user with a valid account but no access to the admin could be presented with a page that says “You’re not authorized, click here to request access”, etc., all by having login.cfm post to itself. Yeah, the code would have to be old-school, but it works. Hasn’t everyone, at least once, written a data-entry page that posts to itself with at the top of it to handle the POST data?

In any case, there are many ways to deal with this on either end of the login system… you could integrate the AminAPI into a user-management application, you could use a bulk create/update scheduled task, you could… well you could many things. I guess the point is that even with the encrypted code in the Administrator’s directories, there are still ways to tinker with and modify CF Admin’s functionality to get it to do what you want it to.

I am going to end this post with a question: Knowing what you now know, how would you accomplish the task of using an alternative source of credentials for the CF Administrator?

Tag Cloud