public Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, RouteHandlingDelegate continueExecution) { if (!request.Headers.ContainsKey(HeaderKey)) { return(continueExecution(controllerType, httpApp, request)); } return(EastFive.Azure.AppSettings.TableInformationToken.ConfigurationString( async headerToken => { if (request.Headers[HeaderKey].First() != headerToken) { return request.CreateResponse(System.Net.HttpStatusCode.Unauthorized); } if (request.Headers.ContainsKey("X-StorageTableInformation-List")) { var tableData = await controllerType.StorageGetAll().ToArrayAsync(); return request.CreateExtrudedResponse( System.Net.HttpStatusCode.OK, tableData); } if (request.Headers.ContainsKey("X-StorageTableInformation-RepairModifiers")) { var query = request.Headers .Where(hdr => "X-StorageTableInformation-Query".Equals(hdr.Key, StringComparison.OrdinalIgnoreCase)) .First( (hdr, next) => hdr.Value.First( (hdrValue, next) => hdrValue, () => string.Empty), () => string.Empty); return new WriteStreamAsyncHttpResponse(request, System.Net.HttpStatusCode.OK, $"{controllerType.FullName}.repair.txt", "text/text", true, async stream => { string [] repairs = await controllerType .StorageRepairModifiers(query) .Select( async line => { var bytes = line.GetBytes(Encoding.UTF8); await stream.WriteAsync(bytes, 0, bytes.Length); return line; }) .Await(readAhead: 100) .ToArrayAsync(); }); } var tableInformation = await controllerType.StorageTableInformationAsync(); return request.CreateResponse(System.Net.HttpStatusCode.OK, tableInformation); }, why => continueExecution(controllerType, httpApp, request))); }
public Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, RouteHandlingDelegate continueExecution) { Func <Task <IHttpResponse> > skip = () => continueExecution(controllerType, httpApp, request); if (!AppSettings.CorsCorrection.ConfigurationBoolean(s => s, onNotSpecified: () => false)) { return(skip()); } // Configs to set: // // EastFive.Api.CorsCorrection=true // cors:Origins=https://myserver.com,etc. (localhost included by default so this app setting can remain absent/unconfigured if just need localhost) // cors:MaxAgeSeconds=60 (default is 5 seconds if not set) // return(GetResponse()); string GetAllowedOrigin() { // accept localhost (all ports) // accept request server // accept any additional servers in config request.Headers.TryGetValue("Origin", out string[] reqOrigins); var localhostAuthorities = reqOrigins .NullToEmpty() .SelectMany(reqOrigin => reqOrigin.Split(','.AsArray(), StringSplitOptions.RemoveEmptyEntries)) .Where( (reqOrigin) => { if (!Uri.TryCreate(reqOrigin, UriKind.Absolute, out Uri reqOriginUri)) { return(false); } return(reqOriginUri.GetLeftPart(UriPartial.Authority).IndexOf("localhost", StringComparison.OrdinalIgnoreCase) != -1); }); var requestAuthority = request.RequestUri.GetLeftPart(UriPartial.Authority); var corsAuthorities = "cors:Origins".ConfigurationString( (v) => v.Split(','.AsArray(), StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim()).ToArray(), (why) => new string[] { }); var allowableOriginValues = localhostAuthorities .Append(requestAuthority) .Concat(corsAuthorities) .Distinct() .ToArray(); var allowedOrigin = allowableOriginValues .NullToEmpty() .First( (allowed, next) => { if (!reqOrigins.NullToEmpty().Contains(allowed, StringComparer.OrdinalIgnoreCase)) { return(next()); } return(allowed); }, () => default(string)); return(allowedOrigin); } string GetAllowedMethods() { // accept OPTIONS // accept any additional methods in config request.Headers.TryGetValue("Access-Control-Request-Method", out string[] reqMethod); return(reqMethod .NullToEmpty() .Append("OPTIONS") .Distinct() .Join(",")); } string GetAllowedHeaders() { // accept any headers requested request.Headers.TryGetValue("Access-Control-Request-Headers", out string[] reqHeaders); return(reqHeaders .NullToEmpty() .Distinct() .Join(",")); } string GetMaxAgeSeconds() { return("cors:MaxAgeSeconds" .ConfigurationLong( (v) => v, (why) => 5, () => 5) .ToString()); } Task <IHttpResponse> GetResponse() { if (request.Method.Method.ToLower() != HttpMethod.Options.Method.ToLower()) { return(skip()); } var allowedOrigin = GetAllowedOrigin(); if (allowedOrigin == default) { return(request.CreateResponse(System.Net.HttpStatusCode.Forbidden).AddReason("origin not allowed").AsTask()); } var response = request.CreateResponse(System.Net.HttpStatusCode.OK); response.SetHeader("Access-Control-Allow-Origin", allowedOrigin); response.SetHeader("Access-Control-Allow-Methods", GetAllowedMethods()); response.SetHeader("Access-Control-Allow-Headers", GetAllowedHeaders()); response.SetHeader("Vary", "origin"); response.SetHeader("Access-Control-Max-Age", GetMaxAgeSeconds()); return(response.AsTask()); } }
public async Task <string> TeamsNotifyAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, IHttpResponse response, string teamsNotifyParam, string collectionFolder) { var monitoringRequest = await Api.Azure.Monitoring.MonitoringRequest.CreateAsync( controllerType, resourceInvoker, httpApp, request, collectionFolder); var monitoringRequestId = monitoringRequest.id.ToString(); var responseParam = response.Headers .Where(hdr => hdr.Key == Middleware.HeaderStatusName) .Where(hdr => hdr.Value.AnyNullSafe()) .First( (hdr, next) => hdr.Value.First(), () => ""); var message = await CreateMessageCardAsync( teamsNotifyParam, $"{request} = {response.StatusCode} / {response.ReasonPhrase}", monitoringRequest, httpApp, request, () => { var cardSection = new MessageCard.Section { title = "Request/Response Information", markdown = false, // so that underscores are not stripped facts = new MessageCard.Section.Fact[] { new MessageCard.Section.Fact { name = "Response Param:", value = responseParam, }, new MessageCard.Section.Fact { name = "Http Method:", value = request.Method.Method, }, new MessageCard.Section.Fact { name = "URL:", value = request.GetAbsoluteUri().OriginalString, }, new MessageCard.Section.Fact { name = "Status Code:", value = $"{response.StatusCode.ToString()} / {(int)response.StatusCode}", }, new MessageCard.Section.Fact { name = "Reason:", value = response.ReasonPhrase, }, new MessageCard.Section.Fact { name = "RequestID:", value = monitoringRequestId, }, }, }; return(cardSection); }); return(await message.SendAsync(teamsHookUrl)); }
public async Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, RouteHandlingDelegate continueExecution) { var response = await continueExecution(controllerType, httpApp, request); if (deactivated) { return(response); } string teamsNotifyParam = GetTeamsNotifyParameter(); if (!ShouldNotify(out string collectionFolder)) { return(response); } try { string messageId = await TeamsNotifyAsync(controllerType, resourceInvoker, httpApp, request, response, teamsNotifyParam, collectionFolder); } catch (HttpRequestException) { } catch (Exception) { } return(response); string GetTeamsNotifyParameter() { return(request.Headers .Where(kvp => kvp.Key.Equals("X-Teams-Notify", StringComparison.OrdinalIgnoreCase)) .First( (teamsNotifyParams, next) => { return teamsNotifyParams.Value.First( (teamsNotifyParam, next) => teamsNotifyParam, () => default(string)); }, () => { return default(string); })); } bool HasReportableError() { if (((int)response.StatusCode) < 400) { return(false); } if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { return(false); } if (response.StatusCode == System.Net.HttpStatusCode.Conflict) { return(false); } return(true); } bool RequestTeamsNotify() => teamsNotifyParam != default; bool ShouldNotify(out string collectionFolder) { collectionFolder = default; if (RequestTeamsNotify()) { return(true); } if (HasReportableError()) { return(true); } if (TeamsNotification.IsMatch(request, response, out collectionFolder)) { return(true); } return(false); } }
public Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, RouteHandlingDelegate continueExecution) { if (!request.RequestUri.TryGetQueryParam(ParameterName, out string apiVoucher)) { if (!request.TryGetHeader(ParameterName, out apiVoucher)) { return(continueExecution(controllerType, httpApp, request)); } } if (request.TryGetAuthorization(out string auth)) { return(continueExecution(controllerType, httpApp, request)); } return(EastFive.Security.VoucherTools.ValidateUrlToken(apiVoucher, async(voucherTokenId) => { return await await voucherTokenId.AsRef <VoucherToken>() .StorageGetAsync( voucherToken => { return EastFive.Security.AppSettings.TokenScope.ConfigurationUri( scope => { var tokenExpiration = TimeSpan.FromMinutes(1.0); request.RequestUri = request.RequestUri.RemoveQueryParameter("api-voucher"); var sessionId = apiVoucher.MD5HashGuid(); var claims = voucherToken.claims; return JwtTools.CreateToken(sessionId, scope, tokenExpiration, claims, (tokenNew) => { request.SetAuthorization(tokenNew); return continueExecution(controllerType, httpApp, request); }, (missingConfig) => continueExecution(controllerType, httpApp, request), (configName, issue) => continueExecution(controllerType, httpApp, request)); }, (why) => continueExecution(controllerType, httpApp, request)); }, () => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason("Voucher token does not exist.") .AsTask()); }, why => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason(why) .AsTask(), why => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason(why) .AsTask(), why => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason(why) .AsTask(), why => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason(why) .AsTask(), (name, why) => request .CreateResponse(System.Net.HttpStatusCode.Unauthorized) .AddReason(why) .AsTask())); }
public async Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest routeData, RouteHandlingDelegate continueExecution) { var stopwatch = Stopwatch.StartNew(); var requestId = Guid.NewGuid().ToString("N"); var telemetry = new RequestTelemetry() { Id = requestId, Source = controllerType.Assembly.FullName, Timestamp = DateTimeOffset.UtcNow, Url = routeData.GetAbsoluteUri(), // request.RequestUri, }; #region User / Session var claims = routeData.GetClaims( claimsEnumerable => claimsEnumerable.ToArray(), () => new Claim[] { }, (why) => new Claim[] { }); var sessionIdClaimType = Api.Auth.ClaimEnableSessionAttribute.Type; var sessionIdMaybe = SessionToken.GetClaimIdMaybe(claims, sessionIdClaimType); if (sessionIdMaybe.HasValue) { telemetry.Context.Session.Id = sessionIdMaybe.Value.ToString().ToUpper(); } var accountIdClaimType = EastFive.Api.AppSettings.ActorIdClaimType.ConfigurationString( (accIdCT) => accIdCT, (why) => default); if (accountIdClaimType.HasBlackSpace()) { var accountIdMaybe = SessionToken.GetClaimIdMaybe(claims, accountIdClaimType); if (accountIdMaybe.HasValue) { var accountIdStr = accountIdMaybe.Value.ToString().ToUpper(); telemetry.Context.User.AccountId = accountIdStr; telemetry.Context.User.AuthenticatedUserId = accountIdStr; } } foreach (var claim in claims.Distinct(claim => claim.Type)) { telemetry.Properties.Add($"claim[{claim.Type}]", claim.Value); } #endregion routeData.Properties.Add(HttpRequestMessagePropertyRequestTelemetryKey, telemetry); var response = await continueExecution(controllerType, httpApp, routeData); telemetry.ResponseCode = response.StatusCode.ToString(); if (response.ReasonPhrase.HasBlackSpace()) { telemetry.Properties.AddOrReplace("reason_phrase", response.ReasonPhrase); } telemetry.Success = response.StatusCode.IsSuccess(); #region Method result identfiers if (response.Headers.TryGetValue(Middleware.HeaderStatusType, out string[] statusNames))
public RouteMatch IsRouteMatch( MethodInfo method, string[] componentsMatched, IInvokeResource resourceInvoker, IHttpRequest request, IApplication httpApp, IEnumerable <string> bodyKeys, CastDelegate fetchBodyParam) { var fileNameCastDelegate = GetFileNameCastDelegate(request, httpApp, componentsMatched, out string[] pathKeys); var fetchQueryParam = GetQueryCastDelegate(request, httpApp, out string[] queryKeys); var parametersCastResults = method .GetParameters() .Where(param => param.ContainsAttributeInterface <IBindApiValue>()) .Select( (param) => { var castValue = param.GetAttributeInterface <IBindApiValue>(); var bindingData = new BindingData { httpApp = httpApp, fetchDefaultParam = fileNameCastDelegate, fetchQueryParam = fetchQueryParam, fetchBodyParam = fetchBodyParam, method = method, parameterRequiringValidation = param, request = request, resourceInvoker = resourceInvoker, }; return(castValue.TryCast(bindingData)); }) .ToArray(); var failedValidations = parametersCastResults .Where(pcr => !pcr.valid) .ToArray(); return(HasExtraParameters(method, pathKeys, queryKeys, bodyKeys, parametersCastResults, () => { if (failedValidations.Any()) { return new RouteMatch { isValid = false, failedValidations = failedValidations, method = method, }; } //var parametersWithValues = parametersCastResults // .Select(parametersCastResult => // parametersCastResult.parameterInfo.PairWithValue(parametersCastResult.value)) // .ToArray(); return new RouteMatch { isValid = true, method = method, parametersWithValues = parametersCastResults, }; }, (extraFileParams, extraQueryParams, extraBodyParams) => { return new RouteMatch { isValid = false, failedValidations = failedValidations, method = method, extraFileParams = extraFileParams, extraQueryParams = extraQueryParams, extraBodyParams = extraBodyParams, }; })); }
public static async Task <MonitoringRequest> CreateAsync( Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, string folderName) { var doc = new MonitoringRequest(); doc.title = $"{request.Method.Method} {resourceInvoker.Namespace} {resourceInvoker.Route}"; doc.monitoringRequestRef = Ref <MonitoringRequest> .NewRef(); doc.when = DateTime.UtcNow; doc.url = request.RequestUri; doc.method = request.Method.Method; doc.ns = resourceInvoker.Namespace; doc.route = resourceInvoker.Route; doc.headers = request.Headers .Where(kvp => kvp.Value.AnyNullSafe()) .Select( kvp => new Header() { key = kvp.Key, value = kvp.Value.First(), }) .ToArray(); doc.folderName = folderName; if (request.HasFormContentType) { doc.formData = request.Form .Select( formInfo => { return(new FormData { key = formInfo.Key, contents = formInfo.Value.ToArray(), }); }) .ToArray(); doc.formDataFiles = await request.Form.Files .Select( async file => { var data = await file.OpenReadStream().ToBytesAsync(); var contentRef = await data.CreateBlobRefAsync( (FormFileData ffd) => ffd.contents, file.ContentType); return(new FormFileData { contents = contentRef, name = file.Name, fileName = file.FileName, contentDisposition = file.ContentDisposition, contentType = file.ContentType, headers = file.Headers .Select(hdr => new Header() { key = hdr.Key, value = hdr.Value }) .ToArray(), length = file.Length, }); }) .AsyncEnumerable() .ToArrayAsync(); } else { var bytes = await request.ReadContentAsync(); doc.body = await bytes.CreateBlobRefAsync( (MonitoringRequest mr) => mr.body, contentType : request.GetMediaType()); } return(await doc.StorageCreateAsync((discard) => doc)); }
public Task <IHttpResponse> HandleRouteAsync(Type controllerType, IInvokeResource resourceInvoker, IApplication httpApp, IHttpRequest request, RouteHandlingDelegate continueExecution) { if (!request.RequestUri.TryGetQueryParam( AccessTokenAccountExtensions.QueryParameter, out string accessToken)) { return(continueExecution(controllerType, httpApp, request)); } if (request.GetAuthorization().HasBlackSpace()) { return(continueExecution(controllerType, httpApp, request)); } return(request.RequestUri.ValidateAccessTokenAccount( accessTokenInfo => { return EastFive.Security.AppSettings.TokenScope.ConfigurationUri( scope => { var tokenExpiration = TimeSpan.FromMinutes(1.0); request.RequestUri = request.RequestUri.RemoveQueryParameter( AccessTokenAccountExtensions.QueryParameter); var sessionId = accessTokenInfo.sessionId; var authId = accessTokenInfo.accountId; var duration = accessTokenInfo.expirationUtc - DateTime.UtcNow; return JwtTools.CreateToken(sessionId, authId, scope, duration, tokenCreated: (tokenNew) => { request.SetAuthorization(tokenNew); return continueExecution(controllerType, httpApp, request); }, missingConfigurationSetting: (configName) => continueExecution(controllerType, httpApp, request), invalidConfigurationSetting: (configName, issue) => continueExecution(controllerType, httpApp, request)); }, (why) => continueExecution(controllerType, httpApp, request), () => continueExecution(controllerType, httpApp, request)); }, onAccessTokenNotProvided: () => continueExecution(controllerType, httpApp, request), onAccessTokenInvalid: () => { return request .CreateResponse(System.Net.HttpStatusCode.Forbidden) .AddReason("Access token is invalid") .AsTask(); }, onAccessTokenExpired: () => { return request .CreateResponse(System.Net.HttpStatusCode.Forbidden) .AddReason("Access token is expired") .AsTask(); }, onInvalidSignature: () => { return request .CreateResponse(System.Net.HttpStatusCode.Forbidden) .AddReason("Access token has an invalid signature") .AsTask(); }, onSystemNotConfigured: () => continueExecution(controllerType, httpApp, request))); }