Delphi - Authorization TIdHttp fails over HTTP proxy
Asked Answered
H

1

0

I'm using Delphi XE 6 and TIdHttp component(Indy 10.6.0.5122) and trying to consume a SOAP service - http://www.webservicex.net/globalweather.asmx over a http proxy (CCProxy - http://www.youngzsoft.net/ccproxy/). The issue is that at the first attempt to connect to the webservice I receive an "Unauthorized" respone:

<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><h1>Unauthorized ...</h1>
<h2>IP Address: xxx.xxx.xxx.:61295<br>
MAC Address: <br>
Server Time: 2014-11-18 14:19:00<br>
Auth Result: </h2></body></html>

I've linked an IdSSLIOHandlerSocketOpenSSL and IdLogDebug1 components to IdHttp in order to debug the issue.

Logs of the operations performed

***********************IdSSLIOHandlerSocketOpenSSL1Status 
Connecting to xxx.xxx.xxx.xxx.

***********************IdLogDebug1Send 
POST http://www.webservicex.net/globalweather.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Content-Length: 388
SOAPAction: http://www.webserviceX.NET/GetCitiesByCountry
Host: www.webservicex.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)



***********************IdLogDebug1Send 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCitiesByCountry xmlns="http://www.webserviceX.NET">
      <CountryName>Romania</CountryName>
    </GetCitiesByCountry>
  </soap:Body>
</soap:Envelope>

***********************IdLogDebug1Receive 
HTTP/1.0 407 Unauthorized
Server: Proxy
Proxy-Authenticate: Basic realm="CCProxy Authorization"
Cache-control: no-cache

<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body><h1>Unauthorized ...</h1>
<h2>IP Address: xxx.xxx.xxx.xxx:61295<br>
MAC Address: <br>
Server Time: 2014-11-18 14:19:00<br>
Auth Result: </h2></body></html>

***********************IdSSLIOHandlerSocketOpenSSL1Status 
Disconnected.

Now, what is interesting is that if I'm trying again to call the webservice everything works correctly. Log of the operations:

***********************IdSSLIOHandlerSocketOpenSSL1Status 
Connecting to xxx.xxx.xxx.xxx.

***********************IdLogDebug1Send 
POST http://www.webservicex.net/globalweather.asmx HTTP/1.1
Content-Type: text/xml; charset=utf-8
Content-Length: 388
SOAPAction: http://www.webserviceX.NET/GetCitiesByCountry
Host: www.webservicex.net
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)
Proxy-Authorization: Basic YW1ibzphbWJvIQ==



***********************IdLogDebug1Send 
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetCitiesByCountry xmlns="http://www.webserviceX.NET">
      <CountryName>Romania</CountryName>
    </GetCitiesByCountry>
  </soap:Body>
</soap:Envelope>

***********************IdLogDebug1Receive 
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Length: 2456


***********************IdLogDebug1Receive 
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/7.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Tue, 18 Nov 2014 12:26:21 GMT

<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetCitiesByCountryResponse xmlns="http://www.webserviceX.NET"><GetCitiesByCountryResult>&lt;NewDataSet&gt;
  &lt;Table&gt;
    &lt;Country&gt;Romania&lt;/Country&gt;
    &lt;City&gt;Arad&lt;/City&gt;
  &lt;/Table&gt;
  &lt;Table&gt;
    &lt;Country&gt;Romania&lt;/Country&gt;
    &lt;City&gt;Bacau&lt;/City&gt;
  &lt;/Table&gt;
  ......

And the response it's correct.

Code of the application

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, Soap.SOAPHTTPTrans,
  IdAuthentication, IdHeaderList, IdIntercept, IdLogBase, IdLogDebug,
  IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL
  ,IdGlobal;

type
  TForm1 = class(TForm)
    IdHTTP1: TIdHTTP;
    Button1: TButton;
    HTTPReqResp1: THTTPReqResp;
    Memo1: TMemo;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL;
    IdLogDebug1: TIdLogDebug;
    Memo2: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure IdHTTP1ProxyAuthorization(Sender: TObject;
      Authentication: TIdAuthentication; var Handled: Boolean);
    procedure IdSSLIOHandlerSocketOpenSSL1StatusInfo(const AMsg: string);
    procedure IdSSLIOHandlerSocketOpenSSL1Status(ASender: TObject;
      const AStatus: TIdStatus; const AStatusText: string);
    procedure IdLogDebug1Receive(ASender: TIdConnectionIntercept;
      var ABuffer: TIdBytes);
    procedure IdLogDebug1Send(ASender: TIdConnectionIntercept;
      var ABuffer: TIdBytes);
    procedure IdHTTP1Authorization(Sender: TObject;
      Authentication: TIdAuthentication; var Handled: Boolean);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  postData: TMemoryStream;
begin
  postData := TMemoryStream.Create;
  try
    Memo1.Lines.Clear;
    postData.LoadFromFile('..\..\soap1.1.txt');
    IdHTTP1.Request.ContentType := 'text/xml';
    IdHTTP1.Request.Charset := 'utf-8';
    IdHTTP1.Request.CustomHeaders.Values['SOAPAction'] := 'http://www.webserviceX.NET/GetCitiesByCountry';
    IdHTTP1.ProtocolVersion := pv1_1;
    IdHTTP1.HTTPOptions := IdHTTP1.HTTPOptions + [hoKeepOrigProtocol];
    Memo1.Lines.Text := IdHTTP1.Post('http://www.webservicex.net/globalweather.asmx', postData);
  finally
    postData.Free;
  end;
end;


