#1 mORMot 2 » ORM limitations in MS SQL Virtual Tables with Always Encrypted Columns » 2023-09-29 09:24:41

damiand
Replies: 1

I'm posting this message to share with you my experience with ORM limitations in inserting or updating objects which are stored as virtual tables in Microsoft SQL Server and have one or more string properties encrypted using the Always Encrypted feature. Typical IRestORM Add or Update operations fail, because the corresponding parametrized queries send by SQLLite to the SQL server via ODBC, don't also set the parameters' column length, resulting in errors such as:

EOdbcException {Message:"TSqlDBOdbcStatement - TOdbcLib error: [22018] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]

Operand type clash: varchar(max) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', 
column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'InsPumps') collation_name = 'Greek_CI_AS' is incompatible with 
nvarchar(20) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', 
column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'InsPumps') (206)

[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Statement(s) could not be prepared. 

This is a known MS SQL Server limitation with a workaround given in the following stack overflow post:

https://stackoverflow.com/questions/510 … ted-column

So in my case, I defined service methods to update or add such an object, which used ADO parametrized queries with size set for all the corresponding string properties.

#2 Re: mORMot 2 » How to update TAuthGroup AccessRights? » 2023-09-15 09:21:42

Indeed @ab, you are right! Btw, the compiler is 32bit, Delphi XE.

#3 Re: mORMot 2 » How to update TAuthGroup AccessRights? » 2023-09-15 06:40:16

Well, since the connected account is Admin and no update to the AccessRights property of the AuthGroup object was made before the Rest db update action, I did a debug on the client side, and found the following:

The TOrmAccessRights record (not an object, indeed), did not preserve the GET, PUT, POST, DELETE arrays between the call of the Edit method and a call of the toString method. To testify this, the following workaround code works fine!

var
  grp: TAuthGroup;
  oar: TOrmAccessRights;
  ars: RawUTF8;
begin
  grp := TAuthGroup.CreateAndFillPrepare(MainForm.RestClient.Orm, 'ID=?', [3]);
  try
    if grp.FillOne then
    begin
      oar := grp.SqlAccessRights;
      oar.Edit(MainForm.RestClient.Model, TStatus, false, true, false, false);
      oar.Edit(MainForm.RestClient.Model, TPerson, false, true, false, false);
      FormatUtf8('%,%,%,%,%', [byte(oar.AllowRemoteExecute),
        GetBitCsv(oar.GET, MAX_TABLES), GetBitCsv(oar.POST, MAX_TABLES),
        GetBitCsv(oar.PUT, MAX_TABLES), GetBitCsv(oar.DELETE, MAX_TABLES)], ars);
      grp.AccessRights := ars;
      MainForm.RestClient.Update(grp);
    end;
  finally
    grp.Free
  end;
end;

#4 mORMot 2 » How to update TAuthGroup AccessRights? » 2023-09-14 06:40:04

damiand
Replies: 4

Hi, the following code (when running it as Admin) executes with no exceptions, but the AccessRights field is not updated from its default value:

  grp := TAuthGroup.CreateAndFillPrepare(MainForm.RestClient.Orm, 'ID=?', [3]);
  try
    if grp.FillOne then
    begin
      grp.SqlAccessRights.Edit(MainForm.RestClient.Model, TStatus,
              false, true, false, false);
      grp.SqlAccessRights.Edit(MainForm.RestClient.Model, TPerson,
              false, true, false, false);
      MainForm.RestClient.Update(grp);
    end;
  finally
    grp.Free
  end;

why?

#5 Re: mORMot 2 » TSynValidatePassWord on which property? » 2023-09-11 08:59:13

ab wrote:

TAuthUser has no PasswordPlain published property

That was exactly the meaning of my message @ab. smile The TAuthUser class has no published property with the plain password and I presume that it is meaningless to apply this validator on the PasswordHasHexa property. So, on which property can we apply this password validator? Can we create a dummy, DTO like class (not a TOrm descendant, or if it is mandatory to be a TOrm child to not include it in the model creation, so to avoid having this table in our DB), with a plain password as published property, then apply this validator on this property and if it succeeds to use this value to assign it to the PasswordPlain property of the TAuthUser class instance?

Not a quite elegant solution...

#6 mORMot 2 » TSynValidatePassWord on which property? » 2023-09-11 06:30:36

damiand
Replies: 3

On which property of the TAuthUser class can we apply the TSynValidatePassWord validator? The following:

TAuthUser.AddFilterOrValidate('PasswordPlain', TSynValidatePassWord.Create())

raises an exception:

Project InsPumpSrv.exe raised exception class EOrmException with message 'TOrmPropInfoList.IndexByNameOrExcept(PasswordPlain): unkwnown field in TAuthUser'.

#7 mORMot 2 » Getting Client inside a TOrm class? » 2023-09-01 11:07:03

damiand
Replies: 1

Hi, is there any way to get the Client (IRestORM) instance used in a TOrm class constructor, from within a method of this TOrm class?

#8 Re: mORMot 2 » CreateAndFillPrepareJoined fails when ORM table gets 2nd row » 2023-07-08 10:14:18

Thanks Thomas for one more time. smile My approach at the end was to create a service which returns the result of the joined SQL query directly from the MS SQL server, bypassing SQLite:

function TInsPump.ListDoctors: RawUTF8;
var
  st: TSqlDBOdbcStatement;
begin
  Result := '';
  st := TSqlDBOdbcStatement.Create(dbConn);
  try
    st.Prepare(StringToUtf8(c_SQL_List_Doctors), true);
    st.ExecutePreparedAndFetchAllAsJson(false, Result);    
  finally
    st.Free
  end;
end;

At the client side, a TOrmTableJson is used to get the json and feed the TOrmTableToGrid with that:

  restClient.Services['InsPump'].Get(insPump);
  table := TOrmTableJson.Create('',insPump.ListDoctors);
  table.OwnerMustFree := false;
  OrmTableToGrid := TOrmTableToGrid.Create(DrawGrid1, table, restClient);
  OrmTableToGrid.OnValueText := OnValueText;

A (final) question, because the documentation is not informant on this point: The first argument of the TOrmTableJson constructor, aSQL holds a comma separated list of column types? If yes, in what syntax, Delphi?

#9 Re: mORMot 2 » CreateAndFillPrepareJoined fails when ORM table gets 2nd row » 2023-07-07 08:12:35

Thanks Thomas. I need to feed a TOrmTableToGrid object via the FillTable method, i.e. my goal is to fill a TOrmTable object with the TDoctor instances, with the joined objects expanded. If CreateAndFillPrepareJoined method worked as I expected, then it would be very easy, just by calling the FillTable method of the TOrm object. Now I have to find an alternative approach.

#10 Re: mORMot 2 » CreateAndFillPrepareJoined fails when ORM table gets 2nd row » 2023-07-05 21:06:50

But, it is a many (TDoctor) to one (TLocArea) relation, not a m:n. So far I haven't seen in the documentation (either in 1.18 or in 2.0) that a TOrmMany base class is needed for such relationship. hmm

#11 mORMot 2 » CreateAndFillPrepareJoined fails when ORM table gets 2nd row » 2023-07-05 05:24:47

damiand
Replies: 7

I got two ORM classes joined, TDoctor and TLocArea:

TDoctor = class(TOrm)
  private
    fFirstName, fSurName: RawUTF8;
    fLocationArea: TLocArea;
    fMISId, fSAPId: TNullableUtf8Text;
  published
    property FirstName: RawUTF8 index 50 read fFirstName write fFirstName;
    property SurName: RawUTF8 index 50 read fSurName write fSurName;
    property LocationArea: TLocArea read fLocationArea write fLocationArea;
    property MISId: TNullableUtf8Text index 20 read fMISId write fMISId;
    property SAPId: TNullableUtf8Text index 20 read fSAPId write fSAPId;
end;

TLocArea = class(TOrm)
  private
    fArea, fNs: RawUTF8;
  published
    property Area: RawUTF8 index 50 read fArea write fArea;
    property Ns: RawUTF8 index 10 read fNs write fNs;
end;

The actual tables are implemented in MS SQL Server, as virtual tables through SQLite. The connection is via ODBC. In the client application the data is retrieved using the CreateAndFillPrepareJoined method.

 doctor := TDoctor.CreateAndFillPrepareJoined(restClient.Orm, '', [], []);

I am able to create some TLocArea objects and a TDoctor object, assign its properties and store it in the DB. I retrieve back the instance with CreateAndFillPrepareJoined with no problem. When I add a second TDoctor object it is persisted in the DB, however the CreateAndFillPrepareJoined returns an empty set, i.e. calling:

doctor.FillOne

returns false and no objects can be retrieved, although they are already stored in the DB tables. Activating logging in the mormot server traces the following related lines:

20230705 04572622 DB    mormot.db.sql.odbc.TSqlDBOdbcStatement(02b02e50) Prepare t=10.59ms q=select FirstName,SurName,LocationArea,MISId,SAPId,ID from dbo.Doctor
20230705 04572622 SQL   mormot.db.sql.odbc.TSqlDBOdbcStatement(02b02e50) Execute t=10.87ms q=
20230705 04572622 SQL   mormot.orm.sql.TOrmVirtualTableCursorExternal(02ab0680) Search select FirstName,SurName,LocationArea,MISId,SAPId,ID from dbo.Doctor
20230705 04572622 DB    mormot.db.sql.odbc.TSqlDBOdbcStatement(02b03030) Prepare t=59us q=select Area,Ns,ID from dbo.LocArea where ID=?
20230705 04572622 warn  vt_Filter Search()
20230705 04572622 ERROR mormot.rest.sqlite3.TRestServerDB(023d4d40) {"ESqlite3Exception(02507d38)":{Message:"Error SQLITE_ERROR (1) [Step] using 3.41.0 - SQL logic error",ErrorCode:1,SQLite3ErrorCode:"secERROR"}} for SELECT Doctor.RowID as `Doctor.RowID`,Doctor.FirstName as `Doctor.FirstName`,Doctor.SurName as `Doctor.SurName`,Doctor.MISId as `Doctor.MISId`,Doctor.SAPId as `Doctor.SAPId`,LocationArea.RowID as `LocationArea.RowID`,LocationArea.Area as `LocationArea.Area`,LocationArea.Ns as `LocationArea.Ns` FROM Doctor LEFT JOIN LocArea AS LocationArea ON Doctor.LocationArea=LocationArea.RowID //  

The profiler at the MS SQL server side traces only the first Prepare statement from the lines above.
Last, when I try to retrieve the TDoctor objects with no joins, e.g. by using the CreateAndFillPrepare method, I can get the two stored rows with no problem. Any help is very much appreciated.

#12 Re: mORMot 2 » Apply CDS updates to rest server » 2023-06-12 09:26:52

Thanks, but the problem persists. So I presume that the above commit just documents the missing code and doesn't port it into mormot2?

#14 mORMot 2 » Apply CDS updates to rest server » 2023-06-11 11:31:34

damiand
Replies: 6

I have a TClientDataSet instance created via the ToClientDataSet function of the mormot.db.rad.ui.cds unit:

  status := TStatus.CreateAndFillPrepare(restClient.Orm,'');
  dsStatus.DataSet := ToClientDataSet(Self, status.FillTable, restClient);

dsStatus is a TDataSource component linked to a DBGrid on a form. How can I apply the updates in the grid back to database? The following has no effect:

  if (dsStatus.DataSet is TClientDataSet) and dsStatus.DataSet.Active then
     TClientDataSet(dsStatus.DataSet).ApplyUpdates(0);

#15 Re: mORMot 2 » TOrmTableToGrid Hints display wrongly encoded chars » 2023-06-08 06:13:29

You're welcome, thank you @ab (and the rest of the mormot contributors) for your outstanding work! smile

#16 Re: mORMot 2 » TOrmTableToGrid Hints display wrongly encoded chars » 2023-06-06 21:06:56

I found the problem's cause in mormot.ui.core unit, in procedure DrawTextUtf8. Utf8DecodeToUnicode doesn't seem to work properly. Below is the modified version which works in my system (the original code is commented out):

procedure DrawTextUtf8(Control: TCustomControl; Canvas: TCanvas;
  const Text: RawUtf8; var Rect: TRect; CalcOnly: boolean);
var
//  tmp: TSynTempBuffer;
  s: string;
begin
//  Utf8DecodeToUnicode(Text, tmp);
  s := Utf8ToString(Text);
  DrawText(Canvas.Handle, PWideChar(s), length(s), Rect,
    DrawTextFlags(Control, CalcOnly));
//  DrawText(Canvas.Handle, tmp.buf, tmp.len shr 1, Rect,
//    DrawTextFlags(Control, CalcOnly));
//  tmp.Done;
end;

#17 mORMot 2 » TOrmTableToGrid Hints display wrongly encoded chars » 2023-06-05 16:33:47

damiand
Replies: 3

In MS Windows 10, a win32 mormot app compiled with Delphi XE. The following displays the contents of a TOrm class (TStatus) in a DrawGrid, using the TOrmTabletoGrid class (table is a local TOrmTable variable):

  if ConnectToServer then
  begin
    status := TStatus.CreateAndFillPrepare(Client.Orm, '');
    try
      table := status.FillTable;
      table.OwnerMustFree := false;
      OrmTableToGrid := TOrmTableToGrid.Create(DrawGrid1, status.FillTable, Client);
      OrmTableToGrid.OnHintText := OnHintText;
    finally
      status.Free
    end
  end

ctrl-click displays hint strings polluted with erroneous characters, like Chinese, rectangles etc. However the debug of OnHintText event handler shows that the Text output variable contains the correct string values:

function TMainForm.OnHintText(Sender: TOrmTable; FieldIndex, RowIndex: Integer;
  var Text: string): boolean;
begin
  Text := (Sender as TOrmTable).GetString(RowIndex,FieldIndex);
  Result := true
end;

Any help is welcome.

#18 mORMot 2 » NullableDateTimeToValue doesn't work in ISO8601 variant strings » 2023-05-29 15:19:41

damiand
Replies: 1

NullableDateTimeToValue fails to get the value of TNullableDateTime properties stored in the DB as ISO8601 strings, because the VariantToDouble internal call fails to convert the stored value into double.

A workaround is to use the functions NullableUtf8TextToValue in conjunction with the Iso8601ToDateTime, to convert the stored value into TDatetime.

#20 mORMot 2 » Calculated fields are actually created as table fields in the DB? » 2023-05-22 12:01:22

damiand
Replies: 2

Hi

I've noticed that calculated fields in ORM classes created as read-only published properties, are actually created as DB tables. For example in this class:

TPerson = class(TOrm)
  private
    fFirstName, fSurName: RawUTF8;
  protected
    function GetFullName: RawUTF8;
  published
    property FirstName: RawUTF8 index 50 read fFirstName write fFirstName;
    property SurName: RawUTF8 index 50 read fSurName write fSurName;
    property FullName: RawUTF8 read GetFullName;
end;

registered as a virtual MS-SQL Server table, ORM actually creates a field named FullName in the database table. Is there any purpose on this? In case I don't want a calculated field to be actually created as a db table field, do I have to use a public calculation function instead (e.g. GetFullName), and not a published property?

#21 mORMot 2 » ODBC Function sequence error » 2023-05-01 18:37:56

damiand
Replies: 6

Hi,

I have a case where all model tables have to be created as virtual and reside to a Microsoft SQL Server. Unfortunately this has to be done via ODBC, because in some columns I have to enable the Always Encrypted feature.

So far I have a simple model with only TAuthUser and TAuthGroup defined. These have been registered with VirtualTableExternalRegister and with the corresponding  TSqlDBOdbcConnectionProperties props object. The ODBC data source uses the version 17 of the Microsoft SQL Server ODBC driver. The RestServerDB is created as an in memory instance (with ':memory:').

The call of CreateMissingTables method raises the following non-blocking exception for each of the created tables (e.g. two times):

exception class EOdbcException with message 'TSqlDBOdbcStatement - TOdbcLib error: [HY010] [Microsoft][ODBC Driver Manager] Function sequence error (0)

Though it is a non-blocking exception, I post it here in case it might affect other SQL operations by the ORM classes.

#22 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-12 11:38:46

@ab

I pulled the changes from the repository, built and run again the program. Now all tests pass successfully! wink

#23 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-12 09:49:06

I confirm that this is a locale related problem. My current locale setting is el_GR.UTF-8. When I run the mormot2tests program inside a container (via DDEV-Local) which by default, has locale C.UTF-8 all tests are passed successfully.

Is it possible to have a locale agnostic static version of quickjs?

#24 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-11 07:07:45

@ab

damian@ifestos:~/mORMot2/static/x86_64-linux> ll quickjs.o
-rw-r--r-- 1 damian users 1072632 Απρ   2 22:24 quickjs.o

My CPU is Intel Core i5-4460 @ 3.20GHz

#25 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-10 09:46:28

Thanks a lot @ttomas for the hint. With this option I can debug the tests project. The failed test is at line 166 of test.core.script unit:

        CheckEqual(Run(
          'add(434.732,343.045)'), '777.777');

The evaluation of the javascript expression gives the following result:

<JSVALUE> = { U = { I32 = 777,  TAG = 65536,  F64 = 1.3906711615708398e-309,  PTR = $1000000000309,  U64 = 281474976711433}}

which is apparently integer and the equality check with the float value '777.777' fails.

#26 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-09 11:57:37

With the debugging options defined in the Lazarus project, I can't debug it. I get the following GDB error:

The GDB command:
"-break-insert +0"
did not return any result.

I changed the build mode to lin64, rebuilt and run the project again. Now all tests are passed, except the 1000 quickjs related errors. All these are raised in section  3.1 (SOA/Core script).

I changed the debugging options but I'm still getting the same GDB error.

Lazarus (2.0.11)/FPC (3.2.0) installation is made via fpcupdeluxe.

UPDATE: I created another Lazarus project besides the mormot2tests and tried to re edit it from scratch. I kept the program body minimum (only a writeln statement) and I added progressively the uses section. Debugging was available and OK, till I added the test.core.base unit.

#27 Re: mORMot 1 » mORMot 2 in Good Shape » 2021-07-08 08:58:57

Hi

I cloned the mORMot2 repository, downloaded the static files with SQLite 3.35.5, compiled and run the regression tests, using FPC 3.2.0 on OpenSUSE Linux. Unfortunately, some tests failed:

/home/damian/mORMot2/test/fpc/bin/x86_64-linux/mormot2tests 0.0.0.0 (2021-07-07 16:36:50)
Host=XXXX User=damian CPU=4xIntel(R)Core(TM)i5-4460CPU@3.20GHz(x64):FFFBEBBFBFFBFA7FAB270000000000000006009C OS=Suse=Linux-5.3.18-lp152.78-default#1-SMP-Tue-Jun-1-14:53:21-UTC-2021-(556d823) Wow64=0 Freq=1000000
TSynLog 2.0.1 2021-07-07T13:38:23

0000000000000001  ! fail  #5 ../../src/core/mormot.core.test.pas tsyntestcase.testfailed (944) ../../src/core/mormot.core.test.pas tsyntests.run (1171) mormot2tests.dpr main (96) 
00000000000156FF  ! fail  #7 ../../src/core/mormot.core.test.pas tsyntestcase.testfailed (944) ../../src/core/mormot.core.test.pas tsyntests.run (1171) mormot2tests.dpr main (96) 
0000000000016CF9  ! fail  #9 ../../src/core/mormot.core.test.pas tsyntestcase.testfailed (944) ../../src/core/mormot.core.test.pas tsyntests.run (1171) mormot2tests.dpr main (96) 
00000000027A9CFE  ! fail  #6046 777<>777.777 ../../src/core/mormot.core.test.pas tsyntestcase.testfailed (944) test.core.script.pas ttestcorescript.quickjslowlevel (170) ../../src/core/mormot.core.test.pas tsyntests.run (1171) mormot2tests.dpr main (96) 
... (the error above is repeated 1000 times up to #40012)

Any hints?

#28 mORMot 1 » TSynDaemon.Stop is called twice » 2021-04-05 18:42:15

damiand
Replies: 1

I've noticed when running a TSynDaemon program with --console parameter on Linux that the method Stop is called twice when I press  [Enter] to quit. This raised an AV Exception in method implementation as the following:

procedure TBareDaemon.Stop;
begin
    fHttpServer.Free;
    fServer.Free;
end;

and I had to rewrite it as follows:

procedure TBareDaemon.Stop;
begin
  if fHttpServer <> nil then
  begin
    fHttpServer.Free;
    fHttpServer := nil
  end;
  if fServer <> nil then
  begin
    fServer.Free;
    fServer := nil
  end;
end;

I got this AV Exception also in sample "37 - FishShop Service", for the same reason.

#29 Re: mORMot 1 » Front desk method based service class with jwt authentication » 2021-04-04 11:39:32

OK I see. However, I think that the token's renewal in the context of a valid request when it is close to expire is simpler (it acts also as an implicit renewal request), with no need to implement a separate renewal action from the client's side or worse, to put this action inside polling (e.g. like window.setInterval etc.), which is more error-prone  and risks mishandling of idle user. The only consequence is a need for a blacklist (also recommended as a good practice by owasp), which is covered fine by a TSynDictonary property.

#30 Re: mORMot 1 » Front desk method based service class with jwt authentication » 2021-04-02 10:11:27

@ab, I don't put the JWT in the URI. The Login request uses POST method and the token is returned as the last item of the Ctxt.Results array, in the context of the POST response.

if bSuccess then
     Ctxt.Results(['User ' + jData.username + ' logged in successfully', token])
  else Ctxt.Error('Incorrect username or password', HTTP_UNAUTHORIZED);

At the client's side the token is stored in a global js variable or in the sessionStorage and it is included in the requests' header (as a Bearer).

Concerning the expiration, I think that as long as the client is active (e.g. sends requests to the server), it will be annoying to prompt for a login page. If the client is idle, then it is reasonable ask for a new login, since the token is expired.

#31 Re: mORMot 1 » Front desk method based service class with jwt authentication » 2021-04-01 18:55:59

ab wrote:

You don't have a JWT any more, but a "classical" session token.

I could understand this argument if the token is actually stored in the cookie, but this is not the case here. The hardened cookie is used just to create a path outside javascript scope, so the server can cross check not only the token's signature but also the client's origin (sender). That is I think the reasoning of the OWASP recommendation.

ab wrote:

On the client side, you may safely store the credentials (user/password) in memory using our CryptDataForCurrentUser() function, to avoid prompting the end-user.

This is suitable for a Delphi client, but in this case I'm talking about a javascript client (e.g. a single page js application). As far as the service receives valid requests I see no reasons to leave the token expire and prompt the web user to enter again the credentials, moreover since the timeout is kept quite short (15 minutes).

ab wrote:

There is a SessionID session in mORMot. Just store the session ID within the JWT, and compare it with the token. When the client closes its session, the SessionID will be rejected.

The web client's session is between the js web app and the front desk in memory service which by default doesn't handle user's authentication (aHandleUserAuthentication is false). So I presume that any SessionID will be between the front desk service and another mORMot service which maintains the user credentials. It doesn't seem very useful to me to pass this SessionID to the web client.

Thanks @ab for your remarks.

#32 mORMot 1 » Front desk method based service class with jwt authentication » 2021-04-01 12:06:48

damiand
Replies: 8

Following the advice of ab to not expose a TSQLModel in a web service intended to publish a web api to a javascript application, I created a simple method based class to handle user authentication, using an abstract username/password check and jwt afterwards. This class is intended to be used as a base class to descent a front desk method based service to implement the actual web api. You can find the source code below:

https://gist.github.com/dadim/4443f9062 … 24a70d2077

The class TFronDeskMethodService descents from TSQLRestServerFullMemory. It has two published methods, Login and Logout. The first expects a POST method request with a json with two properties, username and password (plain). Login uses an abstract method, BackendLogin, which checks the user's credentials against a back end service (e.g. another mORMot service, an external provider etc.). If it is successful, computes and sends to the web client a jwt token as the last item of the results array. The secret of all computed tokens is a random 32 bytes long RawBytesString, created in every instance of this class.

To prevent sidejacking, I followed OWASP's recommendation to include in the token (as subject), a random string for each authenticated user. This random string is also sent to the user as a hardened cookie. The class uses also a TSynDictionary to store blacklisted token IDs, whose entries timeout after the elapsing of fJWTMinutesValid (by default 15 min).

Every other api method has to check the request in a similar way as the logout method does, e.g. by a combination of AuthenticationCheck, blacklist dictionary lookup and subject check. The token is renewed by CheckSubject method if it is called within the last 33% of the fJWTMinutesValid time.

You are welcomed to use, modify, distribute it or at least post comments or suggestions. wink

#34 mORMot 1 » POST requests in method based services? » 2021-03-28 16:15:27

damiand
Replies: 2

As far as I've read in the documentation, post method requests are not supported in method based services. Am I correct?

#35 Re: mORMot 1 » Rest delete on AuthUser delays to respond » 2021-02-26 07:16:16

ab wrote:

I usually publish the services using a TSQLRestServerFullMemory instance with nothing but the SOA interfaces published in it - i.e. a void model with no TSQLRecord in it.

How do you handle user authentication/authorization at this level?

#37 Re: mORMot 1 » Rest delete on AuthUser delays to respond » 2021-02-25 17:53:19

mpv wrote:

@damiand - try to replace in THttpSocket.GetBody
...
Does it help?

mpv I followed your advice and in line  6897 of SynCrtSock I made the following addition after the else:

  if ContentLength>0 then begin
...
  end else ContentLength := 0;
  if ContentLength<0 then begin // ContentLength=-1 if no Content-Length
...
  end;

And the problem solved! smile Do you think ab that this change should be applied also in the git repository? Many thanks.

#38 Re: mORMot 1 » Rest delete on AuthUser delays to respond » 2021-02-25 14:05:25

ab wrote:

Please try locally, directly on the mORMot server.

I've already tried it - as I wrote in my previous post - and there was no delay, that's why I concluded that the problem must be in the middle.

ab wrote:

BTW you should better not publish the ORM remote REST access to the clients...

How can I prevent this publishing? I did nothing about it, that's the default way of operation. As far as I tested it - and as written in the documentation - only the Admin and Supervisor roles have access to these tables.

Thanks, ab. smile

#39 Re: mORMot 1 » Rest delete on AuthUser delays to respond » 2021-02-25 12:05:10

My latest mORMot commit applied is 60aeaba63d770dcad2841aa0fe9e234d1982b51a from Feb 8 if I get it correctly (I'm not experienced with git). The whole installation is on OpenSUSE Linux Leap 15.2. The server is compiled with FPC 3.2.0. There is a reverse proxy indeed, because the client is an HTML page with jQuery, served by nginx hosted in a docker container, configured by ddev. The nginx is configured with TLS/SSL and as a reverse proxy to the mORMot server, located outside the container in the main operating system.

There is a similar behavior with Firefox and Chrome. A plain GET request to timestamp as you suggested takes 1ms to complete. Other CRU db actions to AuthUser using REST are performed rapidly. When I bypass the web server/docker container and load the html page from the filesystem, the delete is performed with no delay, provided that I have included also in the mORMot server compilation the  AccessControlAllowOrigin := '*' setting. The latter is neither necessary in the previous (web hosted) setup, nor affects the delete performance. In conclusion, the delay resides in-between in the nginx reverse proxy, but I can't find out the reason yet. hmm

#40 mORMot 1 » Rest delete on AuthUser delays to respond » 2021-02-24 15:45:01

damiand
Replies: 16

When I send (as an Admin user) from a web application delete ajax requests to AuthUser the response is very slow, about 10 secs in a local development  environment.  The debug log shows no signs of execution delay:

20210224 13241602  #  +    mORMotSQLite3.TSQLRestServerDB(7ffff7f99590).URI DELETE root/AuthUser/11?session_signature=021c47f800d436046250611b in=0 B
20210224 13241602  # auth      mORMot.TSQLRestRoutingREST(7ffff4c473d0) Admin/35407864 172.18.0.3
20210224 13241602  # cache     SynSQLite3.TSQLDatabase(7ffff7fee220) bareserver.db3 cache flushed
20210224 13241602  # DB        mORMotSQLite3.TSQLRestServerDB(7ffff7f99590) prepared 22us bareserver.db3 DELETE FROM AuthUser WHERE RowID=?;
20210224 13241602  # SQL       mORMotSQLite3.TSQLRestServerDB(7ffff7f99590) 6.09ms  DELETE FROM AuthUser WHERE RowID=:(11):;
20210224 13241602  # srvr      mORMotSQLite3.TSQLRestServerDB(7ffff7f99590) Admin 172.18.0.3 DELETE root/AuthUser Write=200 out=0 B in 6.16ms
20210224 13241602  #  -    00.006.186

But at the browser's side, when inspecting the network debugging info, at the Timings tab there is a 10.06 sec waiting time for the DELETE request to get its HTTP_OK (200) response code. Is this an on purpose security measure by mORMot or not?

#41 Re: mORMot 1 » Ajax friendly error messages in interface based services » 2021-02-18 09:53:19

Thanks a lot ab you are very clear in functional terms, but my problem is how to make the system interpret the enumerated code returned by the interface method to respond with an HTTP_BADREQUEST, together with a json with ErrorCode, ErrorText properties. By just returning the second enumerated value the system responds with an HTTP_OK and the web client interprets this as a normal execution.

My (seemly unorthodox) solution is the following:

1. Inherit the service class from an TInterfacedObjectWithCustomCreate and add a method of TNotifyErrorURI type, where I catch the EServiceExcepton and call Ctx.Error accordingly:

function TBareService.NotifyErrorURI(Ctxt: TSQLRestServerURIContext;
  E: Exception): boolean;
begin
  if E is EServiceException then
  begin
    Ctxt.Error(E.Message);
    Result := false
  end
  else Result := true
end;

2. In the overridden constructor I linked the sever's OnErrorURI event to the aforementioned method:

  inherited Create;
  aServer.OnErrorURI := @NotifyErrorURI;

3. Raise inside the service method an EServiceException according to my error checking logic with a descriptive message to the client.

I would clearly prefer the return of enumerated values but obviously I missed the way to do it, as I explained in my first paragraph.

Ps. This topic is similar to the following old forum topic: https://synopse.info/forum/viewtopic.php?id=4293

#42 Re: mORMot 1 » Ajax friendly error messages in interface based services » 2021-02-17 15:09:07

OK thanks, but should I use TCQRSQueryObject and ICQRSService whenever I want a proper error handling? If I have a simple TInterfacedObject descendant service, implementing an IInvokable descendant interface what should I use inside a service method to pass the error to the web client?

#43 mORMot 1 » Ajax friendly error messages in interface based services » 2021-02-17 10:20:22

damiand
Replies: 6

As far as I know, by my testings and by reading also the following reply of ab in a previous forum topic:

https://synopse.info/forum/viewtopic.php?pid=9445#p9445

error messages in interface bases services are returning to clients using exceptions. I've noticed however that this is returned to a web client not in the usual json format with ErrorCode, ErrorText properties. Instead the repsonseJSON is an object, with its textual representation given by another property, responseText as follows:

responseText: "{\r\n\"errorCode\":500,\r\n\"error\":\r\n{\"EServiceException\":{\r\n\t\"ClassName\":\"EServiceException\",\r\n\t\"Address\":\"  $0000000000690839  CHANGEPASSWORD,  line 36 of bareservice.pas\",\r\n\t\"Message\": \"My error message\"\r\n}}\r\n}"

Is there any way to handle errors in interface based service implementations, so to be sent back to web browsers as ErrorCode,ErrorText jsons with less severe error codes than 500?

#44 Re: mORMot 1 » Memcached Boilerplate HTTP Server for Synopse mORMot Framework » 2020-12-21 13:58:30

Eugene Ilyin wrote:

Please let me know if the issue is gone or still exist on the latest mORMot revision when you add this line to the end of your source code.

Eugene with your modification the issue is gone, thanks a lot! smile Thanks also macfly for your kind suggestion.

#45 Re: mORMot 1 » Memcached Boilerplate HTTP Server for Synopse mORMot Framework » 2020-12-17 09:03:38

I've downloaded release 2.4 (thanks Eugene smile ) and built and run the Demo in Linux x64 with FPC  3.2.0 / Lazarus 2.0.9. When the application is shutting down (after pressing the Enter key), it raises an access violation exception at line 982 of unit mORMotHttpServer, when it attempts to execute

fDBServers[0].Server.EndCurrentThread(Sender);

This happens also in Windows 10 64 bit with FPC/Lazarus but not with Delphi 10.3.

It might have something to do with the usage of IAutoFree, because using a classic try/finally approach doesn't raise any exception. hmm

#46 Re: mORMot 1 » Error 400 in a bare, hello world MVC project » 2020-12-15 12:14:21

damiand wrote:

Start calls only the inherited method...

I missed one more call after the inherited Start method. This call in its simplest form (without any cache) is:

fMainRunner := TMVCRunOnRestServer.Create(Self);

#47 mORMot 1 » Error 400 in a bare, hello world MVC project » 2020-12-11 15:34:36

damiand
Replies: 1

I tried to test a bare, hello world MVC project in FPC/linux. I created an interface, IBareApplication descendant of IMVCApplication with no extra methods and a class, TBareApplication, descendant of TMVCApplication which implements the IBareApplication. This class has only two methods Start and Default. Start calls only the inherited method and Default is empty. Then I created the Views folder with Default and Error html files, the former contains the hello world message and the latter is a modified copy from the Sample 30.

The main file creates an empty model, an in memory Rest Server and the above application:

  aModel := TSQLModel.Create([],'test');
  try
    aServer := TSQLRestServerFullMemory.Create(aModel);
    try
      aApplication := TBareApplication.Create;
      try
        aApplication.Start(aServer);
        aHTTPServer := TSQLHttpServer.Create('8092',aServer);
        try
          aHTTPServer.RootRedirectToURI('test/default');
          aServer.RootRedirectGet := 'test/default';
          writeln('Running, press ENTER to exit');
          readln;
        finally
...

When I try to access localhost:8092/ it redirects to http://localhost:8092/test/default as expected, with a Json response contains an Error 400/Bad request message. What am I missing here? hmm

#48 Re: mORMot 1 » Infinity and beyond » 2020-11-05 09:13:52

I'm wandering if it would be possible to adapt the cross platform units (SynCrossPlatformSpecific, SynCrossPlatformREST, SynCrossPlatformCrypto) to be compatible also to pas2js. Ab do you have any hint / suggestion on what directive section to use as a basis, maybe the ISDWS? HASINLINE has to be included also?

#49 Re: mORMot 1 » Cross platform SMS sample - GetClient with generic server address » 2020-10-22 08:15:27

I found a solution by passing window.location.hostname. Sorry for my rather silly question. roll

#50 mORMot 1 » Cross platform SMS sample - GetClient with generic server address » 2020-10-22 08:01:59

damiand
Replies: 1

I modified slightly the Sample 27 - SMS cross platform client to use it in a reverse proxy nginx setup. It works only when the server address parameter of the GetClient method contains a specific address (either 127.0.0.1 or my local ip). Is there any way to pass a generic value to use the host ip address, whatever it is? I tried both the empty string and '/' without success.

By the way in the Smart Mobile Studio project, the assignment of Client to nil and the if check at the first line of the TForm1.BtnConnectClick event handler, doesn't seem to work:

  if Client=nil then
     ...  
  else begin
    ...
    Client.Free;
    Client := nil;
  end;

A second press on the 'Server Connect' button produces nothing because the Client object hasn't become nil. A raw and clumsy solution like the following, does the work properly:

  if BtnConnect.Caption='Server Connect' then
        ...
        BtnConnect.Caption := 'Disconnect';
        ...
  else begin
        ...
    BtnConnect.Caption := 'Server Connect';
        ...
  end;

Board footer

Powered by FluxBB