Implementing your own password hash in mORMot

We have to authenticate against a legacy user database already populated, storing passwords encrypted in a proprietary format. The table also use the USERNAME field as the primary index. This means that we can’t use the ORM, as mORMot’s ORM requires a single integer primary key field.

Authentication in mORMot is handled through a dedicates set classes: TSQLAuthUser and TSQLAuthGroup.

For simplicity’s sake we’ll keep the implementation brief and simple.

mORMot ships with a couple of predefined groups: Admin, Supervisor, User and Guest. See the documentation for more information.

In our sample we’ll force all our users to belong to group User.

The first step would be to derive our own User from TSQLAuthUser:

type
  TDummySQLUser = class(TSQLAuthUser)
  protected
    class function ComputeHashedPassword(const aPasswordPlain: RawUTF8): RawUTF8; override;
  public
    constructor Create( aUserName, aPassword : RawUTF8 ); reintroduce;
  end;

The new constructor simply gives us an opportunity to set some of the internals to sane defaults. For one we need to set the internal ID, the encrypted password (as it’s stored in the database) and we need to force the group. Browsing through the mORMot code shows that the User group will be assign the ID 3. This means that the User’s GroupRights property should be set to 3, but because mORMot’s ORM stores referenced objects as their ID’s, 3 needs to be type casted:

  GroupRights := TSQLAuthGroup($3);

This leaves us with the following implementation:

constructor TDummySQLUser.Create( aUserName, aPassword : RawUTF8 );
begin
  inherited Create;
  fID := 1;
  LogonName := aUserName;
  PasswordHashHexa := aPassword;
  GroupRights := TSQLAuthGroup($3);
end;

The magic happens in the overridden method ComputeHashedPassword. This method gets called by the system to calculate the hash of a plain text password. In our version we simply implement it as such:

class function TDummySQLUser.ComputeHashedPassword(const aPasswordPlain: RawUTF8): RawUTF8; 
begin
  Result := EncryptMyPass( Value );
end;
Advertisements

Connecting to legacy databases and publishing a RESTful interface to it

Most systems, especially in the DDD area, needs to integrate to a legacy system. In our case our we had to communicate to a Firebird 1.5 database.

The first step was to define our Data Transfer Objects:

type
  TLegacyID = type RAWUTF8;

  TLegacyAccount = class(TSynPersistent)
  private
    fLegacyID: TLegacyID;
    fDateModified: TDateTime;
    fUserCreated: RAWUTF8;
    fDateCreated: TDateTime;
    fName: RAWUTF8;
    fisActive: Boolean;
  public
    constructor Create; overload; override;
    constructor Create( aID : TLegacyID; aName : RAWUTF8; aIsActive : Boolean;
      aDateCreated, aDateModified : TDateTime; aUserCreated : RAWUTF8 ); overload;
  published
    property ID : TLegacyID read fRevelightID write fRevelightID;
    property Name : RAWUTF8 read fName write fName;
    property isActive : Boolean read fisActive write fisActive;
    property DateCreated : TDateTime read fDateCreated write fDateCreated;
    property DateModified : TDateTime read fDateModified write fDateModified;
    property UserCreated : RAWUTF8 read fUserCreated write fUserCreated;
  end;

  TLegacySupplier = class(TLegacyAccount)
  end;

Here we declare a unique type to identify our legacy IDs (strings). We also do a basic map of our data layout, with a customized constructor for ease of creation. One can add other methods later to handle copies and assignments.

The next step was to define our service:

type
  ILegacyStockQuery = interface(IInvokable)
    ['{2BDC9F78-B9C2-4621-A557-F87F02AC0581}']
    function GetSupplier(const aID: TLegacyID; out Supplier: TLegacySupplier): TCQRSResult;
  end;

we’ll publish a service as LegacyStockQuery with a single method GetSupplier. This method will return a JSON encoded representation of our TLegacySupplier, ready to be consumed by a client.

To implement it:

type
  TLegacyStockQuery = class(TInterfacedObject, ILegacyStockQuery)
  private
    fDbConnection : TSQLDBConnectionProperties;
  public
    constructor Create( const aProps: TSQLDBConnectionProperties ); overload;
    function GetSupplier(const aID: TRevelightID; out Supplier: TLegacySupplier): TCQRSResult;

    property DbConnection : TSQLDBConnectionProperties read fDbConnection write fDbConnection;
  end;

We keep a copy of our database connection properties local to our instance to ensure thread safety.

{ TLegacyStockQuery }

constructor TLegacyStockQuery.Create(const aProps: TSQLDBConnectionProperties);
begin
  fDbConnection := aProps;
  inherited Create;
end;

function TLegacyStockQuery.GetSupplier(const aID: TLegacyID; out Supplier: TLegacySupplier): TCQRSResult;
var
  Res, Address : ISQLDBRows;
begin
  Result := cqrsNotFound;
  Res := fDbConnection.Execute( 'select * from SUPPLIERS where SUPPLIER_ID=? ', [aID] );
  if Res.Step then begin
    Result := cqrsSuccess;
    Supplier.ID := Res['SUPPLIER_ID'];
    Supplier.Name := Res['SUPPLIER_NAME'];
    Supplier.isActive := Res['ACTIVE_FLAG'] = 'Y';

    Supplier.DateCreated := Res['DATE_CREATED'];
    Supplier.DateModified := Res['DATE_MODIFIED'];
    Supplier.UserCreated := Res['USER_CREATED'];
  end;
end;

Execute the query against the legacy database and populate the DTO. Using the ISQLDBRows interface means less object maintenance and cleaner code.

To kick the whole thing off we have:

procedure StartServer( aDbURI : RawURF8 );
var
  aDbConnection : TSQLDBConnectionProperties;
  aStockServer  : TSQLRestServerFullMemory;
  aHTTPServer   : TSQLHttpServer;
begin
  aDbConnection := TSQLDBZEOSConnectionProperties.Create( aDbURI, '', '', '' );
  aStockServer  := TSQLRestServerFullMemory.Create([]);
  try
    aStockServer.ServiceDefine( TLegacyStockQuery.Create( aDbConnection ), [ILegacyStockQuery]);
    aHTTPServer := TSQLHttpServer.Create( DEFAULT_HTTP_PORT, [aStockServer] );
    try
      aHttpServer.AccessControlAllowOrigin := '*'; // allow cross-site AJAX queries
      writeln('Background server is running.'#10);
      writeln('Cross-Platform wrappers are available at ',
          DEFAULT_HTTP_PORT ,'/', DEFAULT_SERVER_ROOT );
      write('Press [Enter] to close the server.');
      readln;
    finally
      aHTTPServer.Free;
    end;
  finally
    aStockServer.Free;
  end;
end; 

It would be better to use dependency injection here, but we’ll get in to that later.

When invoking it, we used a Zeos DB URI, like this one:

zdbc:firebird-1.5://tst-db-svr-1/srv/db/EXAMPLE.FDB?username=SYSDBA;password=masterkey

Tips!

Remember to register your Object array types with

TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(<ArrayType>),<ObjType>);

e.g.

TJSONSerializer.RegisterObjArrayForJSON(TypeInfo(TLegacySupplierObjArray),TLegacySupplier);