├── 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 |
104 | TDataModule
105 |
106 |
107 |
108 |
109 | TDataModule
110 |
111 |
112 |
113 |
114 |
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 |
--------------------------------------------------------------------------------