Professional ASP.NET Security - Jeff Ferguson
.pdfCustom Authentication
|
|
|
|
|
|
|
gHS^ J^MSNfitor.ey j|] MSNMoney Top10 j |
|
||
|
|
|
|
|
|
|
|
Call Now to Test Your App 1.877.33. VOCAL |
||
|
|
|
|
|
|
Europe {*«)207961 398S OtherInlemational {*1)408.90i'.r328 |
||||
L°g |
out |
VbiceXML Checker |
||||||||
Home |
Home > Tools 6 FiJe Management > VoiceXML Checker |
|||||||||
|
|
|
|
|
|
|
||||
Tool. & File |
|
* getphonenurr.ber.vxml > |
||||||||
|
|
:ML Checker |
Statue |
|||||||
|
|
|
|
|
|
|
|
|
||
|
|
ElilSf |
|
|
|
|
|
|
|
|
|
Uia.&.r.SmSSr. |
|
<!DOCTYPE vxm! PUBLIC V/BeVocal Inc//VoiceXML2.Q//EN" "http:| |
|||||||
|
TraCpTpcl |
|
vxml version-"2.D"> |
|||||||
|
|
|
|
^var name-"chentTelCo" expr--SeS5i0n.telephone.anlV>| |
||||||
|
|
|
|
|
|
|||||
|
|
|
|
|
|
form id="frmData"> |
||||
|
|
|
|
|
|
< block > |
||||
<scnpt> |
|
|
|
|
|
|
|
|||
<![CDATA[ |
|
|
|
|
|
f{uftction QetReSult(cal!Id) |
||||
Dftotoyment |
|
|
|
|
|
|||||
Supoetf |
|
|
|
|
|
•eturn 'http://www.tat |
||||
} |
|
|||||||||
7890987 |
|
ua^BBBMBBHI |
|
|||||||
Fch^kl [ Save j fci^Tj [ Re««tl |
||||||||||
|
|
|
||||||||
>^ |
|
Cafe Home | Developer ftoreement | Privacy Policy | Site Mao | Terms & Coodition: |
When you're comfortable that a Voice XML code is functional - by clicking the Check button - Cafe performs a compilation on the code and presents a results screen that highlights any errors.
The following screen capture presents an example of the information the Cafe compiler provides during Voice XML development.
f
— :- |
• |
J ' |
, |
Search |
Favoi te; |
%^» Med« |
^ |
|
A.rjdtcss |
.jD http://cafe.bevocal.com/toote/vxmlchecker/vxmlchecl'e |
;; • le- s |
|
|||||
---^tphonenumber. .•l.ml,iJ:?pe=F;LE !•=-!$ .^Jblogs ^development |
.j^ Amerrtrade |
^ BogThei $£ MSKB |
||||||
^JMSNMoney |
^j MSN Money Top 10 |
|
|
|
|
|
|
|
|
|
Line 5: |
|
<vart nama—'dierrtTeico" »^pr="5ef---ion.tel8phorte.4niV> |
||||
|
|
[Error]: Attribute "expr" must be declared for element type "vart". |
||||||
|
|
Line 25: |
|
• . |
|
|
|
|
|
|
[Error]:The content of element type "vxml" must match "(catcrijhelp|noinput|nomatch|er |
||||||
|
|
|
2 |
< fDOCTYPE vxml PUBLIC |
M" "http;//cafe,b |
|||
|
|
|
"-//Be |
|
|
|
|
Cafe actually highlights the run-time errors and presents information related to Voice XML schema validation, syntax, and other possible areas for concern. Using this checker, you can work until you're confident that everything's working OK. Once you've written good, clean Voice XML (which we'll explore in a little more detail further on in this chapter) that functions properly, you can activate your desired startup script on the Tools and File Management page.
Now that we've had a quick introduction to the features the BeVocal Cafe application provider can provide in our development of Voice XML scripts, we'll take a quick introduction to the language itself.
Using BeVocal Cafe as a Service Provider
By being registered with a BeVocal Cafe account, our proposed TeleBank application can use BeVocal as the main point of access. To facilitate this process, we will create a simple Voice XML script to act as the activated script in the TeleBank BeVocal storage facility. This script uses the
session, telephone . ani variable outlined earlier as the parameter to aJavaScript function. When executed later in the Voice XML markup, this script redirects the client to the TeleBank application.
<?xml version="l.0"?>
<!DOCTYPE vxml PUBLIC "-//BeVocal Inc//VoiceXML 2.0//EN" "http://cafe.bevocal.com/libraries/dtd/vxml2-0-bevocal.dtd"> <vxml version="2.0">
262
Custom Authentication
<var name="clientTelco" expr="session.telephone.ani"/>
<form id="frmData"> <block>
<script>
<![CDATA[
function getResult(callld)
{
return 'http://www.tatochip.com/WROX_TeleBank/Launch.aspx?clientTelco=' + callld;
}
]]>
</script>
<data name="srvRet" src="http://www.tatochip.com/WROX_TeleBank/Launch.aspx" namelist="clientTelco"/>
<assign name="document.srvRet" expr="srvRet.documentElement"/> <goto expr="getResult(clientTelco)"/> </block> </form>
</vxml>
Note that, for the purposes of this example, we are hosting our telebank application on a server called tatochip, and referencing the Launch. aspx page. We will investigate these parts of our application a little later.
From this point BeVocal begins to interpret markup that the TeleBank application responds with. BeVocal serves, in a way, as a text-to-speech translating application service provider in the TeleBank example. We'll now look at the parts of our application hosted on our own server: the database in which we store our users' account information, and the .NET application that implements the business logic of our application.
The SQL Server User/Account Database
The first step in designing this application will be to establish a database design. This database, created in SQL Server 2000, will contain three interrelated tables. We'll also create stored procedures that perform various functions required to obtain the relevant data elements.
Table Structures
The first of our three tables will contain the account information elements for users that will access our system. All users that register with the TeleBank service will have their account information stored in this table. Note that the account table omits a field specific to the account's telephone number. Providing a single telephone number for each user account is a little unrealistic by modern standards: think of how frustrating the system would be if it were only accessible via your home telephone number. The telephone numbers associated with an account will be housed in a separate table that we'll discuss in a moment. For now, we'll summarize the structure of the wrox_tb_user table, which contains summary information about specific user accounts. This contains the following fields:
oc-a
When the BeVocal interface (which we've yet to create) redirects telephone-based clients to our application, it will pass a query string variable containing the caller identification number of the telephone being used. We'll need to use this variable as the single parameter in a query whose purpose it is to pull up a user's account information. This stored procedure, named
wrox_tb_sp_f ind_user_by_telco, receives an input parameter containing the currently accessed telephone number, and obtains the user_id associated with it:
ALTER PROCEDURE dbo.wrox_tb_sp_find_user_by_telco
{
Stelco bigint
)
AS
SET NOCOUNT ON
SELECT [user_id] FROM wrox_tb_user_telco_listing
WHERE wrox_tb_user_telco_listing.telco = @telco
RETURN
The output of this stored procedure will be an integer value that reflects the user ID of the user associated with the current telephone number. When we begin creating our own custom identity objects to serve in our application, this will come in handy for providing a simple point of access for the user ID.
In addition to this stored procedure, we'll create one that obtains the total number of accounts the active telephone number is associated with. This may seem like overkill, but think of it this way: if we tried to create a user identity object for a user that doesn't exist, we'd run into problems. So, we perform a check to see if any accounts in our database are associated with the provided telephone number. Should our check result in zero, we know that there is no reason to go any further. This stored procedure, named wrox_tb_sp_total_accounts_by_telco, is constructed as follows:
ALTER PROCEDURE dbo.wrox_tb_sp_total_accounts_by_telco
(
@telco bigint
)
AS
SET NOCOUNT ON
SELECT COUNT)*)AS total_accounts_found FROM wrox_tb_user_telco_listing INNER JOIN wrox_tb_account ON
wrox_tb_user_telco_listing.user_id = wrox_tb_account.user_id WHERE (wrox_tb_user_telco_listing.telco = @telco)
RETURN
This step should be implemented first in our process: we'll see if the active telephone number is associated with any accounts. If it is not, we also know that it isn't associated with any users, either. Of course, we may find some accounts associated with a given telephone number. In this case it would be nice to have a function that obtains the account list. This stored procedure, shown below, returns a dataset-like collection of rows containing the account names, balances, and account identification numbers.
266
Custom Authentication
ALTER PROCEDURE dbo . wrox_tb_sp_account_data
(
Stelco bigint
)
AS
SET NOCOUNT ON
SELECT wrox_tb_account . account_id, wrox_tb_account . account_label ,
FROM |
wrox_tb_account . account_balance |
wrox_tb_user_telco_listing INNER |
|
JOIN wrox_tb_account |
|
ON |
wrox_tb_user_telco_listing.user_id = wrox_tb_account . user_id WHERE |
wrox_tb_user_telco_listing. telco = @telco RETURN
With the stored procedures written we now have a chance to work on the actual business logic. The main database access class, DBAccess, contains a rather complicated construction method. This method's responsibility is two-fold: first, it determines whether any accounts exist that are associated with the active caller identification data, and if so, collects that data into a local property. We'll create a class containing some public properties so that classes requiring data access will have simple interfaces to those data elements. The code below outlines the beginnings of this public class, with the properties inserted for reference purposes.
using System;
using System. Collections; using System. Data;
using System. Data. SqlClient;
namespace DataAccess {
///<summary>
///Summary description for DBAccess.
///</summary>
public class DBAccess
{
private SqlConnection conn; private SqlCommand cmd;
private string str_conn = "Password=;User ID=sa; Initial Catalog=telebank;Data Source=sqll4 .wrox.com" ;
private long user_ID = 0; private int intTotalAccounts = 0;
private bool blnUserExists = false; private ArrayList arrAccounts;
//property to indicate if the user exists public bool UserExists
{
get
{
return blnUserExists;
//property to expose the total number of accounts
public int AccountTotal { get
{
return intTotalAccounts;
} }
//property to expose the current user ID public long UserlD {
get
{
return user_ID;
//property to expose the arraylist of accounts held public ArrayList Accounts
{
get
{
return arrAccounts;
You probably noticed that the Accounts property returns an instance of an ArrayList object. Each item in the collection will be a custom object of type Account. The code below demonstrates the structure of an Account object. Notice especially the constructor of the object, which takes three input parameters that are used to set the local properties' values.
//class to represent an account public class Account
public string Label; public decimalBalance;publiclong AccountID;
public Account(long acct_id, string Ibl, decimal bal)
this.Label = Ibl; this.Balance = bal; this.AccountID = acct_id;
We've exposed the valuable assets of any given TeleBank subscription via public properties of this data access component. Now, during construction, we'll retrieve all the subscription information. To make things even simpler (and more reusable!), we'll require the existence of one parameter during construction - the active telephone number data that BeVocal's interface has already obtained. The beginnings of the constructor code, overleaf, show its initial check for accounts associated with the active telephone number:
268
Custom Authentication
//constructor with no parameter
//just calls constructor with phoneNumber 0 public DBAccessO : this(O)
{
}
//constructor
public DBAccess (long phoneNumber}
{
//create the connection
conn = new SqlConnection(str_conn) ;
if (conn. State == ConnectionState. Closed) conn.
Open ( ) ;
and = new SglCommand( "wrox_tb_sp_total_accounts_by_telco" , conn); cmd . CommandType = CommandType. StoredProcedure;
cmd. Parameters .Add ( "@telco" , phoneNumber) ; intTotalAccounts = (int) cmd.ExecuteScalar ();
Following this process, the constructor should obtain all of the accounts, should any exist. To perform this task, the constructor obtains a forward-reading cursor containing the results of our wrox_tb_sp_account_data procedure. For each row in the result set, a new Account object is created and added to the Accounts collection property of the component:
if (intTotalAccounts > 0) {
SqlDataReader rdr ;
//get the user id
cmd = new SqlCommand( "wrox_tb_sp_f ind_user_by_telco" , conn); cmd . CommandType = CommandType . StoredProcedure ;
cmd. Parameters. Add ( "@telco" .phoneNumber) ; user_ID = (long) cmd.ExecuteScalar ();
//get the accounts
arrAccounts = new ArrayList (intTotalAccounts) ;
cmd = new SqlCommandt "wrox_tb_sp_account_data" , conn); cmd . CommandType = CommandType. StoredProcedure ;
cmd. Parameters .Add ( "Stelco" , phoneNumber) ;
rdr = cmd.ExecuteReader ( ) ;
while (rdr .Read () ) {
arrAccounts . Add (new Account ( (long ) rdr [0] , (string) rdr [1] , (decimal) rdr [2] ) ) ;
In this way, the data access object obtains everything we'll need as soon as it's created, resulting in fewer component calls or database connections. From here, we'll create a custom Identity object similar to (though far more useful than!) the one outlined earlier in this chapter.
Custom Identity and Principal Objects
The identity objects outlined in this example contain virtually every aspect of an account. Of course, you may choose not to extend your own identity classes in this manner. Due to the absence of a request-response or multi-URL model of Voice XML applications (we'll look at this in a moment), our client environment requires a holistic model of the user account.
269
Once again, we'll create a custom identity object that implements the System. Principal . Ildentity interface. To facilitate this implementation we must implement three properties: AuthenticationType, IsAuthenticated, and Name. In addition to these required properties, we'll add some code to the class's constructor, providing access to the data access component we just completed:
using System;
using System. Security. Principal ; using System. Collections;
using DataAccess;
namespace securityCodeConversion
{
///<summary>
///Summary description for TeleBankldentity .
///</summary>
public class TeleBankldentity : Ildentity
{
protected internal string strUsername; protected internal int strPrimaryTelco = 0; protected internal ArrayList arrAccounts; protected internal long IngActivePhone = 0 ; private DBAccess objDBAccess;
public TeleBankldentity (long telephoneNum)
{
objDBAccess = new DBAccess (telephoneNum) ; IngActivePhone = telephoneNum; arrAccounts = objDBAccess .Accounts; }
public string AuthenticationType { get
{
return ( "WROXBAUTH" ) ;
public bool IsAuthenticated { get
{
return true ;
} }
public string Name { get
{
return (objDBAccess .UserlD.ToString ( ) ) ;
To complete the custom identity object's interface, we'll provide access to the accounts associated with the active telephone number. This method, AccountList, will simply expose the global ArrayList that was set during construction.
270