DelphiXE8 and WSDL, disable authentication via Smart Card Reader - wsdl

With Delphi XE8 I imported a WSDL and use the HTTPRIO component
For use authentication:
property HTTPRIO:
HTTPWebNode ->
ClientCertificate -> CertName = ServiceSSL.cer
InvokeOptions [soIgnoreInvalidCerts,soPickFirstClientCertificate]
Event HTTPRIO
HTTPWebNode -> OnBeforePost = HTTPRIO1HTTPWebNode1BeforePost
procedure HTTPRIO1HTTPWebNode1BeforePost(const HTTPReqResp: THTTPReqResp;
Data: Pointer);
var
auth: String;
FUserName, FPassword : string;
begin
FUserName:=UtenteTS;
FPassword:=PassTS;
auth := 'Authorization: Basic ' + TNetEncoding.Base64.Encode(FUserName + ':' + FPassword);
HttpAddRequestHeaders(Data, PChar(auth), Length(auth), HTTP_ADDREQ_FLAG_ADD);
end;
Works well.
The problem is:
when a Smart Card Reader with a digital signature card is inserted in the PC,
during authentication it communicates with the smart card, opening a PIN request window.
I don't want this, i want to prioritize authentication through HTTPRIO1HTTPWebNode1BeforePost !
Note:
Run debug, HTTPRIO1HTTPWebNode1BeforePost, it is processed and then communication with the
smart card is started.
How can I disable Smart Card Reader reading?
The card with the digital signature I need connected to the PC because my program has to digitally sign documents.
I add details.
I noticed that after the call HTTPRIO1HTTPWebNode1BeforePost
this code is executed twice
{ line 1151 of soap.SOAPHTTPtrans.pas Posting Data Event }
if Assigned(FOnPostingData) then
FOnPostingData(DatStr.Size, BuffSize);
RetVal := ERROR_SUCCESS;
{$IFDEF UNICODE}
HttpSendRequest(Request, nil, 0,
DatStr.Bytes, DatStr.Size);
{$ELSE}
HttpSendRequest(Request, nil, 0,
#DatStr.DataString[1],
Length(DatStr.DataString));
{$ENDIF}
RetVal := HandleWinInetError(GetLastError, Request, True);
case RetVal of
ERROR_SUCCESS: break;
ERROR_CANCELLED: System.SysUtils.Abort;
ERROR_INTERNET_FORCE_RETRY: {Retry the operation};
end;
The first RetVal pass has value 12032 = ERROR_INTERNET_FORCE_RETRY: {Retry the operation};
the second step HttpSendRequest(Request, nil, 0, DatStr.Bytes, DatStr.Size); starts the smart card reader and opens the message login window which requests the PIN of the digit signature entered in the reader. RetVal = 0
I tried to use InternetSetOption(Data, INTERNET_FLAG_NO_AUTH, ????, ????) but I do not know how I can do it.
I don't want the login message box to open, request smart card PIN. How can I do?
update to my request
after reading this: I tried this:post "Replace WinINet by WinHTTP component. Both have very close APIs, and the 2nd does not create any UI interaction, but will return error codes, just like any other API. The UI part of WinINet may be a good idea for some software, but it sounds like if does not fit your needs."
How can I use winHttp with HttpRio? I tried:
uses winHTTP;
...
procedure HTTPRIOHTTPWebNode1BeforePost(const HTTPReqResp: THTTPReqResp; Data: Pointer);
auth := 'Authorization: Basic ' + TNetEncoding.Base64.Encode(FUserName + ':' + FPassword);
if not WinHttpAddRequestHeaders(Data, PChar(auth), Length(auth), WinHTTP_ADDREQ_FLAG_ADD) then ShowMessage('demat WinHttpAddRequestHeaders: ' + SysErrorMessage(GetLastError()));
if I use HttpAddRequestHeaders (in winInet.dll) it works:
HttpAddRequestHeaders(Data, PChar(auth), Length(auth), HTTP_ADDREQ_FLAG_ADD)
if I use WinHttpAddRequestHeaders (in winhttp.dll) it NOT works, and I receive error: invalid handle.:
Handle is Data type Point.
in post About HINTERNET Handles
"Microsoft Win32 Internet (WinInet) functions also use HINTERNET handles. However, the handles used in WinInet functions cannot be interchanged with the handles used in WinHTTP functions. For more information about WinInet, see About WinINet."
So I can't use WinHttpAddRequestHeaders with Httprio. I am sorry.
" WinHttpAddRequestHeaders(Data, PChar(auth), Length(auth), WinHTTP_ADDREQ_FLAG_ADD)
I want to try using winHttp with HttpRio instead of winInet. As I read, using winHttp, I will not receive the windows security window (PIN request). I don't know how to use winHttp with HttpRio. " You can help me?

