[Note: The code samples in this Tech Tip are
extracted and modified from the code solutions in the instructional units
Security_Authentication and Security_Role-Based Authorization. We don’t attempt
herein to explain exactly where each variable comes from, but rather to provide
just enough context to illustrate a point. See the instructional units for the
complete context and details.]
To perform authentication,
you invoke the Login() method on a PersistenceManager, passing it an
ILoginCredential object:
C#
using
IdeaBlade.Persistence;
userName
= mUserNameTextBox.Text;
password
= mPasswordTextBox.Text;
domain
= mDomainTextBox.Text;
LoginCredential aCredential = new LoginCredential(
userName,
IdeaBlade.Util.CryptoFns.MD5HashUTF16ToString(password),
domain);
aPersistenceManager.Login(aCredential);
VB
Imports
IdeaBlade.Persistence
userName = mUserNameTextBox.Text
password = mPasswordTextBox.Text
domain = mDomainTextBox.Text
Dim aCredential As LoginCredential = New
LoginCredential( _
userName,
IdeaBlade.Util.CryptoFns.MD5HashUTF16ToString(password), domain)
aPersistenceManager.Login(aCredential)
Internally,
PersistenceManager.Login() forwards the login request to the PersistenceServer,
which looks for a class that implements the DevForce IPersistenceLoginManager
interface. That interface requires only a single member – a Login() method,
which takes an ILoginCredential and a PersistenceManager as inputs:
The
PersistenceServer looks for this class by reflecting through the probe
assemblies associated with the datasource keys found in ibconfig:
<rdbKey name="default"
databaseProduct="Unknown">
<connection>Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security
Info=False;User ID=sa;Initial Catalog=IdeaBladeTutorial;Data Source=.</connection>
<probeAssemblyName>Model</probeAssemblyName>
<probeAssemblyName>Server</probeAssemblyName>
</rdbKey>
For
the above-illustrated datasource key, assemblies named “Model” and “Server” are
named as probe assemblies, so the PersistenceServer reflects against those
assemblies looking for a class that implements IPersistenceLoginManager. If it finds one, it calls the Login() method
in that class to perform the actual authentication, passing on both the credential
information that your code provided to it, and a reference to a server-side PersistenceManager
that the method can use if needed.
Since
it is you who provides the implementation of IPersistenceLoginManager, you
write its Login() method to perform authentication by any means you desire. In
the “Security_Authentication” instructional
unit, a sample implementation of IPersistenceLoginManager.Login() performs
authentication by looking up in a database the user named in the credential,
then comparing a hashed password stored in the database for that user with the
hashed password included in the credential. If they match, the method returns
an IPrincipal object that identifies the authenticated user. If they don’t
match, it throws an exception:
C#
if (pCredential == null)
{
throw new LoginException(LoginExceptionType.NoCredentials, null, null);
}
if
(pUser.IsNullEntity) {
throw new LoginException(LoginExceptionType.InvalidUserName, null, pUsername);
}
if
(pPassword != pUser.UserPassword) {
throw new LoginException(LoginExceptionType.InvalidPassword, null, pUsername);
}
return principal;
VB
If pCredential Is Nothing Then
Throw New
LoginException(LoginExceptionType.NoCredentials, Nothing,
Nothing)
End If
If pUser.IsNullEntity Then
Throw New
LoginException(LoginExceptionType.InvalidUserName, Nothing,
pUsername)
End If
If pPassword <> pUser.UserPassword Then
Throw New
LoginException(LoginExceptionType.InvalidPassword, Nothing,
pUsername)
End If
Return principal
The
PersistenceServer either forwards the exception to the client-side code that
called PersistenceManager.Login(), or wraps the IPrincipal object in another
DevForce object called a SessionBundle, and returns that to PersistenceManager.Login().
Independent of anything you do or don’t do with that SessionBundle, it is kept
around by the PersistenceManager, which passes it to the PersistenceServer
as part of each subsequent contact. The
PersistenceServer uses the SessionBundle to authenticate the incoming request
from the client.
You
may or may not find a use yourself for the returned SessionBundle. It does
include a Principal property, which contains an IPrincipal object that
represents the authenticated user. That is of some interest, since it may
contain additional information about that user previously unknown to the
client. (See the discussion below on
attaching role information.)
On
the other hand, upon successful login, the PersistenceManager also attaches the
same IPrincipal to the running thread, whence it may subsequently be accessed
as follows:
C#
authenticatedPrincipal=System.Threading.Thread.CurrentPrincipal;
VB
authenticatedPrincipal=System.Threading.Thread.CurrentPrincipal
So,
if you like, you can quite safely ignore the SessionBundle returned by Login().
Attaching
Role Information to the Authenticated IPrincipal
When
you authenticate your user in the IPersistenceLoginManager.Login() method, you
may also wish to their assigned security roles and attach those to the returned
IPrincipal so it can be queried for them.
The code below gets the role information from a User business object
whose class was generated by the DevForce Object Mapper from the schema of a
User table. [Note: see the User, UserRole, and Role tables in the IdeaBladeTutorial
database.]
C#
public IPrincipal Login(ILoginCredential pCredential, PersistenceManager
pManager) {
IPrincipal principal = new GenericPrincipal(identity, GetRoles(pManager,username));
return principal;
}
private string[] GetRoles(PersistenceManager pManager, string pUserName) {
RdbQuery anRdbQuery = new RdbQuery(typeof(User));
anRdbQuery.AddClause(User.UserNameEntityColumn, RdbQueryOp.EQ, pUserName);
User aUser = pManager.GetEntity<User>(anRdbQuery);
EntityList<Role> roles = aUser.Roles;
System.Collections.ArrayList roleNames = new System.Collections.ArrayList();
foreach (Role aRole in aUser.Roles) {
roleNames.Add(aRole.Name);
}
return (String[])roleNames.ToArray(typeof(String));
}
VB
Public Function Login(ByVal
pCredential As ILoginCredential, _
ByVal pManager As PersistenceManager) As
IPrincipal
IPrincipal
principal = New GenericPrincipal(identity,
GetRoles(pManager,username))
Return principal
End Function
Private Function GetRoles(ByVal
pManager As PersistenceManager, _
ByVal pUserName As String) As String()
Dim anRdbQuery As
RdbQuery = New RdbQuery(GetType(User))
anRdbQuery.AddClause(User.UserNameEntityColumn, RdbQueryOp.EQ,
pUserName)
Dim aUser As User =
pManager.GetEntity(Of User)(anRdbQuery)
Dim roles As
EntityList(Of Role) = aUser.Roles
Dim roleNames As
System.Collections.ArrayList = New
System.Collections.ArrayList()
For Each aRole As Role In
aUser.Roles
roleNames.Add(aRole.Name)
Next aRole
Return CType(roleNames.ToArray(GetType(String)), String())
End Function
In
your UI, you can use this information about role memberships to suppress or
display particular features or capabilities:
C#
private void
LoadUserUserControl() {
if (!authenticatedPrincipal.IsInRole("admin")) {
return;
} else {
ConstructUserTab();
UserUserControl
aUserUserControl = new UserUserControl();
mUsersPanel.Controls.Add(aUserUserControl);
aUserUserControl.InitializeUserControl();
}
}
VB
Private Sub LoadUserUserControl()
If (Not authenticatedPrincipal.IsInRole("admin")) Then
Return
Else
ConstructUserTab()
Dim aUserUserControl As
UserUserControl = New UserUserControl()
mUsersPanel.Controls.Add(aUserUserControl)
aUserUserControl.InitializeUserControl()
End If
End Sub
This
concludes our introduction to authentication in DevForce There are many other
aspects to DevForce’s security facilities: we’ll address these in future Tech
Tips. Stay tuned!