<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="generator" content="Adobe GoLive">
<title>InsomniaServer - Tutorial 3</title>
<style><!--
.code {
word-wrap:break-word;
margin:10px;
padding:10px;
border:2px ridge white;
background-color:#eeeeee;
font-family:Courier New;
font-size:10pt;
}
.step { font-weight: bold; background-color: #dcdcdc; font-style: italic; margin: 1px 2px; padding: 1px 2px; border: solid 1px #898989; }
--></style>
</head>
<body>
<p><font size="+2"><strong>Tutorial 3 - Creating a simple login</strong></font></p>
<p>Welcome back to our InsomniaServer course. After this lesson you will be able to create dynamic pages, to let users log into your pages, to create a members-only area and to store data, using sessions. Sounds good, doesn't it?</p>
<p><em>The final code and the resulting assemblies are stored in "Examples/Tutorial 3 - SimpleLogin/"<br />
</em></p>
<p>As the last tutorial, this one is based on lesson one. So copy its code - or simply repeat the steps within a new project.</p>
<p>Later we want to display all login attempts, so create a list box, named <em>listBoxLogins</em>, on the main form. Furthermore, add a new class <em>Login</em> to our project. As you probably imagine, it will contain all functions, which belong to login things.<br />
<br />
</p>
<p><span class="step">Load user database</span></p>
<p>To let somebody log in, one needs to know who is allowed to do so and who is not. Under real conditions, a database would take this job - but to keep things simple, I decided to use a small xml file. The file looks like this:</p>
<div class='code'>
<font color='blue'><</font><font color='maroon'>?xml</font><font color='red'> version</font><font color='blue'>="1.0"?></font><font color='black'><br />
</font><font color='blue'><</font><font color='maroon'>login</font><font color='blue'>></font><font color='black'><br />
</font><font color='blue'><</font><font color='maroon'>user</font><font color='blue'>></font><font color='black'></font><font color='blue'><</font><font color='maroon'>name</font><font color='blue'>></font><font color='black'>admin</font><font color='blue'></</font><font color='maroon'>name</font><font color='blue'>></font><font color='black'></font><font color='blue'><</font><font color='maroon'>pw</font><font color='blue'>></font><font color='black'>password</font><font color='blue'></</font><font color='maroon'>pw</font><font color='blue'>></font><font color='black'></font><font color='blue'></</font><font color='maroon'>user</font><font color='blue'>></font><font color='black'><br />
</font><font color='blue'><</font><font color='maroon'>user</font><font color='blue'>></font><font color='black'></font><font color='blue'><</font><font color='maroon'>name</font><font color='blue'>></font><font color='black'>mike</font><font color='blue'></</font><font color='maroon'>name</font><font color='blue'>></font><font color='black'></font><font color='blue'><</font><font color='maroon'>pw</font><font color='blue'>></font><font color='black'>flower</font><font color='blue'></</font><font color='maroon'>pw</font><font color='blue'>></font><font color='black'></font><font color='blue'></</font><font color='maroon'>user</font><font color='blue'>></font><font color='black'><br />
</font><font color='blue'></</font><font color='maroon'>login</font><font color='blue'>></font><font color='black'></font>
</div>
<p>All data is stored within <em>LinkedList<User> users</em>, where <em>User</em> is the simplest structure, you can imagine, having only two public <em>string</em> variables, named <em>name</em> and <em>pw</em>.</p>
<p>Loading users is as simple, so I will not explain it further, since this is general .NET stuff.</p>
<div class='code'>
<font color='blue'>public static void </font><font color='black'>LoadUsers(</font><font color='blue'>string </font><font color='black'>usersFile)<br />
{<br />
users.Clear()</font><font color='blue'>;<br />
<br />
</font><font color='black'>XmlTextReader xmlReader </font><font color='blue'>= new </font><font color='black'>XmlTextReader(usersFile)</font><font color='blue'>;<br />
</font><font color='black'>XmlDocument xmlDoc </font><font color='blue'>= new </font><font color='black'>XmlDocument()</font><font color='blue'>;<br />
</font><font color='black'>xmlDoc.Load(xmlReader)</font><font color='blue'>;<br />
<br />
</font><font color='black'>XmlNode curNode </font><font color='blue'>= </font><font color='black'>xmlDoc.FirstChild</font><font color='blue'>;<br />
while </font><font color='black'>(curNode !</font><font color='blue'>= null</font><font color='black'>)<br />
{<br />
</font><font color='blue'>if </font><font color='black'>(curNode.Name.ToLower() </font><font color='blue'>== </font><font color='#808080'>"login"</font><font color='black'>)<br />
{<br />
XmlNode curSubNode </font><font color='blue'>= </font><font color='black'>curNode.FirstChild</font><font color='blue'>;<br />
while </font><font color='black'>(curSubNode !</font><font color='blue'>= null</font><font color='black'>)<br />
{<br />
User newUser </font><font color='blue'>= new </font><font color='black'>User()</font><font color='blue'>;<br />
if </font><font color='black'>(curSubNode.Name.ToLower() </font><font color='blue'>== </font><font color='#808080'>"user"</font><font color='black'>)<br />
{<br />
</font><font color='blue'>foreach </font><font color='black'>(XmlNode attribute </font><font color='blue'>in </font><font color='black'>curSubNode.ChildNodes)<br />
{<br />
</font><font color='blue'>if </font><font color='black'>(attribute.Name.ToLower() </font><font color='blue'>== </font><font color='#808080'>"name"</font><font color='black'>)<br />
newUser.name </font><font color='blue'>= </font><font color='black'>attribute.InnerText</font><font color='blue'>;<br />
<br />
if </font><font color='black'>(attribute.Name.ToLower() </font><font color='blue'>== </font><font color='#808080'>"pw"</font><font color='black'>)<br />
newUser.pw </font><font color='blue'>= </font><font color='black'>attribute.InnerText</font><font color='blue'>;<br />
</font><font color='black'>}<br />
}<br />
users.AddLast(newUser)</font><font color='blue'>;<br />
<br />
</font><font color='black'>curSubNode </font><font color='blue'>= </font><font color='black'>curSubNode.NextSibling</font><font color='blue'>;<br />
</font><font color='black'>}<br />
}<br />
curNode </font><font color='blue'>= </font><font color='black'>curNode.NextSibling</font><font color='blue'>;<br />
</font><font color='black'>}<br />
xmlReader.Close()</font><font color='blue'>;<br />
</font><font color='black'>}</font>
</div>
<p><br />
<br>
<span class="step">Taking off</span></p>
<p>We already know how to add pre-defined sources. Would it not be nice to write new ones? Using InsomniaServer, a object you send to the client does not necessarily need to exist on your disk - you can create it at runtime, which is one of IS's big goals, and a thing we will do now - at least we will write a own <em>GetSource</em> function</p>
<p>A normal login in this sample goes like this:</p>
<ul>
<li type="disc">Loading <em>index.html</em> and filling in username and password<li type="disc">Clicking Login, which submits the entered data to <em>dologin.html</em>
<ul>
<li type="disc">If name and pw are correct, a new user session is created on the server and the client is redirected to <em>sec/index.html?sid=[Session ID]</em>
<li type="disc">If not, an error page is displayed</ul>
</ul>
<p><br>
<strong>The index page:</strong></p>
<p>To make it short, the index page is only a FileSource:</p>
<div class='code'>
<font color='black'>server.fileSystem.AddSource(</font><font color='#808080'>"index.html"</font><font color='black'>, FileSource.GetSource, </font><font color='#808080'>"../pages/index.html"</font><font color='black'>)</font><font color='blue'>;</font>
</div>
<p><br>
<strong>Much more interesting: <em>dologin.html</em>:</strong></p>
<p>This page will be our first own source. As every get source function's prototype, the one of <em>GetDoLogin</em> does look like this:</p>
<div class='code'>
<font color='blue'>public static </font><font color='black'>ObjectType GetDoLogin(</font><font color='blue'>string </font><font color='black'>subPath, HttpRequest request, </font><font color='blue'>object </font><font color='black'>userData, </font><font color='blue'>out </font><font color='black'>Source source)</font>
</div>
<p>So, what are we to do? We need to set the source parameter and return the object's type.</p>
<p>The latter is always ObjectType.File here, as it will never be untraceable or a folder. Well, let me describe all return values:</p>
<dl>
<dd><em>ObjectType.File</em>: <em>subPath</em> is an object's identifier (maybe including a path). source has to be set (not null)<dd><em>ObjectType.Folder</em>: <em>subPath</em> does not point to an object, but at a folder. source is ignored and can be null<dd><em>ObjectType.NotFound</em>: Guess what ; ) <em>subPath</em> points to an object, which is not "contained" within this <em>GetSource</em> callback
</dl>
<p><em>source</em> can be filled with an instance of a class derived from <em>Source</em>. Later, you may write completely own sources - and you will see, that this is quite straightforward when you just explore the documentation and try things on you own - but for now we will stick to pre-defined ones, which will meet most recommendations (I hope ^^).</p>
<p>But before I go on, look at the final code:</p>
<div class='code'>
<font color='blue'>public static </font><font color='black'>ObjectType GetDoLogin(</font><font color='blue'>string </font><font color='black'>subPath, HttpRequest request, </font><font color='blue'>object </font><font color='black'>userData, </font><font color='blue'>out </font><font color='black'>Source source)<br />
{<br />
</font><font color='blue'>string </font><font color='black'>passedName </font><font color='blue'>= null;<br />
string </font><font color='black'>passedPW </font><font color='blue'>= null;<br />
string </font><font color='black'>assignedID </font><font color='blue'>= null;<br />
try<br />
</font><font color='black'>{<br />
passedName </font><font color='blue'>= </font><font color='black'>request.GetParameter(</font><font color='#808080'>"username"</font><font color='black'>).ToString()</font><font color='blue'>;<br />
</font><font color='black'>passedPW </font><font color='blue'>= </font><font color='black'>request.GetParameter(</font><font color='#808080'>"password"</font><font color='black'>).ToString()</font><font color='blue'>;<br />
</font><font color='black'>}<br />
</font><font color='blue'>catch </font><font color='black'>{ }<br />
<br />
</font><font color='blue'>bool </font><font color='black'>matchFound </font><font color='blue'>= false;<br />
try<br />
</font><font color='black'>{<br />
</font><font color='blue'>if </font><font color='black'>(passedName !</font><font color='blue'>= null </font><font color='black'>&& passedPW !</font><font color='blue'>= null</font><font color='black'>)<br />
{<br />
</font><font color='blue'>try<br />
</font><font color='black'>{<br />
</font><font color='blue'>foreach </font><font color='black'>(User curUser </font><font color='blue'>in </font><font color='black'>users)<br />
{<br />
</font><font color='blue'>if </font><font color='black'>(curUser.name </font><font color='blue'>== </font><font color='black'>passedName && curUser.pw </font><font color='blue'>== </font><font color='black'>passedPW)<br />
{<br />
matchFound </font><font color='blue'>= true;<br />
break;<br />
</font><font color='black'>}<br />
}<br />
}<br />
</font><font color='blue'>catch </font><font color='black'>{ }<br />
}<br />
<br />
</font><font color='blue'>if </font><font color='black'>(matchFound)<br />
{<br />
</font><font color='darkgreen'>//Create a new session, to store user's data<br />
</font><font color='black'>Session sess </font><font color='blue'>= </font><font color='black'>request.server.sessionManager.RegisterSession()</font><font color='blue'>;<br />
</font><font color='black'>sess.SetStringValue(</font><font color='#808080'>"username"</font><font color='black'>, passedName)</font><font color='blue'>;<br />
</font><font color='black'>sess.SetValue(</font><font color='#808080'>"ip"</font><font color='black'>, request.remoteEndPoint.Address)</font><font color='blue'>;<br />
</font><font color='black'>assignedID </font><font color='blue'>= </font><font color='black'>sess.id</font><font color='blue'>;<br />
<br />
</font><font color='darkgreen'>//Redirect request to the secured area<br />
</font><font color='black'>source </font><font color='blue'>= new </font><font color='black'>MovedPermanentlySource(</font><font color='#808080'>"/sec/index.html?sid=" </font><font color='black'>+ sess.id)</font><font color='blue'>;<br />
return </font><font color='black'>ObjectType.File</font><font color='blue'>;<br />
</font><font color='black'>}<br />
</font><font color='blue'>else<br />
</font><font color='black'>{<br />
</font><font color='darkgreen'>//Login failed - send failed response<br />
</font><font color='black'>source </font><font color='blue'>= </font><font color='black'>FileSource.Get(</font><font color='#808080'>"../pages/loginfailed.html"</font><font color='black'>)</font><font color='blue'>;<br />
return </font><font color='black'>ObjectType.File</font><font color='blue'>;<br />
</font><font color='black'>}<br />
}<br />
</font><font color='blue'>finally<br />
</font><font color='black'>{<br />
</font><font color='blue'>if </font><font color='black'>(mainForm !</font><font color='blue'>= null</font><font color='black'>)<br />
{<br />
</font><font color='darkgreen'>//Write login data and result to the form's list box<br />
</font><font color='black'>mainForm.Invoke(</font><font color='blue'>new </font><font color='black'>Form1.AddListBoxItemDelegate(mainForm.listBoxLogins.Items.Add),<br />
DateTime.Now.ToShortDateString() + </font><font color='#808080'>" " </font><font color='black'>+ DateTime.Now.ToLongTimeString() + </font><font color='#808080'>": " </font><font color='black'>+<br />
(passedName !</font><font color='blue'>= null </font><font color='black'>? passedName : </font><font color='#808080'>"[null]"</font><font color='black'>) + </font><font color='#808080'>" / " </font><font color='black'>+ (passedPW !</font><font color='blue'>= null </font><font color='black'>? passedPW : </font><font color='#808080'>"[null]"</font><font color='black'>) + </font><font color='#808080'>" " </font><font color='black'>+<br />
(matchFound ? (</font><font color='#808080'>"SUCCEEDED (" </font><font color='black'>+ assignedID + </font><font color='#808080'>")"</font><font color='black'>) : </font><font color='#808080'>"FAILED"</font><font color='black'>))</font><font color='blue'>;<br />
</font><font color='black'>}<br />
}<br />
}</font>
</div>
<p>The first thing, we do here, is querying username and password from the request's parameters. Then they are checked for matches with existing users (as loaded before). If none is found, <em>source</em> is set an error page, which is sent, using a simple <em>FileSource</em>.</p>
<p>Much more interesting is the case, when a user is found. First, we use InsomniaServer's SessionManager to create a Session, in which we store all data, belonging to this user - in this example only username and the client's ip address, but much more could be added here.<br>
Afterwards, source is assigned a MovedPermanentlySource, that redirects the client to the secured area's index page.</p>
<p>Okay, now take some seconds, to think about everything and try to understand, what you just read before going on.</p>
<p><br>
<strong>Approaching the climax: <em>GetMembersonlyContent</em>:</strong></p>
<p>The point of having a members-only area makes it interesting to address the user directly by using his name within the pages. So, what we need is a way to put this name into out sites. Easy as winking:</p>
<div class='code'>
<font color='blue'>public static </font><font color='black'>ObjectType GetMembersonlyContent(</font><font color='blue'>string </font><font color='black'>subPath, HttpRequest request, </font><font color='blue'>object </font><font color='black'>userData, </font><font color='blue'>out </font><font color='black'>Source source)<br />
{<br />
source </font><font color='blue'>= null;<br />
<br />
</font><font color='darkgreen'>//Create full local path of the requested source<br />
</font><font color='blue'>string </font><font color='black'>fullPath </font><font color='blue'>= </font><font color='#808080'>"../pages/sec/" </font><font color='black'>+ subPath</font><font color='blue'>;<br />
<br />
if </font><font color='black'>(File.Exists(fullPath))<br />
{<br />
</font><font color='blue'>if </font><font color='black'>(subPath.EndsWith(</font><font color='#808080'>".html"</font><font color='black'>) || subPath.EndsWith(</font><font color='#808080'>".htm"</font><font color='black'>))<br />
{<br />
Session sess </font><font color='blue'>= null;<br />
try<br />
</font><font color='black'>{<br />
</font><font color='darkgreen'>//Load user's session<br />
</font><font color='black'>sess </font><font color='blue'>= </font><font color='black'>request.server.sessionManager.GetSession(request.GetParameter(</font><font color='#808080'>"sid"</font><font color='black'>).ToString())</font><font color='blue'>;<br />
</font><font color='black'>}<br />
</font><font color='blue'>catch </font><font color='black'>{ }<br />
<br />
</font><font color='darkgreen'>//Replace placeholders in html pages<br />
</font><font color='black'>StreamReader reader </font><font color='blue'>= new </font><font color='black'>StreamReader(fullPath, Encoding.ASCII)</font><font color='blue'>;<br />
string </font><font color='black'>pageContent </font><font color='blue'>= </font><font color='black'>reader.ReadToEnd()</font><font color='blue'>;<br />
</font><font color='black'>reader.Dispose()</font><font color='blue'>;<br />
<br />
if </font><font color='black'>(sess !</font><font color='blue'>= null</font><font color='black'>)<br />
{<br />
pageContent </font><font color='blue'>= </font><font color='black'>pageContent.Replace(</font><font color='#808080'>"<!--#USER#-->"</font><font color='black'>, sess.GetStringValue(</font><font color='#808080'>"username"</font><font color='black'>))</font><font color='blue'>;<br />
</font><font color='black'>pageContent </font><font color='blue'>= </font><font color='black'>pageContent.Replace(</font><font color='#808080'>"<!--#SID#-->"</font><font color='black'>, sess.id)</font><font color='blue'>;<br />
</font><font color='black'>pageContent </font><font color='blue'>= </font><font color='black'>pageContent.Replace(</font><font color='#808080'>"<!--#IP#-->"</font><font color='black'>, sess.GetValue(</font><font color='#808080'>"ip"</font><font color='black'>).ToString())</font><font color='blue'>;<br />
</font><font color='black'>}<br />
source </font><font color='blue'>= new </font><font color='black'>BinarySource(Encoding.ASCII.GetBytes(pageContent), ContentType.FromFileName(subPath))</font><font color='blue'>;<br />
</font><font color='black'>}<br />
</font><font color='blue'>else<br />
</font><font color='black'>source </font><font color='blue'>= </font><font color='black'>FileSource.Get(fullPath)</font><font color='blue'>;<br />
return </font><font color='black'>ObjectType.File</font><font color='blue'>;<br />
</font><font color='black'>}<br />
</font><font color='blue'>else<br />
</font><font color='black'>{<br />
</font><font color='blue'>if </font><font color='black'>(Directory.Exists(fullPath))<br />
</font><font color='blue'>return </font><font color='black'>ObjectType.Folder</font><font color='blue'>;<br />
else<br />
return </font><font color='black'>ObjectType.NotFound</font><font color='blue'>;<br />
</font><font color='black'>}<br />
}</font>
</div>
<p>What you see here is a multi-file source, as you them know from the last tutorial. It is able to respond differently, depending on the request.</p>
<p>If an html file is requested, we load the user's session created before, load the desired file and replace some placeholders with their belonging values. Finally, a <em>ByteSource</em>, containing the processed file's content is assigned to source. When some different type of file is requested, we create a <em>FileSource</em> again.</p>
<p>Another thing, worth looking at, are the last six lines. It is checked, whether a folder is addressed through the requested <em>subPath</em> - if so, the previously explained <em>ObjectType.Folder</em> is returned. If no object could be found, <em>ObjectType.NotFound</em> is the return value of our choice.</p>
<p><br>
<strong>
Reached the peak: Access control:</strong></p>
<p>Until now, anyone is able to access our so called members-only area. Do we want that? Probably not. So, let's fix this.<br>
InsomniaServer provides a neat way to do so: Security validators. A security validator is a function, which is called every time, a secured source is requested. It has the possibilities either to allow the request, or to dismiss it by sending an error page.</p>
<p>Setting such a validator is done by only calling: </p>
<div class='code'>
<font color='black'>FSNode.SetSecurityValidator(SecurityValidatorDelegate validator, </font><font color='blue'>object </font><font color='black'>validatorUserData, GetSourceCallback unauthorizedPage, </font><font color='blue'>object </font><font color='black'>unauthorizedPageUserData)</font>
</div>
<dl>
<dd><font color="black"><em>validator</em> is a delegate of the validation function</font>
<dd><font color="black"><em>validatorUserData</em> specifies an object, which is passed to the validator every time it is called</font>
<dd><font color="black"><em>unauthorizedPage</em> sets the <em>GetSourceCallback</em>, which is to be used, if the authorisation failed</font>
<dd><font color="black"><em>unauthorizedPageUserData</em> is an object, which is passed to the <em>GetSourceCallback</em> of <em>unauthorizedPage</em></font>
</dl>
<p></p>
<p>And this is our simple validator, which only checks, whether the passed session exists and if the client's ip equals the saved one:</p>
<div class='code'>
<font color='blue'>public static bool </font><font color='black'>SecurityValidator(</font><font color='blue'>object </font><font color='black'>validatorUserData, </font><font color='blue'>string </font><font color='black'>subPath, HttpRequest request, </font><font color='blue'>object </font><font color='black'>sourceUserData)<br />
{<br />
</font><font color='blue'>try<br />
</font><font color='black'>{<br />
Session sess </font><font color='blue'>= </font><font color='black'>request.server.sessionManager.GetSession(request.GetParameter(</font><font color='#808080'>"sid"</font><font color='black'>).ValueToString())</font><font color='blue'>;<br />
if </font><font color='black'>(!sess.GetValue(</font><font color='#808080'>"ip"</font><font color='black'>).Equals(request.remoteEndPoint.Address))<br />
</font><font color='blue'>return false;<br />
return true;<br />
</font><font color='black'>}<br />
</font><font color='blue'>catch<br />
</font><font color='black'>{<br />
</font><font color='blue'>return false;<br />
</font><font color='black'>}<br />
}</font>
</div>
<p><br>
<br>
<span class="step">Putting things together</span></p>
<p>There is only one missing thing: a function, which sets all <em>GetSource</em> callbacks and initializes our security validator:</p>
<div class='code'>
<font color='blue'>public static void </font><font color='black'>AddSources(InsomniaServer server, Form1 callingForm)<br />
{<br />
mainForm </font><font color='blue'>= </font><font color='black'>callingForm</font><font color='blue'>;<br />
<br />
</font><font color='black'>server.fileSystem.AddSource(</font><font color='#808080'>"index.html"</font><font color='black'>, FileSource.GetSource, </font><font color='#808080'>"../pages/index.html"</font><font color='black'>)</font><font color='blue'>;<br />
</font><font color='black'>server.fileSystem.AddSource(</font><font color='#808080'>"dologin.html"</font><font color='black'>, GetDoLogin, </font><font color='blue'>null</font><font color='black'>)</font><font color='blue'>;<br />
</font><font color='black'>server.fileSystem.AddSource(</font><font color='#808080'>"sec/*"</font><font color='black'>, GetMembersonlyContent, </font><font color='blue'>null</font><font color='black'>)</font><font color='blue'>;<br />
<br />
</font><font color='black'>server.fileSystem.GetNode(</font><font color='#808080'>"sec"</font><font color='black'>).SetSecurityValidator(SecurityValidator, </font><font color='blue'>null</font><font color='black'>, InternalRedirectSource.GetSource, </font><font color='#808080'>"/index.html"</font><font color='black'>)</font><font color='blue'>;<br />
</font><font color='black'>}</font>
</div>
<p><br>
<br>
Finally, the desired moment has come. Compile! Run! Try!<br>
<font size="-1">Hint: username: <em>admin</em> / password: <em>password</em></font></p>
<p>Anything else? Only one little, but very important thing: <strong>Thanks for your patience : )</strong></p>
</body>
</html>