Related

How to activate/force Basic Auth on THTTPClient

I can't find an option to activate Basic Auth on THTTPClient.
My main "issue" is that the first request response with a HTTP/1.1 401 Unauthorized and it triggers an auth event. I want to do it on the first "try".
Here is my code:
uses
System.Net.HTTPClient, System.Net.URLClient, System.NetEncoding;
procedure DoRequest(const url, username, password, filename: string);
var
http: THTTPClient;
ss: TStringStream;
base64: TBase64Encoding;
begin
http := THTTPClient.Create;
ss := TStringStream.Create('', TEncoding.UTF8);
base64 := TBase64Encoding.Create(0);
try
// option #1 - build in credential storage
http.CredentialsStorage.AddCredential(
TCredentialsStorage.TCredential.Create(
TAuthTargetType.Server, '', url, username, password
));
// option #2 - custom "Authorization"
http.CustomHeaders['Authorization'] := 'Basic ' + base64.Encode(username + ':' + password);
http.ProxySettings.Create('127.0.0.1', 8888); // for fiddler
http.Get(url, ss);
ss.SaveToFile(filename);
finally
base64.Free;
ss.Free;
http.Free;
end;
end;
Option #2 includes the Authorization in the first request. Option #1 does not.
How can I do that?
Update - PreemptiveAuthentication does not exist in Seattle
In Delphi 11 and later, using THTTPClient.CredentialsStorage should work, you just need to set THTTPClient.PreemptiveAuthentication to true:
Property controls preemptive authentication. When set to True, then basic authentication will be provided before the server gives an unauthorized response.
However, PreemptiveAuthentication does not exist in Delphi 10.x, including Seattle, so you will have to stick with your CustomHeaders solution until you can upgrade to a modern Delphi version.

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')

JCLMAPI Delphi Non-Modal Email popup

I am currently using Delphi 7 on XP, but I would like to eventually migrate the code to DXE on Win8.
I am trying to send email using the JCL, using JCLMAPI to be specific. I tried using the JclSimpleSendMail routine in the JCLMAPI unit. Here's the interface to the call.
function JclSimpleSendMail(const Recipient, Name, Subject, Body: AnsiString; const Attachment: TFileName; ShowDialog: Boolean; ParentWND: THandle; const ProfileName: AnsiString; const Password: AnsiString): Boolean;
The problem is it pops up the default MAPI client message box modally (in my case Outlook 2010). I would like it to just open the email message window, but allow the user to continue working in the Delphi App. until they are ready to send, eg, in case a user wants to continue working in the Delphi App before sending the email. Is this possible?
I noticed there is a ParentHWND property in TJCLEmail, I tried setting that to zero (I know it was a reach), but I was hoping that removing the parent handle might change the modal behavior (no luck!)
function TForm1.SimpleSendHelper2(const ARecipient, AName, ASubject, ABody: AnsiString; const AAttachment: TFileName;
AShowDialog: Boolean; AParentWND: THandle; const AProfileName, APassword, AAddressType: AnsiString): Boolean;
var
AJclEmail: TJclEmail;
begin
AJclEmail := TJclEmail.Create;
try
**AJCLEmail.ParentWnd := 0; //TRIED FORCING THE ATTACHED HANDLE TO ZERO**
*//if AParentWND <> 0 then
// AJclEmail.ParentWnd := AParentWND;*
if ARecipient <> '' then
AJclEmail.Recipients.Add(ARecipient, AName, rkTO, AAddressType);
AJclEmail.Subject := ASubject;
AJclEmail.Body := ABody;
if AAttachment <> '' then
AJclEmail.Attachments.Add(AnsiString(AAttachment));
if AProfileName <> '' then
AJclEmail.LogOn(AProfileName, APassword);
Result := AJclEmail.Send(AShowDialog);
finally
AJclEmail.Free;
end;
end;
This also successfully opened up the Default MAPI app and filled in all of the information passed (TO, Subject, Body, Attachment). Unfortunately it still opens the message box modally.
Finally, I also tried the code at http://www.delphifaq.com/faq/delphi/network/f236.shtml This code just Uses MAPI directly (no JCL). Unfortunately, it also pops up the message box modally.
Any thoughts on how I can open the default MAPI client non-modally?
Thank you!
You can use Windows API function MAPISendMailW with flag MAPI_DIALOG_MODELESS assigned.
But then you have to use MAPISendMailHelper function for Win8 and later and MAPISendMailW for Windows 7 and earlier. And for Windows 7 such functionality available only with some (latest) versions of Office and only with Windows SDK for Windows 8 installed (according to MSDN). If another email client used (not MS Outlook), then there is no guarantee to get it working.
In other words, it is possible, but it is tricky. I suggest you keep it in modal form, it is safer for many reasons. If user "is not ready to send email", then he will not activate such function (or cancel it to return to the program).

