diff --git a/3_0_0_breaking_changes.md b/3_0_0_breaking_changes.md index 7f2ac384..9e988d28 100644 --- a/3_0_0_breaking_changes.md +++ b/3_0_0_breaking_changes.md @@ -49,4 +49,5 @@ The memory allocated for the TJSONValue descendant (e.g. TJSONObject, TJSONArray function GetNewStompClient(const AClientId: string = ''): IStompClient; ``` -- ```TMVCConfigKey.ISAPIPath``` has been substituted with ```TMVCConfigKey.PathPrefix``` and now is applicated to all kind of projects (not only ISAPI); \ No newline at end of file +- ```TMVCConfigKey.ISAPIPath``` has been substituted with ```TMVCConfigKey.PathPrefix``` and now is applicated to all kind of projects (not only ISAPI); +- ```MapperSerializeAsString``` attribute is now ```MVCSerializeAsString``` \ No newline at end of file diff --git a/CHANGES.TXT b/CHANGES.TXT index 3673e0a7..b411ad98 100644 --- a/CHANGES.TXT +++ b/CHANGES.TXT @@ -1,3 +1,6 @@ +11/07/2017 +- Added LiveValidityWindowsInSeconds in the JWT middleware. + 02/10/2016 - Added LoadViewFragment. The view fragment is appended to the ResponseStream verbatim. No processing happens. procedure LoadViewFragment(const ViewFragment: string); diff --git a/samples/jsonwebtoken_livevaliditywindow/AppControllerU.pas b/samples/jsonwebtoken_livevaliditywindow/AppControllerU.pas new file mode 100644 index 00000000..977f858c --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/AppControllerU.pas @@ -0,0 +1,104 @@ +unit AppControllerU; + +interface + +uses + MVCFramework, + MVCFramework.Commons, + MVCFramework.Logger, + Web.HTTPApp; + +type + + [MVCPath('/')] + TApp1MainController = class(TMVCController) + public + [MVCPath('/public')] + [MVCHTTPMethod([httpGET])] + procedure PublicSection(ctx: TWebContext); + [MVCPath('/')] + [MVCHTTPMethod([httpGET])] + procedure Index(ctx: TWebContext); + end; + + [MVCPath('/admin')] + TAdminController = class(TMVCController) + public + [MVCPath('/role1')] + [MVCProduces('text/html')] + [MVCHTTPMethod([httpGET])] + procedure OnlyRole1(ctx: TWebContext); + [MVCPath('/role1')] + [MVCProduces('application/json')] + [MVCHTTPMethod([httpGET])] + procedure OnlyRole1EmittingJSON; + [MVCPath('/role2')] + [MVCProduces('text/html')] + [MVCHTTPMethod([httpGET])] + procedure OnlyRole2(ctx: TWebContext); + end; + +implementation + +uses + System.SysUtils, System.JSON, System.Classes; + +{ TApp1MainController } + +procedure TApp1MainController.Index(ctx: TWebContext); +begin + Redirect('/index.html'); +end; + +procedure TApp1MainController.PublicSection(ctx: TWebContext); +begin + Render('This is a public section'); +end; + +{ TAdminController } + +procedure TAdminController.OnlyRole1(ctx: TWebContext); +begin + ContentType := TMVCMediaType.TEXT_PLAIN; + ResponseStream.AppendLine('Hey! Hello ' + ctx.LoggedUser.UserName + + ', now you are a logged user and this is a protected content!'); + ResponseStream.AppendLine('As logged user you have the following roles: ' + + sLineBreak + string.Join(sLineBreak, Context.LoggedUser.Roles.ToArray)); + RenderResponseStream; +end; + +procedure TAdminController.OnlyRole1EmittingJSON; +var + lJObj: TJSONObject; + lJArr: TJSONArray; + lQueryParams: TStrings; + I: Integer; +begin + ContentType := TMVCMediaType.APPLICATION_JSON; + lJObj := TJSONObject.Create; + lJObj.AddPair('message', 'This is protected content accessible only by user1'); + lJArr := TJSONArray.Create; + lJObj.AddPair('querystringparameters', lJArr); + + lQueryParams := Context.Request.QueryStringParams; + for I := 0 to lQueryParams.Count - 1 do + begin + lJArr.AddElement(TJSONObject.Create(TJSONPair.Create( + lQueryParams.Names[I], + lQueryParams.ValueFromIndex[I]))); + end; + + Render(lJObj); +end; + +procedure TAdminController.OnlyRole2(ctx: TWebContext); +begin + ContentType := TMVCMediaType.TEXT_PLAIN; + ResponseStream.AppendLine('Hey! Hello ' + ctx.LoggedUser.UserName + + ', now you are a logged user and this is a protected content!'); + ResponseStream.AppendLine('As logged user you have the following roles: ' + + sLineBreak + string.Join(sLineBreak, Context.LoggedUser.Roles.ToArray)); + RenderResponseStream; +end; + +end. diff --git a/samples/jsonwebtoken_livevaliditywindow/AuthenticationU.pas b/samples/jsonwebtoken_livevaliditywindow/AuthenticationU.pas new file mode 100644 index 00000000..014b0164 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/AuthenticationU.pas @@ -0,0 +1,78 @@ +unit AuthenticationU; + +interface + +uses + System.SysUtils, MVCFramework.Commons, System.Generics.Collections, + MVCFramework; + +type + TAuthenticationSample = class(TInterfacedObject, IMVCAuthenticationHandler) + protected + procedure OnRequest(const ControllerQualifiedClassName: string; + const ActionName: string; var AuthenticationRequired: Boolean); + procedure OnAuthentication(const UserName: string; const Password: string; + UserRoles: System.Generics.Collections.TList; + var IsValid: Boolean; const SessionData: TSessionData); + procedure OnAuthorization(UserRoles + : System.Generics.Collections.TList; + const ControllerQualifiedClassName: string; const ActionName: string; + var IsAuthorized: Boolean); + end; + +implementation + +{ TMVCAuthorization } + +procedure TAuthenticationSample.OnAuthentication(const UserName, + Password: string; UserRoles: System.Generics.Collections.TList; + var IsValid: Boolean; const SessionData: TSessionData); +begin + IsValid := UserName.Equals(Password); // hey!, this is just a demo!!! + if IsValid then + begin + if UserName = 'user1' then + begin + UserRoles.Add('role1'); + end; + if UserName = 'user2' then + begin + UserRoles.Add('role2'); + end; + if UserName = 'user3' then // all the roles + begin + UserRoles.Add('role1'); + UserRoles.Add('role2'); + end; + end + else + begin + UserRoles.Clear; + end; +end; + +procedure TAuthenticationSample.OnAuthorization + (UserRoles: System.Generics.Collections.TList; + const ControllerQualifiedClassName, ActionName: string; + var IsAuthorized: Boolean); +begin + IsAuthorized := False; + if ActionName = 'Logout' then + IsAuthorized := True; // you can always call logout + if ActionName = 'OnlyRole2' then + IsAuthorized := UserRoles.Contains('role2'); + if ActionName = 'OnlyRole1' then + IsAuthorized := UserRoles.Contains('role1'); + if ActionName = 'OnlyRole1EmittingJSON' then + IsAuthorized := UserRoles.Contains('role1'); +end; + +procedure TAuthenticationSample.OnRequest(const ControllerQualifiedClassName, + ActionName: string; var AuthenticationRequired: Boolean); +begin + AuthenticationRequired := ControllerQualifiedClassName = + 'AppControllerU.TAdminController'; + +end; + +end. diff --git a/samples/jsonwebtoken_livevaliditywindow/JWTServer.dpr b/samples/jsonwebtoken_livevaliditywindow/JWTServer.dpr new file mode 100644 index 00000000..c87c2a1f --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/JWTServer.dpr @@ -0,0 +1,50 @@ +program JWTServer; + +{$APPTYPE CONSOLE} + + +uses + System.SysUtils, + Winapi.Windows, + Winapi.ShellAPI, + Web.WebReq, + Web.WebBroker, + IdHTTPWebBrokerBridge, + WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule}, + AppControllerU in 'AppControllerU.pas', + AuthenticationU in 'AuthenticationU.pas'; + +{$R *.res} + + +procedure RunServer(APort: Integer); +var + LServer: TIdHTTPWebBrokerBridge; +begin + Writeln(Format('Starting HTTP Server or port %d', [APort])); + LServer := TIdHTTPWebBrokerBridge.Create(nil); + try + LServer.DefaultPort := APort; + LServer.Active := True; + Writeln('THIS SERVER USES JWT WITH LIVEVALIDITYWINDOWS FEATURE'); + Writeln('Press RETURN to stop the server'); + // ShellExecute(0, 'open', PChar('http://localhost:' + IntToStr(APort)), nil, nil, SW_SHOW); + ReadLn; + finally + LServer.Free; + end; +end; + +begin + ReportMemoryLeaksOnShutdown := True; + try + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := WebModuleClass; + WebRequestHandlerProc.MaxConnections := 1024; + RunServer(8080); + except + on E: Exception do + Writeln(E.ClassName, ': ', E.Message); + end + +end. diff --git a/samples/jsonwebtoken_livevaliditywindow/JWTServer.dproj b/samples/jsonwebtoken_livevaliditywindow/JWTServer.dproj new file mode 100644 index 00000000..5b1640ec --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/JWTServer.dproj @@ -0,0 +1,648 @@ + + + {7B54055A-5749-4136-9FE2-35FDBEEA874C} + 18.2 + VCL + JWTServer.dpr + True + Debug + Win32 + 1 + Console + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + false + $(BDS)\bin\delphi_PROJECTICNS.icns + JWTServer + ..\..\sources;..\..\lib\delphistompclient;..\..\lib\loggerpro;..\..\lib\dmustache;$(DCC_UnitSearchPath) + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + None + 1040 + $(BDS)\bin\delphi_PROJECTICON.ico + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + true + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + true + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + true + true + android-support-v4.dex.jar;apk-expansion.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + true + Debug + true + true + + + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= + Debug + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(MSBuildProjectName) + iPhoneAndiPad + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + true + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + + + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= + Debug + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(MSBuildProjectName) + iPhoneAndiPad + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + true + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + + + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png + true + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes= + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png + iPhoneAndiPad + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + cxPivotGridChartRS17;JvMM;dxSkinSevenRS17;dxSkinBlueprintRS17;dxSkinHighContrastRS17;dxSkinOffice2007BlackRS17;dxCoreRS17;cxPageControldxBarPopupMenuRS17;dxSkinXmas2008BlueRS17;dxPSDBTeeChartRS17;JvCrypt;dxPSTeeChartRS17;dxSkinSummer2008RS17;dxPScxSchedulerLnkRS17;dxSkinBlueRS17;dxSkinDarkRoomRS17;DBXInterBaseDriver;DataSnapServer;DataSnapCommon;dxPScxTLLnkRS17;JvNet;JvDotNetCtrls;dxRibbonRS17;DbxCommonDriver;cxDataRS17;vclimg;dxSkinsdxBarPainterRS17;dxPSdxDBTVLnkRS17;dbxcds;DatasnapConnectorsFreePascal;NxDBGridDsgn_dxe3;JvXPCtrls;dxSkinMoneyTwinsRS17;vcldb;cxExportRS17;dxPSCoreRS17;dxBarExtItemsRS17;dxGDIPlusRS17;FMXfrx17;dxNavBarRS17;CustomIPTransport;cxLibraryRS17;cxGridRS17;dxSkinOffice2010BlackRS17;dsnap;IndyIPServer;IndyCore;dxSkinMcSkinRS17;CloudService;dxPScxCommonRS17;FmxTeeUI;frxDB17;AnyDAC_PhysDb2_D17;dxSkinsdxDLPainterRS17;dxSkiniMaginaryRS17;JvDB;JvRuntimeDesign;dxPScxVGridLnkRS17;JclDeveloperTools;dxSkinSevenClassicRS17;dxPScxExtCommonRS17;MyFrameTestPackage;dxPScxSSLnkRS17;NxGridRun_dxe3;dxSkinLilianRS17;fs17;dxPSdxLCLnkRS17;dxSkinOffice2010BlueRS17;NxCommonRun_dxe3;bindcompfmx;DataBindingsVCL170;dxSkinOffice2010SilverRS17;vcldbx;cxSchedulerGridRS17;dbrtl;bindcomp;inetdb;JvPluginSystem;dxBarRS17;DataBindings;DBXOdbcDriver;IcsCommonDXE3Run;JvCmp;dxBarDBNavRS17;dxSkinWhiteprintRS17;JvTimeFramework;xmlrtl;dxSkinsdxRibbonPainterRS17;ibxpress;dxDockingRS17;vclactnband;bindengine;soaprtl;FMXTee;dxADOServerModeRS17;bindcompvcl;dxBarExtDBItemsRS17;dxPSPrVwRibbonRS17;Jcl;vclie;dxSkinOffice2007PinkRS17;cxPageControlRS17;dxSkinscxPCPainterRS17;AnyDAC_PhysADS_D17;AnyDAC_PhysIB_D17;dxmdsRS17;dxSkinTheAsphaltWorldRS17;DBXInformixDriver;Intraweb;dxPsPrVwAdvRS17;NxInspectorRun_dxe3;dxSkinSilverRS17;dxdborRS17;dsnapcon;DBXFirebirdDriver;fsDB17;inet;dorm_runtime_xe3;JvPascalInterpreter;vclx;dxSkinStardustRS17;cxEditorsRS17;DBXSybaseASADriver;NxInspectorDsgn_dxe3;dbexpress;IndyIPClient;AnyDAC_PhysMySQL_D17;cxTreeListdxBarPopupMenuRS17;dxSkinVS2010RS17;NxGridDsgn_dxe3;dxThemeRS17;DBXSqliteDriver;dxPScxGridLnkRS17;fmx;JvDlgs;IndySystem;TeeDB;dxSkinValentineRS17;vclib;inetdbbde;DataSnapClient;dxSkinDevExpressStyleRS17;DataSnapProviderClient;DBXSybaseASEDriver;cxBarEditItemRS17;AnyDAC_PhysMSAcc_D17;dxServerModeRS17;cxPivotGridOLAPRS17;cxSchedulerRS17;MetropolisUILiveTile;AnyDAC_PhysSQLITE_D17;dxPSLnksRS17;dxSkinPumpkinRS17;dxPSdxDBOCLnkRS17;cxVerticalGridRS17;dxSkinSpringTimeRS17;vcldsnap;dxSkinDevExpressDarkStyleRS17;DBXDb2Driver;AnyDAC_ComI_D17;DBXOracleDriver;AnyDAC_PhysMSSQL_D17;JvCore;NxDBGridRun_dxe3;vclribbon;AnyDAC_Comp_D17;cxSpreadSheetRS17;dxSkinLiquidSkyRS17;AnyDAC_PhysODBC_D17;fmxase;vcl;dxSkinOffice2007SilverRS17;AnyDAC_PhysPg_D17;IndyIPCommon;DBXMSSQLDriver;CodeSiteExpressPkg;dxPSdxOCLnkRS17;dcldxSkinsCoreRS17;JvAppFrm;AnyDAC_PhysASA_D17;inetdbxpress;webdsnap;NxCollectionRun_dxe3;AnyDAC_PhysOracle_D17;dxSkinCoffeeRS17;JvDocking;adortl;dxSkinscxSchedulerPainterRS17;JvWizards;NxCollectionDsgn_dxe3;frx17;NxCommonDsgn_dxe3;dxtrmdRS17;dxPScxPCProdRS17;AnyDAC_GUIxForms_D17;JvBands;rtl;DbxClientDriver;AnyDAC_PhysTDBX_D17;dxTabbedMDIRS17;dxComnRS17;dxSkinSharpPlusRS17;dxSkinsCoreRS17;dxSkinLondonLiquidSkyRS17;dxdbtrRS17;Tee;JclContainers;NxAddonsRun_dxe3;CPortLibDXE;JvSystem;dxorgcRS17;svnui;dxSkinBlackRS17;JvControls;NxSheetRun_dxe3;IndyProtocols;DBXMySQLDriver;dxLayoutControlRS17;bindcompdbx;TeeUI;JvJans;JvPrintPreview;JvPageComps;JvStdCtrls;JvCustom;dxSkinOffice2007BlueRS17;dxPScxPivotGridLnkRS17;dxSpellCheckerRS17;vcltouch;dxSkinOffice2007GreenRS17;dxSkinSharpRS17;websnap;dxSkinFoggyRS17;dxTileControlRS17;VclSmp;FMXfrxDB17;dxSkinDarkSideRS17;cxPivotGridRS17;DataSnapConnectors;AnyDAC_Phys_D17;fmxobj;SynEdit_RXE3;JclVcl;cxTreeListRS17;dxPSdxFCLnkRS17;dxSkinGlassOceansRS17;frxe17;svn;dxFlowChartRS17;fmxdae;dxSkinsdxNavBarPainterRS17;bdertl;VirtualTreesR;DataSnapIndy10ServerTransport;dxDBXServerModeRS17;dxSkinCaramelRS17;$(DCC_UsePackage) + + + cxPivotGridChartRS17;JvMM;dxSkinSevenRS17;dxSkinBlueprintRS17;dxSkinHighContrastRS17;dxSkinOffice2007BlackRS17;dxCoreRS17;cxPageControldxBarPopupMenuRS17;dxSkinXmas2008BlueRS17;dxPSDBTeeChartRS17;JvCrypt;dxPSTeeChartRS17;dxSkinSummer2008RS17;dxPScxSchedulerLnkRS17;dxSkinBlueRS17;dxSkinDarkRoomRS17;DBXInterBaseDriver;DataSnapServer;DataSnapCommon;dxPScxTLLnkRS17;JvNet;dxRibbonRS17;DbxCommonDriver;cxDataRS17;vclimg;dxSkinsdxBarPainterRS17;dxPSdxDBTVLnkRS17;dbxcds;DatasnapConnectorsFreePascal;NxDBGridDsgn_dxe3;dxSkinMoneyTwinsRS17;vcldb;cxExportRS17;dxPSCoreRS17;dxBarExtItemsRS17;dxGDIPlusRS17;dxNavBarRS17;CustomIPTransport;cxLibraryRS17;cxGridRS17;dxSkinOffice2010BlackRS17;dsnap;IndyIPServer;IndyCore;dxSkinMcSkinRS17;dxPScxCommonRS17;AnyDAC_PhysDb2_D17;dxSkinsdxDLPainterRS17;dxSkiniMaginaryRS17;JvDB;dxPScxVGridLnkRS17;dxSkinSevenClassicRS17;dxPScxExtCommonRS17;dxPScxSSLnkRS17;NxGridRun_dxe3;dxSkinLilianRS17;dxPSdxLCLnkRS17;dxSkinOffice2010BlueRS17;NxCommonRun_dxe3;bindcompfmx;dxSkinOffice2010SilverRS17;cxSchedulerGridRS17;dbrtl;bindcomp;inetdb;JvPluginSystem;dxBarRS17;DBXOdbcDriver;JvCmp;dxBarDBNavRS17;dxSkinWhiteprintRS17;JvTimeFramework;xmlrtl;dxSkinsdxRibbonPainterRS17;ibxpress;dxDockingRS17;vclactnband;bindengine;soaprtl;dxADOServerModeRS17;bindcompvcl;dxBarExtDBItemsRS17;dxPSPrVwRibbonRS17;vclie;dxSkinOffice2007PinkRS17;cxPageControlRS17;dxSkinscxPCPainterRS17;AnyDAC_PhysADS_D17;AnyDAC_PhysIB_D17;dxmdsRS17;dxSkinTheAsphaltWorldRS17;DBXInformixDriver;dxPsPrVwAdvRS17;NxInspectorRun_dxe3;dxSkinSilverRS17;dxdborRS17;dsnapcon;DBXFirebirdDriver;inet;JvPascalInterpreter;vclx;dxSkinStardustRS17;cxEditorsRS17;DBXSybaseASADriver;NxInspectorDsgn_dxe3;dbexpress;IndyIPClient;AnyDAC_PhysMySQL_D17;cxTreeListdxBarPopupMenuRS17;dxSkinVS2010RS17;NxGridDsgn_dxe3;dxThemeRS17;DBXSqliteDriver;dxPScxGridLnkRS17;fmx;JvDlgs;IndySystem;TeeDB;dxSkinValentineRS17;vclib;DataSnapClient;dxSkinDevExpressStyleRS17;DataSnapProviderClient;DBXSybaseASEDriver;cxBarEditItemRS17;AnyDAC_PhysMSAcc_D17;dxServerModeRS17;cxPivotGridOLAPRS17;cxSchedulerRS17;AnyDAC_PhysSQLITE_D17;dxPSLnksRS17;dxSkinPumpkinRS17;dxPSdxDBOCLnkRS17;cxVerticalGridRS17;dxSkinSpringTimeRS17;vcldsnap;dxSkinDevExpressDarkStyleRS17;DBXDb2Driver;AnyDAC_ComI_D17;DBXOracleDriver;AnyDAC_PhysMSSQL_D17;JvCore;NxDBGridRun_dxe3;AnyDAC_Comp_D17;cxSpreadSheetRS17;dxSkinLiquidSkyRS17;AnyDAC_PhysODBC_D17;fmxase;vcl;dxSkinOffice2007SilverRS17;AnyDAC_PhysPg_D17;IndyIPCommon;DBXMSSQLDriver;dxPSdxOCLnkRS17;dcldxSkinsCoreRS17;JvAppFrm;AnyDAC_PhysASA_D17;inetdbxpress;webdsnap;NxCollectionRun_dxe3;AnyDAC_PhysOracle_D17;dxSkinCoffeeRS17;adortl;dxSkinscxSchedulerPainterRS17;JvWizards;NxCollectionDsgn_dxe3;NxCommonDsgn_dxe3;dxtrmdRS17;dxPScxPCProdRS17;AnyDAC_GUIxForms_D17;JvBands;rtl;DbxClientDriver;AnyDAC_PhysTDBX_D17;dxTabbedMDIRS17;dxComnRS17;dxSkinSharpPlusRS17;dxSkinsCoreRS17;dxSkinLondonLiquidSkyRS17;dxdbtrRS17;Tee;NxAddonsRun_dxe3;JvSystem;dxorgcRS17;dxSkinBlackRS17;JvControls;NxSheetRun_dxe3;IndyProtocols;DBXMySQLDriver;dxLayoutControlRS17;bindcompdbx;TeeUI;JvJans;JvPrintPreview;JvPageComps;JvStdCtrls;JvCustom;dxSkinOffice2007BlueRS17;dxPScxPivotGridLnkRS17;dxSpellCheckerRS17;vcltouch;dxSkinOffice2007GreenRS17;dxSkinSharpRS17;websnap;dxSkinFoggyRS17;dxTileControlRS17;VclSmp;dxSkinDarkSideRS17;cxPivotGridRS17;DataSnapConnectors;AnyDAC_Phys_D17;fmxobj;SynEdit_RXE3;cxTreeListRS17;dxPSdxFCLnkRS17;dxSkinGlassOceansRS17;dxFlowChartRS17;fmxdae;dxSkinsdxNavBarPainterRS17;DataSnapIndy10ServerTransport;dxDBXServerModeRS17;dxSkinCaramelRS17;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + 3 + true + 1033 + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + 1033 + + + + MainSource + + +
WebModule1
+ TWebModule +
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + + + + + False + False + 1 + 0 + 0 + 0 + False + False + False + False + False + 1040 + 1252 + + + + + 1.0.0.0 + + + + + + 1.0.0.0 + + + + + + + + + + + + JWTServer.dpr + + + Embarcadero C++Builder Office 2000 Servers Package + Embarcadero C++Builder Office XP Servers Package + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + JWTServer.exe + true + + + + + 1 + + + Contents\MacOS + 0 + + + + + classes + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + False + False + False + False + True + False + + + 12 + + + + +
diff --git a/samples/jsonwebtoken_livevaliditywindow/JWTServer.res b/samples/jsonwebtoken_livevaliditywindow/JWTServer.res new file mode 100644 index 00000000..6876088a Binary files /dev/null and b/samples/jsonwebtoken_livevaliditywindow/JWTServer.res differ diff --git a/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.dfm b/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.dfm new file mode 100644 index 00000000..a295b945 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.dfm @@ -0,0 +1,12 @@ +object WebModule1: TWebModule1 + OldCreateOrder = False + OnCreate = WebModuleCreate + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + end> + Height = 230 + Width = 415 +end diff --git a/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.pas b/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.pas new file mode 100644 index 00000000..fb85df98 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/WebModuleUnit1.pas @@ -0,0 +1,71 @@ +unit WebModuleUnit1; + +interface + +uses + System.SysUtils, + System.Classes, + Web.HTTPApp, + MVCFramework, + MVCFramework.Commons; + +type + TWebModule1 = class(TWebModule) + procedure WebModuleCreate(Sender: TObject); + + private + MVC: TMVCEngine; + + public + { Public declarations } + end; + +var + WebModuleClass: TComponentClass = TWebModule1; + +implementation + +{$R *.dfm} + + +uses + AppControllerU, + System.Generics.Collections, + AuthenticationU, + MVCFramework.Middleware.JWT, + MVCFramework.JWT, + System.DateUtils; + +procedure TWebModule1.WebModuleCreate(Sender: TObject); +var + lClaimsSetup: TJWTClaimsSetup; +begin + lClaimsSetup := procedure(const JWT: TJWT) + begin + JWT.Claims.Issuer := 'Delphi MVC Framework JWT Middleware Sample'; + JWT.Claims.NotBefore := Now - OneMinute * 5; // valid since 5 minutes ago + JWT.Claims.IssuedAt := Now; + JWT.CustomClaims['mycustomvalue'] := 'hello there'; + // Here we dont use a fixed ExpirationTime but a LiveValidityWindowInSeconds + // to make the ExpirationTime dynamic, incrementing the + // ExpirationTime by LiveValidityWindowInSeconds seconds at each request + JWT.LiveValidityWindowInSeconds := 5; //60 * 60; // 1 hour + end; + + MVC := TMVCEngine.Create(Self); + MVC.Config[TMVCConfigKey.DocumentRoot] := '..\..\www'; + MVC.Config[TMVCConfigKey.SessionTimeout] := '30'; + MVC.Config[TMVCConfigKey.DefaultContentType] := 'text/html'; + MVC.AddController(TApp1MainController).AddController(TAdminController) + .AddMiddleware(TMVCJWTAuthenticationMiddleware.Create( + TAuthenticationSample.Create, + lClaimsSetup, + 'mys3cr37', + [ + TJWTCheckableClaim.ExpirationTime, + TJWTCheckableClaim.NotBefore, + TJWTCheckableClaim.IssuedAt + ], 0)); +end; + +end. diff --git a/samples/jsonwebtoken_livevaliditywindow/pgJWT.groupproj b/samples/jsonwebtoken_livevaliditywindow/pgJWT.groupproj new file mode 100644 index 00000000..a93c8214 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/pgJWT.groupproj @@ -0,0 +1,48 @@ + + + {517F9067-355C-443F-9B0D-95E2E904C708} + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dpr b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dpr new file mode 100644 index 00000000..2b53534b --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dpr @@ -0,0 +1,14 @@ +program JWTClient; + +uses + Vcl.Forms, + MainClientFormU in 'MainClientFormU.pas' {MainForm}; + +{$R *.res} + +begin + Application.Initialize; + Application.MainFormOnTaskbar := True; + Application.CreateForm(TMainForm, MainForm); + Application.Run; +end. diff --git a/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dproj b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dproj new file mode 100644 index 00000000..e82d3642 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.dproj @@ -0,0 +1,574 @@ + + + {E7317702-64D3-4A65-8734-030F3AE3DBBC} + 18.2 + VCL + JWTClient.dpr + True + Debug + Win32 + 1 + Application + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + 1040 + ..\..\..\sources;..\..\..\lib\delphistompclient;..\..\..\lib\loggerpro;..\..\..\lib\dmustache;$(DCC_UnitSearchPath) + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + JWTClient + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + + + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + true + $(BDS)\bin\default_app.manifest + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + 1033 + DBXSqliteDriver;DBXDb2Driver;dxCoreRS23;vclactnband;frxe23;vclFireDAC;dxPSLnksRS23;dxPSdxLCLnkRS23;tethering;cxDataRS23;dxPSdxOCLnkRS23;dxTabbedMDIRS23;FireDACADSDriver;dxSkinBlackRS23;dxSkinLondonLiquidSkyRS23;JvPluginSystem;dxDBXServerModeRS23;dxHttpIndyRequestRS23;dxPScxGridLnkRS23;cxSchedulerRS23;FireDACMSSQLDriver;dclRBDBE1723;vcltouch;JvBands;vcldb;rbDB1723;svn;dxWizardControlRS23;dxSkinMcSkinRS23;dxPScxCommonRS23;JvJans;Intraweb;dxSkinOffice2007BlueRS23;rbIBE1723;dxBarRS23;cxSchedulerRibbonStyleEventEditorRS23;dxSkinOffice2013WhiteRS23;JvDotNetCtrls;dxPSTeeChartRS23;cxLibraryRS23;dxSkinVisualStudio2013LightRS23;vclib;cxPivotGridChartRS23;rbDBE1723;dxSkinSummer2008RS23;dxPSdxDBOCLnkRS23;dxGDIPlusRS23;dxSkinDarkSideRS23;FireDACDBXDriver;dxSkinFoggyRS23;dxSkinSevenRS23;vclx;rbCIDE1723;dxSkinOffice2010SilverRS23;dxdborRS23;RESTBackendComponents;dxLayoutControlRS23;dxPSPrVwRibbonRS23;VCLRESTComponents;dxSkinDevExpressStyleRS23;dxSkinWhiteprintRS23;vclie;bindengine;CloudService;rbRAP1723;JvHMI;FireDACMySQLDriver;dxSkinOffice2013DarkGrayRS23;DataSnapClient;dxPScxPCProdRS23;bindcompdbx;DBXSybaseASEDriver;IndyIPServer;dxSkinPumpkinRS23;IndySystem;dsnapcon;cxTreeListdxBarPopupMenuRS23;dclRBIBE1723;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;dxSkinLilianRS23;Jcl;rbADO1723;dxBarDBNavRS23;dxFlowChartRS23;dxSkinOffice2016ColorfulRS23;rbUSER1723;DBXOdbcDriver;FireDACTDataDriver;FMXTee;ipstudiowinclient;soaprtl;DbxCommonDriver;dxSpreadSheetRS23;AsyncProDR;JvManagedThreads;dxSkinOffice2007PinkRS23;dxPSdxSpreadSheetLnkRS23;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;DTKANPRPackage;dxSkinHighContrastRS23;rtl;dxSkinSevenClassicRS23;DbxClientDriver;dxSkinDevExpressDarkStyleRS23;DBXSybaseASADriver;dxNavBarRS23;dxSkinMetropolisDarkRS23;CodeSiteExpressPkg;dxSkinTheAsphaltWorldRS23;JvSystem;SampleListViewMultiDetailAppearancePackage;dxRichEditControlRS23;JvStdCtrls;JvTimeFramework;ipstudiowin;appanalytics;cxPivotGridRS23;rbUSERDesign1723;dxSkinsdxDLPainterRS23;IndyIPClient;dxRibbonRS23;dxPScxVGridLnkRS23;bindcompvcl;frxDB23;vcldbx;dxSkinOffice2007SilverRS23;dxPScxTLLnkRS23;dxMapControlRS23;TeeUI;rbDIDE1723;JvPascalInterpreter;JvDocking;VclSmp;dxPScxSchedulerLnkRS23;cxTreeListRS23;FireDACODBCDriver;JclVcl;DataSnapIndy10ServerTransport;dxRibbonCustomizationFormRS23;dxPSRichEditControlLnkRS23;dxBarExtDBItemsRS23;DataSnapProviderClient;FireDACMongoDBDriver;dxSkiniMaginaryRS23;frx23;dxSpellCheckerRS23;JvControls;dxSkinsdxBarPainterRS23;JvPrintPreview;dxSkinCoffeeRS23;DataSnapServerMidas;RESTComponents;DBXInterBaseDriver;rbRTL1723;dxADOServerModeRS23;emsclientfiredac;DataSnapFireDAC;svnui;dxmdsRS23;dxSkinLiquidSkyRS23;dxdbtrRS23;dxSkinSpringTimeRS23;dxPSDBTeeChartRS23;JvGlobus;dxSkinscxPCPainterRS23;dxPSCoreRS23;DBXMSSQLDriver;JvMM;dxSkinXmas2008BlueRS23;rbDAD1723;DatasnapConnectorsFreePascal;bindcompfmx;JvNet;DBXOracleDriver;dxSkinSilverRS23;dxSkinValentineRS23;inetdb;JvAppFrm;ipstudiowinwordxp;rbTC1723;FmxTeeUI;dxBarExtItemsRS23;FireDACIBDriver;fmx;fmxdae;DelphiCookbookListViewAppearance;dxServerModeRS23;dxPsPrVwAdvRS23;dxSkinOffice2010BlackRS23;JvWizards;cxPageControlRS23;dxSkinStardustRS23;cxSchedulerGridRS23;dbexpress;IndyCore;dxSkinSharpPlusRS23;UIBD21Win32R;JvPageComps;dsnap;DataSnapCommon;emsclient;FireDACCommon;dxSkinOffice2010BlueRS23;bdertl;JvDB;dxSkinVS2010RS23;dxSkinMetropolisRS23;DataSnapConnectors;cxVerticalGridRS23;soapserver;dxSkinCaramelRS23;frxTee23;dxTileControlRS23;JclDeveloperTools;cxGridRS23;CPortLibDXE;FireDACOracleDriver;DBXMySQLDriver;JvCmp;rbFireDAC1723;DBXFirebirdDriver;FireDACCommonDriver;rbTCUI1723;LockBoxDR;inet;IndyIPCommon;JvCustom;dxSkinDarkRoomRS23;dxDockingRS23;vcl;dxSkinOffice2007GreenRS23;dxPScxExtCommonRS23;JvXPCtrls;dxSkinsCoreRS23;FireDACDb2Driver;dxThemeRS23;dxSkinsdxRibbonPainterRS23;dxSkinVisualStudio2013BlueRS23;rbRest1723;TSG5201;dxSkinMoneyTwinsRS23;dxPSdxFCLnkRS23;dxtrmdRS23;TeeDB;FireDAC;cxSchedulerTreeBrowserRS23;JvCore;dxFireDACServerModeRS23;dxSkinBlueRS23;OverbyteIcsD10SRun;JvCrypt;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;cxEditorsRS23;dxSkinGlassOceansRS23;JvDlgs;JvRuntimeDesign;dxSkinsdxNavBarPainterRS23;dxGaugeControlRS23;ibxpress;Tee;dxSkinSharpRS23;DataSnapServer;ibxbindings;cxPivotGridOLAPRS23;rbIDE1723;vclwinx;FireDACDSDriver;dxSkinBlueprintRS23;dxSkinOffice2007BlackRS23;CustomIPTransport;vcldsnap;rbBDE1723;dxSkinOffice2013LightGrayRS23;bindcomp;DBXInformixDriver;officeXPrt;dxPSdxGaugeControlLnkRS23;dxPScxPivotGridLnkRS23;dxorgcRS23;dxPSdxDBTVLnkRS23;dclRBADO1723;vclribbon;dbxcds;KernowSoftwareFMX;adortl;dclRBFireDAC1723;dclRBE1723;dxComnRS23;dsnapxml;dbrtl;inetdbxpress;IndyProtocols;cxExportRS23;dxSkinOffice2016DarkRS23;JclContainers;dxSkinVisualStudio2013DarkRS23;rbRCL1723;dxSkinscxSchedulerPainterRS23;rbRIDE1723;fmxase;$(DCC_UsePackage) + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName) + + + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + DBXSqliteDriver;DBXDb2Driver;dxCoreRS23;vclactnband;vclFireDAC;dxPSLnksRS23;dxPSdxLCLnkRS23;tethering;cxDataRS23;dxPSdxOCLnkRS23;dxTabbedMDIRS23;FireDACADSDriver;dxSkinBlackRS23;dxSkinLondonLiquidSkyRS23;dxDBXServerModeRS23;dxHttpIndyRequestRS23;dxPScxGridLnkRS23;cxSchedulerRS23;FireDACMSSQLDriver;vcltouch;vcldb;dxWizardControlRS23;dxSkinMcSkinRS23;dxPScxCommonRS23;Intraweb;dxSkinOffice2007BlueRS23;dxBarRS23;cxSchedulerRibbonStyleEventEditorRS23;dxSkinOffice2013WhiteRS23;dxPSTeeChartRS23;cxLibraryRS23;dxSkinVisualStudio2013LightRS23;vclib;cxPivotGridChartRS23;dxSkinSummer2008RS23;dxPSdxDBOCLnkRS23;dxGDIPlusRS23;dxSkinDarkSideRS23;FireDACDBXDriver;dxSkinFoggyRS23;dxSkinSevenRS23;vclx;dxSkinOffice2010SilverRS23;dxdborRS23;RESTBackendComponents;dxLayoutControlRS23;dxPSPrVwRibbonRS23;VCLRESTComponents;dxSkinDevExpressStyleRS23;dxSkinWhiteprintRS23;vclie;bindengine;CloudService;FireDACMySQLDriver;dxSkinOffice2013DarkGrayRS23;DataSnapClient;dxPScxPCProdRS23;bindcompdbx;DBXSybaseASEDriver;IndyIPServer;dxSkinPumpkinRS23;IndySystem;dsnapcon;cxTreeListdxBarPopupMenuRS23;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;dxSkinLilianRS23;dxBarDBNavRS23;dxFlowChartRS23;dxSkinOffice2016ColorfulRS23;DBXOdbcDriver;FireDACTDataDriver;FMXTee;ipstudiowinclient;soaprtl;DbxCommonDriver;dxSpreadSheetRS23;AsyncProDR;dxSkinOffice2007PinkRS23;dxPSdxSpreadSheetLnkRS23;xmlrtl;soapmidas;DataSnapNativeClient;fmxobj;dxSkinHighContrastRS23;rtl;dxSkinSevenClassicRS23;DbxClientDriver;dxSkinDevExpressDarkStyleRS23;DBXSybaseASADriver;dxNavBarRS23;dxSkinMetropolisDarkRS23;dxSkinTheAsphaltWorldRS23;dxRichEditControlRS23;ipstudiowin;appanalytics;cxPivotGridRS23;dxSkinsdxDLPainterRS23;IndyIPClient;dxRibbonRS23;dxPScxVGridLnkRS23;bindcompvcl;dxSkinOffice2007SilverRS23;dxPScxTLLnkRS23;dxMapControlRS23;TeeUI;VclSmp;dxPScxSchedulerLnkRS23;cxTreeListRS23;FireDACODBCDriver;DataSnapIndy10ServerTransport;dxRibbonCustomizationFormRS23;dxPSRichEditControlLnkRS23;dxBarExtDBItemsRS23;DataSnapProviderClient;FireDACMongoDBDriver;dxSkiniMaginaryRS23;dxSpellCheckerRS23;dxSkinsdxBarPainterRS23;dxSkinCoffeeRS23;DataSnapServerMidas;RESTComponents;DBXInterBaseDriver;dxADOServerModeRS23;emsclientfiredac;DataSnapFireDAC;dxmdsRS23;dxSkinLiquidSkyRS23;dxdbtrRS23;dxSkinSpringTimeRS23;dxPSDBTeeChartRS23;dxSkinscxPCPainterRS23;dxPSCoreRS23;DBXMSSQLDriver;dxSkinXmas2008BlueRS23;DatasnapConnectorsFreePascal;bindcompfmx;DBXOracleDriver;dxSkinSilverRS23;dxSkinValentineRS23;inetdb;FmxTeeUI;dxBarExtItemsRS23;FireDACIBDriver;fmx;fmxdae;dxServerModeRS23;dxPsPrVwAdvRS23;dxSkinOffice2010BlackRS23;cxPageControlRS23;dxSkinStardustRS23;cxSchedulerGridRS23;dbexpress;IndyCore;dxSkinSharpPlusRS23;dsnap;DataSnapCommon;emsclient;FireDACCommon;dxSkinOffice2010BlueRS23;dxSkinVS2010RS23;dxSkinMetropolisRS23;DataSnapConnectors;cxVerticalGridRS23;soapserver;dxSkinCaramelRS23;dxTileControlRS23;cxGridRS23;FireDACOracleDriver;DBXMySQLDriver;DBXFirebirdDriver;FireDACCommonDriver;LockBoxDR;inet;IndyIPCommon;dxSkinDarkRoomRS23;dxDockingRS23;vcl;dxSkinOffice2007GreenRS23;dxPScxExtCommonRS23;dxSkinsCoreRS23;FireDACDb2Driver;dxThemeRS23;dxSkinsdxRibbonPainterRS23;dxSkinVisualStudio2013BlueRS23;dxSkinMoneyTwinsRS23;dxPSdxFCLnkRS23;dxtrmdRS23;TeeDB;FireDAC;cxSchedulerTreeBrowserRS23;dxFireDACServerModeRS23;dxSkinBlueRS23;OverbyteIcsD10SRun;FireDACSqliteDriver;FireDACPgDriver;ibmonitor;FireDACASADriver;cxEditorsRS23;dxSkinGlassOceansRS23;dxSkinsdxNavBarPainterRS23;dxGaugeControlRS23;ibxpress;Tee;dxSkinSharpRS23;DataSnapServer;ibxbindings;cxPivotGridOLAPRS23;vclwinx;FireDACDSDriver;dxSkinBlueprintRS23;dxSkinOffice2007BlackRS23;CustomIPTransport;vcldsnap;dxSkinOffice2013LightGrayRS23;bindcomp;DBXInformixDriver;officeXPrt;dxPSdxGaugeControlLnkRS23;dxPScxPivotGridLnkRS23;dxorgcRS23;dxPSdxDBTVLnkRS23;vclribbon;dbxcds;adortl;dxComnRS23;dsnapxml;dbrtl;inetdbxpress;IndyProtocols;cxExportRS23;dxSkinOffice2016DarkRS23;dxSkinVisualStudio2013DarkRS23;dxSkinscxSchedulerPainterRS23;fmxase;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + true + CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName) + 1033 + Debug + true + true + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + true + true + + + + MainSource + + +
MainForm
+ dfm +
+ + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + JWTClient.dpr + + + Embarcadero C++Builder Office 2000 Servers Package + Embarcadero C++Builder Office XP Servers Package + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + JWTClient.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 0 + + + + + classes + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\ + 1 + + + + + Contents + 1 + + + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + True + False + + + 12 + + + + +
diff --git a/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.res b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.res new file mode 100644 index 00000000..506733d5 Binary files /dev/null and b/samples/jsonwebtoken_livevaliditywindow/vclclient/JWTClient.res differ diff --git a/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.dfm b/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.dfm new file mode 100644 index 00000000..c08ca943 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.dfm @@ -0,0 +1,110 @@ +object MainForm: TMainForm + Left = 0 + Top = 0 + Caption = 'JWT with LiveValidityWindow feature' + ClientHeight = 379 + ClientWidth = 721 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + PixelsPerInch = 96 + TextHeight = 13 + object Splitter1: TSplitter + Left = 0 + Top = 179 + Width = 721 + Height = 3 + Cursor = crVSplit + Align = alTop + ExplicitTop = 147 + ExplicitWidth = 30 + end + object Memo1: TMemo + Left = 0 + Top = 81 + Width = 721 + Height = 98 + Align = alTop + Font.Charset = ANSI_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = 'Courier New' + Font.Style = [] + ParentFont = False + ReadOnly = True + TabOrder = 0 + ExplicitTop = 49 + ExplicitWidth = 513 + end + object Memo2: TMemo + Left = 0 + Top = 182 + Width = 721 + Height = 197 + Align = alClient + Font.Charset = ANSI_CHARSET + Font.Color = clWindowText + Font.Height = -13 + Font.Name = 'Courier New' + Font.Style = [] + ParentFont = False + ReadOnly = True + TabOrder = 1 + ExplicitTop = 150 + ExplicitWidth = 513 + ExplicitHeight = 229 + end + object Panel1: TPanel + Left = 0 + Top = 0 + Width = 721 + Height = 81 + Align = alTop + TabOrder = 2 + object Label1: TLabel + AlignWithMargins = True + Left = 338 + Top = 4 + Width = 379 + Height = 73 + Align = alClient + Caption = + 'At each authenticated request, the server increments the "exp" p' + + 'roperty of the JWT of LiveValidityWindowInSeconds seconds. So th' + + 'at a JWT doesn'#39't expire never if used at least one time in LiveV' + + 'alidityWindowInSeconds seconds. It is useful to mimic the classi' + + 'c session cookie with the semplicity of the JWT.' + WordWrap = True + ExplicitWidth = 368 + ExplicitHeight = 65 + end + object btnGet: TButton + AlignWithMargins = True + Left = 171 + Top = 4 + Width = 161 + Height = 73 + Align = alLeft + Caption = 'Get a protected resource (with an updated JWT)' + TabOrder = 0 + WordWrap = True + OnClick = btnGetClick + end + object btnLOGIN: TButton + AlignWithMargins = True + Left = 4 + Top = 4 + Width = 161 + Height = 73 + Align = alLeft + Caption = 'Login' + TabOrder = 1 + OnClick = btnLOGINClick + ExplicitHeight = 41 + end + end +end diff --git a/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.pas b/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.pas new file mode 100644 index 00000000..df8e9fe4 --- /dev/null +++ b/samples/jsonwebtoken_livevaliditywindow/vclclient/MainClientFormU.pas @@ -0,0 +1,114 @@ +unit MainClientFormU; + +interface + +uses + Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, + System.Classes, Vcl.Graphics, + Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls; + +type + TMainForm = class(TForm) + Memo1: TMemo; + Memo2: TMemo; + Panel1: TPanel; + btnGet: TButton; + btnLOGIN: TButton; + Splitter1: TSplitter; + Label1: TLabel; + procedure btnGetClick(Sender: TObject); + procedure btnLOGINClick(Sender: TObject); + private + FJWT: string; + procedure SetJWT(const Value: string); + property JWT: string read FJWT write SetJWT; + public + { Public declarations } + end; + +var + MainForm: TMainForm; + +implementation + +{$R *.dfm} + + +uses + MVCFramework.RESTClient, + MVCFramework.SystemJSONUtils, + MVCFramework.TypesAliases, + System.NetEncoding; + +procedure TMainForm.btnGetClick(Sender: TObject); +var + lClient: TRESTClient; + lResp: IRESTResponse; + lQueryStringParams: TStringList; + tokenOld, tokenNew: string; // NEW CODE +begin + tokenOld := FJWT; // NEW CODE + lClient := TRESTClient.Create('localhost', 8080); + try + lClient.ReadTimeOut(0); + if not FJWT.IsEmpty then + lClient.RequestHeaders.Values['Authentication'] := 'bearer ' + FJWT; + lQueryStringParams := TStringList.Create; + try + lQueryStringParams.Values['firstname'] := 'Daniele'; + lQueryStringParams.Values['lastname'] := 'Teti'; + lResp := lClient.doGET('/admin/role1', [], lQueryStringParams); + + if lResp.HasError then + ShowMessage(lResp.Error.ExceptionMessage); + + finally + lQueryStringParams.Free; + end; + Memo2.Lines.Text := lResp.BodyAsString; + + // NEW CODE + tokenNew := lResp.HeaderValue('Authentication'); + if tokenNew.StartsWith('bearer', True) then + begin + tokenNew := tokenNew.Remove(0, 'bearer'.Length).Trim; + tokenNew := TNetEncoding.URL.URLDecode(tokenNew).Trim; + JWT := tokenNew; + end; // END NEW CODE + finally + lClient.Free; + end; +end; + +procedure TMainForm.btnLOGINClick(Sender: TObject); +var + lClient: TRESTClient; + lRest: IRESTResponse; + lJSON: TJSONObject; +begin + lClient := TRESTClient.Create('localhost', 8080); + try + lClient.ReadTimeOut(0); + lClient + .Header('jwtusername', 'user1') + .Header('jwtpassword', 'user1'); + lRest := lClient.doPOST('/login', []); + lJSON := TSystemJSON.StringAsJSONObject(lRest.BodyAsString); + try + JWT := lJSON.GetValue('token').Value; + finally + lJSON.Free; + end; + finally + lClient.Free; + end; +end; + +procedure TMainForm.SetJWT(const Value: string); +begin + FJWT := Value; + Memo1.Lines.Text := Value; + +end; + +end. diff --git a/sources/MVCFramework.JWT.pas b/sources/MVCFramework.JWT.pas index c55597fe..e52065e1 100644 --- a/sources/MVCFramework.JWT.pas +++ b/sources/MVCFramework.JWT.pas @@ -44,13 +44,13 @@ type TJWTRegisteredClaimNames = class sealed public const - Issuer: String = 'iss'; - Subject: String = 'sub'; - Audience: String = 'aud'; - ExpirationTime: String = 'exp'; - NotBefore: String = 'nbf'; - IssuedAt: String = 'iat'; - JWT_ID: String = 'jti'; + Issuer: string = 'iss'; + Subject: string = 'sub'; + Audience: string = 'aud'; + ExpirationTime: string = 'exp'; + NotBefore: string = 'nbf'; + IssuedAt: string = 'iat'; + JWT_ID: string = 'jti'; Names: array [0 .. 6] of string = ( 'iss', 'sub', @@ -64,15 +64,15 @@ type TJWTDictionaryObject = class private FClaims: TDictionary; - function GetItem(const Index: String): String; - procedure SetItem(const Index, Value: String); - function GetItemAsDateTime(const Index: String): TDateTime; - procedure SetItemAsDateTime(const Index: String; const Value: TDateTime); - property ItemsAsDateTime[const Index: String]: TDateTime read GetItemAsDateTime write SetItemAsDateTime; - property Items[const Index: String]: String read GetItem write SetItem; default; + function GetItem(const Index: string): string; + procedure SetItem(const Index, Value: string); + function GetItemAsDateTime(const Index: string): TDateTime; + procedure SetItemAsDateTime(const Index: string; const Value: TDateTime); + property ItemsAsDateTime[const index: string]: TDateTime read GetItemAsDateTime write SetItemAsDateTime; + property Items[const index: string]: string read GetItem write SetItem; default; protected - function Contains(const Index: String): Boolean; - function Keys: TArray; + function Contains(const Index: string): Boolean; + function Keys: TArray; public constructor Create; virtual; destructor Destroy; override; @@ -83,20 +83,20 @@ type /// TJWTRegisteredClaims = class(TJWTDictionaryObject) private - procedure SetAudience(const Value: String); + procedure SetAudience(const Value: string); procedure SetExpirationTime(const Value: TDateTime); procedure SetIssuedAt(const Value: TDateTime); - procedure SetISSUER(const Value: String); - procedure SetJWT_ID(const Value: String); + procedure SetISSUER(const Value: string); + procedure SetJWT_ID(const Value: string); procedure SetNotBefore(const Value: TDateTime); - procedure SetSubject(const Value: String); - function GetAudience: String; + procedure SetSubject(const Value: string); + function GetAudience: string; function GetExpirationTime: TDateTime; function GetIssuedAt: TDateTime; - function GetJWT_ID: String; + function GetJWT_ID: string; function GetNotBefore: TDateTime; - function GetSubject: String; - function GetIssuer: String; + function GetSubject: string; + function GetIssuer: string; public /// /// "iss" (Issuer) Claim @@ -105,7 +105,7 @@ type /// The " iss " value is a case-sensitive string containing a StringOrURI /// value.Use of this claim is OPTIONAL. /// - property Issuer: String read GetIssuer write SetISSUER; + property Issuer: string read GetIssuer write SetISSUER; /// /// "sub" (Subject) Claim /// The "sub" (subject) claim identifies the principal that is the @@ -116,7 +116,7 @@ type /// "sub" value is a case-sensitive string containing a StringOrURI /// value. Use of this claim is OPTIONAL. /// - property Subject: String read GetSubject write SetSubject; + property Subject: string read GetSubject write SetSubject; /// /// "aud" (Audience) Claim /// The "aud" (audience) claim identifies the recipients that the JWT is @@ -131,7 +131,7 @@ type /// interpretation of audience values is generally application specific. /// Use of this claim is OPTIONAL. /// - property Audience: String read GetAudience write SetAudience; + property Audience: string read GetAudience write SetAudience; /// /// "exp" (Expiration Time) Claim /// The "exp" (expiration time) claim identifies the expiration time on @@ -173,7 +173,7 @@ type /// to prevent the JWT from being replayed. The "jti" value is a case- /// sensitive string. Use of this claim is OPTIONAL. /// - property JWT_ID: String read GetJWT_ID write SetJWT_ID; + property JWT_ID: string read GetJWT_ID write SetJWT_ID; end; TJWTCustomClaims = class(TJWTDictionaryObject) @@ -186,26 +186,33 @@ type FSecretKey: string; FRegisteredClaims: TJWTRegisteredClaims; FCustomClaims: TJWTCustomClaims; - FHMACAlgorithm: String; - FLeewaySeconds: Int64; + FHMACAlgorithm: string; FRegClaimsToChecks: TJWTCheckableClaims; - procedure SetHMACAlgorithm(const Value: String); - procedure SetLeewaySeconds(const Value: Int64); + FLiveValidityWindowInSeconds: Cardinal; + FLeewaySeconds: Cardinal; + procedure SetHMACAlgorithm(const Value: string); procedure SetChecks(const Value: TJWTCheckableClaims); - function CheckExpirationTime(Payload: TJSONObject; out Error: String): Boolean; - function CheckNotBefore(Payload: TJSONObject; out Error: String): Boolean; - function CheckIssuedAt(Payload: TJSONObject; out Error: String): Boolean; + function CheckExpirationTime(Payload: TJSONObject; out Error: string): Boolean; + function CheckNotBefore(Payload: TJSONObject; out Error: string): Boolean; + function CheckIssuedAt(Payload: TJSONObject; out Error: string): Boolean; + procedure SetLiveValidityWindowInSeconds(const Value: Cardinal); + function GetLiveValidityWindowInSeconds: Cardinal; public - constructor Create(const SecretKey: String); virtual; + constructor Create(const SecretKey: string; const ALeewaySeconds: Cardinal = 300); virtual; destructor Destroy; override; - function GetToken: String; - function IsValidToken(const Token: String; out Error: String): Boolean; - procedure LoadToken(const Token: String); + function GetToken: string; + function IsValidToken(const Token: string; out Error: string): Boolean; + procedure LoadToken(const Token: string); property Claims: TJWTRegisteredClaims read FRegisteredClaims; property CustomClaims: TJWTCustomClaims read FCustomClaims; - property HMACAlgorithm: String read FHMACAlgorithm write SetHMACAlgorithm; - property LeewaySeconds: Int64 read FLeewaySeconds write SetLeewaySeconds; + property HMACAlgorithm: string read FHMACAlgorithm write SetHMACAlgorithm; + property LeewaySeconds: Cardinal read FLeewaySeconds; property RegClaimsToChecks: TJWTCheckableClaims read FRegClaimsToChecks write SetChecks; + /// + /// Use LiveValidityWindowInSeconds to make the ExpirationTime dynamic at each request, + /// incrementing the ExpirationTime by LiveValidityWindowInSeconds seconds at each request + /// + property LiveValidityWindowInSeconds: Cardinal read GetLiveValidityWindowInSeconds write SetLiveValidityWindowInSeconds; end; implementation @@ -218,7 +225,7 @@ uses { TJWTRegisteredClaims } -function TJWTRegisteredClaims.GetAudience: String; +function TJWTRegisteredClaims.GetAudience: string; begin Result := Items[TJWTRegisteredClaimNames.Audience]; end; @@ -233,12 +240,12 @@ begin Result := ItemsAsDateTime[TJWTRegisteredClaimNames.IssuedAt]; end; -function TJWTRegisteredClaims.GetIssuer: String; +function TJWTRegisteredClaims.GetIssuer: string; begin Result := Items[TJWTRegisteredClaimNames.Issuer]; end; -function TJWTRegisteredClaims.GetJWT_ID: String; +function TJWTRegisteredClaims.GetJWT_ID: string; begin Result := Items[TJWTRegisteredClaimNames.JWT_ID]; end; @@ -248,12 +255,12 @@ begin Result := ItemsAsDateTime[TJWTRegisteredClaimNames.NotBefore]; end; -function TJWTRegisteredClaims.GetSubject: String; +function TJWTRegisteredClaims.GetSubject: string; begin Result := Items[TJWTRegisteredClaimNames.Subject]; end; -procedure TJWTRegisteredClaims.SetAudience(const Value: String); +procedure TJWTRegisteredClaims.SetAudience(const Value: string); begin Items[TJWTRegisteredClaimNames.Audience] := Value; end; @@ -268,12 +275,12 @@ begin ItemsAsDateTime[TJWTRegisteredClaimNames.IssuedAt] := Value; end; -procedure TJWTRegisteredClaims.SetISSUER(const Value: String); +procedure TJWTRegisteredClaims.SetISSUER(const Value: string); begin Items[TJWTRegisteredClaimNames.Issuer] := Value; end; -procedure TJWTRegisteredClaims.SetJWT_ID(const Value: String); +procedure TJWTRegisteredClaims.SetJWT_ID(const Value: string); begin Items[TJWTRegisteredClaimNames.JWT_ID] := Value; end; @@ -283,22 +290,22 @@ begin ItemsAsDateTime[TJWTRegisteredClaimNames.NotBefore] := Value; end; -procedure TJWTRegisteredClaims.SetSubject(const Value: String); +procedure TJWTRegisteredClaims.SetSubject(const Value: string); begin Items[TJWTRegisteredClaimNames.Subject] := Value; end; { TJWTCustomClaims } -function TJWTDictionaryObject.Contains(const Index: String): Boolean; +function TJWTDictionaryObject.Contains(const Index: string): Boolean; begin - Result := FClaims.ContainsKey(Index); + Result := FClaims.ContainsKey(index); end; constructor TJWTDictionaryObject.Create; begin inherited; - FClaims := TDictionary.Create; + FClaims := TDictionary.Create; end; destructor TJWTDictionaryObject.Destroy; @@ -307,35 +314,35 @@ begin inherited; end; -function TJWTDictionaryObject.GetItem(const Index: String): String; +function TJWTDictionaryObject.GetItem(const Index: string): string; begin - if not FClaims.TryGetValue(Index, Result) then + if not FClaims.TryGetValue(index, Result) then Result := ''; end; -function TJWTDictionaryObject.GetItemAsDateTime(const Index: String): TDateTime; +function TJWTDictionaryObject.GetItemAsDateTime(const Index: string): TDateTime; var lIntValue: Int64; begin - if not TryStrToInt64(Items[Index], lIntValue) then + if not TryStrToInt64(Items[index], lIntValue) then raise Exception.Create('Item cannot be converted as Unix Epoch'); Result := UnixToDateTime(lIntValue, False); end; -function TJWTDictionaryObject.Keys: TArray; +function TJWTDictionaryObject.Keys: TArray; begin Result := FClaims.Keys.ToArray; end; -procedure TJWTDictionaryObject.SetItem(const Index, Value: String); +procedure TJWTDictionaryObject.SetItem(const Index, Value: string); begin - FClaims.AddOrSetValue(Index, Value); + FClaims.AddOrSetValue(index, Value); end; -procedure TJWTDictionaryObject.SetItemAsDateTime(const Index: String; +procedure TJWTDictionaryObject.SetItemAsDateTime(const Index: string; const Value: TDateTime); begin - Items[Index] := IntToStr(DateTimeToUnix(Value, False)); + Items[index] := IntToStr(DateTimeToUnix(Value, False)); end; { TJWTCustomClaims } @@ -348,11 +355,12 @@ end; { TJWT } function TJWT.CheckExpirationTime(Payload: TJSONObject; - out Error: String): Boolean; + out Error: string): Boolean; var lJValue: TJSONValue; lIntValue: Int64; lValue: string; + lExpirationTimeAsDateTime: TDateTime; begin lJValue := Payload.GetValue(TJWTRegisteredClaimNames.ExpirationTime); if not Assigned(lJValue) then @@ -368,7 +376,8 @@ begin Exit(False); end; - if UnixToDateTime(lIntValue, False) <= Now - FLeewaySeconds * OneSecond then + lExpirationTimeAsDateTime := UnixToDateTime(lIntValue, False); + if lExpirationTimeAsDateTime <= Now - FLeewaySeconds * OneSecond then begin Error := 'Token expired'; Exit(False); @@ -377,7 +386,7 @@ begin Result := True; end; -function TJWT.CheckIssuedAt(Payload: TJSONObject; out Error: String): Boolean; +function TJWT.CheckIssuedAt(Payload: TJSONObject; out Error: string): Boolean; var lJValue: TJSONValue; lIntValue: Int64; @@ -406,7 +415,7 @@ begin Result := True; end; -function TJWT.CheckNotBefore(Payload: TJSONObject; out Error: String): Boolean; +function TJWT.CheckNotBefore(Payload: TJSONObject; out Error: string): Boolean; var lJValue: TJSONValue; lIntValue: Int64; @@ -435,16 +444,17 @@ begin Result := True; end; -constructor TJWT.Create(const SecretKey: String); +constructor TJWT.Create(const SecretKey: string; const ALeewaySeconds: Cardinal = 300); begin inherited Create; FSecretKey := SecretKey; FRegisteredClaims := TJWTRegisteredClaims.Create; FCustomClaims := TJWTCustomClaims.Create; FHMACAlgorithm := 'HS256'; - FLeewaySeconds := 300; // 5 minutes of leeway + FLeewaySeconds := ALeewaySeconds; FRegClaimsToChecks := [TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore, TJWTCheckableClaim.IssuedAt]; + FLiveValidityWindowInSeconds := 0; end; destructor TJWT.Destroy; @@ -454,13 +464,18 @@ begin inherited; end; -function TJWT.GetToken: String; +function TJWT.GetLiveValidityWindowInSeconds: Cardinal; +begin + Result := StrToIntDef(FCustomClaims.Items['lvw'], 0); +end; + +function TJWT.GetToken: string; var lHeader, lPayload: TJSONObject; - lHeaderEncoded, lPayloadEncoded, lToken, lHash: String; + lHeaderEncoded, lPayloadEncoded, lToken, lHash: string; lBytes: TBytes; - lRegClaimName: String; - lCustomClaimName: String; + lRegClaimName: string; + lCustomClaimName: string; begin lHeader := TJSONObject.Create; try @@ -500,9 +515,9 @@ begin end; end; -function TJWT.IsValidToken(const Token: String; out Error: String): Boolean; +function TJWT.IsValidToken(const Token: string; out Error: string): Boolean; var - lPieces: TArray; + lPieces: TArray; lJHeader: TJSONObject; lJAlg: TJSONString; lAlgName: string; @@ -584,9 +599,9 @@ begin end; end; -procedure TJWT.LoadToken(const Token: String); +procedure TJWT.LoadToken(const Token: string); var - lPieces: TArray; + lPieces: TArray; lJHeader: TJSONObject; lJPayload: TJSONObject; lJPair: TJSONPair; @@ -595,7 +610,7 @@ var j: Integer; lIsRegistered: Boolean; lValue: string; - lError: String; + lError: string; begin if not IsValidToken(Token, lError) then raise EMVCJWTException.Create(lError); @@ -620,7 +635,7 @@ begin lValue := lJPair.JsonValue.Value; // if is a registered claim, load it in the proper dictionary... - for j := 0 to High(TJWTRegisteredClaimNames.Names) do + for j := 0 to high(TJWTRegisteredClaimNames.Names) do begin if lName = TJWTRegisteredClaimNames.Names[j] then begin @@ -646,14 +661,14 @@ begin FRegClaimsToChecks := Value; end; -procedure TJWT.SetHMACAlgorithm(const Value: String); +procedure TJWT.SetHMACAlgorithm(const Value: string); begin FHMACAlgorithm := Value; end; -procedure TJWT.SetLeewaySeconds(const Value: Int64); +procedure TJWT.SetLiveValidityWindowInSeconds(const Value: Cardinal); begin - FLeewaySeconds := Value; + FCustomClaims.Items['lvw'] := Value.ToString; end; end. diff --git a/sources/MVCFramework.MessagingController.pas b/sources/MVCFramework.MessagingController.pas deleted file mode 100644 index 5d8358aa..00000000 --- a/sources/MVCFramework.MessagingController.pas +++ /dev/null @@ -1,345 +0,0 @@ -// *************************************************************************** -// -// Delphi MVC Framework -// -// Copyright (c) 2010-2017 Daniele Teti and the DMVCFramework Team -// -// https://github.com/danieleteti/delphimvcframework -// -// *************************************************************************** -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// *************************************************************************** } - -unit MVCFramework.MessagingController; - -{$I dmvcframework.inc} - -interface - -uses - System.SysUtils, - System.DateUtils, - System.SyncObjs, - MVCFramework, - MVCFramework.Commons, - MVCFramework.Logger, - MVCFramework.TypesAliases, - StompClient; - -type - - [MVCPath('/messages')] - TMVCBUSController = class(TMVCController) - protected - function GetUniqueDurableHeader(AClientId, ATopicName: string): string; - - procedure InternalSubscribeUserToTopics(AClientId: string; AStompClient: IStompClient); - procedure InternalSubscribeUserToTopic(AClientId: string; ATopicName: string; AStompClient: IStompClient); - - procedure AddTopicToUserSubscriptions(const ATopic: string); - procedure RemoveTopicFromUserSubscriptions(const ATopic: string); - procedure OnBeforeAction(AContext: TWebContext; const AActionNAme: string; var AHandled: Boolean); override; - public - [MVCHTTPMethod([httpPOST])] - [MVCPath('/clients/($clientid)')] - procedure SetClientID(AContext: TWebContext); - - [MVCPath('/subscriptions/($topicorqueue)/($name)')] - [MVCHTTPMethod([httpPOST])] - procedure SubscribeToTopic(AContext: TWebContext); - - [MVCPath('/subscriptions/($topicorqueue)/($name)')] - [MVCHTTPMethod([httpDELETE])] - procedure UnSubscribeFromTopic(AContext: TWebContext); - - [MVCHTTPMethod([httpGET])] - [MVCPath] - procedure ReceiveMessages(AContext: TWebContext); - - [MVCHTTPMethod([httpPOST])] - [MVCPath('/($type)/($topicorqueue)')] - procedure EnqueueMessage(AContext: TWebContext); - - [MVCHTTPMethod([httpGET])] - [MVCPath('/subscriptions')] - procedure CurrentlySubscribedTopics(AContext: TWebContext); - end; - -implementation - -{ TMVCBUSController } - -procedure TMVCBUSController.AddTopicToUserSubscriptions(const ATopic: string); -var - x: string; - topics: TArray; - t: string; - ToAdd: Boolean; -begin - x := Session['__subscriptions']; - topics := x.Split([';']); - ToAdd := true; - for t in topics do - if t.Equals(ATopic) then - begin - ToAdd := False; - end; - if ToAdd then - begin - SetLength(topics, length(topics) + 1); - topics[length(topics) - 1] := ATopic; - Session['__subscriptions'] := string.Join(';', topics); - end; -end; - -procedure TMVCBUSController.CurrentlySubscribedTopics(AContext: TWebContext); -begin - ContentType := TMVCMediaType.TEXT_PLAIN; - Render(Session['__subscriptions']); -end; - -procedure TMVCBUSController.EnqueueMessage(AContext: TWebContext); -var - topicname: string; - queuetype: string; -begin - queuetype := AContext.Request.Params['type'].Trim.ToLower; - if (queuetype <> 'topic') and (queuetype <> 'queue') then - raise EMVCException.Create('Valid type are "queue" or "topic", got ' + queuetype); - - topicname := AContext.Request.Params['topicorqueue'].Trim; - if topicname.IsEmpty then - raise EMVCException.Create('Invalid or empty topic'); - if not AContext.Request.ThereIsRequestBody then - raise EMVCException.Create('Body request required'); - // EnqueueMessageOnTopicOrQueue(queuetype = 'queue', '/' + queuetype + '/' + topicname, - // CTX.Request.BodyAsJSONObject.Clone as TJSONObject, true); - // EnqueueMessage('/queue/' + topicname, CTX.Request.BodyAsJSONObject.Clone as TJSONObject, true); - Render(200, 'Message sent to topic ' + topicname); -end; - -function TMVCBUSController.GetUniqueDurableHeader(AClientId, ATopicName: string): string; -begin - Result := AClientId + '___' + ATopicName.Replace('/', '_', [rfReplaceAll]); -end; - -procedure TMVCBUSController.ReceiveMessages(AContext: TWebContext); -var - Stomp: IStompClient; - LClientID: string; - frame: IStompFrame; - obj, res: TJSONObject; - LFrames: TArray; - arr: TJSONArray; - LLastReceivedMessageTS: TDateTime; - LTimeOut: Boolean; -const - - {$IFDEF TEST} - - RECEIVE_TIMEOUT = 5; // seconds - - {$ELSE} - - RECEIVE_TIMEOUT = 60 * 5; // 5 minutes - - {$ENDIF} - -begin - LTimeOut := False; - LClientID := GetClientID; - Stomp := GetNewStompClient(LClientID); - try - InternalSubscribeUserToTopics(LClientID, Stomp); - // StartReceiving := now; - - LLastReceivedMessageTS := now; - SetLength(LFrames, 0); - while not IsShuttingDown do - begin - LTimeOut := False; - frame := nil; - Log.Info('/messages receive', ClassName); - Stomp.Receive(frame, 100); - if Assigned(frame) then - // get 10 messages at max, and then send them to client - begin - LLastReceivedMessageTS := now; - SetLength(LFrames, length(LFrames) + 1); - LFrames[length(LFrames) - 1] := frame; - Stomp.Ack(frame.MessageID); - if length(LFrames) >= 10 then - break; - end - else - begin - if (length(LFrames) > 0) then - break; - if SecondsBetween(now, LLastReceivedMessageTS) >= RECEIVE_TIMEOUT then - begin - LTimeOut := true; - break; - end; - end; - end; - - arr := TJSONArray.Create; - res := TJSONObject.Create(TJSONPair.Create('messages', arr)); - for frame in LFrames do - begin - if Assigned(frame) then - begin - obj := TJSONObject.ParseJSONValue(frame.GetBody) as TJSONObject; - if Assigned(obj) then - begin - arr.AddElement(obj); - end - else - begin - Log.Error(Format - ('Not valid JSON object in topic requested by user %s. The raw message is "%s"', - [LClientID, frame.GetBody]), ClassName); - end; - end; - end; // for in - res.AddPair('_timestamp', FormatDateTime('yyyy-mm-dd hh:nn:ss', now)); - if LTimeOut then - begin - res.AddPair('_timeout', TJSONTrue.Create); - // Render(http_status.RequestTimeout, res); - end - else - begin - res.AddPair('_timeout', TJSONFalse.Create); - // Render(http_status.OK, res); - end; - - finally - // Stomp.Disconnect; - end; -end; - -procedure TMVCBUSController.RemoveTopicFromUserSubscriptions(const ATopic: string); -var - x: string; - topics, afterremovaltopics: TArray; - IndexToRemove: Integer; - i: Integer; -begin - x := Session['__subscriptions']; - topics := x.Split([';']); - IndexToRemove := 0; - SetLength(afterremovaltopics, length(topics)); - for i := 0 to length(topics) - 1 do - begin - if not topics[i].Equals(ATopic) then - begin - afterremovaltopics[IndexToRemove] := topics[i]; - Inc(IndexToRemove); - end; - end; - if IndexToRemove <> length(ATopic) - 1 then - SetLength(afterremovaltopics, length(topics) - 1); - - if length(afterremovaltopics) = 0 then - Session['__subscriptions'] := '' - else - Session['__subscriptions'] := string.Join(';', afterremovaltopics); -end; - -procedure TMVCBUSController.SetClientID(AContext: TWebContext); -begin - Session[CLIENTID_KEY] := AContext.Request.Params['clientid']; -end; - -procedure TMVCBUSController.SubscribeToTopic(AContext: TWebContext); -var - LStomp: IStompClient; - LClientID: string; - LTopicName: string; - LTopicOrQueue: string; -begin - LClientID := GetClientID; - LTopicName := AContext.Request.Params['name'].ToLower; - LTopicOrQueue := AContext.Request.Params['topicorqueue'].ToLower; - LStomp := GetNewStompClient(LClientID); - try - LTopicName := '/' + LTopicOrQueue + '/' + LTopicName; - InternalSubscribeUserToTopic(LClientID, LTopicName, LStomp); - Render(200, 'Subscription OK for ' + LTopicName); - finally - // Stomp.Disconnect; - end; -end; - -procedure TMVCBUSController.InternalSubscribeUserToTopics(AClientId: string; AStompClient: IStompClient); -var - x, t: string; - topics: TArray; -begin - x := Session['__subscriptions']; - topics := x.Split([';']); - for t in topics do - InternalSubscribeUserToTopic(AClientId, t, AStompClient); -end; - -procedure TMVCBUSController.OnBeforeAction(AContext: TWebContext; const AActionNAme: string; - var AHandled: Boolean); -begin - inherited; - if not StrToBool(Config['messaging']) then - begin - AHandled := true; - raise EMVCException.Create('Messaging extensions are not enabled'); - end; - AHandled := False; -end; - -procedure TMVCBUSController.InternalSubscribeUserToTopic(AClientId, ATopicName: string; - AStompClient: IStompClient); -// var -// LDurSubHeader: string; -// LHeaders: IStompHeaders; -begin - raise EMVCException.Create('Not implemented'); - // LHeaders := TStompHeaders.Create; - // LDurSubHeader := GetUniqueDurableHeader(clientid, topicname); - // LHeaders.Add(TStompHeaders.NewDurableSubscriptionHeader(LDurSubHeader)); - // - // if topicname.StartsWith('/topic') then - // LHeaders.Add('id', clientid); //https://www.rabbitmq.com/stomp.html - // - // StompClient.Subscribe(topicname, amClient, LHeaders); - // LogE('SUBSCRIBE TO ' + clientid + '@' + topicname + ' dursubheader:' + LDurSubHeader); - // AddTopicToUserSubscriptions(topicname); -end; - -procedure TMVCBUSController.UnSubscribeFromTopic(AContext: TWebContext); -var - Stomp: IStompClient; - clientid: string; - thename: string; - s: string; -begin - clientid := GetClientID; - thename := AContext.Request.Params['name'].ToLower; - Stomp := GetNewStompClient(clientid); - s := '/queue/' + thename; - Stomp.Unsubscribe(s); - RemoveTopicFromUserSubscriptions(s); - Render(200, 'UnSubscription OK for ' + s); -end; - -end. diff --git a/sources/MVCFramework.Middleware.JWT.pas b/sources/MVCFramework.Middleware.JWT.pas index 03d378c0..1e98bda9 100644 --- a/sources/MVCFramework.Middleware.JWT.pas +++ b/sources/MVCFramework.Middleware.JWT.pas @@ -47,6 +47,7 @@ type FClaimsToChecks: TJWTCheckableClaims; FSetupJWTClaims: TJWTClaimsSetup; FSecret: string; + FLeewaySeconds: Cardinal; protected procedure InternalRender( AJSONValue: TJSONValue; @@ -88,23 +89,32 @@ type TJWTCheckableClaim.ExpirationTime, TJWTCheckableClaim.NotBefore, TJWTCheckableClaim.IssuedAt - ]); virtual; + ]; + ALeewaySeconds: Cardinal = 300); virtual; end; implementation +uses System.NetEncoding, System.DateUtils; + { TMVCJWTAuthenticationMiddleware } -constructor TMVCJWTAuthenticationMiddleware.Create( - AAuthenticationHandler: IMVCAuthenticationHandler; - AConfigClaims: TJWTClaimsSetup; ASecret: string; - AClaimsToCheck: TJWTCheckableClaims); +constructor TMVCJWTAuthenticationMiddleware.Create(AAuthenticationHandler: IMVCAuthenticationHandler; + AConfigClaims: TJWTClaimsSetup; + ASecret: string = 'D3lph1MVCFram3w0rk'; + AClaimsToCheck: TJWTCheckableClaims = [ + TJWTCheckableClaim.ExpirationTime, + TJWTCheckableClaim.NotBefore, + TJWTCheckableClaim.IssuedAt + ]; + ALeewaySeconds: Cardinal = 300); begin inherited Create; FAuthenticationHandler := AAuthenticationHandler; FSetupJWTClaims := AConfigClaims; FClaimsToChecks := AClaimsToCheck; FSecret := ASecret; + FLeewaySeconds := ALeewaySeconds; end; procedure TMVCJWTAuthenticationMiddleware.InternalRender( @@ -148,7 +158,7 @@ var JWTValue: TJWT; AuthHeader: string; AuthToken: string; - ErrorMsg: String; + ErrorMsg: string; begin // check if the resource is protected FAuthenticationHandler.OnRequest(AControllerQualifiedClassName, AActionName, AuthRequired); @@ -161,7 +171,7 @@ begin // Checking token in subsequent requests // *************************************************** - JWTValue := TJWT.Create(FSecret); + JWTValue := TJWT.Create(FSecret, FLeewaySeconds); try JWTValue.RegClaimsToChecks := Self.FClaimsToChecks; AuthHeader := AContext.Request.Headers['Authentication']; @@ -177,6 +187,7 @@ begin if AuthHeader.StartsWith('bearer', True) then begin AuthToken := AuthHeader.Remove(0, 'bearer'.Length).Trim; + AuthToken := Trim(TNetEncoding.URL.URLDecode(AuthToken)); end; // check the jwt @@ -204,7 +215,14 @@ begin FAuthenticationHandler.OnAuthorization(AContext.LoggedUser.Roles, AControllerQualifiedClassName, AActionName, IsAuthorized); if IsAuthorized then + begin + if JWTValue.LiveValidityWindowInSeconds > 0 then + begin + JWTValue.Claims.ExpirationTime := Now + JWTValue.LiveValidityWindowInSeconds * OneSecond; + AContext.Response.SetCustomHeader('Authentication', 'bearer ' + JWTValue.GetToken); + end; AHandled := False + end else begin RenderError(HTTP_STATUS.Forbidden, 'Authorization Forbidden', AContext); @@ -222,7 +240,7 @@ procedure TMVCJWTAuthenticationMiddleware.OnBeforeRouting( var UserName: string; Password: string; - RolesList: TList; + RolesList: TList; SessionData: TSessionData; IsValid: Boolean; JWTValue: TJWT; @@ -246,9 +264,11 @@ begin FAuthenticationHandler.OnAuthentication(UserName, Password, RolesList, IsValid, SessionData); if IsValid then begin - JWTValue := TJWT.Create(FSecret); + JWTValue := TJWT.Create(FSecret, FLeewaySeconds); try // let's user config claims and custom claims + if not Assigned(FSetupJWTClaims) then + raise EMVCJWTException.Create('SetupJWTClaims not set'); FSetupJWTClaims(JWTValue); // these claims are mandatory and managed by the middleware @@ -259,7 +279,12 @@ begin raise EMVCJWTException.Create('Custom claim "roles" is reserved and cannot be modified in the JWT setup'); JWTValue.CustomClaims['username'] := UserName; - JWTValue.CustomClaims['roles'] := String.Join(',', RolesList.ToArray); + JWTValue.CustomClaims['roles'] := string.Join(',', RolesList.ToArray); + + if JWTValue.LiveValidityWindowInSeconds > 0 then + begin + JWTValue.Claims.ExpirationTime := Now + (JWTValue.LeewaySeconds + JWTValue.LiveValidityWindowInSeconds) * OneSecond; + end; // setup the current logged user from the JWT AContext.LoggedUser.Roles.AddRange(RolesList);