Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SWAT - A simple Web-based Anomalies Tracker - Part 2

0.00/5 (No votes)
17 Jun 2003 1  
An account of my experience in learning to develop in the .NET environment.

Fig.1 Swat Logon Page

SWAT Part 2

In part 1 I described an application that I devised as a learning project to help me get experience developing for the .NET environment, specifically ASP.NET. The application is a WEB based bug tracking tool. In the previous article I described the requirements for the application and defined the database schema to be used by the application. With that behind us we can dive right into the design of the application. Again, as I mentioned in the first article, this depicts my experience and as such the implementation details presented may not always be the most optimal solution. But it works, so enjoy.

UserAuthentication and the Logon Page

Unlike desktop apps where the operating system provides a nice protected environment, web apps must fend for themselves in the hostile world of the internet. It�s like leaving the front door to your house open for anybody in the world to come in. I don�t think we�d be too comfortable with that. So how do we allow our friends and family access to our house yet prevent all others from trashing the place?

ASP.NET provides several �out of the box� solutions to this problem. We�ll implement the application using Forms based Authentication. Basically we�ll create a logon page for the application that will allow the user to enter some credentials which will be used to authenticate the user. In processing the response to the logon we�ll create a token (cookie) that will be used to identify the user throughout the rest of the session. Any subsequent application pages accessed by the user is first checked against the token before allowing access. If the token is not available or not valid the user is automatically directed back to the logon page.

ASP.NET provides some additional built-in mechanisms to automatically validate users (by embedding the information in the web.config file or storing in an XML file). We won�t be using those facilities. Instead we will be validating the users� credentials against information stored in the database.

If the client does not allow cookies then we�re hosed (at least with the out of the box version). This is also not the most secure mechanism if we�re concerned about someone getting a hold of the cookie in transit and impersonating the user. Since we�re not protecting Ft. Knox the �out of the box� solution is fine.

Most WEB applications also consist of more than just one page. So part B of the questions is, how do we allow our family access to our bedroom but restrict our friends to only the living area of the house? Unfortunately, ASP.NET does not provide a clean solution to this problem (without putting some restrictions on the client), so we�ll control it in our code. Basically what we�ll do is assign another token to the user that will indicate which part of the house s/he can enter. This is essentially �roles� based access that we�ll implement in the application�s code.

Note: If you want to get a feel for the development environment, follow along with the text. However, if you'd prefer to bypass the typing you can simply download the complete source code and compile.

So let�s get going. Create a new ASP.Net WEB application and call it Swat. Change the name of the default WebForm1 to SwatLogon. Drag two TextBox controls to the page, one for the user's name and the other for a password. In the password's TextBox property pane set the 'TextMode' property to 'Password'. Add labels and a connect button as shown in Fig.1. Set the control ids as follows: 'txtUserName','txtPassword',and 'btnConnect'. I implemented the page using a table simply for �neatness�, they say that counts. By the way, the bumble bee is optional.

Double-click on the connect button to create an event handler for the button click. Revise the event handler as shown below.
private void btnConnect_Click(object sender, System.EventArgs e)
{
    SqlConnection cnn;
    SqlDataReader dr;
    string strRedirect = "";
    int nUserID = 0;
    int nRoles = 0;
    //Empty database check. If there are no users defined it

    //means it's a new installation.

    //We allow 'Admin' as the user only if the database is empty

    string ConnectionString
                       = "user id=ASPNET;password=;initial catalog=swatbugs;"
                         "data source=localhost;Integrated Security=false;"
                         "connect timeout=30;";
    cnn = new SqlConnection(ConnectionString);
    cnn.Open();
    SqlCommand cmd = cnn.CreateCommand();
    if (txtUserName.Text == "admin")
    {
        //Check to see if the db is empty

        cmd.CommandText = "SELECT * FROM users";
        dr = cmd.ExecuteReader();
        if(dr.Read() == false)
        {
            nUserID = 0;    //It doesn't matter only admin page

                    //will be available

            nRoles = (int)AccessPrivilege.Administrator;
            strRedirect = "SwatMain.aspx";
        }
        dr.Close();
    }
    if (strRedirect.Length == 0)
    {
        cmd.CommandText
                 = "SELECT id, roles FROM users WHERE itemname=@username " +
                   "AND password=@password";
        // Fill our parameters

        cmd.Parameters.Add("@username", 
                           SqlDbType.NVarChar, 64).Value = txtUserName.Text;
        cmd.Parameters.Add("@password", 
                           SqlDbType.NVarChar, 128).Value = txtPassword.Text;
        dr = cmd.ExecuteReader();
        if(dr.Read())
        {
            nUserID = (int)dr["id"];
            if (dr["roles"] != System.DBNull.Value)
            {
                nRoles = System.Convert.ToInt16(dr["roles"]);
                strRedirect = "SwatMain.aspx";
            }
        }
    }            
    cnn.Close();
    if (strRedirect.Length != 0)
    {
        FormsAuthenticationTicket tkt = new FormsAuthenticationTicket(
                    1, //Ticket version

                    txtUserName.Text, //User name associated with ticket

                    DateTime.Now,    //When ticket was issued

                    DateTime.Now.AddMinutes(30),    //When ticket expires

                    true,    //A persistent ticket

                    nRoles.ToString(),    //The user's role

                    FormsAuthentication.FormsCookiePath); 
                                                    //Path cookie valid for

        //Hash the cookie

        string hash = FormsAuthentication.Encrypt(tkt);
        HttpCookie ck = new HttpCookie(FormsAuthentication.FormsCookieName, 
                                       hash);
        //Add cookie to the response

        Response.Cookies.Add(ck);
        Response.Cookies["UserID"].Value = nUserID.ToString();
        Response.Cookies["UserID"].Expires = DateTime.MaxValue;
        Response.Cookies["Roles"].Value = nRoles.ToString();
        Response.Cookies["Roles"].Expires = DateTime.MaxValue;
        Response.Redirect(strRedirect, true);
    }
    else
    {
        lblError.Text = "Invalid logon credentials";
    }
}

