Delphi Datasnap client code not getting unauthorized exception - delphi

I'm using Delphi 10.1 Berlin Update 2 Enterprise and the DataSnap client/server REST framework.
If I run the app without debugging and invoke a method the user isn't authorized to invoke, the code runs without any exception and the method returns a null response.
When interactively debugging a call on the client to a DataSnap server method, I get two popup exceptions regarding "unauthorized".
The first bubbles up and is replaced by the second.
The second exception gets "eaten" and the session/connection simply closed and then the method returns a blank result (e.g. a zero if the return type is integer, and an empty string for a string return type).
This is happening in the following section of code near the end of the ExecuteRequest method in the Datasnap.DSClientRest unit:
except
on E: TDSRestProtocolException do
LSessionExpired;
end;
Why are these exceptions (e.g. TDSRestProtocolException) not reaching my code?
I kind of think this is new to Update 2, and I remember seeing those exceptions bubble up to my code prior to Update 2.
Attached is a skeleton example (standard example generated by Delphi wizards) that demonstrates the issue - click the button and you get "" instead of "4321" because the user isn't authorized - but no runtime exception.
I'm new to DataSnap, so bear with me :-)
Thanks in advance for helpful responses =)

This is happening due to DSAuthenticationManager1 component added to webmodule of the server and client side is failing to authenticate.
Please go through this to check how to work with authentication
Adding Authentication and Authorization

Well..I'm not sure but try providing username and password to DSRestConnection1 component before the instance of server methods gets created
procedure TClientModule1.TestCon(aUsername, aPassword: string);
var
lServerMethodsClient : TServerMethodsClient;
begin
DSRestConnection1.UserName := aUsername;
DSRestConnection1.Password := aPassword;
lServerMethodsClient:=TServerMethodsClient.Create(DSRestConnection1);
end;
and try to call this functn from ur clientform
procedure TF_ClientForm.Button1Click(Sender: TObject);
begin
ClientModule1.TestCon(EdtUsername.Text, EdtPassword.Text);
end;

Maybe a little late but this morning I've had a deep dive into this because, after upgrading from Delphi XE6 to Tokyo 10.2, applications where I used the TDSRestConnection component got broken. Although I supplied the correct username and password, they did not appear in the TDSAuthenticationManager.OnUserAuthenticate event.
The 'problem' has to do with the new System.Net.HttpClient implementation.
To make a long story short (or a little bit less long):
The client component does not send the credentials until the receiving server demands one by sending a 401 response. After receiving this (properly formatted) response the client looks at de TDSConnection credentials en tries again. At the client side a complete list of urls with credential requirements is maintaned so repetitive calls to the same url go 'smoother'.
I added this code to the server's WebModule (where the TDSRESTWebDispatcher resides) which solved my problems:
procedure TwbmMain.WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
LAuthorization: string;
begin
inherited;
if Request.PathInfo.StartsWith('/datasnap/') then
begin
LAuthorization := TNetEncoding.Base64.Decode(Request.Authorization.Replace('Basic ', ''));
if LAuthorization.IsEmpty then
begin
Response.StatusCode := 401;
Response.WWWAuthenticate := 'Basic';
Handled := True;
end;
end;
end;
Because my applications provides some downloadable items like a logo etc., I limited the check to just those URLs that have anything to do with datasnap.
Hope this is useful to others!

Related

Delphi - mORMot Can not access data through client using full memory model and TSQLRestClientURI

