Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   TRestClient digest Authentication? (https://www.delphipraxis.net/208867-trestclient-digest-authentication.html)

taveuni 23. Sep 2021 07:23

Delphi-Version: 5

TRestClient digest Authentication?
 
Hallo zusammen,
Hat schon mal jemand versucht mit den Delphi Komponenten (TRestclient, TRestrequest) einen Rest PUT mit Digest Authentication zu machen? Out of the Box steht diese Authentifizierung nicht zur Verfügung? Auch im allwissenden Internet habe ich nichts gefunden. Ich möchte vermeiden dies mit Indy oder ähnlichem zu lösen.

P.S.: Weshalb steht oben im Post Delphi-Version 5? Ich habe 10.4.2

taveuni 28. Sep 2021 08:39

AW: TRestClient digest Authentication?
 
Ich erlaube mir dies noch einmal nach oben zu holen. Ist noch niemand auf dieses Problem gestossen (und hat sogar eine Lösung dazu)?

Bbommel 28. Sep 2021 11:08

AW: TRestClient digest Authentication?
 
Um wenigstens mal zu antworten, wenn auch leider nicht besonders hilfreich :-) : nein, das Problem hatte ich bisher tatsächlich noch nicht. Entweder ganz profane Baisc-Authentication oder direkt OAuth/OAuth2, also mit einem Baraer Token. Beides wird von Delphi ja unterstützt (wobei man bei OAuth2 meistens noch ein bisschen nacharbeiten muss, aber die Grundlagen sind da). Digest hatte ich bisher nicht.

Union 28. Sep 2021 17:28

AW: TRestClient digest Authentication?
 
Bekommst Du denn eine 401 response vom Server? Befindet der sich im Intranet oder extern?

taveuni 29. Sep 2021 06:21

AW: TRestClient digest Authentication?
 
Ja ich erhalte 401 mit allen in Delphi vorhandenen Authentifikationen. Es handelt sich um HikVision Peoplecounter Kameras welche alle im Intranet sind. Mit einem Overbyte ICS Http Client - Authentication auf Digest funktioniert es problemlos.

Uwe Raabe 29. Sep 2021 08:50

AW: TRestClient digest Authentication?
 
In der Regel wird das Standardangebot an REST-Authentifizierungen in neuen Versionen schrittweise erweitert. Mit einem entsprechenden Feature-Request in QP hat man vermutlich die besten Aussichten. Bis dahin kann man das aber auch wohl selbst implementieren.

Union 29. Sep 2021 22:13

AW: TRestClient digest Authentication?
 
Das hat mich jetzt nicht losgelassen. Ich habe etwas bei Indy abgekupfert.
Delphi-Quellcode:
unit REST.Authenticator.Digest;

interface

uses
  System.Classes,
  Data.Bind.ObjectScope,
  REST.Client;

type
  TSubDigestAuthenticationBindSource = class;

  TDigestAuthenticator = class(TCustomAuthenticator)
  protected
    FBindSource: TSubDigestAuthenticationBindSource;
    function CreateBindSource: TBaseObjectBindSource; override;
  private
    FPassword: string;
    FUsername: string;
    FNonceCount: integer;
    FQopOptions : TStringList;
    FRealm : string;
    FNonce : string;
    FOpaque : string;
    FQop : string;
    FMethod : string;
    FAlgorithm : string;
    FUri : string;
    procedure ReadHeader(ARequest : TCustomRESTRequest);
  protected
    procedure SetPassword(const AValue: string);
    procedure SetUsername(const AValue: string);
    procedure DoAuthenticate(ARequest: TCustomRESTRequest); override;
  public
    constructor Create(const AUsername, APassword: string); reintroduce; overload;
    constructor Create(AOwner: TComponent); overload; override;
    destructor Destroy; override;
  published
    property Username: string read FUsername write SetUsername;
    property Password: string read FPassword write SetPassword;
  end;

  TSubDigestAuthenticationBindSource = class(TRESTAuthenticatorBindSource<TDigestAuthenticator>)
  protected
    function CreateAdapterT: TRESTAuthenticatorAdapter<TDigestAuthenticator>; override;
  end;

  TDigestAuthenticatorAdapter = class(TRESTAuthenticatorAdapter<TDigestAuthenticator>)
  protected
    procedure AddFields; override;
  end;

implementation

