Saturday, 13 January 2024

Splitters Helpers

 - or how to find ways to add own helpers and keeping existing helpers.



Wanted to do a pun on Finders Keepers, but failed - so this post is a sample on how to add your own record helper - in this case for the string type.

Delphi/Object Pascal does not allow for multiple helpers for the same type to be available at the same time.

So in my little example I wanted a string function that would split a string by a char, but up till a given max length.

A scenario for that use could be if you need to feed a system that has limited fixed size fields, spanning over more fields - CompanyName1, CampanyName2 only being 30 chars each. And for readability and UI, you also need to consider not to split mid-word.

To overcome the issue with  the one helper active per type, defined your own matching type:

MyString = type string;

Define the new record helper for that type with its function:

MyStringHelper = record helper for MyString
  function SplitMaxProper(const ch: char; const len: Integer): TArray<string>;
end;

Since we also want to use the normal string helpers within the helper function, we need to do some casting when referring to the helpers type itself:

function MyStringHelper.SplitMaxProper(const ch: char; const len: Integer): TArray<string>;
var
  sl: TStringList;
begin
  var sidx := 0;
  var done := False;
  sl := TStringList.Create;
  try
    while (not done) do
    begin
      var delta := string(Self).LastIndexOf(ch, sidx+len, len);
      if (delta = -1) or (string(Self).Length-sidx <= len) then
      begin
        sl.Add(Trim(string(Self).Substring(sidx)));
        done := True;
      end
      else
        sl.Add(Trim(string(Self).Substring(sidx, delta-sidx)));
      sidx := delta;
    end;
    Result := sl.ToStringArray;
  finally
    sl.Free;
  end;
end;

And when using the new string helper we do need to cast to get to our new function:

procedure TForm6.btnSplitClick(Sender: TObject);
begin
  meSplitText.Clear;
  var len := StrToInt(edSplitLength.Text);
  var str := MyString(edTextToSplit.Text);
  meSplitText.Lines.AddStrings(str.SplitMaxProper(#32, len));
end;

Disclaimer: The code is done so that it does fix better in the narrow width of my blog layout, and the function might also need some optimization :)

A note on the LastIndexOf string helper functions, the current official documentation seems to me a bit unclear:

StartIndex specifies the offset in this 0-based string where the LastIndexOf method begins the search, and Count specifies the end offset where the search ends.

Do remember that the search in this case, of cause goes from right to left - so StartIndex would normally be the length of the string.

I started with a Stephen King book title pun, so I should also comment on the quote used in the image shown - a quote from the brilliant author Tom Holt and the book The Portable Door - which is highly recommended, even if you have watched the movie adaptation, which is also good - but different - since adapting Tom Holt's books is no trivial task.

/Enjoy


Tuesday, 2 January 2024

Just Ping someone!

- or a component for Ping Identity's authentication within your Delphi application.


It has often been the rule that native applications either authenticate the user by current OS user or by an application-centric user and role model - but that might for multiple reasons not be good enough anymore.

The old AD or LDAP lookups are being replaced by cloud IAM platforms, to control and secure the authentication of the identity of the user.

After the user is authenticated (and is authorised "access" your application), the application-centric roles flow can continue as-is.

One of these IAM cloud provides is PingIdentity, and they have a fairly extensive Postman collection for their PingOne Platform API - found here. Their developer documentation is also very useful - found here.

I have previously used various ways against various providers, but it seemed that when testing against PingOne with MFA enabled (Multi-Factor Authentication - which might include the annoying phone thing - that everyone uses) - I was hit by either a CORS issue or something else.

When using an OAuth2/OpenID Connect authentication, it does require that you setup and use a redirect uri, to tell the provider who is locally listening/waiting for the "response".

The idea with this type of authentication, is that the flow is handled securely within a web browser session, and the client does at no point in time know the login credentials - only when the user is authenticated do we need an "id token". So no "local" storage/handling of "passwords".

The listener part and possible timeouts has always annoyed me, so it seemed based on the flow, that I could handle it differently by just intercepting the local redirect call with the auth code - when the user is authenticated on the PingOne side - to get the id token needed.

Implementation

The TPingOneAuth component is a descendant of standard TWebBrowser overriding the IDocHostUIHandler interface, and adding a some properties.

The GetHostInfo override is to ensure that all redirects in the browser component is triggering the OnBeforeNavigate2 event - which then on the redirect auth code call, will get the wanted id token.

function TPingOneAuth.GetHostInfo(var pInfo: TDocHostUIInfo): HRESULT;
begin
  pInfo.cbSize := SizeOf(pInfo);
  pInfo.dwFlags := 0;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_THEME;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION;
  Result := S_OK;
end;

The id token is then parsed using Pablo Rossi's brilliant JOSE and JWT library, that might as well have been done using a call to one of the PingOne's Token Introspection endpoints.

Since we do need an user id - the OpenID Connect scope must include profile also.

To parse the custom claim a custom TJWTClaims class is added, containing the profile claims we want to read.

TPingOneClaims = class(TJWTClaims)
// Adding some given by the OpenID Connect scope: profile
private
  function GetPreferredUsername: string;
  procedure SetPreferredUsername(const Value: string);
  function GetGivenName: string;
  procedure SetGivenName(const Value: string);
  function GetFamilyName: string;
  procedure SetFamilyName(const Value: string);
public
  property PreferredUsername: string read GetPreferredUsername write SetPreferredUsername;
  property GivenName: string read GetGivenName write SetGivenName;
  property FamilyName: string read GetFamilyName write SetFamilyName;
end;


Usage

Install the TPingOneAuth component in the Delphi IDE, and set the library path - business as usual.

There is a small sample application in the GitHub repo, but steps are at follows:

Drop or Create the TPingOneAuth on a form, setting following properties:

  • AuthEndpoint: /as/authorize
  • AuthPath: https://auth.pingone.eu/
  • ClientId:
  • ClientSecret:
  • EnvironmentId:
  • RedirectUri:
  • ResponseType: code
  • Scope: openid profile
  • TokenEndpoint: /as/token
All these values are found in the PingOne console, under the application you setup as a "authorization" reference to your native Delphi application.


Add code to the OnAuthenticated (and OnDenied) event(s) - were on a successful authentication the public properties UserId and GreetName can be useful.

To start the authentication just call the Authorize method - and with MFA enabled, you will see the prompt bellow, once you initially have given your credentials.


After finding your phone, and having swiped to authenticate in the PingOne mobile companion app, the OnAuthenticated event is called.


The code for the component and the sample is found here.

A thing to note:

When having MFA enabled - and you need to pair a device the first time around - the url Ping provides does contain some "strict mode" Javascript that IE will prompt you about twice, the solution to this is to set the SelectedEngine property to EdgeIfAvailable or EdgeOnly and deploy the EdgeView2 Runtime (WebView2Loader.dll) which can be found in GetIt as the EdgeView2 SDK.

/Enjoy



Sunday, 22 October 2023

Use(s) less

- how to visualize your uses - for un-needed and wrong scoped units.

The "Uses" plugin installed and feed with a PAL report.

Disclaimer: Even if the above picture shows my usage of the code in the current state - the code/plugin is by no means done and complete - but I put this out for others to tinker with and benefit from.

One of the things that has been on my list of wish's for the Delphi IDE since forever, has been the ability to identify which units in your uses clause is either not needed, or should/could be moved from the interface section to the implementation section.

There seems to be good reasons for that feature not to be a 100% correct in it assumption in what is needed and what could be moved, and that might also be the reason Embarcadero has not included that feature yet, but..

I only need something that is good enough, and that can help me make the decision on what to clean up.

There are some third-party tools that attempt to do that, one being Peganza's Pascal Analyzer - and the free Lite version is sufficient to what I attempted to try here.

With the new CodeEditorEvents added to the OTAPI, it is now a lot easier to change the rendering of the code editor window in a safer way.

So this is what you need to do to get to a similar result - you need to download and install Pascal Analyzer (Lite).

Setup a project to handle your Delphi project, and you can optimizer the output from the analysis to only output the Uses.txt with only the Usage (USES1) - since that is the only needed.

I should mention that people more clever than me - could probably utilize the LSP or DelphiAST (or whatnot) - to make a more elegant solution - and they should :D.

So the prerequisite for the plugin is in place - a Uses.txt to parse. And to "refresh" the Uses.txt - you would have to run C:\Program Files\Peganza\Pascal Analyzer Lite\palcmd.exe <MyDocuments>\Pascal Analyzer Lite\Projects\<Myproject>.pap again.

The plugin contains code to parse Uses.txt and created a dictionary where each unit/module has a list of flagged units.

That dictionary is used to get the uses statements for the current unit, so that the renderer can either gray out or strikeout the unit name in the Uses clauses'.

As mentioned, the state of the code is more a prof of concept and does include code that is more a comment and an intent to what I had planned at that point of time - but it has now been laying around since summer, and I will not get this perfected in foreseeable future so it will have a better life set free.

The code can be found here: https://github.com/SteveNew/CodeEditorPaintTextPAL


Saturday, 18 February 2023

IAP Client, therefore IAM

- or creating a simple Google IAP client using JOSE and a service account key file.


Google offers a massive amount of services and APIs to these, and I doubt anyone has the full overview unless they are in that domain naming space 24/7, and the same would be true for Amazon, Microsoft and other offerings.

In this example, I am using Googles IAP (Identity-Aware Proxy) to get an OIDC token that can be used to authorise the requests sent by a service account.

And a disclaimer: This is a conceptual example, for more secure and correct use one should not store any form of keys - so for that look into something like Workforce identity federation - which gives the same short-lived OIDC tokens, with the help of an identity provider. But for the purpose of the example - less is more.

First you would need to create or get the service account key file by using the Google Cloud console to create a new key for the service account, and download and store that in a safe place. The Grijjy guys did a similar example years back, where they used a PEM key based from the P12 file.

I just created a class to load our json key into to extract the values needed, and by using the JOSE library, I did a customer header and claim/payload - which was signed as the example given by the bash shell script in Googles documentation here: Get OpenID Connect Id token from key file.

Since the signing requires RS256, it does disqualify some of the other libraries, but JOSE does support that.

The Google API Client libraries and Cloud Client Libraries tend to use what they call the Application Default Credentials (ADC) strategy, and to mimic that, I added a bit of code that should cover the 3 desktop OSs.

You can read more about ADC here: Google Application Default Credentials

One requirement of the signing is the OpenSSL libraries - JOSE does use these. You could of course also "just" use the EVP_DigestSignXXX functions from the SSLEAY library, and wrap what you needed, but JOSE does such a nice job of that, so why bother.

Adding a scope will give an access_token, whereas without it you will only get the id_token which is the OIDC token.

I did add an expiration check on the OIDC token, and it does seem to do the job of not having to request the token more than needed - but it might not be the perfect way.

The OIDC token is used as a bearer token for the actual requests to the service the proxy works for, so I added some HTTP methods as sample - and they just return an IHTTPResponse interface.

So an example of use would be something like:

uses
  System.Net.HttpClient,
  FbC.IAPClient;

procedure TForm1.Button1Click(Sender: TObject);
const
  cSERVICE_ACCOUNT_KEY='lustrous-stack-342709-93cfcb2a8000.json';
  cIAP_CLIENT_ID='108584532133305403517';
  cURL='https://mytest.com';
var
  IAPClient: TIAPClient;
begin
  IAPClient := TIAPClient.Create(cSERVICE_ACCOUNT_KEY, cIAP_CLIENT_ID, cURL);
  try
    Memo1.Text := IAPClient.IdToken;
//    Memo1.Text := IAPClient.Get('/companies').ContentAsString();
  finally
    IAPClient.Free;
  end;
end;

The IdToken property should not be public, and the client code is just meant as a starting point - since it does solve the painful bit - signing and authentication.

The code for the conceptual IAPClient can be found as a gist here: Delphi Google IAPClient

Requirements are also:

JOSE library: https://github.com/paolo-rossi/delphi-jose-jwt

The appropriate OpenSSL dlls: https://github.com/IndySockets/OpenSSL-Binaries

An implementation of TOAuth2Authenticator with this might have been a good idea, but ...

Well I hope it will at least get you on the right track, if you need to go through an IAP. There is no check for an unsuccessful attempt to get the OIDC token - it will just return an empty string - so that is meant as homework.

/Enjoy

Wednesday, 22 June 2022

A SET up

- or how the for-in statement changed  recently.


Before Delphi 10.4, the statement:

for integer in [] do 

would operate on a set, but since then it now operates on an array. Which then also means a bit of different behaviour.

I had missed this change completely, and only recently gotten aware of it since we had a build issue - mixing source from different IDE versions. (BTW: Do not do that).

Since it previously worked on a set - the "list" when traversed would be an ordered list of values with an ordinal value no bigger than 255, and no more than 256 elements, that is now changed. I will list a few examples - illustrating the pre-10.4 behaviour compared to 10.4 and later.

Duplicates and ordering


var
  i: integer;
begin
  for i in [42, 10, 42, 3, 42, 7] do
    WriteLn(i.ToString);
end.

returns:

10.4+: 42, 10, 42, 3, 42, 7
pre10.4: 3,7,10,42

Range


var
  i: integer;
begin
  for i in [42, 365, MaxInt] do
    WriteLn(i.ToString);
end.

10.4+: 42, 365, MaxInt
pre10.4: Does not compile - In 10.3 you get an E1012 Constant expression violates subrange bounds (earlier you might also get an E2024)

To solve that you could replace it with something like:

uses
  System.Types,
  System.SysUtils;

var
  i: integer;
begin
  for i in TIntegerDynArray.Create(42, 365, MaxInt) do
    WriteLn(i.ToString);
end.

I do think the change is a step up, and does syntax-wise work more like one would expect, so a change for the greater good - but do be aware of the change of behaviour. Another benefit of a newer version is the usage of inline variable declaration and type inference, like in this case:

for var i in [42, 10, 42, 3, 42, 7] do
    WriteLn(i.ToString);

/Enjoy

Thursday, 14 October 2021

A Taste of WINE

- or how to detect if your Windows application is run under a flavor of WINE

It has been a while since I mentioned what has been going on with Siege Of Avalon - and the short story is that I have been involved in getting the game re-released on GOG and Steam - the long story deserves a longer post another time.

We currently have a bigger update/patch planned - that would be the 4th - since its re-release in April 2021 - but it is not finalized what goes in and what not.

But one of the minor things, is trying to make the game more Steam Play friendly - so that basically just allowing custom ddraw.dll and disabling a few things when run under Wine/Proton - so that the game is playable without the need to jump through hoops like winetricks and  protontricks.

Since these compatibility layers are meant to be transparent to the windows application, one would need to actually in some detect if running under Wine, luckily there is an extra bit in the NTDll.dll - the wine_get_version function.

So dynamically loading of the Dll, and try to get the address pointer of the function will reveal if your plain Delphi VCL Windows is run under Wine on Linux (or other Wine supported platforms).

The code for the simple test pictured:

procedure TForm10.Button1Click(Sender: TObject);
var
  dll: HMODULE;
  get_WINE_version: function: PAnsiChar;
begin
  dll := LoadLibrary('ntdll.dll');
  if dll<>0 then
  begin
    @get_WINE_version := GetProcAddress(dll, 'wine_get_version');
    if Assigned(@get_WINE_version) then
      Label1.Caption := 'Running under ' + get_WINE_version()
    else
      Label1.Caption := 'Pure ol'' Windows';
    FreeLibrary(dll);
  end;
end;

This is by no means the real deal, but if it helps bringing an existing application run on a different platform and making it aware of that - then it might be helpful.

A more "correct" way to migrate existing VCL applications could be https://www.crossvcl.com, but in this case that is not doable - since not a standard Delphi VCL application.

But as you can see below this detection short-cut can help create an experimental build of Siege Of Avalon that is running on Steam Play out of the box. AS-IS.

Siege of Avalon launched under Steam Play on Linux - also showing the new live-mapping.

Please support the nice publisher of this re-released classic old game, and their willingness to keep releasing the Delphi code of the GOG/Steam releases.

/Enjoy

Thursday, 9 September 2021

My favorite %11 new things in Delphi 11 Alexandria

- or all things "eleven", watch out for dialects.

With the public release of Delphi 11 (and RADStudio and C++Builder) today, I would like to share my %11 initial favorites in the new release.

%01. Styled Form designer

Together with the IDE HighDPI support, it is great that the gap between the running UI and the UI while designing has been reduced, and keeps in my opinion Delphi IDEs the best to design visual applications - whether it is on desktop OSs or mobile OSs.

%10. HTTP/2 support

I am looking forward to start experimenting with this, and see how much benefit and performance it will give when taking advantage of fully.

%11. Remote Desktop Support improvement

In this world where more people are coming to their senses, and wish to work remotely, focus on improving the IDE experience with remote connection is nice. And the things done can also benefit the applications build.


But there are many other things that not even a list of $11 things would cover it, and I am really looking forward to also experience to the various optimizations and running the IDE in HighDPI on my old Surface Pro.

Go EMBT :)

There is one slightly annoying thing that might split the Delphi community - adding to the list of:

- Is Delphi pronounced /del·fai/ or /del·fee/?

- Does "then begin" go on together on the same line?

- FreeAndNil?

and now this to split the community further and create fractions:


https://youtu.be/BOUTfUmI8vs

Well the above was just an excuse for me to include that funny Scottish sketch - no harm done I hope.

BTW: If you wonder about the % prefix - the it is the prefix for binary literals adding in Delphi 11.

/Enjoy