Posts

OxAlto Roombooking System 1.2 Released!

Firstly, there’s now some pretty comprehensive documentation at http://roombooking.readme.io/ – This was a bit of blood, sweat and tears, but the system at readme.io makes this relatively painless, and it’s free for open source projects – hurrah!

This is a pretty big release this one – LOTS of new features and tweaks. Don’t forget to read the Upgrade notes for this one…

Here’s the feature list, but the highlights probably are simple concurrency warnings when booking, event moderation, custom fields and templating from within the application, support for running in a subdirectory and a better list view.

Download 1.2 now

Features:

  • New and comprehensive documentation
  • Creating a new booking now does a simple concurrency check
  • Bookings can now have basic event moderation
  • Moderator permissions can be assigned by role
  • Booking can exist in three states – approved, pending & denied
  • Locations can now have custom layouts
  • Locations can now belong to ‘buildings’
  • Locations now have a basic information page
  • Calendar view shows pending/denied events with different colours/opacity
  • Calendar view location filter now supports buildings and hover filter states
  • Events & Locations can now have Custom Fields added (and stored in the database)
  • Events & Locations can now use Custom Templating for both input + output
  • Added New Custom Template drag and drop creator (from gridmanger.js) for building forms and outputs
  • Installer simplified
  • URL rewriting now off by default to help support sub directory installations more seamlessly
  • Can now run in a subdirectory
  • Roles are now dynamic
  • List View now supports date ranges, multiple locations and filtering by status.
  • Notification emails updated to support mobile/responsive clients
  • Not deleting the /install/ folder now shows a nag message rather than throwing a full error
  • Footer now displays current code + database schema version
  • [Removed] Day view is now deprecated in its current form

JS/CSS:

  • Upgraded to fullcalendar.js 2.3.2
  • Improved datepickers again
  • More JS moved to external js file rather than being embedded in page

Serverside:

  • Moved to cfwheels 1.4.1.

Data:

Tables:

  • Added: customfields,customfieldjoins,customfieldvalues,templates
  • Events – added status column
  • locations – added building /layouts columns

Settings:

  • Added: approveBooking,bccAllEmailTo,bccAllEmail
  • Changed: – siteEmailAddress changed category to Email, version number updated.

Permissions:

  • Added: accessCustomFields,allowApproveBooking,bypassApproveBooking

v1.1 RoomBooking system released

This is a rather large update, bringing with it some nice new features such as resource booking, some basic digital signage facilities, and an almost complete rewrite under the hood.

Demo | Github

What’s new in 1.1?

Features:

– New static ‘Day’ view (in additional to all the fullcalendar.js views)
– New display board view (for digital signage)
– New Resource management feature: add resources (i.e, laptops) to be bookable alongside an event (#23)
– Support for ‘unique’ resource booking: i.e, will check certain devices for concurrent use
– Support for limiting a resource to a specific location
– Resources can be grouped i.e, Computers,Audio Visual,Furniture
– Clone event functionality
– Calendar “MaxTime” added
– All Day events now set time to 0 – 23:59 to help with alt displays

JS/CSS:
– Upgraded to Bootstrap 3.3.1
– Upgraded to fullcalendar.js 2.x
– Improved datepickers/colourpickers
– All JS/CSS now generated via grunt
– Frontend package managment now from bower
– Added bootstrap form validation
– jQueryUI no longer required
– [removed]: Bootstrap Theme Support
– [removed]: Bootstrap CDN support

Serverside:
– Moved from cfwheels 1.2 -> 1.3.2
– Large underlying codebase rewrite.
– All controllers, models and most helper functions now in cfscript rather than tags: note this may cause issues if you’re on CF9.
– Support for servers with prefix json
– Various options removed/changed to support moment.js
– Improved form validation
– Most JS moved to a single JS file generated via grunt
– Extraneous folders/files removed (i.e /files/)

RSS2 & iCal for RoomBooking System

Since Jan 2014, RSS2 and iCal have been added – these are a bit experimental – would appreciate feedback!

Each user can have a unique key generated for them by an administrator (it’s not there by default);
Once an API key is available for that user, and their role allows it, they should be able to access /api/ which will display a list of available feeds.

Feeds are ‘All’ (i.e all locations) and then split down by location, so you could have RSS for a specific location if you wanted.

Note that after creating an API key for that user, the user will need to login/out again to be able to use it, but should then see “Data Feeds” as a menu option on the Events drop down. Once they’ve got the URL with the token, that will then bypass the authentication, so you can get read access to the upcoming events.

Things learned from creating the RoomBooking System (part 2)

The Room Booking system is now available as v 1.01; the main addition being permissions, roles and authentication. As before, I thought it might be useful to highlight a few of the thought processes behind this update, and look at some of the problems come across when beginning to think about an application which is due for distribution in all sorts of environments.

Authentication varies massively depending on the setup – here are a few common scenarios:

Using authentication external to the application, i.e at a IIS/Apache level:

  1. Block the whole website: e.g, have everything behind apache’s htpasswd authentication, or restrict to a specific IP Range, or VPN connection etc
  2. Block only “admin” level functions: e.g specific authentication triggered for all /admin/ requests: again, trigger via .htpasswd or somesuch.
  3. Using Active Directory/LDAP to authenticate a user, and use groups specified outside the main application to load a user into a specific role
  4. Other SSO systems (shibboleth etc) integrated into IIS/Apache

Authentication within the application:

  1. Using traditional cflogin, and built in CF login functions
  2. Testing for session based variables specifically, i.e session.user.loggedinAt

Obviously there are a million other variations on this, but it does highlight that whatever your doing should at least consider these other methods.

What are you trying to protect?

Obviously, this is the first question you have to ask yourself. What is it that requires some level of authentication? Nine times out of ten, it’s the privilege of changing or viewing data, that much is pretty self explanatory. But it’s worth considering all data you’re storing, and how that might be used. For instance, blocking the web interface of an application is one thing, but what about potential API usage? RSS Feeds? Other connection types which don’t require a browser? For the Room Booking system (RBS after this point, I can’t be bothered with the typing), it’s relatively straightforward, at least at the moment. But in the future I’m planning to add RSS/iCS/JSON/XML feeds – perhaps only read only, but whatever authentication system, I’ve got to consider future usage.

What I ended up with:

In order for the RBS to be as flexible as possible to the most number of users, I ended up decided on (application specific) Role based permissions. In short, if person ‘bob’ is in role ‘admin’, and tries to do ‘x’ there needs to be some sort of permissions matrix indicating the admin role’s permission to do ‘x’. The important part to note here is that I’m not ever directly testing against a role. There should be  no conditional statements like if(isAdmin(user)) then do ‘x’; this leads to a nightmare later on when you suddenly decide you want to add a role, as for each ‘isAdmin()’ check, you then have to start adding ‘isEditor()’ or somesuch, and probably all over your application.

It’s better to build a privilege called ‘canDoX’ and test against that, as roles can more easily be added in the future.

Wheels lets us handle this quite neatly with two models, users & permissions. A user has a role, such as ‘admin’ or ‘editor’ etc; Then in the permissions table, you have a permission key, which is the name of your permission, i.e canDoX, and a column for each role, with a tinyint of 1 or 0 depending. So it’s a simple matrix of yes/no type data.

Then, onApplicationStart, we can check for an load in the matrix of permissions into the application scope for referencing later:

<cfset application.rbs.permissionsQuery=model("permission").findAll()>
 <cfloop query="application.rbs.permissionsQuery">
   <cfscript>
      application.rbs.permission["#id#"]={};
      application.rbs.permission["#id#"]["admin"]=application.rbs.permissionsQuery["admin"];
      application.rbs.permission["#id#"]["editor"]=application.rbs.permissionsQuery["editor"];
      application.rbs.permission["#id#"]["user"]=application.rbs.permissionsQuery["user"];
      application.rbs.permission["#id#"]["guest"]=application.rbs.permissionsQuery["guest"];
   </cfscript>
 </cfloop>

Once we’ve got these in the application scope, all we need are four functions to handle most of the permission based work:

<cffunction name="checkPermission" hint="Checks a permission against permissions loaded into application scope for the user" returntype="boolean">
	<cfargument name="permission" required="true" hint="The permission name to check against">
	<cfscript>
		if(_permissionsSetup() AND structKeyExists(application.rbs.permission, arguments.permission)){
			return application.rbs.permission[arguments.permission][_returnUserRole()];
		}
	</cfscript>
</cffunction>

<cffunction name="checkPermissionAndRedirect" hint="Checks a permission and redirects away to access denied, useful for use in filters etc">
	<cfargument name="permission" required="true" hint="The permission name to check against">
	<cfscript>
		if(!checkPermission(arguments.permission)){
			redirectTo(route="denied", error="Sorry, you have insufficient permission to access this. If you believe this to be an error, please contact an administrator.");
		}
	</cfscript>
</cffunction>

<cffunction name="_permissionsSetup" hint="Checks for the relevant permissions structs in application scope">
	<cfscript>
		if(structKeyExists(application, "rbs") AND structKeyExists(application.rbs, "permission")){
				return true;
		}
		else
		{
			return false;
		}
	</cfscript>
</cffunction>

<cffunction name="_returnUserRole" hint="Looks for user role in session, returns guest otherwise">
	<cfscript>
		if(_permissionsSetup() AND isLoggedIn() AND structKeyExists(session.currentuser, "role")){
			return session.currentuser.role;
		} else {
			return "guest";
		}
	</cfscript>
</cffunction>

So all you’d need to do using the above is:

<cfscript>
if(checkPermission("canDoX")){
// Do stuff
}
</cfscript>

The checkPermissionAndRedirect() function is very similar, and can be used on a filter level in your controller to just push away the user if they shouldn’t be accessing a view/function etc

i.e, on my Locations.cfc controller, where CRUD updating for the RBS board locations takes place:

filters(through="checkPermissionAndRedirect", permission="accessLocations");

The advantages of this approach are:

  • You’ve got an ‘out-the-box’ system which doesn’t require extensive server setup
  • You could extend this principle to allow for 3rd party authentication – i.e, if you’re going via LDAP, once a user is approved, a ‘local’ account could easily be created which duplicates core information such as email/name from the initial authentication (see an example I did a few years ago here)
  • Permissions are cached in the application scope, meaning you potentially avoid another database hit for every user login
  • In the future, user based permissions which override role based permissions could easily be added: our checkPermission(“foo”) function could easily look for the equivalent key in the user’s session scope
  • Adding new permissions to use is as simple as adding a line in the permissions table
  • Each installation can redefine the abilities of each role to suit their needs: so in the RBS example, you could make every role do everything, and then use some other authentication method, or lock down guest users etc.

All this code can be seen in context at the GitHub Repo

v1.01 RoomBooking system released

So I admit that’s a pretty quick release cycle, but hey, I was inspired (or something). There’s some massive changes, most noticeably the addition of user authentication, roles, permissions, logging & password resets. All documentation has been moved to https://github.com/neokoenig/RoomBooking/wiki, where you can find a breakdown of all the settings, permissions, roles and upgrading instructions.

Things learned from creating the RoomBooking System

Since launching my little Room Booking system as an open source project, I thought it might be useful to highlight a few techniques used which others might find useful. Obviously, this isn’t to say that this is the ‘only way to do x’, but merely to flag some ways to approach common problems. All the below can be seen in context at the gitHub repo

events/functions.cfm

One issue a had a while back was determining the IP address of a user. Now, historically, this wasn’t an issue, as cgi.remote_host worked fine. However, since Railo’s installer changed the way it forwards requests from Apache (mod_jk-> mod_proxy), it always saw the remote IP as 127.0.0.1. This function checks the incoming request header and tries to grab the IP there, rather than in the CGI scope.

<cffunction name="getIPAddress" hint="Gets IP address even from Railo" returntype="String">
<cfscript>
   var result="127.0.0.1";
   var myHeaders = GetHttpRequestData();
    if(structKeyExists(myHeaders, "headers") AND structKeyExists(myHeaders.headers, "x-forwarded-for")){
      result=myHeaders.headers["x-forwarded-for"];
    }
    return result;
</cfscript>
</cffunction>

events/onrequeststart.cfm

This simply checks every incoming request for the URL or FORM scope, loops over each key in the struct, and trims the whitespace.

<cfif StructCount(form)>
	<cfloop collection="#form#" item="key">
		<cfset form[key] = Trim(form[key])>
	</cfloop>
</cfif>
<cfif StructCount(url)>
	<cfloop collection="#url#" item="key">
		<cfset url[key] = Trim(url[key])>
	</cfloop>
</cfif>

Personally, I think cfWheels should do this by default, but then, it would probably break a load of people’s applications. Simply put, I can’t imagine a case where I’d want whitespace at the beginning or end of any key in my url or form scope.

view/helpers.cfm

This one was an interesting one – Javascript often passes it’s values in Epoch time, i.e number of seconds since 1970; I needed to convert this to localTime in a few places. Surprisingly, I’ve never had to do this before!

<cffunction name="eToLocal" hint="convert epoc to localtime">
	<cfargument name="e">
	<cfreturn DateAdd("s", arguments.e ,DateConvert("utc2Local", "January 1 1970 00:00"))>
</cffunction>

So Bootstrap3 has panels. I love panels. But I’m lazy, I don’t want to type out the markup for a panel all the time. So these two helpers do it for me:

<cffunction name="panel" hint="Renders a bootstrap Panel">
    <cfargument name="title" required="true">
    <cfargument name="class" default="panel-default">
    <cfargument name="ignorebody" default="false">
    <cfset var r ="">
    <cfsavecontent variable="r"><Cfoutput>
    <!--- Start Panel --->
    <div class="panel #arguments.class#">
        <div class="panel-heading">
            <h3 class="panel-title">#arguments.title#</h3>
        </div>
        <cfif !arguments.ignorebody>
        <div class="panel-body">
        </cfif>
    </Cfoutput>
    </cfsavecontent>
    <cfreturn r />
</cffunction>

<cffunction name="panelend" hint="Close Panel">
    <cfargument name="ignorebody" default="false">
    <cfif !arguments.ignorebody>
        <cfreturn "</div></div>" />
    <cfelse>
        <cfreturn "</div>" />
    </cfif>
</cffunction>

Usage:

#panel(title="foo")#
.. things here
#panelEnd()#

Sometimes Bootstrap needs it’s content to be flush with the panel, so you occasionally need to ignore the panel-body – flush list-group-items – I’m looking at YOU.

#panel(title="foo", ignorebody=true)#
<ul class="list-group">
<li class="list-group-item">FOO</li>
</ul>
#panelEnd(ignorebody=true)#

Previously, I’d have done this using custom tags, i.e , but with the added import needed on every page, I find this a *lot* quicker/easier.

views/layout.cfm

One of things I like to do is have all the CSS in the header, and all the JS in the footer. So what happens when you want to do your JS stuff in your view folders? Here’s one solution..

In your view file, simply wrap your JS bits in cfsavecontent, i.e

<cfsavecontent variable="request.js.mystuff">
<script>// JS goes here</script>
</cfsavecontent>

Then in your layout file, after all your calls to jQuery etc:

<cfif structkeyexists(request, "js")>
<cfloop list="#structKeyList(request.js)#" index="key"><cfoutput>#request.js[key]#</cfoutput></cfloop>
</cfif>

The only thing this doesn’t really deal with is calling JS in a specific order, as structs are by their nature, unordered. I guess you could turn this principle into an array based system easily enough though: generally speaking, I haven’t found needing to write JS over many view files which needs to be executed in a specific order. YMMV.

bookings/index.cfm

This bit of JS is very useful:

 $('body').on('hidden.bs.modal', '.modal', function () {
        $(this).removeData('bs.modal');
});

Bootstrap caches modals which have had dynamic data inserted in, so initially, when I was building it, every time I clicked on an event, the modal would display the first event I’d click on’s data. This destroys the modal data after you close it. Handy.

And another thing…

The last thing to note is that this has an example of using Ajax in cfWheels, examples of which are fairly rare. If you’re interested in how that’s done, look at the javascript call in bookings/index.cfm under event sources: it’s just a jQuery ajax request. Now look at controllers/Eventdata.cfc = you’ll see the renderWith call, and if you look at the function ‘prepdata’, you’ll see me converting a query into a array of data (in the format I wanted it), which is then passed back by wheels using renderWith(events);

So there you go. If, of course, you guys have better ways of handling the above, I’d love to hear them.

New Open Source Project – Room Booking System

I’m pleased to announce a new open source project for a room booking system – it’s basically a backend to the excellent fullcalendar.js jQuery plugin, using cfWheels as the back end.

Features include:

  • All (well most) configuration done via database settings
  • Uses the DBMigrate plugin for cfWheels for easy future updates
  • Uses the FullCalendar.js month/day/week views
  • Allows for multiple locations, which can be filtered from the main view
  • Each location can be colour coded, and have room layout options
  • Allows you to bulk create events, so easy to add placeholders for repeating series
  • Completely configurable via web interface, all you need to do is setup a datasource called ‘roombooking’
  • Has a theme switcher so you can use/try any theme on http://bootswatch.com/
  • Can send confirmation emails on a new booking

You can download the code from gitHub – https://github.com/neokoenig/RoomBooking or try a demo at http://roombooking.oxalto.co.uk. Some screenshots below. Full documentation at http://roombooking.readme.io/

 

Feel free to contribute or log a bug at https://github.com/neokoenig/RoomBooking – hopefully it’ll be useful for someone!