From c59b55cf2e3643352c867383555c0e22dd0b3b20 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Thu, 4 Jan 2024 18:34:30 +0100 Subject: [PATCH 01/23] Read user profile data via Microsoft Graph API --- demo/tutorial12/AuthFilter.pas | 100 ++++++++++++++++++ demo/tutorial12/CallbackResource.pas | 73 +++++++++++++ demo/tutorial12/MainUnit.pas | 83 +++++++++++++++ .../tutorial12/ReadUserProfileFromMSGraph.dpr | 41 +++++++ demo/tutorial12/RootResource.pas | 94 ++++++++++++++++ 5 files changed, 391 insertions(+) create mode 100644 demo/tutorial12/AuthFilter.pas create mode 100644 demo/tutorial12/CallbackResource.pas create mode 100644 demo/tutorial12/MainUnit.pas create mode 100644 demo/tutorial12/ReadUserProfileFromMSGraph.dpr create mode 100644 demo/tutorial12/RootResource.pas diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas new file mode 100644 index 00000000..f7a29708 --- /dev/null +++ b/demo/tutorial12/AuthFilter.pas @@ -0,0 +1,100 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +unit AuthFilter; + +// note: this is unsupported example code + +interface + +uses + djWebFilter, + djServerContext, + djTypes, + djInterfaces, + Classes, SysUtils; + +type + + { TAuthFilter } + + TAuthFilter = class(TdjWebFilter) + private + RedirectURI: string; + public + procedure Init(const Config: IWebFilterConfig); override; + procedure DoFilter(Context: TdjServerContext; Request: TdjRequest; + Response: TdjResponse; const Chain: IWebFilterChain); override; + end; + +implementation + +function CreateGUIDString: string; +var + Guid: TGUID; +begin + CreateGUID(Guid); + Result := GUIDToString(Guid); +end; + +{ TAuthFilter } + +procedure TAuthFilter.Init(const Config: IWebFilterConfig); +begin + RedirectURI := Config.GetInitParameter('RedirectURI'); +end; + +procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; + Response: TdjResponse; const Chain: IWebFilterChain); +var + Credentials: string; +begin + // TODO: use a 'Authenticated' Flag (on the Session) + Credentials := Request.Session.Content.Values['access_token']; + if Credentials = '' then + begin + Response.Session.Content.Values['nonce'] := CreateGUIDString; + Response.Session.Content.Values['state'] := CreateGUIDString; + // get an ID token and an access token + Response.Redirect('https://login.microsoftonline.com/common/oauth2/v2.0/authorize' + + '?client_id=eadb0a45-0b5e-433f-9407-b25612f7e7c4' // Your app registration's Application (client) ID + + '&response_type=id_token%20token' // Requests both an ID token and access token + + '&redirect_uri=' + RedirectURI + + '&scope=openid User.Read' + + '&response_mode=form_post' // 'form_post' or 'fragment' + + '&state=' + Request.Session.Content.Values['state'] + + '&nonce=' + Request.Session.Content.Values['nonce'] + ); + end + else + begin + Chain.DoFilter(Context, Request, Response); // pass + end; +end; + +end. diff --git a/demo/tutorial12/CallbackResource.pas b/demo/tutorial12/CallbackResource.pas new file mode 100644 index 00000000..c1a3379b --- /dev/null +++ b/demo/tutorial12/CallbackResource.pas @@ -0,0 +1,73 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +unit CallbackResource; + +// note: this is unsupported example code + +interface + +uses + djInterfaces, djWebComponent, djTypes; + +type + + { TCallbackResource } + + TCallbackResource = class(TdjWebComponent) + public + procedure OnPost(Request: TdjRequest; Response: TdjResponse); override; + end; + +implementation + +uses + SysUtils; + +{ TCallbackResource } + +procedure TCallbackResource.OnPost(Request: TdjRequest; Response: TdjResponse); +var + AccessToken: string; +begin + if Request.Params.Values['state'] <> Request.Session.Content.Values['state'] then + begin + Response.ResponseNo := 401; + WriteLn('Invalid state parameter.'); + Exit; + end; + + // Read the access_token + AccessToken := Request.Params.Values['access_token']; + + Response.Session.Content.Values['access_token'] := AccessToken; + Response.Redirect('/index.html'); +end; + +end. + diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas new file mode 100644 index 00000000..4cb098cb --- /dev/null +++ b/demo/tutorial12/MainUnit.pas @@ -0,0 +1,83 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +unit MainUnit; + +// note: this is unsupported example code + +interface + +procedure Demo; + +implementation + +uses + AuthFilter, + CallbackResource, + RootResource, + djServer, djWebAppContext, djNCSALogFilter, + djWebComponentHolder, djWebFilterHolder, + ShellAPI, SysUtils; + +procedure Demo; +const + // URI must match OAuth 2.0 settings in Entra configuration + REDIRECT_URI = 'http://localhost/callback'; +var + Context: TdjWebAppContext; + FilterHolder: TdjWebFilterHolder; + Server: TdjServer; +begin + FilterHolder := TdjWebFilterHolder.Create(TAuthFilter); + FilterHolder.SetInitParameter('RedirectURI', REDIRECT_URI); + + Context := TdjWebAppContext.Create('', True); + Context.AddWebComponent(TRootResource, '/index.html'); + Context.AddWebComponent(TCallbackResource, '/callback'); + Context.AddWebFilter(FilterHolder, '*.html'); + + Server := TdjServer.Create(80); + try + try + Server.Add(Context); + Server.Start; + + ShellExecute(0, 'open', PChar('http://localhost/index.html'), '', '', 0); + + WriteLn('Server is running, launching http://localhost/index.html ...'); + WriteLn('Hit any key to terminate.'); + except + on E: Exception do WriteLn(E.Message); + end; + ReadLn; + finally + Server.Free; + end; +end; + +end. diff --git a/demo/tutorial12/ReadUserProfileFromMSGraph.dpr b/demo/tutorial12/ReadUserProfileFromMSGraph.dpr new file mode 100644 index 00000000..63575731 --- /dev/null +++ b/demo/tutorial12/ReadUserProfileFromMSGraph.dpr @@ -0,0 +1,41 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +program ReadUserProfileFromMSGraph; + +// note: this is unsupported example code + +{$APPTYPE CONSOLE} + +uses + MainUnit; + +begin + Demo; +end. + diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas new file mode 100644 index 00000000..52698e73 --- /dev/null +++ b/demo/tutorial12/RootResource.pas @@ -0,0 +1,94 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +unit RootResource; + +// note: this is unsupported example code + +interface + +uses + djWebComponent, djTypes; + +type + + { TRootResource } + + TRootResource = class(TdjWebComponent) + private + function ReadUserProfile(const AccessToken: string): string; + public + procedure OnGet(Request: TdjRequest; Response: TdjResponse); override; + end; + +implementation + +uses + IdHTTP, IdSSLOpenSSL, IdSSLOpenSSLHeaders, + SysUtils; + +{ TRootResource } + +procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); +var + AccessToken: string; + APIResponse: string; +begin + AccessToken := Request.Session.Content.Values['access_token']; + + APIResponse := ReadUserProfile(AccessToken); + + Response.ContentText := Format('Hello, World! %s', + [APIResponse]); + Response.ContentType := 'text/html'; + Response.CharSet := 'utf-8'; +end; + +function TRootResource.ReadUserProfile(const AccessToken: string): string; +var + HTTP: TIdHTTP; + IOHandler: TIdSSLIOHandlerSocketOpenSSL; +begin + HTTP := TIdHTTP.Create; + try + try + IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); + IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; + HTTP.IOHandler := IOHandler; + HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; + Result := HTTP.Get('https://graph.microsoft.com/v1.0/users/me'); + except + WriteLn(IdSSLOpenSSLHeaders.WhichFailedToLoad); + raise; + end; + finally + HTTP.Free; + end; +end; + +end. From e5ab0bc4df1f8a208ad4b6c18c5b9f46c73516f7 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Thu, 4 Jan 2024 18:49:48 +0100 Subject: [PATCH 02/23] Read user profile data via Microsoft Graph API Write all header params (including access token) to the console for debugging --- demo/tutorial12/CallbackResource.pas | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/demo/tutorial12/CallbackResource.pas b/demo/tutorial12/CallbackResource.pas index c1a3379b..4571927b 100644 --- a/demo/tutorial12/CallbackResource.pas +++ b/demo/tutorial12/CallbackResource.pas @@ -54,6 +54,7 @@ implementation procedure TCallbackResource.OnPost(Request: TdjRequest; Response: TdjResponse); var AccessToken: string; + P: string; begin if Request.Params.Values['state'] <> Request.Session.Content.Values['state'] then begin @@ -62,6 +63,11 @@ procedure TCallbackResource.OnPost(Request: TdjRequest; Response: TdjResponse); Exit; end; + for P in Request.Params do + begin + WriteLn(P); + end; + // Read the access_token AccessToken := Request.Params.Values['access_token']; From 0be0baf41710442dd612b8681448efc7116e2053 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Thu, 4 Jan 2024 18:56:38 +0100 Subject: [PATCH 03/23] Read user profile data via Microsoft Graph API Use init params --- demo/tutorial12/AuthFilter.pas | 8 ++++++-- demo/tutorial12/MainUnit.pas | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index f7a29708..89b13716 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -45,6 +45,8 @@ interface TAuthFilter = class(TdjWebFilter) private + AuthorizeEndpoint: string; + ClientID: string; RedirectURI: string; public procedure Init(const Config: IWebFilterConfig); override; @@ -66,6 +68,8 @@ function CreateGUIDString: string; procedure TAuthFilter.Init(const Config: IWebFilterConfig); begin + AuthorizeEndpoint := Config.GetInitParameter('AuthorizeEndpoint'); + ClientID := Config.GetInitParameter('ClientID'); RedirectURI := Config.GetInitParameter('RedirectURI'); end; @@ -81,8 +85,8 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; Response.Session.Content.Values['nonce'] := CreateGUIDString; Response.Session.Content.Values['state'] := CreateGUIDString; // get an ID token and an access token - Response.Redirect('https://login.microsoftonline.com/common/oauth2/v2.0/authorize' - + '?client_id=eadb0a45-0b5e-433f-9407-b25612f7e7c4' // Your app registration's Application (client) ID + Response.Redirect(AuthorizeEndpoint + + '?client_id=' + ClientID // Your app registration's Application (client) ID + '&response_type=id_token%20token' // Requests both an ID token and access token + '&redirect_uri=' + RedirectURI + '&scope=openid User.Read' diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index 4cb098cb..8528740e 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -46,6 +46,9 @@ implementation procedure Demo; const + AUTHORIZE_ENDPOINT = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'; + // From Entra configuration + CLIENT_ID = 'eadb0a45-0b5e-433f-9407-b25612f7e7c4'; // URI must match OAuth 2.0 settings in Entra configuration REDIRECT_URI = 'http://localhost/callback'; var @@ -54,6 +57,8 @@ procedure Demo; Server: TdjServer; begin FilterHolder := TdjWebFilterHolder.Create(TAuthFilter); + FilterHolder.SetInitParameter('AuthorizeEndpoint', AUTHORIZE_ENDPOINT); + FilterHolder.SetInitParameter('ClientID', CLIENT_ID); FilterHolder.SetInitParameter('RedirectURI', REDIRECT_URI); Context := TdjWebAppContext.Create('', True); From 27305740976fef812f3b6fd907f8ea8477f2f7c0 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Thu, 4 Jan 2024 19:08:08 +0100 Subject: [PATCH 04/23] Read user profile data via Microsoft Graph API Better variable name: AccessToken --- demo/tutorial12/AuthFilter.pas | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 89b13716..78fd791b 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -76,11 +76,10 @@ procedure TAuthFilter.Init(const Config: IWebFilterConfig); procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; Response: TdjResponse; const Chain: IWebFilterChain); var - Credentials: string; + AccessToken: string; begin - // TODO: use a 'Authenticated' Flag (on the Session) - Credentials := Request.Session.Content.Values['access_token']; - if Credentials = '' then + AccessToken := Request.Session.Content.Values['access_token']; + if AccessToken = '' then begin Response.Session.Content.Values['nonce'] := CreateGUIDString; Response.Session.Content.Values['state'] := CreateGUIDString; From d186637858c4a808d044f7a91de85a106b2044db Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Thu, 4 Jan 2024 19:09:29 +0100 Subject: [PATCH 05/23] Read user profile data via Microsoft Graph API Do not request an id_token (we don't need it) --- demo/tutorial12/AuthFilter.pas | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 78fd791b..9f68565d 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -83,10 +83,10 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; begin Response.Session.Content.Values['nonce'] := CreateGUIDString; Response.Session.Content.Values['state'] := CreateGUIDString; - // get an ID token and an access token + // get an access token Response.Redirect(AuthorizeEndpoint + '?client_id=' + ClientID // Your app registration's Application (client) ID - + '&response_type=id_token%20token' // Requests both an ID token and access token + + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI + '&scope=openid User.Read' + '&response_mode=form_post' // 'form_post' or 'fragment' From be833dfcd1dfca8d6a27d4be974a4822f9ef8d46 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 18:16:55 +0100 Subject: [PATCH 06/23] Do not request openid scope --- demo/tutorial12/AuthFilter.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 9f68565d..68794fc8 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -88,7 +88,7 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; + '?client_id=' + ClientID // Your app registration's Application (client) ID + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI - + '&scope=openid User.Read' + + '&scope=User.Read' + '&response_mode=form_post' // 'form_post' or 'fragment' + '&state=' + Request.Session.Content.Values['state'] + '&nonce=' + Request.Session.Content.Values['nonce'] From 467b4d94ee90c2103ec37578b9c55346c6079485 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 18:17:35 +0100 Subject: [PATCH 07/23] New app registration: "Tutorial 12 for Daraja HTTP Framework" --- demo/tutorial12/MainUnit.pas | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index 8528740e..285f634e 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -46,11 +46,12 @@ implementation procedure Demo; const - AUTHORIZE_ENDPOINT = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize'; + AUTHORIZE_ENDPOINT = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize'; // From Entra configuration - CLIENT_ID = 'eadb0a45-0b5e-433f-9407-b25612f7e7c4'; - // URI must match OAuth 2.0 settings in Entra configuration - REDIRECT_URI = 'http://localhost/callback'; + CLIENT_ID = 'ee5a0402-2861-44a2-b0e1-d79bfafbe56a'; + // Redirect URI must match Entra configuration + REDIRECT_PATH = '/auth-response'; + REDIRECT_URI = 'http://localhost' + REDIRECT_PATH; var Context: TdjWebAppContext; FilterHolder: TdjWebFilterHolder; @@ -63,7 +64,7 @@ procedure Demo; Context := TdjWebAppContext.Create('', True); Context.AddWebComponent(TRootResource, '/index.html'); - Context.AddWebComponent(TCallbackResource, '/callback'); + Context.AddWebComponent(TCallbackResource, REDIRECT_PATH); Context.AddWebFilter(FilterHolder, '*.html'); Server := TdjServer.Create(80); From 003df0ec318dc815a9070dd0c44159f71e6e0825 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:05:52 +0100 Subject: [PATCH 08/23] Add Mail.Send scope --- demo/tutorial12/AuthFilter.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 68794fc8..3d9b5a5e 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -88,7 +88,7 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; + '?client_id=' + ClientID // Your app registration's Application (client) ID + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI - + '&scope=User.Read' + + '&scope=User.Read Mail.Send' + '&response_mode=form_post' // 'form_post' or 'fragment' + '&state=' + Request.Session.Content.Values['state'] + '&nonce=' + Request.Session.Content.Values['nonce'] From 27a814cfde98aa6e45bfe3115b8d199e66139d4c Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:06:18 +0100 Subject: [PATCH 09/23] Send a test mail --- demo/tutorial12/RootResource.pas | 69 +++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index 52698e73..6f2c1eb5 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -42,6 +42,7 @@ interface TRootResource = class(TdjWebComponent) private function ReadUserProfile(const AccessToken: string): string; + function SendMail(const AccessToken: string): string; public procedure OnGet(Request: TdjRequest; Response: TdjResponse); override; end; @@ -50,7 +51,7 @@ implementation uses IdHTTP, IdSSLOpenSSL, IdSSLOpenSSLHeaders, - SysUtils; + SysUtils, Classes; { TRootResource } @@ -63,6 +64,8 @@ procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); APIResponse := ReadUserProfile(AccessToken); + SendMail(AccessToken); + Response.ContentText := Format('Hello, World! %s', [APIResponse]); Response.ContentType := 'text/html'; @@ -91,4 +94,68 @@ function TRootResource.ReadUserProfile(const AccessToken: string): string; end; end; +function TRootResource.SendMail(const AccessToken: string): string; +const + JSON = '{'+ #10 + +'"message": {'+ #10 + +'"subject": "Meet for lunch?",'+ #10 + +'"body": {'+ #10 + +' "contentType": "Text",'+ #10 + +' "content": "The new cafeteria is open."'+ #10 + +'},'+ #10 + +'"toRecipients": ['+ #10 + +' {'+ #10 + +' "emailAddress": {'+ #10 + +' "address": "info@habarisoft.com"'+ #10 + +' }'+ #10 + +' }'+ #10 + +'],'+ #10 + +'"ccRecipients": ['+ #10 + +' {'+ #10 + +' "emailAddress": {'+ #10 + +' "address": "info@habarisoft.com"'+ #10 + +' }'+ #10 + +' }'+ #10 + +']'+ #10 + +'},'+ #10 + +'"saveToSentItems": "false"'+ #10 + +'}'; +var + HTTP: TIdHTTP; + IOHandler: TIdSSLIOHandlerSocketOpenSSL; + Body: TStream; +begin + WriteLn(JSON); + + HTTP := TIdHTTP.Create; + try + try + IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); + IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; + HTTP.IOHandler := IOHandler; + HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; + HTTP.Request.ContentType := 'application/json'; + Body := TStringStream.Create(JSON, TEncoding.UTF8); + WriteLn( + HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', Body) + ); + except + on E: EIdHTTPProtocolException do + begin + // WriteLn(IdSSLOpenSSLHeaders.WhichFailedToLoad); + WriteLn(E.Message); + WriteLn(E.ErrorMessage); + raise; + end; + on E: Exception do + begin + WriteLn(E.Message); + raise; + end; + end; + finally + HTTP.Free; + end; +end; + end. From b3228854a4a03d22f4d5fb783602db1b98f13dd9 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:15:22 +0100 Subject: [PATCH 10/23] No CC address --- demo/tutorial12/RootResource.pas | 50 ++++++++++++++------------------ 1 file changed, 22 insertions(+), 28 deletions(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index 6f2c1eb5..d44d5787 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -96,34 +96,29 @@ function TRootResource.ReadUserProfile(const AccessToken: string): string; function TRootResource.SendMail(const AccessToken: string): string; const - JSON = '{'+ #10 - +'"message": {'+ #10 - +'"subject": "Meet for lunch?",'+ #10 - +'"body": {'+ #10 - +' "contentType": "Text",'+ #10 - +' "content": "The new cafeteria is open."'+ #10 - +'},'+ #10 - +'"toRecipients": ['+ #10 - +' {'+ #10 - +' "emailAddress": {'+ #10 - +' "address": "info@habarisoft.com"'+ #10 - +' }'+ #10 - +' }'+ #10 - +'],'+ #10 - +'"ccRecipients": ['+ #10 - +' {'+ #10 - +' "emailAddress": {'+ #10 - +' "address": "info@habarisoft.com"'+ #10 - +' }'+ #10 - +' }'+ #10 - +']'+ #10 - +'},'+ #10 - +'"saveToSentItems": "false"'+ #10 + JSON = + '{'+ #10 + +' "message": {'+ #10 + +' "subject": "Meet for lunch?",'+ #10 + +' "body": {'+ #10 + +' "contentType": "Text",'+ #10 + +' "content": "The new cafeteria is open."'+ #10 + +' },'+ #10 + +' "toRecipients": ['+ #10 + +' {'+ #10 + +' "emailAddress": {'+ #10 + +' "address": "info@habarisoft.com"'+ #10 + +' }'+ #10 + +' }'+ #10 + +' ]'+ #10 + +' },'+ #10 + +' "saveToSentItems": "false"'+ #10 +'}'; var HTTP: TIdHTTP; IOHandler: TIdSSLIOHandlerSocketOpenSSL; - Body: TStream; + RequestBody: TStream; + ResponseBody: string; begin WriteLn(JSON); @@ -135,10 +130,9 @@ function TRootResource.SendMail(const AccessToken: string): string; HTTP.IOHandler := IOHandler; HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; HTTP.Request.ContentType := 'application/json'; - Body := TStringStream.Create(JSON, TEncoding.UTF8); - WriteLn( - HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', Body) - ); + RequestBody := TStringStream.Create(JSON, TEncoding.UTF8); + ResponseBody := HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', RequestBody); + WriteLn(ResponseBody); except on E: EIdHTTPProtocolException do begin From 1e846a519380cae19fb588d1f00e9c24c1022bfd Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:29:22 +0100 Subject: [PATCH 11/23] Update demo/tutorial12/RootResource.pas --- demo/tutorial12/RootResource.pas | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index d44d5787..78a64006 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -136,7 +136,6 @@ function TRootResource.SendMail(const AccessToken: string): string; except on E: EIdHTTPProtocolException do begin - // WriteLn(IdSSLOpenSSLHeaders.WhichFailedToLoad); WriteLn(E.Message); WriteLn(E.ErrorMessage); raise; From 4b0c6fb6847b39c48b04b6013482dcf1a49bdc44 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:29:30 +0100 Subject: [PATCH 12/23] Update demo/tutorial12/AuthFilter.pas --- demo/tutorial12/AuthFilter.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 3d9b5a5e..506ab548 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -88,7 +88,7 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; + '?client_id=' + ClientID // Your app registration's Application (client) ID + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI - + '&scope=User.Read Mail.Send' + + '&scope=User.Read Mail.Send' // Request read profile and send mail permission + '&response_mode=form_post' // 'form_post' or 'fragment' + '&state=' + Request.Session.Content.Values['state'] + '&nonce=' + Request.Session.Content.Values['nonce'] From 37996927756709f33608d043b1178e99d8613a5a Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:29:39 +0100 Subject: [PATCH 13/23] Update demo/tutorial12/AuthFilter.pas --- demo/tutorial12/AuthFilter.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index 506ab548..d2d8c72e 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -89,7 +89,7 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI + '&scope=User.Read Mail.Send' // Request read profile and send mail permission - + '&response_mode=form_post' // 'form_post' or 'fragment' + + '&response_mode=form_post' + '&state=' + Request.Session.Content.Values['state'] + '&nonce=' + Request.Session.Content.Values['nonce'] ); From feb218654ff8f44d5a68f8630adfdd0c72bce6b1 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:29:46 +0100 Subject: [PATCH 14/23] Update demo/tutorial12/MainUnit.pas --- demo/tutorial12/MainUnit.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index 285f634e..e742f653 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -47,7 +47,7 @@ implementation procedure Demo; const AUTHORIZE_ENDPOINT = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize'; - // From Entra configuration + // Application (client) ID from Entra configuration CLIENT_ID = 'ee5a0402-2861-44a2-b0e1-d79bfafbe56a'; // Redirect URI must match Entra configuration REDIRECT_PATH = '/auth-response'; From bdaab07e7acb006b3ae3acd765dc53adeded0270 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:30:07 +0100 Subject: [PATCH 15/23] Update demo/tutorial12/MainUnit.pas --- demo/tutorial12/MainUnit.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index e742f653..c3710db2 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -75,7 +75,7 @@ procedure Demo; ShellExecute(0, 'open', PChar('http://localhost/index.html'), '', '', 0); - WriteLn('Server is running, launching http://localhost/index.html ...'); + WriteLn('Server is running, launching web browser ...'); WriteLn('Hit any key to terminate.'); except on E: Exception do WriteLn(E.Message); From c0193106cdf14d5eeb3eea827b5a6c6d03b19c40 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:30:17 +0100 Subject: [PATCH 16/23] Update demo/tutorial12/RootResource.pas --- demo/tutorial12/RootResource.pas | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index 78a64006..1b667e23 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -66,7 +66,7 @@ procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); SendMail(AccessToken); - Response.ContentText := Format('Hello, World! %s', + Response.ContentText := Format('