I've been trying to start a new project using mORMOt the DDD-way and have created a few classes and begun to test one of them in an easy/simple way.
I used the regression test code from your DDD-sample about TUser and modified it to suit my class .
I have tried to minimize the code and hopefully, it could contain some clues for you to help me understand what's wrong here.
I found that when only using the server, everything works ok but when using client the ORMselection won't find the data.
I stripped down the code as much as possible and marked with some comments where it works and where it not works.
class procedure TInfraRepoPackageFactory.RegressionTestsPackage(test: TSynTestCase);
procedure TestOne(Rest: TSQLRest);
var cmd: IDomPackageCommand;
qry: IDomPackageQuery;
package: TPackage;
begin
test.Check(Rest.Services.Resolve(IDomPackageCommand,cmd));
package := TPackage.Create;
try
package.articleNo := 10000;
test.check(cmd.Add(package)=cqrsSuccess);
end;
test.check(cmd.Commit=cqrsSuccess);
finally
package.Free;
end;
package := TPackage.Create;
try
test.Check(Rest.Services.Resolve(IDompackageQuery,qry));
test.Check(qry.SelectByArticleNo(10000,false)=cqrsSuccess); // <<-- Debugging shows that it will not find anything when using client.
test.Check(qry.GetCount=1); // <<-- getCount returns zero when using client.
end;
finally
package.Free;
end;
end;
var RestServer: TSQLRestServerFullMemory;
RestClient: TSQLRestClientURI;
begin
RestServer := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordPackage]);
try // first try directly on server side
RestServer.ServiceContainer.InjectResolver([TInfraRepoPackageFactory.Create(RestServer)],true);
TestOne(RestServer); // sub function will ensure that all I*Command are released // <<=== Works
finally
RestServer.Free;
end;
RestServer := TSQLRestServerFullMemory.CreateWithOwnModel([TSQLRecordPackage]);
try // then try from a client-server process
RestServer.ServiceContainer.InjectResolver([TInfraRepoPackageFactory.Create(RestServer)],true);
RestServer.ServiceDefine(TInfraRepoPackage,[IDomPackageCommand,IDomPackageQuery],sicClientDriven);
test.Check(RestServer.ExportServer);
RestClient := TSQLRestClientURIDll.Create(TSQLModel.Create([TSQLRecordPackage]),#URIRequest);
try
RestClient.Model.Owner := RestClient;
RestClient.ServiceDefine([IDomPackageCommand],sicClientDriven);
TestOne(RestServer); // <<=== Works
RestServer.DropDatabase;
USEFASTMM4ALLOC := true; // for slightly faster process
TestOne(RestClient); // <<=== DO NOT Work !!!!
finally
RestClient.Free;
end;
finally
RestServer.Free;
end;
end;
I also tried to put this question on the mORMot forum but the mailer can not reach the site.
Got this message :
An error was encountered
Error: Unable to send email. Please contact the forum administrator with the following error message reported by the SMTP server: "450 4.1.2: Recipient address rejected: Domain not found ".
I finally found what's wrong.
In the agregate, class TPackage, I had set the property of packageNo to "stored AS_UNIQUE" - that resulted in that the commit just stored zero in that field and then the SELECT('packageNo=?,[10001]) couldn't find anything.
I didn't realize that because the package object contained just 10001 and could never think of the possibility that the commit should store a 0.
But when I tested with TSQLHttpServer and TSQLHttpClient and a real database I could see all records containing zeroes in the packageNo field.
Then I understood that it must be something with this field that's wrong. When I looked up TPackage I found my mistake.
I should have set the "STORED AS_UNIQUE" in the TSQLRecordPackage class instead, the one that's used by ORM.
The moral of the story... "OPEN YOUR EYES.. and you will see" ;-)

Delphi 10 Seattle Datasnap error: "Operation failed. Connection was closed."

I created a stand-alone Datasnap TCP/IP server using the Wizard. I selected sample methods (echostring and reversestring). I saved the server and ran it. Then I created a client application, and using the file-new-other, added a ClientModule to that client project, along with the ClientClasses unit. On the main form. I added a button. On the button's onclick event handler, I added the following code:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
The purpose here is to simulate a situation where the client is logging into and logging out of the server regularly rather than keeping a connection. This is especially important on apps deployed to mobile.
You can see I commented out the Connection.Open, because the first call to the ServerMethods1client opens the connection. The generated code is shown here:
function TClientModule1.GetServerMethods1Client: TServerMethods1Client;
begin
if FServerMethods1Client = nil then
begin
SQLConnection1.Open;
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, FInstanceOwner);
end;
Result := FServerMethods1Client;
end;
Now the problem arises. On first click to the button, the connection is opened, and the method is called. On the second click to the button, the connection is closed.
On the 3rd click, an exception is raised "Operation Failed. Connection was Closed" is raised from with the TDBXCommand code.
As a workaround, I tried this:
procedure TForm1.Button1Click(Sender: TObject);
begin
if ClientModule1.SQLConnection1.Connected then
begin
Button1.Text := 'Open';
ClientModule1.SQLConnection1.Close;
ClientModule1.ServerMethods1Client := nil;
end
else
begin
Button1.Text := 'Close';
// ClientModule1.SQLConnection1.Open;
ClientModule1.ServerMethods1Client.ReverseString('myteststring');
end;
end;
This does sort-of solve the problem, since the ClientModule1's FServerMethods1Client instance is reset so the create code runs again like it did on the first run.
The only other problem now, is (I am using Eurekalog) it creates a memory leak.
What am I doing wrong? What's the right way to connected/disconnect from a Datasnap server repeatedly without restarting the app?
The reason for the first error is that the code that binds the client side proxy (which allows server methods to be called) is tied to the local SQL connection. Note the call to create the proxy class:
FServerMethods1Client := TServerMethods1Client.Create(SQLConnection1.DBXConnection, ...)
The underlying DBExpress connection is passed by reference, and the proxy class uses that connection to call the server. You closed and re-opened the connection, but the underlying DBExpress connection that ServerMethodsClient1 was using has been destroyed. Thus, you receive the "Connection was closed" exception. The connection that ServerMethodsClient1 was using has been closed. You have to recreate ServerMethodsClient1 as you did in your second example.
I can't answer your second question, as I believe it is ARC specific. For a VCL DataSnap app, I would call ServerMethodsClient1.Free rather than setting it to nil. Based on my very, very limited understanding of Delphi's ARC implementation (which is all from the newsgroups), I believe you should call ServerMethodsClient1.DisposeOf, since the class descends from TComponent
But I'm not sure about that. I'm sure someone will jump on here that understands ARC and the proper solution to destroy the object rather than having a memory leak.
In my Android FMX implementation, I only call servermethods to get stuff done. (ie I don't use Datasnap data components). There's too much uncontrolled data transmission overhead to the Datasnap architecture to contemplate anything else realistically on a mobile device... To get around it (and not have memory leaks), I now create local instances of the TServermethods1Client as and when I need them and free them in context:
function TClientModule1.PostTheLog: Boolean;
var
Server: TServerMethods1Client;
begin
Server := TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
try
UserID := Server.GetUserID;
...
finally
Server.Free;
end;
end;
Now the ClientModule1.SQLConnection1 can be connected and disconnected at will (preferably connected just before any call to a servermethod, and disconnected thereafter) and no further issues arise.
Which then begs the question: In which ideal world would the publicly accessible ServerMethods1Client actually be useful?

Fault in Ole Automation Server

I set up an OLE Automation Server and an OLE Client in Delphi XE2. My two Methods are the following:
function TMyCom2.Get_Text() : IStrings;
begin
GetOleStrings(unit1.Form1.Memo1.Lines, Result);
end;
and:
procedure TMyCom2.Set_Text(const value: IStrings);
begin
SetOleStrings(unit1.Form1.Memo1.Lines, value);
end;
Now I tried to call both by the client.
The second Method(Set_Text) was working perfectly fine.
But the first one(Get_Text) wich should gather the content of the memo of the server and write it into the memo of the client, caused this exception:
Exception error of the server!
To get the Ole information I wrote this on the client side:
procedure TForm1.Button1Click(Sender: TObject);
var
aStrings : IStrings;
begin
aStrings.Add(Server.Get_Text);
SetOleStrings(Memo1.Lines, aStrings);
end;
I have no idea what could be wrong and I was incredibly grateful if somebody could take a look at this code and tell me what the hell is going wrong;D
PS: I already tested it with Integers and it worked so the Problem has to do something with Strings
I uploaded the two projects on Dropbox so feel free to download them:
Client: here
Server: here

Why won't TIdIRC connect to channel? Is there a better component?

I've been struggling with the crap documentation of Google and can't get the program to join the channel even though it connects to the server fine. (It says Connected to server)
//On Form Make
procedure TForm2.FormCreate(Sender: TObject);
begin
IdIRC1.Connect();
end;
//on connected
procedure TForm2.IdIRC1Connected(Sender: TObject);
begin
ShowMessage('Connected to server');
IdIRC1.Join('#TheChannel', 'password');
end;
Once i close the form an error comes up saying:
Project raised exception class EIdException with message 'Not Connected'
Also once connected what functions do i use to talk on the channel/check input?
And what other IRC connection options (components) are there for Delphi applications?
ANY HELP WOULD BE APPRECIATED, GOOGLE HAS NOTHING ON THIS REALLY.
All i want is to be able to connect/check channel chat messages & talk on the chat; Simple String IO through IRC...
Guess you are not filling all the server requirements. Just IdIrc.Connect isn't enought, this works for me:
FIRC.Host:= 'irc.freenode.org';
FIRC.Port := 6667;
FIRC.Username:= 'SapoIndy';
FIRC.Nickname:= 'SapoIndy';
FIRC.RealName:= 'Fabio Gomes';
FIRC.Connect;
FIRC.Join('#TheChannel');
To figure out what is going on, you need to get the output of some events, I had implemented these:
FIRC.OnStatus:= #Status;
FIRC.OnNotice:= #Notice;
FIRC.OnMOTD:= #MOTD;
Get some events up and you should figure out what the server is telling you, don't go with trial and error.
And about sending and receiving messages, I've implemented some of this some time ago, here is the project, it was made using Lazarus.
https://bitbucket.org/fabioxgn/irc/src/b510d73e553d/main.pas
Do not call Join() in the OnConnected event. That event simply means the underlying socket connection is established. Connect() is still running, and has not actually logged into the IRC server yet when the OnConnected event is triggered. Wait until Connect() exits before issuing any commands:
procedure TForm2.FormCreate(Sender: TObject);
begin
IdIRC1.Connect;
ShowMessage('Connected to server');
IdIRC1.Join('#TheChannel', 'password');
end;

Need a sample/demo of using TIdTelnet to interact with telnet server

I tried to employ Indy 10.5.5 (shipped with Delphi 2010) for:
connecting to telnet server
performing username/password authentication (gaining access to the command shell)
executing a command with returning resulting data back to application
and had no success, additionally i'm completely lost in spaghetti logic of Indy's internals and now have no idea why it didnt work or how i supposed to send strings to the server and grab the results. Need some sample code to study.
Formal form of the question: Where can i get 3-rd party contributed demo covering TIdTelnet component? (indyproject.org demos webpage do not have one)
The main problem with Telnet is that it DOES NOT utilize a command/response model like most other Internet protocols do. Either party can send data at any time, and each direction of data is independant from the other direction. This is reflected in TIdTelnet by the fact that it runs an internal reading thread to receive data. Because of this, you cannot simply connect, send a command, and wait for a response in a single block of code like you can with other Indy components. You have to write the command, then wait for the OnDataAvailable event to fire, and then parse the data to determine what it actually is (and be prepared to handle situations where partial data may be received, since that is just how TCP/IP works).
If you are connecting to a server that actually implements a command/response model, then you are better off using TIdTCPClient directly instead of TIdTelnet (and then implement any Telnet sequence decoding manually if the server really is using Telnet, which is rare nowadays but not impossible). For Indy 11, we might refactor TIdTelnet's logic to support a non-threaded version, but that is undecided yet.
done with indy.
no comments.. just som old code :-)
telnet don't like the send string kommand.. use sendch.
telnetdude.Host := 1.1.1.1;
try
telnetdude.connect;
except
on E: Exception do begin
E.CleanupInstance;
end; {except}
if telnetdude.Connected then begin
for i := 1 to length(StringToSend) do telnetdude.sendch(StringToSend[i]);
telnetdude.sendch(#13);
end;
end; {while}
end; {if}
if telnetdude.Connected then telnetdude.Disconnect;
end;
I hope this helps anyone looking for answers to a similar question.
Firstly, It would seem the typical command/response model (as mentioned above, does indeed NOT apply).
So I just got it working for some very simple application (rebooting my router).
Specific additions to above code from Johnny Lanewood (and perhaps some clarification)
a) You have to send #13 to confirm the command
b) I got "hangs" on every command I sent / response I requested UNTIL I enabled ThreadedEvent. (this was my big issue)
c) the OnDataAvailable event tells you when new data is available from the Telnet Server - however there are no guarantees as to what this data is - i.e. it's pretty what you get in the command line / what ever is appended to the previous responses. But is is NOT a specific response line to your command - it's whatever the telnet server returns (could be welcome info, ASCII drawings etc etc.)
Given (c) above, one would rather check the OnDataAvailable event and parse the data (knowing what you'd expect). When the output stops (i.e. you need build a mechanism for this), you can parse the data and determine whether the server is ready for something new from the client. For the purpose of my code below, I set a read timemout and I just used Sleep(2000) - ignorantly expecting no errors and that the server would be ready after the sleep for the next command.
My biggest stumbling block was ThreadedEvent := True (see above in b)
Thus, my working solution (for specific application, and possibly horrible to some).
lIDTelnet := TIdTelnet.Create(nil);
try
lIdTelnet.ReadTimeout := 30000;
lIDTelnet.OnDataAvailable := TDummy.Response;
lIDTelnet.OnStatus := TDummy.Status;
lIdTelnet.ThreadedEvent := True;
try
lIDTelnet.Connect('192.168.0.1', 23);
if not lIDTelnet.Connected then
Raise Exception.Create('192.168.0.1 TELNET Connection Failed');
Sleep(2000);
lIdtelnet.SendString(cst_user + #13);
Sleep(2000);
lIdtelnet.SendString(cst_pass + #13);
Sleep(2000);
lIdtelnet.SendString(cst_reboot + #13);
Sleep(2000);
if lIDTelnet.Connected then
lIDTelnet.Disconnect;
except
//Do some handling
end;
finally
FreeAndNil(lIdTelnet);
end;
and then
class procedure TDummy.Response(Sender: TIdTelnet; const Buffer: TIdBytes);
begin
Write(TDummy.ByteToString(Buffer));
end;
class function TDummy.ByteToString(
const aBytes: TIdBytes): String;
var
i : integer;
begin
result := '';
for i := 0 to Length(aBytes) -1 do
begin
result := result + Char(aBytes[i]);
end;
end;

Resources