Web server Delphi can't create client - delphi

I have a web server in Delphi with the following function.
function Twebserver.Login(AUserName, APassword: string) : Tcustomer;
and in my client
var
c := getISimpleCalculator.Login(AUserName, APassword);
if c.custLogged = '1' then
begin
showmessage('olaaa11');
end
else
begin
messagedlg('Usuario ou senha incorreta', mterror, [mbok], 0);
end;
and I got this error when I'm trying to log in:
raised exception class ERemotableException
access violation at address....
Why can't I log in? I'm passing the user and password in tedit 1 and tedit2.

A ERemotebleException has been thrown on the server side of the web service. There is not much you can do on the client side if the input values of the remote method call are correct. I suggest debugging the server code using the parameters passed to the Login method.

Related

Check if user authentication in Active DIrectory

i would like to know whether a user inputs the correct combination of Domain, User and Password for his Active Directory user.
I tried to make a very simple program that is not able to connect but by reading the error message i can know if the user/password is correct.
This is trick based (the logic is on reading the Exception message), anyway i testd this prototype on 2 servers and i noticed that the excpetion messages change from server to server so this is not reliable.
uses adshlp, ActiveDs_TLB;
// 3 TEdit and a TButton
procedure TForm4.Button1Click(Sender: TObject);
Var
aUser : IAdsUser;
pDomain, pUser, pPassword : string;
myResult : HRESULT;
Counter: integer;
begin
pDomain := edtDomain.Text;
pUser:= edtUser.Text;
pPassword := edtPwd.Text;
Counter := GetTickCount;
Try
myResult := ADsOpenObject(Format('LDAP://%s',[pDomain]),Format('%s\%s',[pDomain,pUser]),pPassword,
ADS_READONLY_SERVER,
IAdsUser,aUser);
except
On E : EOleException do
begin
if (GetTickCount - Counter > 3000) then ShowMessage ('Problem with connection') else
if Pos('password',E.Message) > 0 then ShowMessage ('wrong username or password') else
if Pos('server',E.Message) > 0 then ShowMessage ('Connected') else
ShowMessage('Unhandled case');
memLog.Lines.Add(E.Message);
end;
end
end;
The reason why i set "Connected" if the message contain "server" is that on my
local machine (on my company ldap server in fact) in case all is fine (domain, user and password) the server replies "The server requires a safer authentication", so the "server" word is in there, while in other cases it says "wrong user or password". SInce this must work on itlian and english servers i set "server" and "pasword" as reliable words. Anyway i tested on another server that gives differente errors.
I started from a reply to this question to do the above.
How can i check if the user set the correct password or not in a more reliable way using a similar technique?
UPDATE (found solution)
Thanks to the replies i managed to write this function that does what i need. It seems quite reliable up to now, I write here to share, hoping it can help others:
// This function returns True if the provided parameters are correct
// login credentials for a user in the specified Domain
// From empirical tests it seems reliable
function UserCanLogin(aDomain, aUser, aPassword: string): Boolean;
var
hToken: THandle;
begin
Result := False;
if (LogonUser(pChar(aUser), pChar(aDomain), pChar(aPassword), LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hToken)) then
begin
CloseHandle(hToken);
Result := True;
end;
end;
You need to check the error code that ADsOpenObject returns. do not base your error checking on the returned exception messages.
if the function succeeded it will return S_OK, otherwise you need to refer to ADSI Error Codes, specifically, the LDAP error codes for ADSI
When an LDAP server generates an error and passes the error to the
client, the error is then translated into a string by the LDAP client.
This method is similar to Win32 error codes for ADSI. In this example,
the client error code is the WIN32 error 0x80072020.
To Determine the LDAP error codes for ADSI
Drop the 8007 from the WIN32 error code. In the example, the remaining hex value is 2020.
Convert the remaining hex value to a decimal value. In the example, the remaining hex value 2020 converts to the decimal value
8224.
Search in the WinError.h file for the definition of the decimal value. In the example, 8224L corresponds to the error
ERROR_DS_OPERATIONS_ERROR.
Replace the prefix ERROR_DS with LDAP_. In the example, the new definition is LDAP_OPERATIONS_ERROR.
Search in the Winldap.h file for the value of the LDAP error definition. In the example, the value of LDAP_OPERATIONS_ERROR in
the Winldap.h file is 0x01.
For a 0x8007052e result (0x052e = 1326) for example you will get ERROR_LOGON_FAILURE
From your comment:
Since the function always raises an exception i am not able to read
the code
You are getting an EOleException because your ADsOpenObject function is defined with safecall calling convention. while other implementations might be using stdcall. when using safecall Delphi will raise an EOleException and the HResult will be reflected in the EOleException.ErrorCode, otherwise (stdcall) will not raise an exception and the HResult will be returned by the ADsOpenObject function.
i would like to know whether a user inputs the correct combination of
Domain, User and Password for his Active Directory user.
You can use LogonUser function to validate user login e.g. :
if (LogonUser(pChar(_Username), pChar(_ADServer), pChar(_Password), LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT, hToken)) then
begin
CloseHandle(hToken);
//...
//DoSomething
end
else raise Exception.Create(SysErrorMessage(GetLastError));
Note: You must use LogonUser within a domain machine to be able to use the domain login or the function will always return The user name or password is incorrect
The alternative is using TLDAPSend e.g. :
function _IsAuthenticated(const lpszUsername, lpszDomain, lpszPassword: string): Boolean;
var
LDAP : TLDAPSend;
begin
Result := False;
if ( (Length(lpszUsername) = 0) or (Length(lpszPassword) = 0) )then Exit;
LDAP := TLDAPSend.Create;
try
LDAP.TargetHost := lpszDomain;
LDAP.TargetPort := '389';
....
LDAP.UserName := lpszUsername + #64 + lpszDomain;;
LDAP.Password := lpszPassword;
Result := LDAP.Login;
finally
LDAP.Free;
end;
end;
How can i check if the user set the correct password or not in a more
reliable way using a similar technique?
Try to use FormatMessage function
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM,
nil,
myResult,
LANG_ENGLISH or SUBLANG_ENGLISH_US,
lpMsg,
0,
nil);
MessageBox(0, lpMsg, 'Msg', 0);
I get (HRESULT) 0x8007052e (2147943726) "unknown user name or bad password" when I use a wrong password. And there is no EOleException, try:
hr := ADsOpenObject('LDAP://'+ ADomain + '/OU=Domain Controllers,' + APath,
AUser, APwd,
ADS_SECURE_AUTHENTICATION or ADS_READONLY_SERVER,
IID_IADs, pObject);
if (hr=HRESULT(2147943726)) then ShowMessage ('wrong username or password')

