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)));
        }
Esempio n. 2
0
        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);
            }
        }
Esempio n. 5
0
        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))
Esempio n. 7
0
        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));
        }
Esempio n. 9
0
        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)));
        }