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


Saturday, 22 May 2021

VAT's in it for me? - or I call my layer.

 - or a shout-out to the various services of apilayer.com



Sorry about the intended pun in the title - but it will all make sense.

Have you ever wanted to know the various VAT rates in the EU - no? - well neither have I, because it might put me in a bad mood, but it fits the intended pun for the post, and I also wanted to show how it is done from from Delphi.

The mother company of Embarcadero, Idera acquired another company early this year - this time apilayer.com, which provides numerous "simple" cloud-based API services - among these are:

  • IP geolocation and reverse lookup
  • Language detection
  • Mail address validation
  • Phone number validation
  • Flight tracking
  • Currency conversion and rates (including crypto currency)
  • Weather data and forecast
  • News, Headline and Stock apis
  • Conversion PDF and scraping
But go to https://apilayer.com/, and read more about their various layers and tiers for these.

We will in this short demo look at the vatlayer - which does EU VAT stuff.

So I started by signing up for the free tier on https://vatlayer.com/ - which gives you an API access key and access to a dashboard. As always do not share the access key - just saying.

Now you can fire up Delphi, create a new application and either use the REST Debugger or just manually throw in the REST component or create runtime - what you prefer.

I just threw in a edit control and a couple of memos and buttons, as seem above. And then I added an parameter on the RESTClient with the access_key - since I wanted to clear the request parameters on the RESTRequest, but keep the access_key. And set the BaseURL property to http://apilayer.net/api.

I just took two of the methods/resources from https://vatlayer.com/documentation - so the VAT lookup button looks like this:

var
  json: TJSONValue;
begin
  Memo1.Clear;
  RESTRequest1.Method := rmGET;
  RESTRequest1.Resource := 'validate';
  RESTRequest1.Params.Clear;
  RESTRequest1.AddParameter('vat_number', LabeledEdit2.Text, pkGETorPOST);
  RESTRequest1.Execute;
  if RESTResponse1.StatusCode=200 then
  begin
    json := RESTResponse1.JSONValue;
    Memo1.Lines.Add(json.GetValue<string>('company_name'));
    Memo1.Lines.Add(json.GetValue<string>('company_address'));
  end;
end;

..and the VAT rates for HU (since they have a higher VAT rate than DK :D) button looks like this:

var
  json: TJSONValue;
begin
  Memo2.Clear;
  RESTRequest1.Method := rmGET;
  RESTRequest1.Resource := 'rate';
  RESTRequest1.Params.Clear;
  RESTRequest1.AddParameter('country_code', 'HU', pkGETorPOST);
  RESTRequest1.Execute;
  if RESTResponse1.StatusCode=200 then
  begin
    json := RESTResponse1.JSONValue;
    Memo2.Lines.Add(json.Format());
  end;
end;

There nice twists to these - by IP address or get list of rate types - and if combining these services by apilayer.com - I could make a speeding white-van "whistle-blower" mobile app - since all company owned registered vehicles in DK - need to have their VAT numbers on their fleet.

So I could lookup the company and their address, get their phone number by a different api layer - and then call and tell on them - MUHAAHAAAHAA (evil laughter)

..or maybe I should just create something useful and fun.

And the best part about writing this post, I now learned that Denmark has only the second highest VAT rate in EU :D

/Enjoy




Monday, 15 February 2021

Closing in on Modal Jump Lists

- or getting a WM_SYSCOMMAND to a modal dialog.

An idea that initially seemed clever, has been haunting me a bit lately - I did put a modal dialog into a project before the main Application block (Initialize .. Run). Shame on me.

So the code looks similar to like this:

if (TfrmLaunchSetting.Execute = mrOK) then
begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.Title := 'MyApplication';
  Application.CreateForm(TfrmMain, frmMain);
  Application.Run;
end;

and the TFrmLaunchSetting.Execute is just a class function returning the ModalResult, from its ShowModal.

But here comes the issue at hand - the Close Window in the task bars Jump List sends a WM_SYSCOMMAND that goes to the system menu in the main window - but if a modal form is shown the main window is disabled - and in this case I do not even have a main form yet.

So TApplication.HookMainWindow to the rescue - that would enable me to intercept messages sent to the main form - and act upon these.

So in the FormCreate of my modal dialog I hook the my hook function up like this:

Application.HookMainWindow(AppHookFunc);

And on FormDestroy I do the following:

Application.UnHookMainWindow(AppHookFunc);

The AppHookFunc goes as follows:

function TfrmLaunchSetting.AppHookFunc(var Message: TMessage): Boolean;
begin
  Result := False;
  if Message.Msg = WM_SYSCOMMAND then
  begin
    PostMessage(Handle, WM_CLOSE, 0, 0);
    Result := True;
  end;
end;

This way modal dialogs can react to Windows messages that normally be intended for the main form.

There are probably side-effects - but the issue at hand is solved - for now?

BTW: Yesterday was Delphi 26th birthday - I did not forget - but I am still wrapping up the present. Stay tuned.

/Enjoy

Wednesday, 30 December 2020

It deploys and runs - what a Big Sur prize

- or the initial steps to get a Windows IDE to run cross-platform using CrossVCL.



Sorry about the pun, and I should also mention that this project is WIP, so no workable release/code yet.

Update: In the original image above the language selection was broken/disabled. Read the update in the end of the post on the changes I decided to do to makes this work better overall.

A few days back was a new update of CrossVCL announced in my email - for the lifetime license I had bought years earlier - which triggered an idea.