├── README.md ├── Server ├── Modules.Config.dfm ├── config │ └── local │ │ ├── secrets.json │ │ ├── keys │ │ ├── 404964ad925b462c891b732813162705.pub │ │ └── 404964ad925b462c891b732813162705.key │ │ └── WinServer.json ├── Services.Sample.Impl.pas ├── WinServer.dpr ├── Modules.Auth.dfm ├── Modules.Api.dfm ├── shared │ └── Services.Sample.pas ├── Modules.Config.pas ├── Config.Secrets.pas ├── Utils.RSAKeyStorage.pas ├── Modules.Api.pas ├── Modules.Auth.pas ├── Services.ProblemDetail.pas ├── Config.Server.pas └── WinServer.dproj └── .gitignore /README.md: -------------------------------------------------------------------------------- 1 | # TMS BIZ Boilerplate projects 2 | 3 | 4 | -------------------------------------------------------------------------------- /Server/Modules.Config.dfm: -------------------------------------------------------------------------------- 1 | object ConfigModule: TConfigModule 2 | OnCreate = DataModuleCreate 3 | Height = 480 4 | Width = 640 5 | end 6 | -------------------------------------------------------------------------------- /Server/config/local/secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "Clients": [ 3 | { 4 | "ClientName": "Client for local server only. Should not be used in production", 5 | "ClientId": "client123", 6 | "ClientSecret": "secret123", 7 | "Scope": "setup:write setup:read", 8 | "Audiences": [ 9 | "http://localhost:2001/tms/api", 10 | "http://localhost:2001/tms/admin" 11 | ] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /Server/config/local/keys/404964ad925b462c891b732813162705.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PUBLIC KEY----- 2 | MIIBCgKCAQEAw0nz5kKX6qAa90QLmOs/UNHEDTPGhc6IrZ6+6uPYz+/FIcGo2r6C 3 | AuSCx7G/Xrb1Vzc5umx1CTKORmr3BX8osXGyXHRYwEDUoCQh19icNbuJg1cwSgSB 4 | 6WZwXaAdLzSde3r6ABw5r6vRfrFOrrj3YOFisGmLZfg2p4eF32zyWbDBDNUc0UIA 5 | f3GTiIUbGDvGB5DKPMjZE3t9yJSZqGErhNd6ptSSmjav8C147/gDtRROFs3OFlmC 6 | J3532kM8flh9Gzh8CNJwjbvlUTmFiM1w5gDUZfjG4Rm2jphbZjbu0OgftTMdBNrV 7 | 00clJdO79pP3clHrNDWxtLtCQ6uualMZAwIDAQAB 8 | -----END RSA PUBLIC KEY----- 9 | -------------------------------------------------------------------------------- /Server/Services.Sample.Impl.pas: -------------------------------------------------------------------------------- 1 | unit Services.Sample.Impl; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | XData.Service.Common, 8 | Services.Sample; 9 | 10 | type 11 | [ServiceImplementation] 12 | TSampleService = class(TInterfacedObject, ISampleService) 13 | public 14 | function EchoString(const Value: string): string; 15 | function Add(const A, B: Double): Double; 16 | end; 17 | 18 | implementation 19 | 20 | { TSampleService } 21 | 22 | function TSampleService.Add(const A, B: Double): Double; 23 | begin 24 | Result := A + B; 25 | end; 26 | 27 | function TSampleService.EchoString(const Value: string): string; 28 | begin 29 | Result := Value; 30 | end; 31 | 32 | initialization 33 | RegisterServiceType(TSampleService); 34 | 35 | end. 36 | 37 | -------------------------------------------------------------------------------- /Server/config/local/WinServer.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApiModule": { 3 | "BaseUrl": "http://+:2001/tms/api", 4 | "Middleware": { 5 | "Jwt": { 6 | "ExpectedIssuers": [ 7 | "http://localhost:2001/tms/auth" 8 | ], 9 | "ExpectedAudiences": [ 10 | "http://localhost:2001/tms/api" 11 | ] 12 | } 13 | } 14 | }, 15 | "AuthModule": { 16 | "BaseUrl": "http://+:2001/tms/auth", 17 | "SigningKeyId": "404964ad925b462c891b732813162705" 18 | }, 19 | "Service": { 20 | "Name": "XDataService", 21 | "DisplayName": "TMS XData Local Server", 22 | "Description": "Runs TMS XData API server local debug mode" 23 | }, 24 | "RSAKeysFolder": "..\\..\\config\\local\\keys", 25 | "SecretsFile": "..\\..\\config\\local\\secrets.json" 26 | } -------------------------------------------------------------------------------- /Server/WinServer.dpr: -------------------------------------------------------------------------------- 1 | program WinServer; 2 | 3 | uses 4 | Sparkle.App, 5 | Vcl.Forms, 6 | Modules.Api in 'Modules.Api.pas' {MainModule: TDataModule}, 7 | Services.Sample in 'shared\Services.Sample.pas', 8 | Modules.Config in 'Modules.Config.pas' {ConfigModule: TDataModule}, 9 | Config.Server in 'Config.Server.pas', 10 | Services.Sample.Impl in 'Services.Sample.Impl.pas', 11 | Modules.Auth in 'Modules.Auth.pas' {AuthModule: TDataModule}, 12 | Config.Secrets in 'Config.Secrets.pas', 13 | Utils.RSAKeyStorage in 'Utils.RSAKeyStorage.pas', 14 | Services.ProblemDetail in 'Services.ProblemDetail.pas'; 15 | 16 | {$R *.res} 17 | 18 | begin 19 | ReportMemoryLeaksOnShutdown := True; 20 | Application.Initialize; 21 | Application.MainFormOnTaskbar := True; 22 | Application.CreateForm(TConfigModule, ConfigModule); 23 | Application.CreateForm(TMainModule, MainModule); 24 | Application.CreateForm(TAuthModule, AuthModule); 25 | Application.Run; 26 | end. 27 | -------------------------------------------------------------------------------- /Server/Modules.Auth.dfm: -------------------------------------------------------------------------------- 1 | object AuthModule: TAuthModule 2 | OnCreate = DataModuleCreate 3 | Height = 364 4 | Width = 516 5 | object SphinxConfig: TSphinxConfig 6 | Clients = <> 7 | OnGetClient = SphinxConfigGetClient 8 | OnConfigureToken = SphinxConfigConfigureToken 9 | OnGetSigningData = SphinxConfigGetSigningData 10 | Left = 80 11 | Top = 48 12 | end 13 | object SphinxServer: TSphinxServer 14 | Config = SphinxConfig 15 | Left = 80 16 | Top = 128 17 | object SphinxServerForward: TSparkleForwardMiddleware 18 | end 19 | object SphinxServerLogging: TSparkleLoggingMiddleware 20 | FormatString = ':method :url :statuscode - :responsetime ms' 21 | ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s' 22 | ErrorResponseOptions.ErrorCode = 'ServerError' 23 | ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s' 24 | end 25 | object SphinxServerCompress: TSparkleCompressMiddleware 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /Server/Modules.Api.dfm: -------------------------------------------------------------------------------- 1 | object MainModule: TMainModule 2 | OnCreate = DataModuleCreate 3 | Height = 394 4 | Width = 602 5 | object ApiServer: TXDataServer 6 | UnknownMemberHandling = Error 7 | InstanceLoopHandling = Error 8 | EntitySetPermissions = <> 9 | SwaggerOptions.Enabled = True 10 | SwaggerUIOptions.Enabled = True 11 | OnModuleException = ApiServerModuleException 12 | Left = 56 13 | Top = 56 14 | object ApiServerGeneric: TSparkleGenericMiddleware 15 | OnRequest = ApiServerGenericRequest 16 | end 17 | object ApiServerForward: TSparkleForwardMiddleware 18 | end 19 | object ApiServerLogging: TSparkleLoggingMiddleware 20 | FormatString = ':method :url :statuscode - :responsetime ms' 21 | ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s' 22 | ErrorResponseOptions.ErrorCode = 'ServerError' 23 | ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s' 24 | end 25 | object ApiServerCompress: TSparkleCompressMiddleware 26 | end 27 | object ApiServerJWT: TSparkleJwtMiddleware 28 | ForbidAnonymousAccess = True 29 | OnGetSecretEx = JWTGetSecretEx 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /Server/shared/Services.Sample.pas: -------------------------------------------------------------------------------- 1 | unit Services.Sample; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, XData.Service.Common; 8 | 9 | type 10 | /// 11 | /// Sample endpoints. 12 | /// 13 | [ServiceContract] 14 | [Route('sample')] 15 | [ValidateParams] 16 | ISampleService = interface(IInvokable) 17 | ['{4A3455BB-86D5-433C-89DB-5686DA7B567A}'] 18 | 19 | /// 20 | /// Echoes the provided string value. 21 | /// 22 | /// The value provided as parameter. 23 | [HttpGet, Route('echo')] 24 | function EchoString(const Value: string): string; 25 | 26 | /// 27 | /// Adds two numbers A and B. 28 | /// 29 | /// 30 | /// Description for A parameter. Only method documentation, doesn't appear in Swagger. 31 | /// 32 | /// 33 | /// Description for B parameter. Only method documentation, doesn't appear in Swagger. 34 | /// 35 | /// The sum of A ad B. 36 | [HttpGet, Route('add')] 37 | function Add(const A, B: Double): Double; 38 | end; 39 | 40 | implementation 41 | 42 | initialization 43 | RegisterServiceType(TypeInfo(ISampleService)); 44 | 45 | end. 46 | -------------------------------------------------------------------------------- /Server/Modules.Config.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Config; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.IOUtils, 7 | Bcl.Logging, Bcl.TMSLogging, TMSLoggingCore, TMSLoggingUtils, Sparkle.App.Config, 8 | TMSLoggingTextOutputHandler, 9 | TMSLoggingDiscordOutputHandler; 10 | 11 | type 12 | TConfigModule = class(TDataModule) 13 | procedure DataModuleCreate(Sender: TObject); 14 | private 15 | { Private declarations } 16 | public 17 | { Public declarations } 18 | end; 19 | 20 | var 21 | ConfigModule: TConfigModule; 22 | 23 | implementation 24 | 25 | uses 26 | Config.Server; 27 | 28 | {%CLASSGROUP 'Vcl.Controls.TControl'} 29 | 30 | {$R *.dfm} 31 | 32 | procedure TConfigModule.DataModuleCreate(Sender: TObject); 33 | begin 34 | // Output log to file 35 | RegisterTMSLogger; 36 | begin 37 | var Handler := TMSDefaultLogger.RegisterOutputHandlerClass(TTMSLoggerTextOutputHandler); 38 | Handler.LogLevelFilters := [Warning, Error, Exception, Info]; 39 | TTMSLoggerTextOutputHandler(Handler).FileName := TPath.ChangeExtension(ParamStr(0), '.log'); 40 | end; 41 | 42 | // logging file config 43 | var LoggingFile := TPath.ChangeExtension(ParamStr(0), '.logging.ini'); 44 | if TFile.Exists(LoggingFile) then 45 | TMSDefaultLogger.LoadConfigurationFromFile(nil, LoggingFile); 46 | 47 | // Service naming 48 | SparkleAppConfig.WinService.Name := ServerConfig.WinService.Name; 49 | SparkleAppConfig.WinService.DisplayName := ServerConfig.WinService.DisplayName; 50 | SparkleAppConfig.WinService.Description := ServerConfig.WinService.Description; 51 | end; 52 | 53 | end. 54 | -------------------------------------------------------------------------------- /Server/config/local/keys/404964ad925b462c891b732813162705.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAw0nz5kKX6qAa90QLmOs/UNHEDTPGhc6IrZ6+6uPYz+/FIcGo 3 | 2r6CAuSCx7G/Xrb1Vzc5umx1CTKORmr3BX8osXGyXHRYwEDUoCQh19icNbuJg1cw 4 | SgSB6WZwXaAdLzSde3r6ABw5r6vRfrFOrrj3YOFisGmLZfg2p4eF32zyWbDBDNUc 5 | 0UIAf3GTiIUbGDvGB5DKPMjZE3t9yJSZqGErhNd6ptSSmjav8C147/gDtRROFs3O 6 | FlmCJ3532kM8flh9Gzh8CNJwjbvlUTmFiM1w5gDUZfjG4Rm2jphbZjbu0OgftTMd 7 | BNrV00clJdO79pP3clHrNDWxtLtCQ6uualMZAwIDAQABAoIBAHg9LUWFlQGp6FB7 8 | /eP+W/KH5Pz39vBl1LOJfSUkela02xxIn41nJKdhSMBpSVmornAvmYSVyI2MKhtj 9 | OoqQ2149kcLpA8gDYqbi+E9sl+lheW57nUjFc7NgFikfLzjkXV4svoaY2k+2K3uk 10 | 8bwUOIPW4auaoHB9HjGR+LQy4Rik7a+KKkATaynN8x7NVngc71fYAAbLTB/GvG86 11 | 0yUgI23UexvzVWa6Y1G4roqADHOEJnUtxdgPwOVbQXPZSy4D8r6OEWte9BIWIE8f 12 | DISunL0cLysBisL5Namh4HsRuKDoKPhazQmXYWSL1rlbh+2lawGYzcS6kmhO6FUx 13 | KIM+nJkCgYEA+EJDjjRzZkK7yukSADLqPshvwYyWj3gUrFInRfT5p+iyGSYm/nZl 14 | uL9dabqr4BIvJzCP/VvHy/HRWMEobP3cGszQ62EZ1MmRx0acVD0ZSGIVmfm5/S+s 15 | L66x0g3tbXjQhcn254GTtQ7oEbGILf+gBus74sDXq+5wnL7bTSPw168CgYEAyWDa 16 | nqSjVshnt5pDbps4cj+O6yTfeBxJnSqhNWAOnsUs5Je/4twlTonn+DI3eD2d5iJn 17 | PME1OXKgEOlXAv8xqXrnpP+mshQ6i/FUYYiJ5wFqoH07GnC/Yjl0IoXOcJwxjOJm 18 | JH6s8R8jYweqegEYY9K0kBHqjg7PxZn4HctKVO0CgYEAmHdRt+8chEwtuxNi275p 19 | QD+m6u5ltRzhTWdy+TLXdLijUHCu+A5MsKbOPtXHIujoDqBzff00+lq74E9YfX93 20 | AvqQwE41KArXPdRuxAl2Eo8yhwron8b8b5hRWxj0nQvavJ/pxAo/Za4O9nbalK1u 21 | YXFsqPkYO15HyOwqEPkmu78CgYAjXs6nZLWHzaVh+0dyyvKsJnu803oox5cqcs1j 22 | 6Z5HxswuznSWrSIkXFaRIojZpsDwJgTKCAmIMJjqv4FX9mjLotnDAmBOEP2DjPzM 23 | MMY/++C1lalm6HzFDOBYG/Z5kwWlADL+n/1SUKzPfQ41ZbHZ/q1WTL9M+k8GHxgU 24 | a5t/tQKBgQDJmVAz1ingahZuHa4aQJKIrsVq702Yye+gsqkeMDOSVdKdjadvq8km 25 | dR9+cmX/kN8pLc0GVGr4ZbjwzPM5nCCXi56m/Ag5fttWqwp6/Z3A8mS0OkpHaxj3 26 | szlpN31tiGltuaOkZMDMm/596R3to9rWCJxjw2zG7ZmOp63uXIv0JQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | *.dll 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | 60 | # Delphi history and backups 61 | __history/ 62 | __recovery/ 63 | *.~* 64 | 65 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 66 | *.stat 67 | 68 | # Specific ignores for this repository 69 | Entitlement.TemplateOSX.xml 70 | info.plist.TemplateOSX.xml 71 | tmsbuild._@emb_.tmp 72 | .idea 73 | dunit.ini 74 | *.deployproj 75 | *.dsv 76 | Server/WinServer.res 77 | WinServer.log 78 | -------------------------------------------------------------------------------- /Server/Config.Secrets.pas: -------------------------------------------------------------------------------- 1 | unit Config.Secrets; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, System.SysUtils, System.IOUtils, 7 | Bcl.Json, Bcl.Logging, Bcl.Collections; 8 | 9 | type 10 | TClientConfig = class 11 | strict private 12 | FClientName: string; 13 | FClientId: string; 14 | FClientSecret: string; 15 | FScope: string; 16 | FAudiences: TList; 17 | public 18 | constructor Create; 19 | destructor Destroy; override; 20 | property ClientName: string read FClientName write FClientName; 21 | property ClientId: string read FClientId write FClientId; 22 | property ClientSecret: string read FClientSecret write FClientSecret; 23 | property Scope: string read FScope write FScope; 24 | property Audiences: TList read FAudiences write FAudiences; 25 | end; 26 | 27 | TSecretValues = TOrderedDictionary; 28 | 29 | TSecretsConfig = class 30 | private 31 | FClients: TList; 32 | FEnvVarPrefix: string; 33 | public 34 | constructor Create; 35 | destructor Destroy; override; 36 | function FindClient(const ClientId: string): TClientConfig; 37 | property Clients: TList read FClients; 38 | property EnvVarPrefix: string read FEnvVarPrefix; 39 | end; 40 | 41 | procedure SetSecretsConfigFileName(const FileName: string); 42 | function SecretsConfig: TSecretsConfig; 43 | 44 | implementation 45 | 46 | var 47 | _SecretsConfigFileName: string; 48 | _SecretsConfig: TSecretsConfig; 49 | 50 | procedure SetSecretsConfigFileName(const FileName: string); 51 | begin 52 | _SecretsConfigFileName := FileName; 53 | FreeAndNil(_SecretsConfig); 54 | end; 55 | 56 | function SecretsConfig: TSecretsConfig; 57 | begin 58 | if _SecretsConfig = nil then 59 | begin 60 | var ConfigFile := _SecretsConfigFileName; 61 | if TFile.Exists(ConfigFile) then 62 | try 63 | _SecretsConfig := TJson.Deserialize(TFile.ReadAllText(ConfigFile)); 64 | except 65 | on E: Exception do 66 | LogManager.GetLogger.Error(Format('Error reading secrets config file %s: %s (%s)', [TPath.GetFileName(ConfigFile), E.Message, E.ClassName])); 67 | end 68 | else 69 | _SecretsConfig := TSecretsConfig.Create; 70 | end; 71 | Result := _SecretsConfig; 72 | end; 73 | 74 | { TClientConfig } 75 | 76 | constructor TClientConfig.Create; 77 | begin 78 | FAudiences := TList.Create; 79 | end; 80 | 81 | destructor TClientConfig.Destroy; 82 | begin 83 | FAudiences.Free; 84 | inherited; 85 | end; 86 | 87 | { TSecretsConfig } 88 | 89 | constructor TSecretsConfig.Create; 90 | begin 91 | FClients := TObjectList.Create; 92 | end; 93 | 94 | destructor TSecretsConfig.Destroy; 95 | begin 96 | FClients.Free; 97 | inherited; 98 | end; 99 | 100 | function TSecretsConfig.FindClient(const ClientId: string): TClientConfig; 101 | begin 102 | for var Client in Clients do 103 | if Client.ClientId = ClientId then 104 | Exit(Client); 105 | Result := nil; 106 | end; 107 | 108 | initialization 109 | _SecretsConfig := nil; 110 | _SecretsConfigFileName := ''; 111 | 112 | finalization 113 | _SecretsConfig.Free; 114 | 115 | end. 116 | -------------------------------------------------------------------------------- /Server/Utils.RSAKeyStorage.pas: -------------------------------------------------------------------------------- 1 | unit Utils.RSAKeyStorage; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, System.Generics.Collections, 7 | System.SyncObjs; 8 | 9 | type 10 | IRSAKeyStorage = interface 11 | ['{747F49FD-A072-48BC-8DA1-DBB55DF17F4A}'] 12 | function PrivateKey(const KeyId: string): TBytes; 13 | function PublicKey(const KeyId: string): TBytes; 14 | end; 15 | 16 | TKeyPair = record 17 | PrivateKey: TBytes; 18 | PublicKey: TBytes; 19 | end; 20 | 21 | TRSAKeyStorage = class(TInterfacedObject, IRSAKeyStorage) 22 | strict private 23 | FKeysFolder: string; 24 | FPrivateKeyFileExtension: string; 25 | FPublicKeyFileExtension: string; 26 | FKeys: TDictionary; 27 | FCriticalSection: TCriticalSection; 28 | function LoadKeyPair(const KeyId: string): TKeyPair; 29 | public 30 | constructor Create(const AKeysFolder: string; 31 | const APrivateKeyFileExtension: string = '.key'; 32 | const APublicKeyFileExtension: string = '.pub'); 33 | destructor Destroy; override; 34 | function PrivateKey(const KeyId: string): TBytes; 35 | function PublicKey(const KeyId: string): TBytes; 36 | end; 37 | 38 | ERSAKeyStorageException = class(Exception); 39 | 40 | ERSAKeyNotFound = class(ERSAKeyStorageException); 41 | 42 | var 43 | RSAKeyStorage: IRSAKeyStorage; 44 | 45 | implementation 46 | 47 | uses 48 | System.IOUtils; 49 | 50 | { TRSAKeyStorage } 51 | 52 | constructor TRSAKeyStorage.Create(const AKeysFolder, APrivateKeyFileExtension, APublicKeyFileExtension: string); 53 | begin 54 | FKeysFolder := AKeysFolder; 55 | FPrivateKeyFileExtension := APrivateKeyFileExtension; 56 | FPublicKeyFileExtension := APublicKeyFileExtension; 57 | FKeys := TDictionary.Create; 58 | FCriticalSection := TCriticalSection.Create; 59 | end; 60 | 61 | destructor TRSAKeyStorage.Destroy; 62 | begin 63 | FCriticalSection.Free; 64 | FKeys.Free; 65 | inherited; 66 | end; 67 | 68 | function TRSAKeyStorage.LoadKeyPair(const KeyId: string): TKeyPair; 69 | 70 | function GetKeyFileName(const KeyFileExtension: string): string; 71 | begin 72 | Result := TPath.ChangeExtension(TPath.Combine(FKeysFolder, KeyId), KeyFileExtension); 73 | end; 74 | 75 | function GetKey(const KeyFileName: string): TBytes; 76 | begin 77 | if TFile.Exists(KeyFileName) then 78 | Result := TFile.ReadAllBytes(KeyFileName) 79 | else 80 | Result := []; 81 | end; 82 | 83 | begin 84 | FCriticalSection.Acquire; 85 | try 86 | if FKeys.ContainsKey(KeyId) then 87 | Exit(FKeys[KeyId]); 88 | Result.PrivateKey := GetKey(GetKeyFileName(FPrivateKeyFileExtension)); 89 | Result.PublicKey := GetKey(GetKeyFileName(FPublicKeyFileExtension)); 90 | FKeys.AddOrSetValue(KeyId, Result); 91 | finally 92 | FCriticalSection.Release; 93 | end; 94 | end; 95 | 96 | function TRSAKeyStorage.PrivateKey(const KeyId: string): TBytes; 97 | var 98 | KeyPair: TKeyPair; 99 | begin 100 | KeyPair := LoadKeyPair(KeyId); 101 | Result := KeyPair.PrivateKey; 102 | end; 103 | 104 | function TRSAKeyStorage.PublicKey(const KeyId: string): TBytes; 105 | var 106 | KeyPair: TKeyPair; 107 | begin 108 | KeyPair := LoadKeyPair(KeyId); 109 | Result := KeyPair.PublicKey; 110 | end; 111 | 112 | initialization 113 | 114 | finalization 115 | RSAKeyStorage := nil; 116 | 117 | end. 118 | -------------------------------------------------------------------------------- /Server/Modules.Api.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Api; 2 | 3 | interface 4 | 5 | uses 6 | System.Generics.Collections, System.SysUtils, System.Classes, Sparkle.HttpServer.Module, Sparkle.HttpServer.Context, 7 | Sparkle.Comp.Server, XData.Comp.Server, Sparkle.Comp.GenericMiddleware, Sparkle.Comp.LoggingMiddleware, 8 | XData.Server.Module, Config.Server, Sparkle.Comp.JwtMiddleware, 9 | Sparkle.Comp.ForwardMiddleware, Sparkle.Comp.CompressMiddleware; 10 | 11 | type 12 | TMainModule = class(TDataModule) 13 | ApiServer: TXDataServer; 14 | ApiServerLogging: TSparkleLoggingMiddleware; 15 | ApiServerJWT: TSparkleJwtMiddleware; 16 | ApiServerForward: TSparkleForwardMiddleware; 17 | ApiServerCompress: TSparkleCompressMiddleware; 18 | ApiServerGeneric: TSparkleGenericMiddleware; 19 | procedure DataModuleCreate(Sender: TObject); 20 | procedure JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext; 21 | var Secret: TBytes); 22 | procedure ApiServerGenericRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc); 23 | procedure ApiServerModuleException(Sender: TObject; 24 | Args: TModuleExceptionArgs); 25 | private 26 | procedure ConfigureJwtMiddleware(Config: TJwtMiddlewareConfig; Middleware: TSparkleJwtMiddleware); 27 | public 28 | end; 29 | 30 | var 31 | MainModule: TMainModule; 32 | 33 | implementation 34 | 35 | {%CLASSGROUP 'System.Classes.TPersistent'} 36 | 37 | uses 38 | System.IOUtils, 39 | Bcl.Jose.Consumer, 40 | Bcl.Utils, 41 | Bcl.Logging, 42 | Bcl.Json.BaseObjectConverter, 43 | Utils.RSAKeyStorage, 44 | Services.ProblemDetail; 45 | 46 | {$R *.dfm} 47 | 48 | procedure TMainModule.DataModuleCreate(Sender: TObject); 49 | begin 50 | // Set servers properties based on config file 51 | ApiServer.BaseUrl := ServerConfig.ApiModule.BaseUrl; 52 | ConfigureJWTMiddleware(ServerConfig.ApiModule.Middleware.Jwt, ApiServerJWT); 53 | 54 | // Check cofig 55 | CheckServerConfig; 56 | 57 | // Log start 58 | var Logger := LogManager.GetLogger; 59 | Logger.Info('Api data module created'); 60 | end; 61 | 62 | procedure TMainModule.JWTGetSecretEx(Sender: TObject; const JWT: TJWT; Context: THttpServerContext; 63 | var Secret: TBytes); 64 | begin 65 | if JWT.Header.Algorithm = 'RS256' then 66 | Secret := RSAKeyStorage.PublicKey(JWT.Header.KeyID) 67 | else 68 | raise EInvalidJWTException.CreateFmt( 69 | 'JWS algorithm [%s] is not supported', [JWT.Header.Algorithm]); 70 | end; 71 | 72 | procedure TMainModule.ApiServerGenericRequest(Sender: TObject; Context: THttpServerContext; Next: THttpServerProc); 73 | begin 74 | Context.Response.OnHeaders( 75 | procedure(Response: THttpServerResponse) 76 | begin 77 | Response.Headers.SetValue('Server-Version', '1'); 78 | end); 79 | Next(Context); 80 | end; 81 | 82 | procedure TMainModule.ApiServerModuleException(Sender: TObject; 83 | Args: TModuleExceptionArgs); 84 | begin 85 | if TProblemDetailExceptionHandler.HandleModuleException(Args) then 86 | Exit; 87 | 88 | // not a problem detail exception, do something else, if needed 89 | end; 90 | 91 | procedure TMainModule.ConfigureJwtMiddleware(Config: TJwtMiddlewareConfig; Middleware: TSparkleJwtMiddleware); 92 | begin 93 | Middleware.RequireExpirationTime := True; 94 | Middleware.AllowExpiredToken := False; 95 | Middleware.ForbidAnonymousAccess := False; 96 | 97 | for var Issuer in Config.ExpectedIssuers do 98 | Middleware.ExpectedIssuers.Add(Issuer); 99 | if Middleware.ExpectedIssuers.Count = 0 then 100 | Middleware.ExpectedIssuers.Add('Unknown'); 101 | 102 | for var Audience in Config.ExpectedAudiences do 103 | Middleware.ExpectedAudiences.Add(Audience); 104 | if Middleware.ExpectedAudiences.Count = 0 then 105 | Middleware.ExpectedAudiences.Add('Unknown'); 106 | end; 107 | 108 | end. 109 | -------------------------------------------------------------------------------- /Server/Modules.Auth.pas: -------------------------------------------------------------------------------- 1 | unit Modules.Auth; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, Sparkle.HttpServer.Module, Sparkle.HttpServer.Context, Sphinx.Server.Module, 7 | Sparkle.Comp.Server, XData.Comp.Server, Sphinx.Comp.Server, Sphinx.Comp.Config, 8 | System.Hash, Sparkle.Comp.CompressMiddleware, Sparkle.Comp.LoggingMiddleware, Sparkle.Comp.ForwardMiddleware, 9 | Config.Secrets; 10 | 11 | type 12 | TAuthModule = class(TDataModule) 13 | SphinxConfig: TSphinxConfig; 14 | SphinxServer: TSphinxServer; 15 | SphinxServerForward: TSparkleForwardMiddleware; 16 | SphinxServerLogging: TSparkleLoggingMiddleware; 17 | SphinxServerCompress: TSparkleCompressMiddleware; 18 | procedure SphinxConfigGetClient(Sender: TObject; Client: TSphinxClientApp; var Accept: Boolean); 19 | procedure SphinxConfigGetSigningData(Sender: TObject; Args: TGetSigningDataArgs); 20 | procedure DataModuleCreate(Sender: TObject); 21 | procedure SphinxConfigConfigureToken(Sender: TObject; Args: TConfigureTokenArgs); 22 | public 23 | end; 24 | 25 | var 26 | AuthModule: TAuthModule; 27 | 28 | implementation 29 | 30 | {%CLASSGROUP 'System.Classes.TPersistent'} 31 | 32 | uses 33 | System.TimeSpan, System.StrUtils, Config.Server, Utils.RSAKeyStorage, Bcl.Logging, Bcl.Json.Classes, Sphinx.Consts; 34 | 35 | {%CLASSGROUP 'System.Classes.TPersistent'} 36 | 37 | {$R *.dfm} 38 | 39 | procedure TAuthModule.DataModuleCreate(Sender: TObject); 40 | begin 41 | SphinxServer.BaseUrl := ServerConfig.AuthModule.BaseUrl; 42 | 43 | LogManager.GetLogger.Info(Format('Secrets file: total of %d client secrets configured', [SecretsConfig.Clients.Count])); 44 | 45 | RSAKeyStorage := TRSAKeyStorage.Create(ServerConfig.FullRSAKeysFolder); 46 | end; 47 | 48 | procedure TAuthModule.SphinxConfigConfigureToken(Sender: TObject; Args: TConfigureTokenArgs); 49 | begin 50 | // Add client audience 51 | var Audiences := SplitString(Args.Client.GetParam(JwtClaimNames.Audience), ','); 52 | if Length(Audiences) = 1 then 53 | Args.Token.Claims.AddOrSet(JwtClaimNames.Audience, Audiences[0]) 54 | else 55 | if Length(Audiences) > 1 then 56 | 57 | begin 58 | var AudienceClaim := Sparkle.Security.TUserClaim.Create(JwtClaimNames.Audience); 59 | Args.Token.Claims.AddOrSet(AudienceClaim); 60 | var JAudiences := TJArray.Create; 61 | AudienceClaim.SetJElement(JAudiences); 62 | for var Aud in Audiences do 63 | JAudiences.Add(Aud); 64 | end; 65 | 66 | // add email and code if present 67 | var Email := Args.Client.GetParam('email'); 68 | var Code := Args.Client.GetParam('code'); 69 | if (Email <> '') and (Code <> '') then 70 | begin 71 | Args.Token.Claims.AddOrSet('email', Email); 72 | // Args.Token.Claims.AddOrSet('code', Code); 73 | 74 | // Remove the client_id claim 75 | Args.Token.Claims.Remove(JwtClaimNames.ClientId); 76 | end; 77 | 78 | end; 79 | 80 | procedure TAuthModule.SphinxConfigGetClient(Sender: TObject; Client: TSphinxClientApp; var Accept: Boolean); 81 | begin 82 | // Reject by default 83 | Accept := False; 84 | 85 | // First check if the client is a valid admin client 86 | begin 87 | var Config := SecretsConfig.FindClient(Client.ClientId); 88 | if Config <> nil then 89 | begin 90 | Accept := True; 91 | Client.RequireClientSecret := True; 92 | Client.AddSha256Secret(THashSHA2.GetHashBytes(Config.ClientSecret)); 93 | Client.AccessTokenLifetimeSpan := TTimeSpan.FromHours(1); 94 | Client.AllowedGrantTypes := [TGrantType.gtClientCredentials]; 95 | Client.ValidScopes.AddStrings(SplitString(Config.Scope, ' ')); 96 | Client.DefaultScopeValues.Assign(Client.ValidScopes); 97 | Client.AddCustomParam(JwtClaimNames.Audience, string.Join(',', Config.Audiences.ToArray)); 98 | Exit; 99 | end 100 | end; 101 | end; 102 | 103 | procedure TAuthModule.SphinxConfigGetSigningData(Sender: TObject; Args: TGetSigningDataArgs); 104 | begin 105 | Args.Data.Algorithm := 'RS256'; 106 | Args.Data.KeyId := ServerConfig.AuthModule.SigningKeyId; 107 | Args.Data.Key := RSAKeyStorage.PrivateKey(Args.Data.KeyId); 108 | end; 109 | 110 | end. 111 | -------------------------------------------------------------------------------- /Server/Services.ProblemDetail.pas: -------------------------------------------------------------------------------- 1 | unit Services.ProblemDetail; 2 | 3 | // A sample unit that shows how to support specific custom exceptions types and responses in XData. 4 | // Using problem details RFC as the example, according to this discussion: 5 | // https://support.tmssoftware.com/t/rfc-7807-9457-http-status-code-400-with-data/25481 6 | // 7 | // To see it in action, visit URL /problem-detail (e.g., http://localhost:2001/tms/api/problem-detail) 8 | // 9 | // For the sake of example, we put everything related to problem details in this single unit: 10 | // the DTO class (THttpProblemDetail) and descendants, the exception class (EHttpProblemException), 11 | // the OnModuleException event handler, and also a sample service described by its interface contract and 12 | // implementation class. Ideally those concepts should be in separated units for better code organization and reuse. 13 | 14 | interface 15 | 16 | uses 17 | System.SysUtils, 18 | Bcl.Json, 19 | Bcl.Json.Attributes, 20 | Bcl.Json.NamingStrategies, 21 | XData.Module.Events, 22 | XData.Service.Common, 23 | XData.Sys.Exceptions; 24 | 25 | type 26 | /// 27 | /// DTO class used to hold information about the problem details and to be serialized as JSON in response 28 | /// 29 | [JsonNamingStrategy(TCamelCaseNamingStrategy)] // convert field names to lowercase 30 | [JsonInclude(TInclusionMode.NonDefault)] // do not include default values in JSON 31 | THttpProblemDetail = class 32 | private 33 | FType: string; 34 | FStatus: Integer; 35 | FTitle: string; 36 | FDetail: string; 37 | FInstance: string; 38 | strict protected 39 | function GetExceptionMessage: string; virtual; 40 | public 41 | constructor Create; overload; 42 | constructor Create(const AStatus: Integer); overload; 43 | constructor Create(const AStatus: Integer; const AType: string); overload; 44 | property ExceptionMessage: string read GetExceptionMessage; 45 | property &Type: string read FType write FType; 46 | property Status: Integer read FStatus write FStatus; 47 | property Title: string read FTitle write FTitle; 48 | property Detail: string read FDetail write FDetail; 49 | property Instance: string read FInstance write FInstance; 50 | end; 51 | 52 | THttpOutOfCreditProblem = class(THttpProblemDetail) 53 | private 54 | FBalance: Currency; 55 | FAccounts: TArray; 56 | public 57 | property Balance: Currency read FBalance write FBalance; 58 | property Accounts: TArray read FAccounts write FAccounts; 59 | end; 60 | 61 | /// 62 | /// Exception class to be raised if we want the server response to be a problem details content (application/problem+json) 63 | /// 64 | EHttpProblemException = class(EXDataHttpException) 65 | strict private 66 | private 67 | FProblem: THttpProblemDetail; 68 | public 69 | constructor Create(AProblem: THttpProblemDetail); 70 | destructor Destroy; override; 71 | property Problem: THttpProblemDetail read FProblem; 72 | end; 73 | 74 | /// 75 | /// Sample endpoint that returns an error in HTTP Problem Details standard 76 | /// 77 | [ServiceContract] 78 | [Route('problem-detail')] 79 | IProblemDetailSampleService = interface(IInvokable) 80 | ['{4CCB47A3-7EEF-4187-83F8-E6366C5B8A7C}'] 81 | [HttpGet, Route('')] procedure RaiseProblem; 82 | end; 83 | 84 | [ServiceImplementation] 85 | TProblemDetailSampleService = class(TInterfacedObject, IProblemDetailSampleService) 86 | public 87 | procedure RaiseProblem; 88 | end; 89 | 90 | TProblemDetailExceptionHandler = class 91 | public 92 | class function HandleModuleException(Args: TModuleExceptionArgs): Boolean; 93 | end; 94 | 95 | implementation 96 | 97 | { THttpProblemDetail } 98 | 99 | function THttpProblemDetail.GetExceptionMessage: string; 100 | begin 101 | Result := Detail; 102 | if Result = '' then 103 | Result := Title; 104 | if Result = '' then 105 | Result := &Type; 106 | end; 107 | 108 | constructor THttpProblemDetail.Create; 109 | begin 110 | inherited Create; 111 | FType := 'about:blank'; 112 | FStatus := 400; 113 | end; 114 | 115 | constructor THttpProblemDetail.Create(const AStatus: Integer); 116 | begin 117 | Create; 118 | FStatus := AStatus; 119 | end; 120 | 121 | constructor THttpProblemDetail.Create(const AStatus: Integer; 122 | const AType: string); 123 | begin 124 | Create(AStatus); 125 | FType := AType; 126 | end; 127 | 128 | { EHttpProblemException } 129 | 130 | constructor EHttpProblemException.Create(AProblem: THttpProblemDetail); 131 | begin 132 | inherited Create(AProblem.Status, AProblem.ExceptionMessage); 133 | FProblem := AProblem; 134 | end; 135 | 136 | destructor EHttpProblemException.Destroy; 137 | begin 138 | FProblem.Free; 139 | inherited; 140 | end; 141 | 142 | { TProblemDetailSampleService } 143 | 144 | procedure TProblemDetailSampleService.RaiseProblem; 145 | begin 146 | var Problem := THttpOutOfCreditProblem.Create(400); 147 | Problem.&Type := 'https://example.com/probs/out-of-credit'; 148 | Problem.Title := 'You do not have enough credit.'; 149 | Problem.Detail := 'Your current balance is 30, but that costs 50.'; 150 | Problem.Instance := '/account/12345/msgs/abc'; 151 | Problem.Balance := 30; 152 | Problem.Accounts := ['/account/12345', '/account/67890']; 153 | raise EHttpProblemException.Create(Problem); 154 | end; 155 | 156 | { TProblemDetailExceptionHandler } 157 | 158 | class function TProblemDetailExceptionHandler.HandleModuleException( 159 | Args: TModuleExceptionArgs): Boolean; 160 | var 161 | Problem: THttpProblemDetail; 162 | begin 163 | if not (Args.Exception is EHttpProblemException) then 164 | Exit(False); 165 | 166 | Result := True; 167 | Problem := (Args.Exception as EHttpProblemException).Problem; 168 | Args.Handler.Response.StatusCode := Problem.Status; 169 | Args.Handler.Response.Headers.SetValue('content-type', 'application/problem+json'); 170 | Args.Handler.Response.Close(TEncoding.UTF8.GetBytes(TJson.Serialize(Problem))); 171 | end; 172 | 173 | initialization 174 | RegisterServiceType(TypeInfo(IProblemDetailSampleService)); 175 | RegisterServiceType(TProblemDetailSampleService); 176 | end. 177 | -------------------------------------------------------------------------------- /Server/Config.Server.pas: -------------------------------------------------------------------------------- 1 | unit Config.Server; 2 | 3 | interface 4 | 5 | uses 6 | Generics.Collections, SysUtils, IOUtils, Bcl.Json.Attributes, Bcl.Json.NamingStrategies, Bcl.Json, Bcl.Logging, 7 | Config.Secrets; 8 | 9 | type 10 | [JsonManaged] 11 | [JsonInclude(TInclusionMode.NonDefault)] 12 | TJwtMiddlewareConfig = class 13 | strict private 14 | FExpectedIssuers: TList; 15 | FExpectedAudiences: TList; 16 | public 17 | constructor Create; 18 | destructor Destroy; override; 19 | property ExpectedIssuers: TList read FExpectedIssuers; 20 | property ExpectedAudiences: TList read FExpectedAudiences; 21 | end; 22 | 23 | TMiddlewareListConfig = class 24 | strict private 25 | FJwt: TJwtMiddlewareConfig; 26 | public 27 | constructor Create; 28 | destructor Destroy; override; 29 | property Jwt: TJwtMiddlewareConfig read FJwt write FJwt; 30 | end; 31 | 32 | [JsonManaged] 33 | [JsonInclude(TInclusionMode.NonDefault)] 34 | TModuleConfig = class 35 | strict private 36 | FBaseUrl: string; 37 | FMiddleware: TMiddlewareListConfig; 38 | FSigningKeyId: string; 39 | public 40 | constructor Create; 41 | destructor Destroy; override; 42 | property BaseUrl: string read FBaseUrl write FBaseUrl; 43 | property SigningKeyId: string read FSigningKeyId write FSigningKeyId; 44 | property Middleware: TMiddlewareListConfig read FMiddleware; 45 | end; 46 | 47 | [JsonManaged] 48 | [JsonInclude(TInclusionMode.NonDefault)] 49 | TWinServiceConfig = class 50 | strict private 51 | FName: string; 52 | FDisplayName: string; 53 | FDescription: string; 54 | public 55 | property Name: string read FName; 56 | property DisplayName: string read FDisplayName; 57 | property Description: string read FDescription; 58 | end; 59 | 60 | [JsonManaged] 61 | [JsonInclude(TInclusionMode.NonDefault)] 62 | TServerConfig = class 63 | private 64 | FApiModule: TModuleConfig; 65 | FAuthModule: TModuleConfig; 66 | FRSAKeysFolder: string; 67 | FSecretsFile: string; 68 | FWinService: TWinServiceConfig; 69 | function FullFolder(const Value: string): string; 70 | public 71 | constructor Create; 72 | destructor Destroy; override; 73 | function FullRSAKeysFolder: string; 74 | function FullSecretsFile: string; 75 | 76 | property SecretsFile: string write FSecretsFile; 77 | property ApiModule: TModuleConfig read FApiModule; 78 | property AuthModule: TModuleConfig read FAuthModule; 79 | property WinService: TWinServiceConfig read FWinService; 80 | end; 81 | 82 | function ServerConfig: TServerConfig; 83 | procedure ReloadServerConfig(const FileName: string); 84 | procedure CheckServerConfig; 85 | 86 | implementation 87 | 88 | var 89 | _ServerConfig: TServerConfig; 90 | 91 | procedure CheckNotEmpty(const Name, Value: string); 92 | begin 93 | if Value = '' then 94 | raise Exception.CreateFmt('Configuration parameter "%s" must not be empty', [Name]); 95 | end; 96 | 97 | procedure CheckServerConfig; 98 | begin 99 | if not TFile.Exists(ServerConfig.FullSecretsFile) then 100 | raise Exception.Create('Invalid secrets file: ' + ServerConfig.FullSecretsFile); 101 | CheckNotEmpty('ApiModule.BaseUrl', ServerConfig.ApiModule.BaseUrl); 102 | CheckNotEmpty('AuthModule.BaseUrl', ServerConfig.AuthModule.BaseUrl); 103 | end; 104 | 105 | function ServerConfigFileName(Suffix: string = ''): string; 106 | var 107 | JsonFileName: string; 108 | Dir: string; 109 | begin 110 | JsonFileName := TPath.GetFileName(TPath.ChangeExtension(ParamStr(0), Format('%s.json', [Suffix]))); 111 | Dir := TPath.GetDirectoryName(ParamStr(0)); 112 | 113 | Result := TPath.Combine(Dir, JsonFileName); 114 | if TFile.Exists(Result) then 115 | Exit; 116 | 117 | Result := TPath.GetFullPath(TPath.Combine(Dir, '..\..\config\local')); 118 | Result := TPath.Combine(Result, JsonFileName); 119 | if TFile.Exists(Result) then 120 | Exit; 121 | 122 | raise Exception.CreateFmt('Config file %s not found', [JsonFileName]); 123 | end; 124 | 125 | procedure ReloadServerConfig(const FileName: string); 126 | begin 127 | FreeAndNil(_ServerConfig); 128 | if TFile.Exists(FileName) then 129 | try 130 | _ServerConfig := TJson.Deserialize(TFile.ReadAllText(FileName)); 131 | except 132 | on E: Exception do 133 | LogManager.GetLogger.Error(Format('Error reading config file %s: %s (%s)', [TPath.GetFileName(FileName), E.Message, E.ClassName])); 134 | end 135 | else 136 | _ServerConfig := TServerConfig.Create; 137 | SetSecretsConfigFileName(_ServerConfig.FullSecretsFile); 138 | end; 139 | 140 | function ServerConfig: TServerConfig; 141 | begin 142 | if _ServerConfig = nil then 143 | ReloadServerConfig(ServerConfigFileName); 144 | Result := _ServerConfig; 145 | end; 146 | 147 | { TServerConfig } 148 | 149 | constructor TServerConfig.Create; 150 | begin 151 | inherited Create; 152 | FApiModule := TModuleConfig.Create; 153 | FAuthModule := TModuleConfig.Create; 154 | FWinService := TWinServiceConfig.Create; 155 | end; 156 | 157 | destructor TServerConfig.Destroy; 158 | begin 159 | FApiModule.Free; 160 | FAuthModule.Free; 161 | FWinService.Free; 162 | inherited; 163 | end; 164 | 165 | function TServerConfig.FullFolder(const Value: string): string; 166 | begin 167 | if TPath.IsRelativePath(Value) then 168 | Result := TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), Value) 169 | else 170 | Result := Value; 171 | end; 172 | 173 | function TServerConfig.FullRSAKeysFolder: string; 174 | begin 175 | Result := FullFolder(FRSAKeysFolder); 176 | end; 177 | 178 | function TServerConfig.FullSecretsFile: string; 179 | begin 180 | Result := FullFolder(FSecretsFile); 181 | end; 182 | 183 | { TJwtMiddlewareConfig } 184 | 185 | constructor TJwtMiddlewareConfig.Create; 186 | begin 187 | FExpectedIssuers := TList.Create; 188 | FExpectedAudiences := TList.Create; 189 | end; 190 | 191 | destructor TJwtMiddlewareConfig.Destroy; 192 | begin 193 | FExpectedIssuers.Free; 194 | FExpectedAudiences.Free; 195 | inherited; 196 | end; 197 | 198 | { TModuleConfig } 199 | 200 | constructor TModuleConfig.Create; 201 | begin 202 | FMiddleware := TMiddlewareListConfig.Create; 203 | end; 204 | 205 | destructor TModuleConfig.Destroy; 206 | begin 207 | FMiddleware.Free; 208 | inherited; 209 | end; 210 | 211 | { TMiddlewareListConfig } 212 | 213 | constructor TMiddlewareListConfig.Create; 214 | begin 215 | FJwt := TJwtMiddlewareConfig.Create; 216 | end; 217 | 218 | destructor TMiddlewareListConfig.Destroy; 219 | begin 220 | FJwt.Free; 221 | inherited; 222 | end; 223 | 224 | initialization 225 | _ServerConfig := nil; 226 | 227 | finalization 228 | _ServerConfig.Free; 229 | 230 | end. 231 | -------------------------------------------------------------------------------- /Server/WinServer.dproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | {464BCDC4-FAF3-4913-83C2-EA352AFE2ECF} 4 | 20.3 5 | VCL 6 | True 7 | Debug 8 | Win32 9 | 1 10 | Application 11 | WinServer.dpr 12 | WinServer 13 | 14 | 15 | true 16 | 17 | 18 | true 19 | Base 20 | true 21 | 22 | 23 | true 24 | Base 25 | true 26 | 27 | 28 | true 29 | Base 30 | true 31 | 32 | 33 | true 34 | Cfg_1 35 | true 36 | true 37 | 38 | 39 | true 40 | Base 41 | true 42 | 43 | 44 | true 45 | Cfg_2 46 | true 47 | true 48 | 49 | 50 | .\$(Platform)\$(Config) 51 | .\$(Platform)\$(Config) 52 | false 53 | false 54 | false 55 | false 56 | false 57 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 58 | $(BDS)\bin\delphi_PROJECTICON.ico 59 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png 60 | $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png 61 | WinServer 62 | 63 | 64 | TMSScripter_Memo;vclwinx;DataSnapServer;ACBr_TCP;dacvcl280;ACBr_MDFeDamdfeFR;ibdac280;FixInsight_11;TMSLogging;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;aurelius;appanalytics;IndyProtocols;vclx;ACBr_OpenSSL;sparkle;IndyIPClient;dbxcds;vcledge;sphinx;dac280;bindcompvclwinx;ibdacfmx280;FmxTeeUI;AureliusIBDAC;ibdacvcl280;emsedge;bindcompfmx;DBXFirebirdDriver;ACBr_CTeDacteFR;GraphQLHttp;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;TMSScripter_IDE;Tee;soapmidas;JclVcl;vclactnband;TeeUI;TMSScripter;fmxFireDAC;dbexpress;Jcl;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;dacfmx280;vcltouch;fmxase;TMSWEBCorePkgLibDXE14;DBXOdbcDriver;dbrtl;TMSDiagram;FireDACDBXDriver;FireDACOracleDriver;TMSWEBCorePkgDXE14;fmxdae;TeeDB;GraphQLCore;FireDACMSAccDriver;tmsbcl;CustomIPTransport;FireDACMSSQLDriver;ACBr_NFSeX;DataSnapIndy10ServerTransport;JclDeveloperTools;ACBr_NFe;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;ACBr_Diversos;FireDACTDataDriver;vcldb;ACBr_Integrador;ACBr_PIXCD;JclContainers;vclFireDAC;ACBr_CTe;ACBr_BoletoFR;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;TMSScripter_VCL;bindcompdbx;rtl;FireDACMySQLDriver;ACBr_NFeDanfeFR;FireDACADSDriver;remotedb;RESTComponents;ACBr_NFSeXDANFSeFR;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;TMSScripter_Legacy;DBXSybaseASEDriver;ACBr_Serial;DBXDb2Driver;ACBr_Comum;ACBr_MDFe;ACBr_NFeDanfeESCPOS;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ACBr_Boleto;crcontrols280;bindcompvcl;dsnap;xdata;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 65 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 66 | Debug 67 | true 68 | CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 69 | 1033 70 | $(BDS)\bin\default_app.manifest 71 | 72 | 73 | vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;GraphQLHttp;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;TeeDB;GraphQLCore;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;vcldb;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) 74 | 75 | 76 | DEBUG;$(DCC_Define) 77 | true 78 | false 79 | true 80 | true 81 | true 82 | true 83 | true 84 | 85 | 86 | false 87 | PerMonitorV2 88 | 89 | 90 | false 91 | RELEASE;$(DCC_Define) 92 | 0 93 | 0 94 | 95 | 96 | PerMonitorV2 97 | 98 | 99 | 100 | MainSource 101 | 102 | 103 |
MainModule
104 | TDataModule 105 |
106 | 107 | 108 |
ConfigModule
109 | TDataModule 110 |
111 | 112 | 113 | 114 |
AuthModule
115 | TDataModule 116 |
117 | 118 | 119 | 120 | 121 | Base 122 | 123 | 124 | Cfg_1 125 | Base 126 | 127 | 128 | Cfg_2 129 | Base 130 | 131 |
132 | 133 | Delphi.Personality.12 134 | Application 135 | 136 | 137 | 138 | WinServer.dpr 139 | 140 | 141 | 142 | 143 | 144 | WinServer.exe 145 | true 146 | 147 | 148 | 149 | 150 | 151 | 152 | 1 153 | 154 | 155 | Contents\MacOS 156 | 1 157 | 158 | 159 | 0 160 | 161 | 162 | 163 | 164 | res\xml 165 | 1 166 | 167 | 168 | res\xml 169 | 1 170 | 171 | 172 | 173 | 174 | library\lib\armeabi 175 | 1 176 | 177 | 178 | library\lib\armeabi 179 | 1 180 | 181 | 182 | 183 | 184 | library\lib\armeabi-v7a 185 | 1 186 | 187 | 188 | 189 | 190 | library\lib\mips 191 | 1 192 | 193 | 194 | library\lib\mips 195 | 1 196 | 197 | 198 | 199 | 200 | library\lib\armeabi-v7a 201 | 1 202 | 203 | 204 | library\lib\arm64-v8a 205 | 1 206 | 207 | 208 | 209 | 210 | library\lib\armeabi-v7a 211 | 1 212 | 213 | 214 | 215 | 216 | res\drawable 217 | 1 218 | 219 | 220 | res\drawable 221 | 1 222 | 223 | 224 | 225 | 226 | res\drawable-anydpi-v21 227 | 1 228 | 229 | 230 | res\drawable-anydpi-v21 231 | 1 232 | 233 | 234 | 235 | 236 | res\values 237 | 1 238 | 239 | 240 | res\values 241 | 1 242 | 243 | 244 | 245 | 246 | res\values-v21 247 | 1 248 | 249 | 250 | res\values-v21 251 | 1 252 | 253 | 254 | 255 | 256 | res\values-v31 257 | 1 258 | 259 | 260 | res\values-v31 261 | 1 262 | 263 | 264 | 265 | 266 | res\values-v35 267 | 1 268 | 269 | 270 | res\values-v35 271 | 1 272 | 273 | 274 | 275 | 276 | res\drawable-anydpi-v26 277 | 1 278 | 279 | 280 | res\drawable-anydpi-v26 281 | 1 282 | 283 | 284 | 285 | 286 | res\drawable 287 | 1 288 | 289 | 290 | res\drawable 291 | 1 292 | 293 | 294 | 295 | 296 | res\drawable 297 | 1 298 | 299 | 300 | res\drawable 301 | 1 302 | 303 | 304 | 305 | 306 | res\drawable 307 | 1 308 | 309 | 310 | res\drawable 311 | 1 312 | 313 | 314 | 315 | 316 | res\drawable-anydpi-v33 317 | 1 318 | 319 | 320 | res\drawable-anydpi-v33 321 | 1 322 | 323 | 324 | 325 | 326 | res\values 327 | 1 328 | 329 | 330 | res\values 331 | 1 332 | 333 | 334 | 335 | 336 | res\values-night-v21 337 | 1 338 | 339 | 340 | res\values-night-v21 341 | 1 342 | 343 | 344 | 345 | 346 | res\drawable 347 | 1 348 | 349 | 350 | res\drawable 351 | 1 352 | 353 | 354 | 355 | 356 | res\drawable-xxhdpi 357 | 1 358 | 359 | 360 | res\drawable-xxhdpi 361 | 1 362 | 363 | 364 | 365 | 366 | res\drawable-xxxhdpi 367 | 1 368 | 369 | 370 | res\drawable-xxxhdpi 371 | 1 372 | 373 | 374 | 375 | 376 | res\drawable-ldpi 377 | 1 378 | 379 | 380 | res\drawable-ldpi 381 | 1 382 | 383 | 384 | 385 | 386 | res\drawable-mdpi 387 | 1 388 | 389 | 390 | res\drawable-mdpi 391 | 1 392 | 393 | 394 | 395 | 396 | res\drawable-hdpi 397 | 1 398 | 399 | 400 | res\drawable-hdpi 401 | 1 402 | 403 | 404 | 405 | 406 | res\drawable-xhdpi 407 | 1 408 | 409 | 410 | res\drawable-xhdpi 411 | 1 412 | 413 | 414 | 415 | 416 | res\drawable-mdpi 417 | 1 418 | 419 | 420 | res\drawable-mdpi 421 | 1 422 | 423 | 424 | 425 | 426 | res\drawable-hdpi 427 | 1 428 | 429 | 430 | res\drawable-hdpi 431 | 1 432 | 433 | 434 | 435 | 436 | res\drawable-xhdpi 437 | 1 438 | 439 | 440 | res\drawable-xhdpi 441 | 1 442 | 443 | 444 | 445 | 446 | res\drawable-xxhdpi 447 | 1 448 | 449 | 450 | res\drawable-xxhdpi 451 | 1 452 | 453 | 454 | 455 | 456 | res\drawable-xxxhdpi 457 | 1 458 | 459 | 460 | res\drawable-xxxhdpi 461 | 1 462 | 463 | 464 | 465 | 466 | res\drawable-small 467 | 1 468 | 469 | 470 | res\drawable-small 471 | 1 472 | 473 | 474 | 475 | 476 | res\drawable-normal 477 | 1 478 | 479 | 480 | res\drawable-normal 481 | 1 482 | 483 | 484 | 485 | 486 | res\drawable-large 487 | 1 488 | 489 | 490 | res\drawable-large 491 | 1 492 | 493 | 494 | 495 | 496 | res\drawable-xlarge 497 | 1 498 | 499 | 500 | res\drawable-xlarge 501 | 1 502 | 503 | 504 | 505 | 506 | res\values 507 | 1 508 | 509 | 510 | res\values 511 | 1 512 | 513 | 514 | 515 | 516 | res\drawable-anydpi-v24 517 | 1 518 | 519 | 520 | res\drawable-anydpi-v24 521 | 1 522 | 523 | 524 | 525 | 526 | res\drawable 527 | 1 528 | 529 | 530 | res\drawable 531 | 1 532 | 533 | 534 | 535 | 536 | res\drawable-night-anydpi-v21 537 | 1 538 | 539 | 540 | res\drawable-night-anydpi-v21 541 | 1 542 | 543 | 544 | 545 | 546 | res\drawable-anydpi-v31 547 | 1 548 | 549 | 550 | res\drawable-anydpi-v31 551 | 1 552 | 553 | 554 | 555 | 556 | res\drawable-night-anydpi-v31 557 | 1 558 | 559 | 560 | res\drawable-night-anydpi-v31 561 | 1 562 | 563 | 564 | 565 | 566 | 1 567 | 568 | 569 | Contents\MacOS 570 | 1 571 | 572 | 573 | 0 574 | 575 | 576 | 577 | 578 | Contents\MacOS 579 | 1 580 | .framework 581 | 582 | 583 | Contents\MacOS 584 | 1 585 | .framework 586 | 587 | 588 | Contents\MacOS 589 | 1 590 | .framework 591 | 592 | 593 | 0 594 | 595 | 596 | 597 | 598 | 1 599 | .dylib 600 | 601 | 602 | 1 603 | .dylib 604 | 605 | 606 | 1 607 | .dylib 608 | 609 | 610 | Contents\MacOS 611 | 1 612 | .dylib 613 | 614 | 615 | Contents\MacOS 616 | 1 617 | .dylib 618 | 619 | 620 | Contents\MacOS 621 | 1 622 | .dylib 623 | 624 | 625 | 0 626 | .dll;.bpl 627 | 628 | 629 | 630 | 631 | 1 632 | .dylib 633 | 634 | 635 | 1 636 | .dylib 637 | 638 | 639 | 1 640 | .dylib 641 | 642 | 643 | Contents\MacOS 644 | 1 645 | .dylib 646 | 647 | 648 | Contents\MacOS 649 | 1 650 | .dylib 651 | 652 | 653 | Contents\MacOS 654 | 1 655 | .dylib 656 | 657 | 658 | 0 659 | .bpl 660 | 661 | 662 | 663 | 664 | 0 665 | 666 | 667 | 0 668 | 669 | 670 | 0 671 | 672 | 673 | 0 674 | 675 | 676 | 0 677 | 678 | 679 | Contents\Resources\StartUp\ 680 | 0 681 | 682 | 683 | Contents\Resources\StartUp\ 684 | 0 685 | 686 | 687 | Contents\Resources\StartUp\ 688 | 0 689 | 690 | 691 | 0 692 | 693 | 694 | 695 | 696 | 1 697 | 698 | 699 | 1 700 | 701 | 702 | 703 | 704 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 705 | 1 706 | 707 | 708 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 709 | 1 710 | 711 | 712 | 713 | 714 | ..\ 715 | 1 716 | 717 | 718 | ..\ 719 | 1 720 | 721 | 722 | ..\ 723 | 1 724 | 725 | 726 | 727 | 728 | Contents 729 | 1 730 | 731 | 732 | Contents 733 | 1 734 | 735 | 736 | Contents 737 | 1 738 | 739 | 740 | 741 | 742 | Contents\Resources 743 | 1 744 | 745 | 746 | Contents\Resources 747 | 1 748 | 749 | 750 | Contents\Resources 751 | 1 752 | 753 | 754 | 755 | 756 | library\lib\armeabi-v7a 757 | 1 758 | 759 | 760 | library\lib\arm64-v8a 761 | 1 762 | 763 | 764 | 1 765 | 766 | 767 | 1 768 | 769 | 770 | 1 771 | 772 | 773 | 1 774 | 775 | 776 | Contents\MacOS 777 | 1 778 | 779 | 780 | Contents\MacOS 781 | 1 782 | 783 | 784 | Contents\MacOS 785 | 1 786 | 787 | 788 | 0 789 | 790 | 791 | 792 | 793 | library\lib\armeabi-v7a 794 | 1 795 | 796 | 797 | 798 | 799 | 1 800 | 801 | 802 | 1 803 | 804 | 805 | 1 806 | 807 | 808 | 809 | 810 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 811 | 1 812 | 813 | 814 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 815 | 1 816 | 817 | 818 | ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF 819 | 1 820 | 821 | 822 | 823 | 824 | ..\ 825 | 1 826 | 827 | 828 | ..\ 829 | 1 830 | 831 | 832 | ..\ 833 | 1 834 | 835 | 836 | 837 | 838 | 1 839 | 840 | 841 | 1 842 | 843 | 844 | 1 845 | 846 | 847 | 848 | 849 | ..\$(PROJECTNAME).launchscreen 850 | 64 851 | 852 | 853 | ..\$(PROJECTNAME).launchscreen 854 | 64 855 | 856 | 857 | 858 | 859 | 1 860 | 861 | 862 | 1 863 | 864 | 865 | 1 866 | 867 | 868 | 869 | 870 | Assets 871 | 1 872 | 873 | 874 | Assets 875 | 1 876 | 877 | 878 | 879 | 880 | Assets 881 | 1 882 | 883 | 884 | Assets 885 | 1 886 | 887 | 888 | 889 | 890 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 891 | 1 892 | 893 | 894 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 895 | 1 896 | 897 | 898 | 899 | 900 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 901 | 1 902 | 903 | 904 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 905 | 1 906 | 907 | 908 | 909 | 910 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 911 | 1 912 | 913 | 914 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 915 | 1 916 | 917 | 918 | 919 | 920 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 921 | 1 922 | 923 | 924 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 925 | 1 926 | 927 | 928 | 929 | 930 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 931 | 1 932 | 933 | 934 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 935 | 1 936 | 937 | 938 | 939 | 940 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 941 | 1 942 | 943 | 944 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 945 | 1 946 | 947 | 948 | 949 | 950 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 951 | 1 952 | 953 | 954 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 955 | 1 956 | 957 | 958 | 959 | 960 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 961 | 1 962 | 963 | 964 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 965 | 1 966 | 967 | 968 | 969 | 970 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 971 | 1 972 | 973 | 974 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 975 | 1 976 | 977 | 978 | 979 | 980 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 981 | 1 982 | 983 | 984 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 985 | 1 986 | 987 | 988 | 989 | 990 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 991 | 1 992 | 993 | 994 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 995 | 1 996 | 997 | 998 | 999 | 1000 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1001 | 1 1002 | 1003 | 1004 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1005 | 1 1006 | 1007 | 1008 | 1009 | 1010 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1011 | 1 1012 | 1013 | 1014 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1015 | 1 1016 | 1017 | 1018 | 1019 | 1020 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1021 | 1 1022 | 1023 | 1024 | ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset 1025 | 1 1026 | 1027 | 1028 | 1029 | 1030 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1031 | 1 1032 | 1033 | 1034 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1035 | 1 1036 | 1037 | 1038 | 1039 | 1040 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1041 | 1 1042 | 1043 | 1044 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1045 | 1 1046 | 1047 | 1048 | 1049 | 1050 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1051 | 1 1052 | 1053 | 1054 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1055 | 1 1056 | 1057 | 1058 | 1059 | 1060 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1061 | 1 1062 | 1063 | 1064 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1065 | 1 1066 | 1067 | 1068 | 1069 | 1070 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1071 | 1 1072 | 1073 | 1074 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1075 | 1 1076 | 1077 | 1078 | 1079 | 1080 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1081 | 1 1082 | 1083 | 1084 | ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset 1085 | 1 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | True 1103 | False 1104 | 1105 | 1106 | S:\tms\tmsbuild\server\test\tmssetupserver_tests.dproj 1107 | 1108 | 1109 | 12 1110 | 1111 | 1112 | 1113 | 1114 |
1115 | --------------------------------------------------------------------------------