-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStartup.cs
216 lines (178 loc) · 8.3 KB
/
Startup.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
using System;
using System.IO;
using System.Reflection;
using System.Security.Principal;
using System.Linq;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Logging;
using WebApp.Models;
using WebApp.Middleware;
using WebApp.Services;
using WebApp.Services.Registration;
namespace WebApp {
public class Startup {
private IConfiguration _configuration;
private IWebHostEnvironment _env;
internal static String ROOT_PATH;
internal static String WEB_PATH;
internal static String BIN_PATH;
public Startup(IConfiguration configuration, IWebHostEnvironment env) {
_configuration = configuration;
_env = env;
ROOT_PATH = env.ContentRootPath;
WEB_PATH = env.WebRootPath;
BIN_PATH = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var cwd = Directory.GetCurrentDirectory();
NLog.LogManager.LoadConfiguration(String.Concat(cwd, "/nlog.config"));
}
public void ConfigureServices(IServiceCollection services) {
Program._bootStatus.Update(BOOT_CORE_STATE.REGISTRATION, BOOT_STATE_TYPE.PRE);
services.AddSingleton<IConfiguration>(_configuration);
services.ConfigureLoggerService();
// allow everything to access the user principal if needed.
services.AddTransient<IPrincipal>(provider => {
var http = provider.GetService<IHttpContextAccessor>();
return http.HttpContext.User;
});
services.AddTransient<IActiveDirectory, ActiveDirectory>();
services.AddScoped<Authentication>();
// add identity jwt token bearer auth for microsoft's mvc chain.
services.AddAuthentication(o => {
o.DefaultAuthenticateScheme = "smart";
o.DefaultSignInScheme = "smart";
//o.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddPolicyScheme("smart", "Switch between bearer and ntlm", options => {
options.ForwardDefaultSelector = context => {
var authHeader = context.Request.Headers["Authorization"].FirstOrDefault();
// preferrentially use the cookie/local-storage based token.
if (authHeader?.ToLower().StartsWith("bearer ") == true) {
return JwtBearerDefaults.AuthenticationScheme;
}
// otherwise, presume the request is pushing through kerberos/ntlm credentials.
return Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.Negotiate;
};
})
.AddJwtBearer(o => { // we shall use Bearer authentication, which is FORCED into the browser's request system via websocket request middleware on frontend. It will send bearer auth with every request.
o.TokenValidationParameters = ormConfig.GetJWTTokenValidationParameters();
})
.AddNegotiate(); // allow kerberos/ntlm negotiation & challenges.
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
// Microsoft IDentity requries cookie storage to keep the auth details.
// this must occur PRIOR to signalR registration. Without this, IPrincipal does not contain the claims upon login. 'Cause Microsoft is "awesome" and stuff or whatever? Yea that. cool.
// we are using Bearer auth anyway, but this is still apparently requried to hook everything up.
services.ConfigureApplicationCookie(o => {
o.AccessDeniedPath = "/";
o.Cookie.Name = "WebApp";
o.Cookie.HttpOnly = true;
o.ExpireTimeSpan = TimeSpan.FromMinutes(30); // todo: add to configuration variable??
o.LoginPath = "/";
o.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
o.SlidingExpiration = true; // refresh timeout upon page / action executed.
});
services.AddMemoryCache();
services.AddSignalR(); // SPA
services.AddCors(options => {
#if RELEASE
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin() // todo: restrict this
.AllowAnyMethod()
.AllowAnyHeader()
//.AllowCredentials() // cannot have both credentials and allow any origin.
);
#else
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
//.AllowCredentials() // cannot have both credentials and allow any origin.
);
#endif
});
// add mvc specifically for our custom "api" lib dll, as well for swagger ui.
services
.AddMvc()
.AddNewtonsoftJson() // api lib technically contains the dll ref for this (ms.netcore.mvc.newton), but it is dependent upon mvc callback (here). Can't register directly onto services, and we don't want to register mvc twice. Otherwise this line would be in it's PROPER home, the api lib project.
.SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_3_0)
.AddApplicationPart(typeof(API.Bootstrap).Assembly) // external library reference
.AddControllersAsServices();
// we are using a seperate library to manage the api, for cleanliness. Register it w/ swagger, and apply an authorization filter to prevent unauthorized access by way of MS Identity tokens.
services.AddExternalAPILib<SwaggerAuthorizationFilter>();
// use bearer authorization for all exposed services. Since we use CookieAuth for microsoft Identity (under the hood), this only truly affects the swagger api.
services.AddAuthorization(auth => {
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build());
});
// quartz job scheduler w/ DI injection
services.AddBackgroundJobs();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceScopeFactory scopeFactory, ILoggerManager logger) {
logger.LogInfo("Configure() executed..");
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
// since we use react on frontend, we will be provisioning STATIC files (ie html).
app.UseStaticFiles();
app.UseAuthentication(); // will attach Identity resultants to HttpContext.Current.user. Must be called prior to AuthServer middleware (aka "AuthenticateAsync" internal call)
//app.UseAuthorization(); // not needed. middleware for mvc. very particular on order of ops here. may need moved.
// this MUST occur after the "UseAuthentication" middleware just above.
app.UseMiddleware<NTLMAuthMiddleware>();
// Runs path matching on url. An endpoint is selected and set on the HttpContext if a match is found.
app.UseRouting();
// register routing decisions, in order of priority.
app.UseEndpoints(endpoints => {
// todo: remove these two - they serve no purpose for our project
endpoints.MapControllers();
endpoints.MapRazorPages();
// add our custom api library...
API.Bootstrap.OnConfigure(endpoints, ROOT_PATH);
});
// Load swagger ui for easy developer usage of api..
var UseSwagger = env.IsDevelopment();
if (UseSwagger) {
app
.UseSwagger()
.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApp API");
c.OAuthClientId(ormConfig.Jwt.ProviderID);
c.OAuthClientSecret(ormConfig.Jwt.ProviderSecret);
c.OAuthRealm(ormConfig.Jwt.Realm);
c.OAuthAppName("WebApp API");
});
}
logger.LogInfo("System has been configured. Running now:");
app.Run(async (context) => {
var uri = context.Request.Path.ToUriComponent();
if (uri.EndsWith(".map"))
return;
// http://localhost:51021/dist/hot/main.655d7e8ba4d21f447fbb.hot-update.json
else if (env.IsDevelopment() && uri.StartsWith("/dist/")) {
var the_file = Path.Combine(WEB_PATH, uri.TrimStart('/').Replace("/", "\\"));
if (File.Exists(the_file)) {
using (var reader = new StreamReader(File.OpenRead(the_file)))
await context.Response.WriteAsync(reader.ReadToEnd());
}
}
else {
// by default all requests will return the root html file which is the react SPA..
using (var reader = new StreamReader(File.OpenRead("wwwroot/index.html")))
await context.Response.WriteAsync(reader.ReadToEnd());
}
});
}
}
}