uses
  System.Sysutils,
  System.Hash,
  Data.Bind.Components,
  REST.Consts,
  REST.Types;


{ TDigestAuthenticator }

constructor TDigestAuthenticator.Create(const AUsername, APassword: string);
begin
  Create(NIL);

  FUsername := AUsername;
  FPassword := APassword;
end;

constructor TDigestAuthenticator.Create(AOwner: TComponent);
begin
  inherited;
  FNonceCount := 1;
  FQopOptions := TStringList.Create;
end;

function TDigestAuthenticator.CreateBindSource: TBaseObjectBindSource;
begin
  FBindSource := TSubDigestAuthenticationBindSource.Create(Self);
  FBindSource.Name := 'BindSource'; { Do not localize }
  FBindSource.SetSubComponent(True);
  FBindSource.Authenticator := Self;

  result := FBindSource;
end;

destructor TDigestAuthenticator.Destroy;
begin
  FQopOptions.Free;
  inherited;
end;

procedure TDigestAuthenticator.DoAuthenticate(ARequest: TCustomRESTRequest);

function Hash(const AString : string) : string;
begin
  Result := THashMD5.GetHashString(AString);
end;

var
  LCNonce : string;
  LA1     : string;
  LA2     : string;
  LResponse : string;
  LResult : string;
begin
  if ARequest.Response.StatusCode <> 401 then Exit;
 
  ReadHeader(ARequest);
  LCNonce := Hash(DateTimeToStr(Now));

  LA1 := Username + ':' + FRealm + ':' + Password; {do not localize}
  LA2 := FMethod + ':' + FUri; {do not localize}
  LResponse := IntToHex(FNonceCount, 8) + ':' + LCNonce + ':' + FQop + ':'; {do not localize}
  LResponse := Hash( Hash(LA1) + ':' + FNonce + ':' + LResponse + Hash(LA2) ); {do not localize}

  LResult := 'Digest ' + {do not localize}
    'username="' + Username + '", ' + {do not localize}
    'realm="' + FRealm + '", ' +  {do not localize}
    'nonce="' + FNonce + '", ' + {do not localize}
    'algorithm="' + FAlgorithm + '", ' + {do not localize}
    'uri="' + FUri + '", ';

  LResult := LResult +
    'qop="' + FQop + '", ' + {do not localize}
    'nc=' + IntToHex(FNonceCount, 8) + ', ' + {do not localize}
    'cnonce="' + LCNonce + '", '; {do not localize}

  LResult := LResult + 'response="' + LResponse + '"'; {do not localize}

  if FOpaque <> '' then begin
    LResult := LResult + ', opaque="' + FOpaque + '"'; {do not localize}
  end;

  Inc(FNonceCount);
  ARequest.AddAuthParameter(HTTP_HEADERFIELD_AUTH, LResult, TRESTRequestParameterKind.pkHTTPHEADER,
  [TRESTRequestParameterOption.poDoNotEncode]);
end;

procedure TDigestAuthenticator.ReadHeader(ARequest : TCustomRESTRequest);
  function Unquote(S: String): String;
  var
    I, Len: Integer;
  begin
    Result := S;
    Len := Length(Result);
    I := 2; // skip first quote
    while I <= Len do
    begin
      if Result[I] = '"' then begin
        Break;
      end;
      if Result[I] = '\' then begin
        Inc(I);
      end;
      Inc(I);
    end;
    Result := Copy(Result, 2, I-2);
  end;
const
  AUTHVALUE = 'WWW-Authenticate';
  DIGESTAUTH = 'Digest';
var
  S : String;
begin
  FQopOptions.Clear;
  S := ARequest.Response.Headers.Values[AUTHVALUE];
  if S.StartsWith(DIGESTAUTH) then
  begin
    S := Copy(S, Length(DIGESTAUTH) + 2);
    FQopOptions.CommaText := S;
    FRealm     := UnQuote(FQopOptions.Values['realm']);
    FNonce     := UnQuote(FQopOptions.Values['nonce']);
    FOpaque    := UnQuote(FQopOptions.Values['opaque']);
    FQop       := UnQuote(FQopOptions.Values['qop']);
    FAlgorithm := FQopOptions.Values['algorithm'];

    case ARequest.Method of
      rmPOST: FMethod := 'POST';
      rmPUT: FMethod := 'PUT';
      rmGET: FMethod := 'GET';
      rmDELETE: FMethod := 'DELETE';
      rmPATCH: FMethod := 'PATCH';
    else
      raise ERESTException.Create('Unknown Method');
    end;
    FUri := ARequest.GetFullRequestURL();
    FUri := Copy(FUri, Pos('://', FUri) + 3);
    FUri := Copy(FUri, Pos('/', FUri));
  end;
