We are getting into some pretty tricky areas this time around, so this makeover will be slightly different. We will go into a little greater depth in the code breakdown, and we will also pick up a few new techniques along the way. If you did not do the optional exercise in Step 6 to create "Edit News Item" and "Insert News Item" functionality, you might want to get caught up by using the Beelze-Bubba web site files from the CompletedFiles\MakeoverExercises\Step08 folder to make sure you are working with the right code before proceeding with this exercise. The first thing we are going to do is create a login form, but not just any old login form. Instead of the traditional form page/action page combo we have come to know and love, we are going to try something just a bit different. Many developers build a single template that acts as both the form page and the action page. The first time the page is requested, it acts as a form and displays the appropriate form information. When the user clicks the Submit button, the form then submits the data back to itself and processes the data contained in the form fields. This technique is popular because it keeps all the code for the form page and the action page in one place. Let's give this technique a shot. Before we start coding the login form, let's understand what functions this template will have to perform. It will have to display the login form fields to gather a user's username and password, along with a Submit button. It will have to perform form validation to make sure the appropriate fields have been filled in. It will have to check the submitted information against our database of authorized users. If the information submitted on the form does not match the database, it must display an error message. If the information on the form does match the database, it must log that user in and send him to the administration section home page. Seems like a tall order for just one template, but we are all pretty bright around here, and I think we can do it. Let's have a go at it. The first (and easiest) thing to do is create the login form portion of the template. We would like to present our users with an interface that is familiar and easy to use. Figure 9.3 shows what our login form will look like. Figure 9.3. The Login form. Let's have a look at the code for the form portion of our LoginForm.cfm template. [View full width] <!--- display the form ---> <!--- outer table for black outline ---> <TABLE ALIGN="center" BGCOLOR="#000000" CELLPADDING="1" CELLSPACING="1"> <TR> <TD> <!--- inner table for form ---> <TABLE ALIGN="Center" BGCOLOR="#CFCFCF"> <CFFORM ACTION="LoginForm.cfm" METHOD="POST" ENABLECAB="Yes"> <TR> <TD BGCOLOR="#003399" COLSPAN="2"><FONT COLOR="#FFFFFF"><B>Login Form</B></ FONT></TD> </TR> <TR> <TD>Username</TD> <TD><CFINPUT TYPE="Text" NAME="UserName" MESSAGE="Please enter a username" REQUIRED="Yes"></TD> </TR> <TR> <TD>Password</TD> <TD><CFINPUT TYPE="Password" NAME="Password" MESSAGE="Please enter a password" REQUIRED="Yes"></TD> </TR> <TR ALIGN="right"> <TD> </TD> <TD><INPUT TYPE="Submit" NAME="Submit" VALUE="Login"></TD> </TR> </CFFORM> </TABLE> </TD> </TR> </TABLE> No big surprises here. We use an empty outer table to create that oh-so-attractive black outline and use an inner table that contains a <CFFORM> to gather the user information and perform client-side form validation to make sure the inputs have been filled in. We will be calling this template LoginForm.cfm. Notice that the ACTION attribute of the <CFFORM> tag in this template nominates itself as the action page. "So how does that work?" you might be asking. This first time this page is called, it will display the form. When the user clicks the Login button, the page calls itself again, but this time it passes itself all the collected Form scope variables for processing. Seem logical enough? Hopefully, the question coming to your mind is, "Well, how does the template know whether it is being called as a form page or an action page?" We can determine whether to display the form information or run the action page code by testing for the existence of any Form scope variables. The following code shows how we can do this: <!--- The FORM.Submit variable will not exist unless the form has been submitted if it does not exist, display the form ---> <CFIF NOT IsDefined("FORM.Submit") > form code goes here <CFELSE> action code goes here </CFIF> In the preceding code, we have an IF statement that checks to see whether the FORM.Submit variable (the value passed when you click the Submit button on the form) exists. If it does, the form must have been submitted, and we run the action code. If it does not, it must be the first time the page has been requested, and we will display the form. Now we need to fill in the action code portion of the template. The following code is the action portion of the template that checks the user's information against the database. <!--- run a query to match the username and password against the database ---> <CFQUERY NAME="qLogin" DATASOURCE="#BBWebAppDSN#"> SELECT * FROM Users WHERE UserName = '#Trim(FORM.UserName)#' AND Password = '#Trim(FORM.Password)#' </CFQUERY> <!--- see if our query retrieved anything ---> <CFIF qLogin.RecordCount GT 0> <CFLOGIN> <CFLOGINUSER Name="#qLogin.UserName#" Password="#qLogin.Password#" Roles="#qLogin.Roles#"> <!--- you can place any additional code you like here ---> </CFLOGIN> <CFLOCATION URL="AdminHome.cfm" ADDTOKEN="No"> <CFELSE> <!--- error level 1 is invalid login credentials ---> <CFLOCATION URL="LoginForm.cfm?Error=1" ADDTOKEN="No"> </CFIF> In this code, we query the Users table in the database using the information passed via the form. If the user's information matches that stored in the database (if the query record count is greater than 0), the user is logged in using the <CFLOGIN> and <CFLOGINUSER> tags. If the user's information is not in the database (if the record count equals 0), we use <CFLOCATION> to call the page again, but this time we add a URL variable to trigger the display of an error message, as shown in Figure 9.4. You'll learn more about the error code in a moment. Figure 9.4. Displaying an error message. We will use an error code to determine which message to display above the login form. We will use a <CFPARAM> tag to set the default value to 0, and as we saw in the preceding code, we use a URL variable to set the error code to 1 if the user's information does not match any records in the database. We then simply use a SWITCH/CASE block at the top of the template to determine which message to display, as shown in the following code: <CFPARAM NAME="URL.Error" DEFAULT="0"> <!--- switch / case statement to display error code message ---> <DIV ALIGN="center"> <CFSWITCH EXPRESSION="#URL.Error#"> <CFCASE VALUE="0" > <B>Please Login</B> </CFCASE> <CFCASE VALUE="1"> <FONT COLOR="#FF0000"><B>Invalid login, please try again</B></FONT> </CFCASE> </CFSWITCH> </DIV> Now it's time to put it all together. Open your text editor and type the code in Listing 9.1 or you can find the completed code in your CompletedFiles\MakeoverExercies\Step09 folder. Example 9.1 LISTING 9.1 LoginForm.cfm [View full width] <!--- File: LoginForm.cfm Description: Dual purpose login form for secured directories Author: Created: ---> <!--- This form is a dual purpose template It displays a form first time around it then submits to itself and processes the login ---> <HTML> <HEAD> <TITLE>Login Form</TITLE> </HEAD> <BODY> <!--- set default error level ---> <CFPARAM NAME="URL.Error" DEFAULT="0"> <!--- switch / case statement to display error code message ---> <DIV ALIGN="center"> <CFSWITCH EXPRESSION="#URL.Error#"> <CFCASE VALUE="0" > <B>Please Login</B> </CFCASE> <CFCASE VALUE="1"> <FONT COLOR="#FF0000"><B>Invalid login, please try again</B></FONT> </CFCASE> </CFSWITCH> </DIV> <!--- The FORM.Submit variable will not exist unless the form has been submitted if it does not exist, display the form ---> <CFIF NOT IsDefined("FORM.Submit") > <!--- display the form ---> <!--- outer table for black outline ---> <TABLE ALIGN="center" BGCOLOR="#000000" CELLPADDING="1" CELLSPACING="1"> <TR> <TD> <!--- inner table for form ---> <TABLE ALIGN="Center" BGCOLOR="#CFCFCF"> <CFFORM ACTION="LoginForm.cfm" METHOD="POST" ENABLECAB="Yes"> <TR> <TD BGCOLOR="#003399" COLSPAN="2"><FONT COLOR="#FFFFFF"><B>Login Form</ B></FONT></TD> </TR> <TR> <TD>Username</TD> <TD><CFINPUT TYPE="Text" NAME="UserName" MESSAGE="Please enter a username" REQUIRED="Yes"></TD> </TR> <TR> <TD>Password</TD> <TD><CFINPUT TYPE="Password" NAME="Password" MESSAGE="Please enter a password" REQUIRED="Yes"></TD> </TR> <TR ALIGN="right"> <TD> </TD> <TD><INPUT TYPE="Submit" NAME="Submit" VALUE="Login"></TD> </TR> </CFFORM> </TABLE> </TD> </TR> </TABLE> <!--- if there was a FORM.Submit variable when the page loaded i.e the form has just been submitted we jump to the code below and process the login ---> <CFELSE> <!--- run a query to match the username and password against the database ---> <CFQUERY NAME="qLogin" DATASOURCE="#BBWebAppDSN#"> SELECT * FROM Users WHERE UserName = '#Trim(FORM.UserName)#' AND Password = '#Trim(FORM.Password)#' </CFQUERY> <!--- see if our query retrieved anything ---> <CFIF qLogin.RecordCount GT 0> <CFLOGIN> <CFLOGINUSER Name="#qLogin.UserName#" Password="#qLogin.Password#" Roles="#qLogin.Roles#"> <!--- you can place additional code here ---> </CFLOGIN> <CFLOCATION URL="AdminHome.cfm" ADDTOKEN="No"> <CFELSE> <!--- error level 1 is invalid login credentials ---> <CFLOCATION URL="LoginForm.cfm?Error=1" ADDTOKEN="No"> </CFIF> </CFIF> </BODY> </HTML> Save this file in your NewSite\Admin directory. We are not quite ready to use the form yet, but if you are keen to test it, you can use the direct URL of http://localhost/NewSite/Admin/LoginForm.cfm. To test the login functionality, there are two users in the Users table of the Beelze-Bubba database. The Users table is shown in Figure 9.5. Figure 9.5. The Users table. The login form now exists, but none of our pages is actually secure. Unauthenticated users can still access pages in the Admin directory. We are going to restrict access to pages in this directory through the use of the Application.cfm file in the Admin directory. Remember that the Application.cfm file in the Admin directory will be added to the top of every page in that directory at the time it is requested. So if we want to add code to every page in the Admin directory that checks to see whether the user requesting that page has been logged in, the Application.cfm page would be an ideal location for that code. Open the NewSite\Admin\Application.cfm page in your text editor and amend the code to match Listing 9.2. Example 9.2 LISTING 9.2 Admin\Application.cfm <!--- File: Application.cfm Description: Application settings for the Admin subdirectory Author: Created: ---> <!--- include all settings from the root level Application.cfm file ---> <CFINCLUDE TEMPLATE="../Application.cfm"> <!--- additional security code will go here ---> <!--- if there is no authorized user value include the login form and stop ---> <CFIF GETAUTHUSER() IS ""> <CFINCLUDE TEMPLATE="LoginForm.cfm"> <CFABORT> </CFIF> Save the file. In the preceding code, we include the settings in the root-level Application.cfm through the use of the <CFINCLUDE> tag. We then use an IF statement to check whether there is a GetAuthUser() value. Remember that this function will return the name of a logged-in user as specified by the NAME attribute of the <CFLOGINUSER> tag. If this function returns no value, it means that nobody is logged in for this session. If we determine that the user is not logged in, we use the <CFINCLUDE> tag to include our LoginForm.cfm template and then use the <CFABORT> tag to stop any further page processing. So, basically, no matter which page in the Admin directory gets requested, the user never gets past the <CFABORT> tag until he has logged in. At this point, our authentication security is functional but not complete. If you browse to http://loclhost/NewSite/Admin/AdminHome.cfm, you should be presented with the login form. If you then enter any of the credentials shown in Figure 9.5, you should be logged in and directed to the AdminHome.cfm page. You will notice in Figure 9.5, however, that our two users have different role information stored as part of their profiles. Emma is an Editor, and Alan is both an Editor and an Administrator. Now let's say that in our Beelze-Bubba administration section, we want to give Editors the capability to add and edit news items, but only Administrators will have the right to change product information. We are now talking about authorization. Next we are going to amend our inc_Navigation.cfm file so that the menu only shows the tools appropriate for the logged-in user's the role(s). Open your inc_Naviagation.cfm file and amend the code to match Listing 9.3. Example 9.3 LISTING 9.3 inc_Navigation.cfm <!--- File: inc_Navigation.cfm Description: Role based navigation menu for admin panel Author: Created: ---> <!--- Display Links For All Users ---> <P> <A HREF="AdminHome.cfm">Admin Home</A><BR> <A HREF="../Index.cfm">Public Web Site</A> </P> <!--- Administrators can change product info ---> <CFIF IsUserInRole("Administrator")> <P> <CFOUTPUT><IMG src="/books/4/16/1/html/2/#AppImagePath#/admintools_icon.gif" ALT="Admin Tools" WIDTH="20" HEIGHT="20" BORDER="0"></CFOUTPUT> <FONT COLOR="#800000"><B>Administrator Tools</B></FONT><BR> List Products<BR> <A HREF="ProductList.cfm?Category=Chillies">Chillies</A><BR> <A HREF="ProductList.cfm?Category=Sauces">Sauces</A><BR> <A HREF="ProductList.cfm?Category=Clothing">Clothing</A><BR> <A HREF="ProductList.cfm?Category=Gifts">Gifts</A><BR> </P> </CFIF> <!--- Editors can insert and update Newsitems---> <CFIF IsUserInRole("Editor")> <P> <CFOUTPUT><IMG src="/books/4/16/1/html/2/#AppImagePath#/edittools_icon.gif" ALT="Editor Tools" WIDTH="20" HEIGHT="20" BORDER="0"></CFOUTPUT> <FONT COLOR="#800000"><B>Editor Tools</B></FONT><BR> <A HREF="NewsNew.cfm">Insert a New News Item</A><BR> <A HREF="NewsList.cfm">List Existing News Items</A> </P> </CFIF> Save the file. In the preceding code, we use IF statements to determine whether the logged-in user belongs to a particular role by using the IsUserInRole() function. If the user belongs to that role, we display the appropriate links. If he doesn't belong to the role, the links are not displayed. Open your browser and browse to http://loclhost/NewSite/Admin/AdminHome.cfm. You should be presented with the login form if you are not already logged in. Use the form to log in using Emma's credentials, as displayed in Figure 9.5. You should see the Editor Tools but not the Admin Tools, as shown in Figure 9.6. Figure 9.6. Tools available to the Editor role. It is probably dawning on you that, at the moment, we have no way to log out and try our other user, and you probably don't feel like hanging around for 20 minutes until the Session variables time out. So let's build in some logout functionality. Open the inc_Banner.cfm file in your text editor and amend it to match the code in Listing 9.4. Example 9.4 LISTING 9.4 inc_Banner.cfm [View full width] <!--- File: inc_Banner.cfm Description: Top banner for admin panel Author: Barry Moore Created: 30-Mar-2002 ---> <!--- HTML meta tags to help prevent caching ---> <META HTTP-EQUIV="Expires" CONTENT="mon, 01 jan 1990 00:00:01 gmt"> <META HTTP-EQUIV="Pragma" CONTENT="no-cache"> <META HTTP-EQUIV="cache-control" VALUE="no-cache, no-store, must-revalidate"> <!--- CF Tags to help prevent caching ---> <CFHEADER NAME="Expires" VALUE="mon, 01 jan 1990 00:00:01 gmt"> <CFHEADER NAME="Pragma" VALUE="no-cache"> <CFHEADER NAME="cache-control" VALUE="no-cache, no-store, must-revalidate"> <TABLE CELLSPACING="2" CELLPADDING="2" BORDER="0"> <TR> <TD WIDTH="200"> <A HREF="AdminHome.cfm"><IMG src="/books/4/16/1/html/2/../images/logo_sml.gif" ALT="Click to return to main panel" WIDTH="133" HEIGHT="50" BORDER="0"></A> </TD> <TD STYLE="font-size: larger; color: #990033;"> Beelze-Bubba Web Site Administration Panel </TD> </TR> <!--- add a row for user info and logout ---> <TR> <TD COLSPAN="2" ALIGN="left"> Welcome <CFOUTPUT><B>#GetAuthUser()#</B></CFOUTPUT> <B>:</B> <A HREF="Logout.cfm"><B>Logout</B></A> </TD> </TR> </TABLE> <HR COLOR="#990033" NOSHADE> Save the file. In this code, we have added an extra row to the table that displays a personalized welcome message and a link to a logout page. Oh, and we have thrown in some HTML <META> tags and <CFHEADER> tags to help keep the administration pages from being cached anywhere. For more information on caching, see the sidebar "Somebody Get Me Out of This Cache" earlier in this step. Now all we have left to do is create the Logout.cfm page. This will be easy. Open your text editor and type the code in Listing 9.5. Listing 9.5 Logout.cfm <!--- File: Logout.cfm Description: Log user out and redirect them to homepage Author: Created: ---> <!--- it does not get any simpler than this ---> <CFLOGOUT> <CFLOCATION URL="../Index.cfm" ADDTOKEN="No"> Phew! That was a tough one. Save this file as Logout.cfm in your NewSite\Admin directory. The code in the Logout.cfm file simply logs the current user out and redirects him to the public web site's home page. If you are still logged in as Emma, refresh the page and follow the Logout link. If you are not currently logged in, log in as Emma and note the links available on the navigation menu to the right. Then follow the Logout link in the web page header. If you are not currently logged in or have just logged out as Emma, try logging in as Alan using the credentials shown in Figure 9.5. Because Alan is both an Administrator and an Editor, you should see both menus (as displayed in Figure 9.7). Figure 9.7. Tools available to Editors And Administrators. If you were bucking for extra credit points, you would have picked up on the fact that when you logged in to the AdminHome.cfm page as Emma, the current product special listing had an Edit link. Because Emma is not an Administrator, she is not supposed to be able to do that sort of thing. We can apply the same logic we used on the inc_Navigation.cfm file to any page in the directory. If you open the AdminHome.cfm file in your text editor and find the code for the link, you can remedy the situation by replacing it with the following code: <!--- only Admins can edit ---> <CFIF IsUserInRole("Administrator")> <A HREF="ProductEdit.cfm?pid=#ProductNo#">Edit</A> <CFELSE> </CFIF> Log out and log in again as each user and check the availability of the link. We now have a user security system based on the new ColdFusion MX security tags and functions. |