Delphi Datasnap client code not getting unauthorized exception

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!

FireDac connection issue

I was using an example from here :
establishing connection
Did it this way :
procedure TDataModule2.DataModuleCreate(Sender: TObject);
begin
with FDGUIxLoginDialog1.VisibleItems do begin
Clear;
Add('Server=Strežnik');
Add('User_name=Uporabnik');
Add('Password=Geslo');
Add('Database=Baza');
end;
try
FDConnection1.Connected := True;
except
on E: EAbort do
application.terminate; // user pressed Cancel button in Login dialog
on E:EFDDBEngineException do
case E.Kind of
ekUserPwdInvalid: ; // user name or password are incorrect
//ekUserPwdExpired: ; // user password is expired
ekServerGone: ; // DBMS is not accessible due to some reason
else
// other issues
end;
end;
end;
However, application will not terminate after hitting cancel in the login dialog but shows my main form. What should I do to correct this ?
Also, how can I flash a message if the password was wrong, in this scenario ?
Firedac is connecting to SQL Server.
ps
Even this will not work :
.....
except
on E: EFDDBEngineException do
if E.Kind = ekUserPwdInvalid then
begin
ShowMessage('A user name or a password are invalid');
Abort;
end;
An application usually quits by closing its main form.
You should never let an application quit by issuing an exception; it's bad form.
EAbort is not an exception that gets issued by the login dialog. In fact under normal circumstances te login dialog will not issue any exception.
If the user presses Cancel then the connection will not get ehm.. connected, you can test that.
Here's a list of all the errors TFDConnection can generate.
The following code should work:
type
TAction = (aSuccess, aGiveup, aWrongPassword, aPasswordExpired);
procedure TDataModule2.DataModuleCreate(Sender: TObject);
var
VI: TStrings;
WhatToDo: TAction;
begin
WhatToDo:= aSuccess;
VI:= FDGUIxLoginDialog1.VisibleItems;
VI.Clear;
VI.Add('Server=Strežnik');
VI.Add('User_name=Uporabnik');
VI.Add('Password=Geslo');
VI.Add('Database=Baza');
try
FDConnection1.Connected := True;
except
on E:EFDDBEngineException do case E.Kind of
ekUserPwdInvalid: WhatToDo:= aWrongPassword;
ekUserPwdExpired: WhatToDo:= aPasswordExpired;
else WhatToDo:= aGiveUp;
end; {case}
end;
if Not(FDConnection1.Connected) then WhatToDo:= aGiveUp;
case Action of
aWrongPassword: begin
ShowMessage('You've entered a wrong username or password please try again');
DataModuleCreate(Sender);
Action:= aSuccess;
end;
aPasswordExpired: ShowMessage('You password has expired, please request a new one from the PHB');
end;
if Action <> aSuccess then Application.Terminate;
end;
Comments about coding style
Do not use with. With is evil use a temporary variable instead and use that to reference the nested variable(s).
Terminating on an error without issuing an error is really silly.
No user is going to be happy having an application die on them without any explanation why.
You should never terminate on a wrong password or username.
As least give the user the change to try again 3 or 4 times.
If he doesn't get it right by then issue the "wrong username or password" error message and quit.
Never tell the user which (username or password) is wrong, an attacker can use that to generate a list of usernames.

Internal Server Error calling a Datasnap server using Indy

I have set up a very basic DataSnap Server Application (Delphi XE3), containing the 2 sample methods, EchoString and ReverseString. I have added authentication so that only if the user calling the method is called "standard", they have access to the ReverseString method.
procedure TServerContainer1.DSAuthenticationManager1UserAuthenticate(
Sender: TObject; const Protocol, Context, User, Password: string;
var valid: Boolean; UserRoles: TStrings);
begin
valid := (User <> '');
if (SameText(User, 'standard') = True) then
begin
UserRoles.Add('standard');
end;
end;
type
TServerMethods1 = class(TDSServerModule)
private
{ Private declarations }
public
{ Public declarations }
function EchoString(Value: string): string;
[TRoleAuth('standard')]
function ReverseString(Value: string): string;
end;
If I call this method from a browser directly, e.g.
http://localhost:8080/datasnap/rest/TServerMethods1/ReverseString/TestFromBrowser
then I get the expected response (after the browser's default login prompt, in which I enter an invalid user, e.g. Jason):
{"error":"jason is not authorized to perform the requested action."}
However, if I call it from a Delphi client application using Indy (TIdHTTP):
IdHTTP1.Request.BasicAuthentication := True;
IdHTTP1.Request.Username := 'jason';
IdHTTP1.Request.Password := 'jason';
Label2.Caption := IdHTTP1.Get('http://localhost:8080/datasnap/rest/TServerMethods1/ReverseString/TestFromDelphi');
I get this response:
HTTP/1.1 500 Internal Server Error
How can I avoid the error and receive the same RESTful response that I got in the browser? I have been trying to figure out how to view the HTTP request sent by the browser vs that sent by Indy but haven't managed it, even using Ethereal.
I have been able to establish what is going on. Using Wireshark and Rawcap (to capture local loopback requests), I can see that even though the browser is showing the meaningful message:
{"error":"jason is not authorized to perform the requested action."}
it is also returning an HTTP 500 Internal Server Error. However, the meaningful response text is also returned as part of this response, which is being displayed by the browser.
Checking the Indy HTTP response, it is the same; the actual response code is a 500, but the text I want to display is also being returned.
This is accessible by capturing the EIdHTTPProtocolException and accessing its ErrorMessage property.

How to Prevent dialog (Basic Authentication prompt) during call of Webservice

In a delphi program (running as a service) i need to call some webservices.
The calls works fine if basic Authentications is not requerired. The calls also works fine if Basic Authentication is requerired and username/password is provided (in BeforePost) using:
InternetSetOption(Data, INTERNET_OPTION_USERNAME,...
InternetSetOption(Data, INTERNET_OPTION_PASSWORD,...
But if Basic Authentication is Requeried, and username/password is not provided, the program brings up af prompt for the username/password (thats a NO-GO in a servcice).
So how can I signal that i DON'T want a prompt, but instead an error?
The problem is, as i can se it, in the SOAPHTTPTrans function THTTPReqResp.Send(const ASrc: TStream): Integer; (line 762 (second call to InternetErrorDlg i that method)).
EDIT1:
if i change the Flags in the beginning of the send method (in SOAPHTTPTRANS) to include INTERNET_FLAG_NO_AUTH, it works as i wanted.
But how do i do that without changing the SAOPHTTPTrans (if possible)?
EDIT2:
ws := THTTPRIO.Create(Self);
ws.URL := 'http://excample.com/ws.asmx';
ws.HTTPWebNode.InvokeOptions := [soIgnoreInvalidCerts];
ws.HTTPWebNode.OnBeforePost := WebServiceCallBeforePost;
AvailabilityWebservice := (ws as AvailabilityServiceSoap);
sTemp := AvailabilityWebservice.GetVersion;
Where AvailabilityServiceSoap is the interface generated using the WSDL importer.
I had this problem when trying to let Windows Live Messenger work through a web filter.
I ended up writing a small program that auto-authenticates every so often.
Hope this helps you too.
uses
... IdHTTP ...;
...
var
httpGetter: TIdHTTP;
...
httpGetter.Request.Username := username;
httpGetter.Request.Password := password;
httpGetter.HandleRedirects := True;
httpGetter.Request.BasicAuthentication := True;
//custom useragent required to let live messenger work
//this part is probably not necessary for your situation
httpGetter.Request.UserAgent := 'MSN Explorer/9.0 (MSN 8.0; TmstmpExt)';
httpGetter.Get(url,MS);
...
You could create a new class which Inherits from THTTPReqResp and override the send method so that you can include your own flags. You should be able to set ws.HTTPWebNode to a new node using the new class.
Something Like
ws := THTTPRIO.Create(Self);
MyNewNode := MyNewClass.Create;
ws.HTTPWebNode := MyNewNode;
ws.URL := 'http://excample.com/ws.asmx';
ws.HTTPWebNode.InvokeOptions := [soIgnoreInvalidCerts];
ws.HTTPWebNode.OnBeforePost := WebServiceCallBeforePost;
AvailabilityWebservice := (ws as AvailabilityServiceSoap);
sTemp := AvailabilityWebservice.GetVersion;
How about checking the servers authentication mode first?
http://en.wikipedia.org/wiki/Basic_access_authentication
The client asks for a page that
requires authentication but does not
provide a user name and password.
Typically this is because the user
simply entered the address or
followed a link to the page.
The server responds with the 401
response code and provides the
authentication realm.
So the client service application could send a Get and see if the response has a header like
WW-Authenticate: Basic realm="Secure Area"

Resources