The egg is the answer

If we are validating the user based on information stored in the database how can a user logon the first time (to add users to the database)? We could provide a default user/pwd in the database but the user must make sure to at least change the password or it won�t be too secure. I chose a different approach. I hard-coded an �admin� user and allow access only initially when the database is empty (no users). Once a user is added to the database then the �admin� user is no longer allowed access. Unless of course it exists in the database with a valid password.

The first part of the code in the event handler above deals with checking to see if it�s the first time. If the user's name is 'admin' we check to see that the database is empty. If the database is empty then the user is re-directed to the application's main page. If not then the user�s name and password are checked to see if they exist in the database. If the user�s credentials match then his/her role and ID are stored in a cookie and persisted on the client�s machine and we send the user to SWAT�s main page. Otherwise we display a message indicating to the user that the credentials could not be verified.

Since we're using some external SQL and security classes we need to add the following declarations to the top of the SwatLogon code source.

...
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Web.Security;

There are three roles that are supported by the application. The roles determine what parts of the application the user is allowed to access. Add the following enum just before the SwatLogon class definition.

    ...
    public enum AccessPrivilege
    {
        Developer = 1,
        Administrator = 2,
        Manager = 4
    }
    ...

To complete the forms authentication mechanism we need to make the following changes to Web.Config in the 'AUTHENTICATION' section.

...
<authentication mode="Forms"> 
    <forms name="SWAT.ASPXFORMSAUTH" loginUrl="SwatLogon.aspx" 
           protection="All">
    </forms>
</authentication>
<authorization>
    <deny users="?"/>
</authorization>
...

You�ll note that the application does not currently encrypt the user�s password. An enhancement to SWAT would be to encrypt the user�s password (note the database already provides space to hold an encrypted password). Along with this, perhaps a page that allows the user to change their password and also their start page. See how easy feature creep is?

SWAT�s Main Page

One of the design goals I have for the application is that the UI needs to be similar to what the user is accustomed to with a traditional desktop app. To me (since I�m the user) that means having a title bar, a toolbar, and a main work area and not seeing the effects of navigation (the window changing every time a link is selected). To accomplish this I implemented the main page of the application using frames. This allowed me to change the contents of the main work area while not affecting what�s displayed in the rest of the window.

Add a new WEB Form to the project and name it SwatMain. Replace the BODY tag in the page source with the following:

<frameset id="SwatMain" rows="40, 32, *">
    <frame name="TitleBar" src="Titlebar.aspx" frameBorder="0" noResize 
           scrolling="no">
    <frame name="ToolBar" src="Toolbar.aspx" frameBorder="0" noResize 
           scrolling="no">
    <frame name="MainFrame" borderColor="#cccccc" src="<%= MainFramePage %>" 
           frameBorder="yes" noResize>
</frameset>

The source for the title bar and the toolbar is always known so they are hardcoded on the page. You�ll notice however, that the source for the bottom frame is a variable reference since it will be determined dynamically and is dependent on the user�s selection. Revise the SwatMain class as follows.