end;

procedure TDigestAuthenticator.SetPassword(const AValue: string);
begin
 if (AValue <> FPassword) then
  begin
    FPassword := AValue;
    PropertyValueChanged;
  end;
end;

procedure TDigestAuthenticator.SetUsername(const AValue: string);
begin
  if (AValue <> FUsername) then
  begin
    FUsername := AValue;
    PropertyValueChanged;
  end;
end;

{ TDigestAuthenticatorAdapter }

procedure TDigestAuthenticatorAdapter.AddFields;
const
  sUserName = 'UserName';
  sPassword = 'Password';
var
  LGetMemberObject: IGetMemberObject;
begin
  CheckInactive;
  ClearFields;
  if Authenticator <> nil then
  begin
    LGetMemberObject := TBindSourceAdapterGetMemberObject.Create(Self);
    CreateReadWriteField<string>(sUserName, LGetMemberObject, TScopeMemberType.mtText,
      function: string
      begin
        result := Authenticator.Username;
      end,
      procedure(AValue: string)
      begin
        Authenticator.Username := AValue;
      end);
    CreateReadWriteField<string>(sPassword, LGetMemberObject, TScopeMemberType.mtText,
      function: string
      begin
        result := Authenticator.Password;
      end,
      procedure(AValue: string)
      begin
        Authenticator.Password := AValue;
      end);
  end;

end;

{ TSubDigestAuthenticationBindSource }

function TSubDigestAuthenticationBindSource.CreateAdapterT: TRESTAuthenticatorAdapter<TDigestAuthenticator>;
begin
  result := TDigestAuthenticatorAdapter.Create(Self);
end;

end.
Für den Test mit httbin:
Delphi-Quellcode:
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, REST.Client;

type
  TMainForm = class(TForm)
    RequestButton: TButton;
    procedure RequestButtonClick(Sender: TObject);
  private
    { Private-Deklarationen }
    procedure CatchHttpProtocolError(Sender : TCustomRESTRequest);
  public
    { Public-Deklarationen }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

uses
  REST.Types,
  REST.Authenticator.Digest;


procedure TMainForm.RequestButtonClick(Sender: TObject);
var
  Client : TRestClient;
  Request : TRestRequest;
  Auth  : TDigestAuthenticator;
begin
  Client := TRestClient.Create('http://httpbin.org');
  try
    Request := TRestRequest.Create(Client);
    Request.Resource := 'digest-auth/auth/test/user';
    Request.OnHTTPProtocolError := CatchHttpProtocolError;
    Request.Execute;
    if Request.Response.StatusCode = 401 then
    begin
      Auth := TDigestAuthenticator.Create('test', 'user');
      try
        Auth.Authenticate(Request);
        Request.Execute;
      finally
        Auth.Free;
      end;
    end;
    ShowMessage(Request.Response.Content);
  finally
    Client.Free;
  end;
end;

procedure TMainForm.CatchHttpProtocolError(Sender: TCustomRESTRequest);
begin
  if Sender.Response.StatusCode <> 401 then
  begin
    raise ERESTException.Create(Sender.Response.Content);
  end;
end;

end.

Uwe Raabe 29. Sep 2021 22:57

AW: TRestClient digest Authentication?
 
Zitat:

Zitat von Union (Beitrag 1495565)
Das hat mich jetzt nicht losgelassen.

:thumb:

taveuni 30. Sep 2021 06:04

AW: TRestClient digest Authentication?
 
Zitat:

Zitat von Union (Beitrag 1495565)
Das hat mich jetzt nicht losgelassen. Ich habe etwas bei Indy abgekupfert.

Wow! Vielen Vielen Dank!

Union 30. Sep 2021 08:54

AW: TRestClient digest Authentication?
 
Leider kann man den Authenticator aus verschiedenen Gründen nicht dem Request zuweisen. Der Datenfluss bei der Requestverarbeitung im Rest Client sieht nur eine Authentifzierung vor dem Absenden des Requests vor.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:51 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz