1
1
using System . Diagnostics ;
2
2
using System . Security . Cryptography ;
3
3
using System . Security . Cryptography . X509Certificates ;
4
+ using System . Text ;
4
5
using Intersect . Core ;
6
+ using Intersect . Framework . Reflection ;
5
7
using Intersect . Server . Web . Configuration ;
6
8
using Microsoft . AspNetCore . Authentication . JwtBearer ;
7
9
using Microsoft . IdentityModel . Tokens ;
@@ -15,58 +17,162 @@ internal partial class ApiService
15
17
private const string SelfSignedCertificateName = "self-signed.crt" ;
16
18
private const string SelfSignedKeyName = "self-signed.key" ;
17
19
18
- private static void UnpackAppSettings ( )
20
+ private const string FileNameAppsettingsJson = "appsettings.json" ;
21
+ private static readonly string FileNameAppsettingsEnvironmentJson ;
22
+
23
+ private static readonly JObject ? AppsettingsToken ;
24
+ private static readonly JObject ? AppsettingsEnvironmentToken ;
25
+
26
+ static ApiService ( )
19
27
{
20
- ApplicationContext . Context . Value ? . Logger . LogInformation ( "Unpacking appsettings..." ) ;
21
- var hostBuilder = Host . CreateApplicationBuilder ( ) ;
28
+ var dummyHostBuilder = Host . CreateApplicationBuilder ( ) ;
29
+ FileNameAppsettingsEnvironmentJson = $ "appsettings. { dummyHostBuilder . Environment . EnvironmentName } .json" ;
22
30
23
- var names = new [ ] { "appsettings.json" , $ "appsettings.{ hostBuilder . Environment . EnvironmentName } .json" } ;
24
- var manifestResourceNamePairs = Assembly . GetManifestResourceNames ( )
25
- . Where ( mrn => names . Any ( mrn . EndsWith ) )
26
- . Select ( mrn => ( mrn , names . First ( mrn . EndsWith ) ) )
27
- . ToArray ( ) ;
31
+ AppsettingsToken = ParseEmbeddedAppsettings ( FileNameAppsettingsJson ) ;
32
+ AppsettingsEnvironmentToken = ParseEmbeddedAppsettings ( FileNameAppsettingsEnvironmentJson ) ;
33
+ }
28
34
29
- foreach ( var ( mrn , name ) in manifestResourceNamePairs )
35
+ private static JObject ? ParseEmbeddedAppsettings ( string name )
36
+ {
37
+ try
30
38
{
31
- if ( string . IsNullOrWhiteSpace ( mrn ) || string . IsNullOrWhiteSpace ( name ) )
39
+ if ( ! Assembly . TryFindResource ( name , out var resourceName ) )
32
40
{
33
- ApplicationContext . Context . Value ? . Logger . LogWarning ( $ "Manifest resource name or file name is null/empty: ( { mrn } , { name } )" ) ;
34
- continue ;
41
+ ApplicationContext . CurrentContext . Logger . LogWarning ( "Failed to find '{AppsettingsName}'" , name ) ;
42
+ return null ;
35
43
}
36
44
37
- if ( File . Exists ( name ) )
45
+ using var resourceStream = Assembly . GetManifestResourceStream ( resourceName ) ;
46
+ if ( resourceStream == default )
38
47
{
39
- ApplicationContext . Context . Value ? . Logger . LogDebug ( $ "'{ name } ' already exists, not unpacking '{ mrn } '") ;
40
- continue ;
48
+ ApplicationContext . Context . Value ? . Logger . LogError (
49
+ "Failed to open resource stream for '{ResourceName}'" ,
50
+ resourceName
51
+ ) ;
52
+ return null ;
41
53
}
42
54
43
- using var mrs = Assembly . GetManifestResourceStream ( mrn ) ;
44
- if ( mrs == default )
55
+ using var reader = new StreamReader ( resourceStream , Encoding . UTF8 ) ;
56
+ var rawJson = reader . ReadToEnd ( ) ;
57
+ if ( ! string . IsNullOrWhiteSpace ( rawJson ) )
45
58
{
46
- ApplicationContext . Context . Value ? . Logger . LogWarning ( $ "Unable to open stream for embedded content: '{ mrn } '") ;
47
- continue ;
59
+ return JObject . Parse ( rawJson ) ;
60
+ }
61
+
62
+ ApplicationContext . Context . Value ? . Logger . LogError (
63
+ "Invalid JSON, embedded resouce was empty or whitespace: '{ResourceName}'" ,
64
+ resourceName
65
+ ) ;
66
+ return null ;
67
+ }
68
+ catch ( Exception exception )
69
+ {
70
+ ApplicationContext . CurrentContext . Logger . LogError (
71
+ exception ,
72
+ "Exception thrown while loading and parsing '{Name}'" ,
73
+ name
74
+ ) ;
75
+ return null ;
76
+ }
77
+ }
78
+
79
+ private static void Dump ( string fileName , JObject ? token )
80
+ {
81
+ try
82
+ {
83
+ if ( token == null )
84
+ {
85
+ ApplicationContext . Context . Value ? . Logger . LogDebug (
86
+ "Skipping dumping '{FileName}' because the token is null" ,
87
+ fileName
88
+ ) ;
89
+ return ;
48
90
}
49
91
50
- ApplicationContext . Context . Value ? . Logger . LogInformation ( $ "Unpacking '{ name } ' in { Environment . CurrentDirectory } ") ;
92
+ if ( File . Exists ( FileNameAppsettingsJson ) )
93
+ {
94
+ ApplicationContext . Context . Value ? . Logger . LogDebug (
95
+ "Skipping dumping '{FileName}' because the file already exists" ,
96
+ fileName
97
+ ) ;
98
+ return ;
99
+ }
51
100
52
- using var fs = File . OpenWrite ( name ) ;
53
- mrs . CopyTo ( fs ) ;
101
+ var rawJson = token . ToString ( Formatting . Indented ) ;
102
+ File . WriteAllText ( fileName , rawJson , Encoding . UTF8 ) ;
103
+ }
104
+ catch ( Exception exception )
105
+ {
106
+ ApplicationContext . CurrentContext . Logger . LogError (
107
+ exception ,
108
+ "Exception thrown while dumping '{FileName}'" ,
109
+ fileName
110
+ ) ;
111
+ }
112
+ }
113
+
114
+ private static void UnpackAppSettings ( )
115
+ {
116
+ ApplicationContext . Context . Value ? . Logger . LogInformation ( "Unpacking appsettings..." ) ;
117
+ Dump ( FileNameAppsettingsJson , AppsettingsToken ) ;
118
+ Dump ( FileNameAppsettingsEnvironmentJson , AppsettingsEnvironmentToken ) ;
119
+ }
120
+
121
+ private static JObject ? LoadOrFallbackTo ( FileInfo fileInfo , JObject ? fallback )
122
+ {
123
+ if ( ! fileInfo . Exists )
124
+ {
125
+ return fallback ;
126
+ }
127
+
128
+ try
129
+ {
130
+ using var reader = fileInfo . OpenText ( ) ;
131
+ var rawJson = reader . ReadToEnd ( ) ;
132
+ JObject parsedObject = JObject . Parse ( rawJson ) ;
133
+ return parsedObject ;
134
+ }
135
+ catch ( Exception exception )
136
+ {
137
+ ApplicationContext . CurrentContext . Logger . LogError (
138
+ exception ,
139
+ "Exception thrown while loading and parsing '{Name}' and will return the fallback instead" ,
140
+ Path . GetRelativePath ( Environment . CurrentDirectory , fileInfo . FullName )
141
+ ) ;
142
+ return fallback ;
54
143
}
55
144
}
56
145
57
146
private static void ValidateConfiguration ( )
58
147
{
59
148
var builder = Host . CreateApplicationBuilder ( ) ;
60
149
61
- var environmentAppSettingsFileName = $ "appsettings.{ builder . Environment . EnvironmentName } .json";
62
- var rawConfiguration = File . ReadAllText ( environmentAppSettingsFileName ) ;
63
- if ( string . IsNullOrWhiteSpace ( rawConfiguration ) || rawConfiguration . Trim ( ) . Length < 2 )
150
+ FileInfo genericConfigurationFileInfo = new ( FileNameAppsettingsJson ) ;
151
+ JObject ? genericConfigurationToken = LoadOrFallbackTo ( genericConfigurationFileInfo , AppsettingsToken ) ;
152
+
153
+ FileInfo environmentConfigurationFileInfo = new ( FileNameAppsettingsEnvironmentJson ) ;
154
+ JObject ? environmentConfigurationToken = LoadOrFallbackTo ( environmentConfigurationFileInfo , AppsettingsToken ) ;
155
+
156
+ JsonMergeSettings jsonMergeSettings = new ( )
157
+ {
158
+ MergeArrayHandling = MergeArrayHandling . Replace ,
159
+ MergeNullValueHandling = MergeNullValueHandling . Merge ,
160
+ PropertyNameComparison = StringComparison . Ordinal ,
161
+ } ;
162
+
163
+ JObject mergedConfiguration = new ( ) ;
164
+
165
+ if ( genericConfigurationToken is not null )
166
+ {
167
+ mergedConfiguration . Merge ( genericConfigurationToken , jsonMergeSettings ) ;
168
+ }
169
+
170
+ if ( environmentConfigurationToken is not null )
64
171
{
65
- rawConfiguration = "{}" ;
172
+ mergedConfiguration . Merge ( environmentConfigurationToken , jsonMergeSettings ) ;
66
173
}
67
174
68
- var configurationJObject = JsonConvert . DeserializeObject < JObject > ( rawConfiguration ) ;
69
- if ( ! configurationJObject . TryGetValue ( "Api" , out var apiSectionJToken ) )
175
+ if ( ! mergedConfiguration . TryGetValue ( "Api" , out var apiSectionJToken ) )
70
176
{
71
177
apiSectionJToken = JObject . FromObject ( new ApiConfiguration ( ) ) ;
72
178
}
@@ -103,14 +209,11 @@ private static void ValidateConfiguration()
103
209
104
210
if ( apiConfiguration . StaticFilePaths == default )
105
211
{
106
- apiConfiguration . StaticFilePaths = new List < StaticFilePathOptions >
107
- {
108
- new ( "wwwroot" )
109
- } ;
212
+ apiConfiguration . StaticFilePaths = [ new StaticFilePathOptions ( "wwwroot" ) ] ;
110
213
}
111
214
112
215
var updatedApiConfigurationJObject = JObject . FromObject ( apiConfiguration ) ;
113
- configurationJObject [ "Api" ] = updatedApiConfigurationJObject ;
216
+ mergedConfiguration [ "Api" ] = updatedApiConfigurationJObject ;
114
217
115
218
updatedApiConfigurationJObject [ nameof ( JwtBearerOptions ) ] = apiSectionJToken [ nameof ( JwtBearerOptions ) ] ;
116
219
var jwtBearerOptionsToken = updatedApiConfigurationJObject [ nameof ( JwtBearerOptions ) ] ;
@@ -155,10 +258,12 @@ out var validIssuerToken
155
258
tokenValidationParameters [ nameof ( TokenValidationParameters . ValidIssuer ) ] = validIssuerToken ;
156
259
}
157
260
158
- var updatedAppSettingsJson = configurationJObject . ToString ( ) ;
159
- File . WriteAllText ( environmentAppSettingsFileName , updatedAppSettingsJson ) ;
261
+ var updatedAppSettingsJson = mergedConfiguration . ToString ( ) ;
262
+ File . WriteAllText ( FileNameAppsettingsEnvironmentJson , updatedAppSettingsJson ) ;
263
+
264
+ // Below this point should be only self-signed certificate generation
160
265
161
- if ( ! configurationJObject . TryGetValue ( "Kestrel" , out var kestrelToken ) || kestrelToken is not JObject kestrel )
266
+ if ( ! mergedConfiguration . TryGetValue ( "Kestrel" , out var kestrelToken ) || kestrelToken is not JObject kestrel )
162
267
{
163
268
return ;
164
269
}
0 commit comments