...        
public string MainFramePage;
private void Page_Load(object sender, System.EventArgs e)
{
   //This is only necessary the first time through when the DB is empty

   //otherwise everybody goes to the bug editing page, for now.

   int nRole = System.Convert.ToInt16(Request.Cookies["Roles"].Value);
   if (nRole == (int)AccessPrivilege.Administrator)
      MainFramePage = "SwatAdmin.aspx";
   else
   {
      if (nRole == (int)AccessPrivilege.Manager)
         MainFramePage = "SwatAnalyze.aspx";
      else
      {
         if (nRole == 0) //Not good, back to logon

            Response.Redirect("SwatLogon.aspx",true);
         else
         {
            //At some point we'll probably allow the user to

            //define his/her start page and go directly there.

            //For now everybody goes to bugs

            MainFramePage = "SwatBugs.aspx";
         }
      }
   }
}

Add a WEB page for the toolbar and the titlebar and name them as indicated above. The titlebar is just a simple two column table that holds a bitmap and the applications title. Here�s the code:

<body bgColor="#10e7d0" leftMargin="0" topMargin="0">
   <form id="Titlebar" method="post" runat="server">
      <table width="100%">
         <tr>
            <td><IMG src="Images/swat.gif" align="left">
            </td>
            <td width="100%"><span style="FONT-WEIGHT: bold; 
     FONT-SIZE: 20pt; COLOR: #306230; FONT-FAMILY: Trebuchet MS">SWAT</span>
               <span style="FONT-WEIGHT: bold; FONT-SIZE: 12pt; 
     COLOR: #306230; FONT-FAMILY: Trebuchet MS">
                  Simple Web-based Anamolies Tracker</span>
            </td>
         </tr>
      </table>
   </form>
</body>

The toolbar is a little more interesting even though it�s also implemented as a table. The toolbar (you say navigation bar I say toolbar) provides the user with the same functionality and feedback as a toolbar on a desktop application. As the user moves the mouse over an item the button changes appearance. When the user selects a toolbar item the main frame will change accordingly. You'll note that the user's role is coded into the button processing code. If the user does not have access to the functionality associated with a particular button the button�s appearance does not change indicating that option is not available to the user and the user cannot select it. A point of interest is to note that all of the code required to implement the toolbar�s functionality is on the client. Place the following script code in the HEAD tag for page.

<SCRIPT LANGUAGE="JavaScript">
function LoadImage(src) 
{
   if (document.images) 
   {
      imgButton = new Image();
      imgButton.src = src;
      return imgButton;
   }
}
         
function ChangeImages() 
{
   if (document.images && (boolImagesLoaded == true)) 
   {
      for (var i=0; i<ChangeImages.arguments.length; i+=2) 
      {
         document[ChangeImages.arguments[i]].src = ChangeImages.arguments[i+1];
      }
   }
}
         
var boolImagesLoaded = false;
function PreloadImages() 
{
   if (document.images) 
   {
      TbHomeImage = LoadImage("Toolbar_Home_hover.gif");
      TbBugsImage = LoadImage("Toolbar_Bugs_hover.gif");
      TbAnalyzeImage = LoadImage("Toolbar_Analyze_hover.gif");
      TbReportImage = LoadImage("Toolbar_Report_hover.gif");
      TbAdminImage = LoadImage("Toolbar_Admin_hover.gif");
      TbSystemImage = LoadImage("Toolbar_System_hover.gif");
      TbHelpImage = LoadImage("Toolbar_Help_hover.gif");
      TbLogoutImage = LoadImage("Toolbar_Logout_hover.gif");
                    
      boolImagesLoaded = true;
   }
}
         
PreloadImages();
</SCRIPT>

The script simply takes care of caching the images for the toolbar buttons on the client and providing an access function for the images.

The table code is also pretty much straightforward but a little 'crammed-in'. All of the functionality is defined within the table code, changing the toolbar image when the mouse is over the button, what to do on a mouse click, etc. For some of the buttons the UserRoles variable is used to determine if the user has access to that functionality. The code simply cancels the event if the user cannot perform the function. An enhancement might be to pop-up a dialog indicating to the user that functionality is not available for the specified role. Add the following to the FORM tag for the toolbar.