procedure TForm1.FormCreate(Sender: TObject);
begin
 with IdHTTP1.ProxyParams do
  begin
    ProxyServer := 'xxx.xxx.xxx.xxx';
    ProxyPort := 808;
    ProxyUsername := 'User-001';
    ProxyPassword := 'User-001!';
  end;

end;

procedure TForm1.IdHTTP1Authorization(Sender: TObject;
  Authentication: TIdAuthentication; var Handled: Boolean);
begin
//
  Authentication.Username := 'User-001';
  Authentication.Password := 'User-001!';
end;

procedure TForm1.IdHTTP1ProxyAuthorization(Sender: TObject;
  Authentication: TIdAuthentication; var Handled: Boolean);
begin
//
// Authentication.Username := 'User-001';
// Authentication.Password := 'User-001!';
// Handled := true;
end;

procedure TForm1.IdLogDebug1Receive(ASender: TIdConnectionIntercept;
  var ABuffer: TIdBytes);
begin
 Memo2.Lines.Add(#13#10+'***********************IdLogDebug1Receive '+#13#10+BytesToString(ABuffer))
end;

procedure TForm1.IdLogDebug1Send(ASender: TIdConnectionIntercept;
  var ABuffer: TIdBytes);
begin
 Memo2.Lines.Add(#13#10+'***********************IdLogDebug1Send '+#13#10+BytesToString(ABuffer))
end;

procedure TForm1.IdSSLIOHandlerSocketOpenSSL1Status(ASender: TObject;
  const AStatus: TIdStatus; const AStatusText: string);
begin
Memo2.Lines.Add(#13#10+'***********************IdSSLIOHandlerSocketOpenSSL1Status '+#13#10+AStatusText)
end;

procedure TForm1.IdSSLIOHandlerSocketOpenSSL1StatusInfo(const AMsg: string);
begin
Memo2.Lines.Add(#13#10+'***********************IdSSLIOHandlerSocketOpenSSL1StatusInfo '+#13#10+AMsg)
end;

end.

How should I make the authentication in order to work from the first attempt?

PS : I've already read this question - Authorization failure TIdHTTP over HTTPS.

ANSWER : based on the indications of Remy Lebeau problem was solved by setting up the

OnProxySelectAuthorization

event and add the hoInProcessAuth to the

IdHTTP1.HTTPOptions

Haematogenous answered 18/11, 2014 at 12:31 Comment(3)
If I may ask, why not consume the webservice with THTTPRIO?Sefton
I've tried to consume the webservice with THttpRio, but delphi SOAP implementation is buggy when a http Proxy exists between the client and webservice, and when both require authentication...Haematogenous
Thank you downvoter. You should also provide me a reason for -1.Haematogenous
P
2

Make sure you have added the IdAuthentication unit to your uses clause so TIdHTTP can process Proxy-Authorization headers in a 407 reply, and also make sure you have a TIdHTTP.OnProxyAuthorization event handler assigned (even if it just returns Handled:=True) otherwise TIdHTTP will not attempt proxy authorization while processing a 407 reply, even though you have provided a username/password in the TIdHTTP.ProxyParams property.

What is most likely happening is that the TIdHTTP.ProxyParams.Authentication property is initially nil during the first request, and gets filled in with a TIdBasicAuthentication object while processing the 407 reply, but a missing OnProxyAuthorization event handler causes TIdHTTP to skip authorization, and then the TIdHTTP.ProxyParams.Authentication property is not nil anymore when the second request is made, so it attempts authorization at that time.

Skipping proxy authorization if the TIdHTTP.OnProxyAuthorization event is not assigned appears to be a bug, IMHO. By comparison, the TIdHTTP.OnAuthorization event can be unassigned as long as a non-empty value is assigned to the TIdHTTP.Request.Password property. I have now updated TIdHTTP with similar logic for the OnProxyAuthorization event regarding the TIdHTTP.ProxyParams.Password property.

So, either update to the latest SVN snapshot, or just assign a TIdHTTP.OnProxyAuthorization event handler, then you should be OK:

procedure TForm1.IdHTTP1ProxyAuthorization(Sender: TObject; Authentication: TIdAuthentication; var Handled: Boolean);
begin
  // prompt the user for username/password (optional) and store them
  // in the Authentication.Username and Authentication.Password
  // properties, respectively. By default, they are initialized with
  // the current values of the ProxyParams.UserName and
  // ProxyParams.Password properties...
  Handled := True;
end;
Prepare answered 18/11, 2014 at 16:28 Comment(5)
I've added the event as you said, but the result is the same even the AuthenticationClass is not nil. IdHTTP1SelectProxyAuthorization(Sender: TObject; var AuthenticationClass: TIdAuthenticationClass;...); if AuthenticationClass = nil then showmessage('nil'); end;Haematogenous
I wasn't suggesting that adding on OnProxySelectAuthorization event handler would fix it. I was suggesting that adding the IdAuthentication unit to the uses clause should fix it. But there appears to be a OnProxyAuthorization bug in play. I updated my answer with details.Prepare
It was already added on the uses clause. I will try your update, and accept the answer if it is correct.Haematogenous
I've uncommented the code from OnProxyAuthorization and delete the code from SelectAuthorization and SelectProxyAuthorization, but the result is the same. First time I get an Unauthorized and second time it is working...Haematogenous
Make sure the hoInProcessAuth flag is enabled in the TIdHTTP.HTTPOptions properties. If it is, and authorization is still not working, then please update your question with the latest code and HTTP trace. When TIdHTTP sees a 407 from a proxy, it should be attempting authorization right away. I don't have an HTTP proxy that I can test this with myself.Prepare

© 2022 - 2024 — McMap. All rights reserved.