DA31 - Simplifying Permissions
by Nathanial Woolls
Note from RemObjects: although we believe the content of this article to be correct, we do not warrant that it is so. We thank Nathanial very much for producing this, which we are pleased to publish here.
A Technique
for Automated Permissions in Data Abstract
One of the most important aspects of any remotely accessed service framework is security. This is no different with the RemObjects SDK and Data Abstract. Luckily, the RemObjects SDK and Data Abstract do most of the heavy lifting for us. By providing a session management framework in the RemObjects SDK and an easy-to-use validation system in Data Abstract, security is ready to go (with some work from the developer) out of the box. And, with the small amount of code discussed in this article, we can have nearly all of our authentication and permission needs taken care of for us with almost no additional coding (after a small amount of initial work).
While the concepts presented in this article are straight-forward, and the code written fairly brief, it is suggested that you have a basic understanding of how the RemObjects SDK’s session managers work. You can read about session managers in the RemObjects SDK here. Another suggested article goes into further depth on session managers and how to use them with Data Abstract, and can be found here.
The idea behind the technique presented in this article is to first extend the session object in the RemObjects SDK to natively hold a list of permissions. While we could probably use the Values property of the regular TROSession object to store this, it is easy to extend the session object to cleanly hold a separate list of values for permissions, and this will be our first step. Then, we will utilize a seldom advertised feature in Data Abstract called Custom Attributes to automate the pairing and checking of permissions when accessing datasets and commands in Data Abstract.
Session Management
To use our own session object, we will use a TROEventSessionManager.
It should be noted that this technique is also possible with the TROInMemorySessionManager, which only requires using OnCustomCreateSession. We examine TROEventSessionManager here for completeness, as extra code is necessary to use our own session object with this component. The events we are interested in are OnCustomCreateSession and OnFindSession.
If we look at these events, you'll notice that the Session parameter's are passed as var's and out's. This allows us to subclass TROSession, creating our own session object with a Permissions property. We can then use our session object within the RemObjects SDK’s session management framework.
We'll start by defining our session object:
unit uAuthSession;
interface
uses uROSessions, Classes;
type
TAuthSession = class(TROSession)
private
FPermissions: TStrings;
public
constructor Create(const aSessionID : TGUID); override;
destructor Destroy; override;
property Permissions: TStrings read FPermissions;
end;
implementation
uses SysUtils;
{ TAuthSession }
constructor TAuthSession.Create(const aSessionID: TGUID);
begin
inherited;
FPermissions := TStringList.Create;
end;
destructor TAuthSession.Destroy;
begin
FreeAndNil(FPermissions);
inherited;
end;
end.
Now, we'll handle the two events we mentioned above to use our own session object instead of the regular TROSession object (again, only CustomCreateSession need be handled with the in-memory session manager):
procedure TSessionsController.SessionManagerCustomCreateSession(const
aSessionID: TGUID; var Session: TROSession);
begin
Session := TAuthSession.Create(aSessionID);
end;
procedure TSessionsController.SessionManagerFindSession(const aSessionID:
TGUID; out aSession: TROSession);
var
Session: TAuthSession;
begin
aSession := nil;
... if code to find session here succeeds
begin
Session := TAuthSession.Create(aSessionID);
aSession := Session;
...
end
end;
And that's it! From now on, when you would normally access your session object in your RemObjects SDK and Data Abstract services, you can now access them as a TAuthSession (with proper casting). Now, in our service's login method, we can use this to store permissions:
function TXXXXServices.Login(const Username, Password: string): Boolean;
var
Session: TAuthSession;
begin
Result := False;
...if login is successful
begin
Session := TAuthSession(Self.Session);
Session.Permissions.Add(...);
Session.Permissions.Add(...);
Session.Permissions.Add(...);
else
DestroySession;
end;
Combining all of this code means that we have a clean place to hold a list of permissions in our session object, and can now use that list of permissions to validate remote service calls as well as dataset retrieval.
Data Access Validation
Data Abstract modules have three events that help simplify the validation of a user before allowing access to Data Abstract elements. These three events are:
- ValidateCommandExecution
- ValidateDatasetAccess
- ValidateDirectSQLAccess
These events have method signatures like the following:
procedure TXXXXServices.DataAbstractServiceValidateDatasetAccess(
Sender: TObject; const aConnection: IDAConnection;
const aDatasetName: string; const aParamNames: array of string;
const aParamValues: array of Variant;
aSchema: TDASchema; var Allowed: Boolean);
As you can see, by handling these events you can inspect the request being made through Data Abstract, evaluate any permissions or conditions, and then set the Allowed parameter accordingly.
Now, at this point it would be trivial to simply check our session object's permissions in these Validate events, check the dataset or command name, do some custom lookup to see what permissions were needed to access that dataset or command, and act accordingly. The downside to this is that, as we add commands and datasets, we must continue to update the code in these Validate methods to keep checking our new Data Abstract elements for authorization.
Tying it All Together
Enter one of the least mentioned features of Data Abstract: Custom Attributes. This is, quite simply, a property, CustomAttributes, with a type of TStrings, which is found on Data Abstract Data Tables, Commands, and Fields. The use of this property, like Tag, is totally up to the developer. The CustomAttributes property can be accessed at design time through the Data Abstract components and also through Schema Modeler:
Armed with this knowledge, we can use the following simple code to tie all of the pieces together:
procedure TXXXXServices.DataAbstractServiceValidateDatasetAccess(Sender:
TObject; const aConnection: IDAConnection; const aDatasetName: string;
const aParamNames: array of string; const aParamValues: array of
Variant; aSchema: TDASchema; var Allowed: Boolean);
var
I: Integer;
Session: TAuthSession;
Dataset: TDADataset;
begin
Session := TAuthSession(Self.Session);
Dataset := MySchema.Datasets.DatasetByName(aDatasetName);
for I := 0 to Dataset.CustomAttributes.Count - 1 do
begin
if Session.Permissions.IndexOf(Dataset.CustomAttributes[I]) = -1 then
begin
Allowed := False;
Break;
end;
end;
end;
With this code in place for datasets and commands, in Schema Modeler we simply need to list the permissions needed to access our datasets and commands in the Custom Attributes area. After this, Data Abstract and the RemObjects SDK’s session management, along with our custom session object, take care of the rest! Trying to access a dataset for which our session object does not contain a Permissions entry results in a nice EDADatasetNotAccessible exception from Data Abstract.



Delphi