<table cellpadding="0" cellspacing="0">
   <tr>
      <td>   </td>
      <td>
         <a ONMOUSEOVER="changeImages('TbHomeImage', 'Toolbar_Home_hover.gif');
           return true; " 
           ONMOUSEOUT="changeImages('TbHomeImage', 'Toolbar_Home.gif'); 
           return true; ">
         <img src="Toolbar_Home.gif" alt="Home" border="0" name="TbHomeImage" 
              height="32" width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a href="SwatBugs.aspx" target="MainFrame"  
            onclick="if (1 & <%=UserRole%>) return true; else return false;" 
            ONMOUSEOVER="if (1 & <%=UserRole%>) changeImages('TbBugsImage', 
           'Toolbar_Bugs_hover.gif'); else window.event.cancelBubble = true; 
            return true; " 
           ONMOUSEOUT="changeImages('TbBugsImage', 'Toolbar_Bugs.gif'); 
           return true; ">
            <img src="Toolbar_Bugs.gif" alt="Bugs" border="0" 
                 name="TbBugsImage" height="32"  width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatAnalyze.aspx" target="MainFrame" 
            onclick="if (4 & <%=UserRole%>) return true; else return false;" 
            ONMOUSEOVER="if (4 & <%=UserRole%>) changeImages('TbAnalyzeImage', 
                        'Toolbar_Analyze_hover.gif');
            else window.event.cancelBubble = true; return true; " 
            ONMOUSEOUT="changeImages('TbAnalyzeImage', 'Toolbar_Analyze.gif'); 
                        return true; ">
            <img src="Toolbar_Analyze.gif" alt="Analyze" border="0" 
                 name="TbAnalyzeImage" height="32" width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatReport.aspx" target="MainFrame" 
            ONMOUSEOVER="changeImages('TbReportImage', 
                        'Toolbar_Report_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbReportImage', 'Toolbar_Report.gif'); 
                        return true; ">
            <img src="Toolbar_Report.gif" alt="Reports" border="0" 
                 name="TbReportImage" height="32" width="32"></a>
      </td>
      <td>  </td>
      <td>
         <a href="SwatAdmin.aspx" target="MainFrame"
            onclick="if (2 & <%=UserRole%>) return true; else return false;" 
           ONMOUSEOVER="if (2 & <%=UserRole%>) changeImages('TbAdminImage', 
           'Toolbar_Admin_hover.gif');
           else window.event.cancelBubble = true; return true; " 
            ONMOUSEOUT="changeImages('TbAdminImage', 'Toolbar_Admin.gif'); 
            return true; ">
            <img src="Toolbar_Admin.gif" alt="Admin" border="0" 
             name="TbAdminImage" height="32" width="32"></a>
      </td>
      <td style="WIDTH: 3px">    </td>
      <td style="WIDTH: 10px">
         <a onclick="if (2 & <%=UserRole%>)
            {refWindow=window.open('SwatOptions.aspx','referralWindow',
            'width=350,height=400,scrollbars=no,menubar=no,resizable=no');
             refWindow.focus();} return false;" 
            ONMOUSEOVER="if (2 & <%=UserRole%>) changeImages('TbOptionsImage', 
            'Toolbar_System_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbOptionsImage', 'Toolbar_System.gif'); 
            return true; " 
            target="_blank" href="SwatOptions.aspx">
            <img src="Toolbar_System.gif" alt="Swat Options" border="0" 
                  name="TbOptionsImage" height="32" 
           width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a onclick="refWindow=window.open('SwatHelp.htm',
           'referralWindow',
            'width=350,height=520,scrollbars=yes,menubar=no,resizable=yes'); 
            refWindow.focus(); return false;" 
            ONMOUSEOVER="changeImages('TbHelpImage', 'Toolbar_Help_hover.gif'); 
            return true; " ONMOUSEOUT="changeImages('TbHelpImage', 
            'Toolbar_Help.gif'); return true; " target="_blank" 
            href="SwatHelp.aspx">
            <img src="Toolbar_Help.gif" alt="Help" border="0" 
            name="TbHelpImage" height="32" width="32"></a>
      </td>
      <td>    </td>
      <td>
         <a ONMOUSEOVER="changeImages('TbLogoutImage', 
             'Toolbar_Logout_hover.gif'); return true; " 
            ONMOUSEOUT="changeImages('TbLogoutImage', 'Toolbar_Logout.gif');
              return true; ">
            <img src="Toolbar_Logout.gif" alt="Logout" border="0" 
                  name="TbLogoutImage" height="32"
            width="32"></a>
      </td>
   </tr>
</table>

On the server side the only thing that�s required is to initialize the UserRole variable when the page gets loaded. Add the following to the Toolbar class.

public int UserRole;

private void Page_Load(object sender, System.EventArgs e)
{
   UserRole=System.Convert.ToInt16(Request.Cookies["Roles"].Value);
}

After making sure you�ve got the images installed compile and run the application. You�ll be able to login using �admin� as the user. Can�t do much yet but you�ll be able to see the toolbar responding to mouse moves and check for any syntax errors. The error that's displayed is because we don't have anything to put in the main frame. That's what we're going to do next. The next article starts coding Swat's admin page and it's a long one so get ready.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here