Hello, World!

User profile:

%s

', [APIResponse]); Response.ContentType := 'text/html'; Response.CharSet := 'utf-8'; From 24e2a4ba443c7bd282f0214de11ee243b2b63709 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Fri, 5 Jan 2024 19:40:20 +0100 Subject: [PATCH 17/23] Improve HTML output, improve response output --- demo/tutorial12/RootResource.pas | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index 1b667e23..eb413ad0 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -66,7 +66,9 @@ procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); SendMail(AccessToken); - Response.ContentText := Format('

Hello, World!

User profile:

%s

', + Response.ContentText := Format('

Graph API example

' + + '

Signed-in user profile:

%s

' + + '

Send email:

Email has been sent.

', [APIResponse]); Response.ContentType := 'text/html'; Response.CharSet := 'utf-8'; @@ -132,7 +134,7 @@ function TRootResource.SendMail(const AccessToken: string): string; HTTP.Request.ContentType := 'application/json'; RequestBody := TStringStream.Create(JSON, TEncoding.UTF8); ResponseBody := HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', RequestBody); - WriteLn(ResponseBody); + WriteLn('Response: "' + ResponseBody + '"'); except on E: EIdHTTPProtocolException do begin From 7ad3bb9f0303d7b4e689a923c000f6070c812073 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Sat, 6 Jan 2024 16:22:53 +0100 Subject: [PATCH 18/23] Rename CallbackResource to AuthResponseResource --- .../{CallbackResource.pas => AuthResponseResource.pas} | 10 +++++----- demo/tutorial12/MainUnit.pas | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename demo/tutorial12/{CallbackResource.pas => AuthResponseResource.pas} (89%) diff --git a/demo/tutorial12/CallbackResource.pas b/demo/tutorial12/AuthResponseResource.pas similarity index 89% rename from demo/tutorial12/CallbackResource.pas rename to demo/tutorial12/AuthResponseResource.pas index 4571927b..f7779889 100644 --- a/demo/tutorial12/CallbackResource.pas +++ b/demo/tutorial12/AuthResponseResource.pas @@ -26,7 +26,7 @@ *) -unit CallbackResource; +unit AuthResponseResource; // note: this is unsupported example code @@ -37,9 +37,9 @@ interface type - { TCallbackResource } + { TAuthResponseResource } - TCallbackResource = class(TdjWebComponent) + TAuthResponseResource = class(TdjWebComponent) public procedure OnPost(Request: TdjRequest; Response: TdjResponse); override; end; @@ -49,9 +49,9 @@ implementation uses SysUtils; -{ TCallbackResource } +{ TAuthResponseResource } -procedure TCallbackResource.OnPost(Request: TdjRequest; Response: TdjResponse); +procedure TAuthResponseResource.OnPost(Request: TdjRequest; Response: TdjResponse); var AccessToken: string; P: string; diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index c3710db2..d267a709 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -38,7 +38,7 @@ implementation uses AuthFilter, - CallbackResource, + AuthResponseResource, RootResource, djServer, djWebAppContext, djNCSALogFilter, djWebComponentHolder, djWebFilterHolder, @@ -64,7 +64,7 @@ procedure Demo; Context := TdjWebAppContext.Create('', True); Context.AddWebComponent(TRootResource, '/index.html'); - Context.AddWebComponent(TCallbackResource, REDIRECT_PATH); + Context.AddWebComponent(TAuthResponseResource, REDIRECT_PATH); Context.AddWebFilter(FilterHolder, '*.html'); Server := TdjServer.Create(80); From 1ad2346026596e683306091747207f636609f109 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Sat, 6 Jan 2024 16:43:33 +0100 Subject: [PATCH 19/23] Add FPC project (lpr) Add NCSA logger --- demo/tutorial12/AuthResponseResource.pas | 2 +- demo/tutorial12/MainUnit.pas | 4 +- .../tutorial12/ReadUserProfileFromMSGraph.lpr | 41 +++++++++++++++++++ demo/tutorial12/RootResource.pas | 2 + 4 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 demo/tutorial12/ReadUserProfileFromMSGraph.lpr diff --git a/demo/tutorial12/AuthResponseResource.pas b/demo/tutorial12/AuthResponseResource.pas index f7779889..2fa7dda0 100644 --- a/demo/tutorial12/AuthResponseResource.pas +++ b/demo/tutorial12/AuthResponseResource.pas @@ -33,7 +33,7 @@ interface uses - djInterfaces, djWebComponent, djTypes; + djWebComponent, djTypes; type diff --git a/demo/tutorial12/MainUnit.pas b/demo/tutorial12/MainUnit.pas index d267a709..cb4af855 100644 --- a/demo/tutorial12/MainUnit.pas +++ b/demo/tutorial12/MainUnit.pas @@ -40,8 +40,7 @@ implementation AuthFilter, AuthResponseResource, RootResource, - djServer, djWebAppContext, djNCSALogFilter, - djWebComponentHolder, djWebFilterHolder, + djServer, djWebAppContext, djNCSALogFilter, djWebFilterHolder, ShellAPI, SysUtils; procedure Demo; @@ -66,6 +65,7 @@ procedure Demo; Context.AddWebComponent(TRootResource, '/index.html'); Context.AddWebComponent(TAuthResponseResource, REDIRECT_PATH); Context.AddWebFilter(FilterHolder, '*.html'); + Context.AddFilterWithMapping(TdjNCSALogFilter, '/*'); Server := TdjServer.Create(80); try diff --git a/demo/tutorial12/ReadUserProfileFromMSGraph.lpr b/demo/tutorial12/ReadUserProfileFromMSGraph.lpr new file mode 100644 index 00000000..63575731 --- /dev/null +++ b/demo/tutorial12/ReadUserProfileFromMSGraph.lpr @@ -0,0 +1,41 @@ +(* + + Daraja HTTP Framework + Copyright (C) Michael Justin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + You can be released from the requirements of the license by purchasing + a commercial license. Buying such a license is mandatory as soon as you + develop commercial activities involving the Daraja framework without + disclosing the source code of your own applications. These activities + include: offering paid services to customers as an ASP, shipping Daraja + with a closed source product. + +*) + +program ReadUserProfileFromMSGraph; + +// note: this is unsupported example code + +{$APPTYPE CONSOLE} + +uses + MainUnit; + +begin + Demo; +end. + diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index eb413ad0..aea8f554 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -50,7 +50,9 @@ TRootResource = class(TdjWebComponent) implementation uses + {$IFDEF FPC}{$NOTES OFF}{$ENDIF}{$HINTS OFF}{$WARNINGS OFF} IdHTTP, IdSSLOpenSSL, IdSSLOpenSSLHeaders, + {$IFDEF FPC}{$ELSE}{$HINTS ON}{$WARNINGS ON}{$ENDIF} SysUtils, Classes; { TRootResource } From b06622ce5d7bf0c399b3fe5016cb1a445d99fa19 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Sat, 6 Jan 2024 16:59:16 +0100 Subject: [PATCH 20/23] Refactor CreateIdHTTPwithSSL12 --- demo/tutorial12/RootResource.pas | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index aea8f554..323fd2f3 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -55,6 +55,19 @@ implementation {$IFDEF FPC}{$ELSE}{$HINTS ON}{$WARNINGS ON}{$ENDIF} SysUtils, Classes; +function CreateIdHTTPwithSSL12(const AccessToken: string): TIdHTTP; +var + IOHandler: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := TIdHTTP.Create; + + IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(Result); + IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; + Result.IOHandler := IOHandler; + + Result.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; +end; + { TRootResource } procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); @@ -79,15 +92,10 @@ procedure TRootResource.OnGet(Request: TdjRequest; Response: TdjResponse); function TRootResource.ReadUserProfile(const AccessToken: string): string; var HTTP: TIdHTTP; - IOHandler: TIdSSLIOHandlerSocketOpenSSL; begin - HTTP := TIdHTTP.Create; + HTTP := CreateIdHTTPwithSSL12(AccessToken); try try - IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); - IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; - HTTP.IOHandler := IOHandler; - HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; Result := HTTP.Get('https://graph.microsoft.com/v1.0/users/me'); except WriteLn(IdSSLOpenSSLHeaders.WhichFailedToLoad); @@ -120,19 +128,14 @@ function TRootResource.SendMail(const AccessToken: string): string; +'}'; var HTTP: TIdHTTP; - IOHandler: TIdSSLIOHandlerSocketOpenSSL; RequestBody: TStream; ResponseBody: string; begin WriteLn(JSON); - HTTP := TIdHTTP.Create; + HTTP := CreateIdHTTPwithSSL12(AccessToken); try try - IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(HTTP); - IOHandler.SSLOptions.SSLVersions := [sslvTLSv1_2]; - HTTP.IOHandler := IOHandler; - HTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + AccessToken; HTTP.Request.ContentType := 'application/json'; RequestBody := TStringStream.Create(JSON, TEncoding.UTF8); ResponseBody := HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', RequestBody); From b989e3a96a23b083e7a121db09ddc214408d2b95 Mon Sep 17 00:00:00 2001 From: Michael Justin Date: Sat, 6 Jan 2024 17:05:32 +0100 Subject: [PATCH 21/23] Add FPC project (lpi) --- .../tutorial12/ReadUserProfileFromMSGraph.lpi | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 demo/tutorial12/ReadUserProfileFromMSGraph.lpi diff --git a/demo/tutorial12/ReadUserProfileFromMSGraph.lpi b/demo/tutorial12/ReadUserProfileFromMSGraph.lpi new file mode 100644 index 00000000..057794ef --- /dev/null +++ b/demo/tutorial12/ReadUserProfileFromMSGraph.lpi @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="ReadUserProfileFromMSGraph.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="ReadUserProfileFromMSGraph"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <SyntaxMode Value="Delphi"/> + </SyntaxOptions> + </Parsing> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf3"/> + </Debugging> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + <Item> + <Name Value="EBTStompClientNotConnectedError"/> + </Item> + <Item> + <Name Value="ESynapseError"/> + </Item> + <Item> + <Name Value="EAssertionFailedError"/> + </Item> + <Item> + <Name Value="EIdReadTimeout"/> + </Item> + <Item> + <Name Value="EConnectionFailedException"/> + </Item> + <Item> + <Name Value="Exception"/> + </Item> + <Item> + <Name Value="EIgnoredTest"/> + </Item> + <Item> + <Name Value="EBTStompServerErrorMessage"/> + </Item> + <Item> + <Name Value="EBTStompServerHeartbeatMissing"/> + </Item> + <Item> + <Name Value="EJMSException"/> + </Item> + <Item> + <Name Value="EBTStompError"/> + </Item> + <Item> + <Name Value="EIdSocketError"/> + </Item> + <Item> + <Name Value="EIdConnClosedGracefully"/> + </Item> + <Item> + <Name Value="EConvertError"/> + </Item> + <Item> + <Name Value="EIllegalStateException"/> + </Item> + <Item> + <Name Value="EIllegalArgumentException"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> From 98c990c6c6a0321f3ca97a09fe6c0e588c4a0d79 Mon Sep 17 00:00:00 2001 From: Michael Justin <michael@mikejustin.com> Date: Sat, 6 Jan 2024 17:06:10 +0100 Subject: [PATCH 22/23] Nonce is not required Use local variable for State --- demo/tutorial12/AuthFilter.pas | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/demo/tutorial12/AuthFilter.pas b/demo/tutorial12/AuthFilter.pas index d2d8c72e..8804dae8 100644 --- a/demo/tutorial12/AuthFilter.pas +++ b/demo/tutorial12/AuthFilter.pas @@ -77,21 +77,20 @@ procedure TAuthFilter.DoFilter(Context: TdjServerContext; Request: TdjRequest; Response: TdjResponse; const Chain: IWebFilterChain); var AccessToken: string; + State: string; begin AccessToken := Request.Session.Content.Values['access_token']; if AccessToken = '' then begin - Response.Session.Content.Values['nonce'] := CreateGUIDString; - Response.Session.Content.Values['state'] := CreateGUIDString; - // get an access token + State := CreateGUIDString; + Response.Session.Content.Values['state'] := State; Response.Redirect(AuthorizeEndpoint + '?client_id=' + ClientID // Your app registration's Application (client) ID + '&response_type=token' // Requests an access token + '&redirect_uri=' + RedirectURI + '&scope=User.Read Mail.Send' // Request read profile and send mail permission + '&response_mode=form_post' - + '&state=' + Request.Session.Content.Values['state'] - + '&nonce=' + Request.Session.Content.Values['nonce'] + + '&state=' + State ); end else From 7547a8979b4fe12a3e88561b7c6db550f16927d0 Mon Sep 17 00:00:00 2001 From: Michael Justin <michael@mikejustin.com> Date: Sat, 6 Jan 2024 17:20:49 +0100 Subject: [PATCH 23/23] Comment out debug logging --- demo/tutorial12/AuthResponseResource.pas | 10 +++++----- demo/tutorial12/RootResource.pas | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/demo/tutorial12/AuthResponseResource.pas b/demo/tutorial12/AuthResponseResource.pas index 2fa7dda0..97760fb6 100644 --- a/demo/tutorial12/AuthResponseResource.pas +++ b/demo/tutorial12/AuthResponseResource.pas @@ -54,7 +54,7 @@ implementation procedure TAuthResponseResource.OnPost(Request: TdjRequest; Response: TdjResponse); var AccessToken: string; - P: string; + // P: string; begin if Request.Params.Values['state'] <> Request.Session.Content.Values['state'] then begin @@ -63,10 +63,10 @@ procedure TAuthResponseResource.OnPost(Request: TdjRequest; Response: TdjRespons Exit; end; - for P in Request.Params do - begin - WriteLn(P); - end; + // for P in Request.Params do + // begin + // WriteLn(P); + // end; // Read the access_token AccessToken := Request.Params.Values['access_token']; diff --git a/demo/tutorial12/RootResource.pas b/demo/tutorial12/RootResource.pas index 323fd2f3..81c85d55 100644 --- a/demo/tutorial12/RootResource.pas +++ b/demo/tutorial12/RootResource.pas @@ -131,7 +131,7 @@ function TRootResource.SendMail(const AccessToken: string): string; RequestBody: TStream; ResponseBody: string; begin - WriteLn(JSON); + // WriteLn(JSON); HTTP := CreateIdHTTPwithSSL12(AccessToken); try @@ -139,7 +139,7 @@ function TRootResource.SendMail(const AccessToken: string): string; HTTP.Request.ContentType := 'application/json'; RequestBody := TStringStream.Create(JSON, TEncoding.UTF8); ResponseBody := HTTP.Post('https://graph.microsoft.com/v1.0/me/sendMail', RequestBody); - WriteLn('Response: "' + ResponseBody + '"'); + // WriteLn('Response: "' + ResponseBody + '"'); except on E: EIdHTTPProtocolException do begin