Reading image data from client using Indy command handler

I have a small client-server application project using Indy 9. My server application is using 'command handler' to handle client's command.
In this case my client application using writeLn procedure to send command and data, which is text based. For example:
IdTcpClient1.WriteLn('cmd1'+'#'+'hello server');
note: 'cmd1' is a command, '#' is command delimiter, and 'hello server' is the data
In order to handle this command (cmd1), my server application has a procedure as follows:
procedure TForm1.IdTcpServer1cmd1Command(Asender:TIdCommand);
var
s: string;
begin
if ( Assigned(ASender.Params) ) then
begin
s:= Asender.Param[0];
...............
...............
end;
end;
So far everything is fine. The problem is that I want to add a feature so that my server application is able to request and receive a JPEG_image from client. If client send this image using: WriteStream procedure, for example:
IdTcpClient1.WriteStream(MyImageStream);
How then the the server handle this event considering that there is no specific command to it (such as 'cmd1' in this example)?
You would simply call ReadStream() in the command handler, eg:
IdTcpClient1.WriteLn('JPEG');
IdTcpClient1.WriteStream(MyImageStream, True, True); // AWriteByteCount=True
procedure TForm1.IdTcpServer1JPEGCommand(ASender: TIdCommand);
var
Strm: TMemoryStream;
begin
...
Strm := TMemoryStream.Create;
try
ASender.Thread.Connection.ReadStream(Strm); // expects AWriteByteCount=True by default
...
finally
Strm.Free;
end;
...
end;
Alternatively:
IdTcpClient1.WriteLn('JPEG#' + IntToStr(MyImageStream.Size));
IdTcpClient1.WriteStream(MyImageStream); // AWriteByteCount=False
procedure TForm1.IdTcpServer1JPEGCommand(ASender: TIdCommand);
var
Size: Integer;
Strm: TMemoryStream;
begin
...
Size := StrToInt(ASender.Params[0]);
Strm := TMemoryStream.Create;
try
if Size > 0 then
ASender.Thread.Connection.ReadStream(Strm, Size, False); // AWriteByteCount=True not expected
...
finally
Strm.Free;
end;
...
end;
How then the the server handle this event
So what is you actually want to learn ? What is your actual question ?
If you want to know how your server would behave - then just run your program and see what happens.
If you want to know how to design your server and client, so client could upload a picture to server - then ask just that.
I think that the proper thing to do would be adding the command "picture-upload" with two parameters: an integer token and a jpeg stream.
When the server would reply to cmd1 command, if it would need, it would generate and attach a unique integer token, asking the client to upload the screenshot.
When client would receive this reply, it would parse it, and if the token would be found - would issue one more command, a specially designed "picture-upload#12345" (where 12345 replaced with actual token value) followed by the picture itself. The server would then use the token value to know which client and why did the upload.
PS. though personally I think you'd better just use standardized HTTP REST rather than making your own incompatible protocol.

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