Exemple #1
0
        public async Task <byte[]> GetAsync(string key, CancellationToken token = default)
        {
            using var activity = applicationContextActivityDecorator.StartActivity();
            activity.AddTag("cacheKey", key);

            logger.LogTrace($"Requesting cache for key {key}");

            var cacheResult = BlogConfiguration.ApplicationCacheType switch
            {
                CacheType.InMemory => memoryCache.Get <byte[]>(key),
                CacheType.Distributed => await distributedCache.GetAsync(key, token),
                _ => throw new ArgumentException($"The given cache type '{BlogConfiguration.ApplicationCacheType}' is not known")
            };

            var tags = new[] {
                new KeyValuePair <string, object>("key", key),
                new KeyValuePair <string, object>("type", BlogConfiguration.ApplicationCacheType.ToString())
            }.Concat(MetricTags.GetDefaultTags()).ToArray();

            CacheHitCounter.Add(1, tags);
            CacheHitSuccessCounter.Add(cacheResult == null ? 0 : 1, tags);
            CacheHitFailedCounter.Add(cacheResult == null ? 1 : 0, tags);

            logger.LogDebug($"Hitting cache for key {key} {(cacheResult == null ? "didn't" : "did")} return a value");

            return(cacheResult);
        }
Exemple #2
0
        public async Task <LoginResponseDto> LoginAsync([FromBody] LoginCredentialsDto credentials)
        {
            using var activity = traceActivityDecorator.StartActivity();
            activity.AddTag("username", credentials.Key);
            activity.AddTag("session", credentials.Session);
            activity.AddTag("type", credentials.Type);

            logger.LogTrace($"Trying to login user {credentials.Key}");

            var response = await RunAllAuthenticationChecksAsync(credentials);

            var tags = new[] {
                new KeyValuePair <string, object>("username", credentials.Key),
                new KeyValuePair <string, object>("session", credentials.Session),
                new KeyValuePair <string, object>("type", credentials.Type)
            }.Concat(MetricTags.GetDefaultTags()).ToArray();

            LoginAttemptsCounter.Add(1, tags);
            LoginSuccessCounter.Add(response.Success ? 1 : 0, tags);
            LoginFailedCounter.Add(response.Success ? 0 : 1, tags);

            if (response.Success && response.Type == LoginResponseType.AuthenticationToken)
            {
                var message = new {
                    User     = credentials.Key,
                    RemoteId = httpContextAccessor?.HttpContext?.Connection?.RemoteIpAddress?.ToString()
                };
                await messageBus.SendMessageAsync("ntfrex.blog.logins", JsonSerializer.Serialize(message));
            }

            logger.LogInformation($"User {credentials.Key} login was {(!response.Success ? "not" : "")} succesfull");
            return(response);
        }
Exemple #3
0
        protected override Task <AuthenticateResult> HandleAuthenticateAsync()
        {
            using var activity = applicationContextActivityDecorator.StartActivity();

            var authenticationResult = TryAuthenticate();

            var tags = new[] {
                new KeyValuePair <string, object>("scheme", AuthenticationScheme),
                new KeyValuePair <string, object>("principal", authenticationResult?.Principal?.GetIdClaim())
            }.Concat(MetricTags.GetDefaultTags()).ToArray();

            RequestCount.Add(1, tags);
            RequestAuthenticatedCount.Add(authenticationResult.Succeeded ? 1 : 0, tags);
            RequestUnauthenticatedCount.Add(authenticationResult.Succeeded ? 0 : 1, tags);

            return(Task.FromResult(authenticationResult));
        }
        public async Task <HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
        {
            var healthCheckName = GetType().Name;

            using var activity = traceActivityDecorator.StartActivity();
            activity.AddTag("healthCheckName", healthCheckName);

            var result = await DoCheckHealthAsync(context, cancellationToken);

            var tags = new[] {
                new KeyValuePair <string, object>("name", healthCheckName)
            }.Concat(MetricTags.GetDefaultTags()).ToArray();

            HealthCheckCounter.Add(1, tags);
            DegratedCounter.Add(result.Status == HealthStatus.Degraded ? 1 : 0, tags);
            UnhealthyCounter.Add(result.Status == HealthStatus.Unhealthy ? 1 : 0, tags);
            HealthyCounter.Add(result.Status == HealthStatus.Healthy ? 1 : 0, tags);

            return(result);
        }
Exemple #5
0
        public async Task InsertCommentAsync(CreateCommentDto model)
        {
            using var activity = traceActivityDecorator.StartActivity();

            var dbModel = mapper.Map <CommentModel>(model);

            dbModel.Date = DateTime.UtcNow;

            await commentRepository.InsertAsync(dbModel);

            await cache.RemoveSaveAsync(CacheKeys.AllComments.Name);

            await cache.RemoveSaveAsync(CacheKeys.CommentsByArticleId.Name(model.ArticleId));

            var tags = new[] {
                new KeyValuePair <string, object>("user", model.User)
            }.Concat(MetricTags.GetDefaultTags()).ToArray();

            CommmentCreatedCounter.Add(1, tags);

            await messageBus.SendMessageAsync("ntfrex.blog.comments", JsonSerializer.Serialize(model));
        }
Exemple #6
0
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCaching();
            services.AddResponseCompression();

            services.AddHttpContextAccessor();

            services.AddOpenTelemetryMetrics(builder =>
            {
                builder
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddMeter(BlogConfiguration.MetricsName);

                if (BlogConfiguration.UsePrometheusScrapingEndpoint)
                {
                    builder.AddPrometheusExporter();
                }

                if (!string.IsNullOrEmpty(BlogConfiguration.OtlpMetricsExporterPath))
                {
                    builder.AddOtlpExporter(options => options.Endpoint = new Uri(BlogConfiguration.OtlpMetricsExporterPath));
                }
            });

            services.Configure <AspNetCoreInstrumentationOptions>(x =>
            {
                x.Enrich = (activity, eventName, rawObject) =>
                {
                    if (eventName.Equals("OnStartActivity"))
                    {
                        if (rawObject is HttpRequest httpRequest)
                        {
                            var traceID = Guid.NewGuid().ToString();
                            httpRequest.HttpContext.Items[HttpContextItemNames.TraceId] = traceID;

                            activity.SetTag("http.path", httpRequest.Path);
                            activity.SetTag("traceId", traceID);
                            activity.AddTag("aspNetCoreTraceId", httpRequest.HttpContext.TraceIdentifier);
                            foreach (var tag in MetricTags.GetDefaultTags())
                            {
                                activity.SetTag(tag.Key, tag.Value);
                            }
                        }
                    }
                };
            });

            services.AddOpenTelemetryTracing(builder => {
                var resourceBuilder = ResourceBuilder.CreateDefault();

                if (BlogConfiguration.IsAwsEC2)
                {
                    resourceBuilder.AddDetector(new AWSEC2ResourceDetector());
                }
                if (BlogConfiguration.IsAwsEBS)
                {
                    resourceBuilder.AddDetector(new AWSEBSResourceDetector());
                }
                if (BlogConfiguration.IsAwsLambda)
                {
                    resourceBuilder.AddDetector(new AWSLambdaResourceDetector());
                }

                builder
                .AddXRayTraceId()
                .AddAspNetCoreInstrumentation()
                .AddHttpClientInstrumentation()
                .AddSqlClientInstrumentation()
                .AddAWSInstrumentation()
                .AddSource(BlogConfiguration.ActivitySourceName)
                .SetResourceBuilder(resourceBuilder);

                if (!string.IsNullOrEmpty(BlogConfiguration.OtlpTraceExporterPath))
                {
                    builder.AddOtlpExporter(options => options.Endpoint = new Uri(BlogConfiguration.OtlpTraceExporterPath));
                }

                if (BlogConfiguration.ApplicationCacheType == CacheType.Distributed)
                {
                    builder.AddRedisInstrumentation(ConnectionMultiplexer.Connect(redisConnectionString));
                }
            });

            services.AddHealthChecks()
            .AddCheck <CertificateExpiringHealthCheck>(nameof(CertificateExpiringHealthCheck))
            .AddCheck <ToManyAdminLoginAttemptsHealthCheck>(nameof(ToManyAdminLoginAttemptsHealthCheck))
            .AddCheck <DoesReturnArticlesHealthCheck>(nameof(DoesReturnArticlesHealthCheck))
            .AddCheck <ResponseStatusCodeHealthCheck>(nameof(ResponseStatusCodeHealthCheck));

            if (BlogConfiguration.ApplicationCacheType == CacheType.Distributed)
            {
                services.AddStackExchangeRedisCache(options =>
                {
                    options.Configuration = redisConnectionString;
                });
            }
            else if (BlogConfiguration.ApplicationCacheType == CacheType.InMemory)
            {
                services.AddMemoryCache();
            }

            if (BlogConfiguration.MessageBus == MessageBusType.RabbitMq)
            {
                services.AddTransient <IMessageBus, RabbitMessageBus>();
            }
            else if (BlogConfiguration.MessageBus == MessageBusType.AwsSqs)
            {
                services.AddTransient <IMessageBus, AwsSqsMessageBus>();
            }
            else if (BlogConfiguration.MessageBus == MessageBusType.AwsEventBus)
            {
                services.AddTransient <IMessageBus, AwsEventBridgeMessageBus>();
            }
            else
            {
                services.AddTransient <IMessageBus, NullMessageBus>();
            }

            if (BlogConfiguration.EnableTwoFactorAuth)
            {
                services.AddTransient <ITwoFactorAuthenticator, MessageBusTwoFactorAuthenticator>();
            }
            else
            {
                services.AddTransient <ITwoFactorAuthenticator, NullTwoFactorAuthenticator>();
            }

            services.AddAuthorization(options => options.AddPolicy(AuthorizationPolicyNames.OnlyAsAdmin, configure => configure.AddRequirements(new OnlyAsAdminAuthorizationRequirement())));
            services.AddSingleton <IAuthorizationHandler, OnlyAsAdminAuthorizationHandler>();
            services.AddTransient <AuthorizationManager>();

            services.AddAuthentication(ApplicationAuthenticationHandler.AuthenticationScheme)
            .AddScheme <ApplicationAuthenticationOptions, ApplicationAuthenticationHandler>(ApplicationAuthenticationHandler.AuthenticationScheme, x => { });

            services.AddControllersWithViews(options =>
            {
                // currently there are two write actions which need to be transactional
                //  - updating db
                //  - publishing to event bus
                // the architecture is so that the publish happens last as it cannot be rolled back
                // in case more write actions are added they either need to support rollbacks/journaling
                // or the api needs to be written in an idempotent fashion
                options.Filters.Add <TransactionActionFilter>();
            });

            services.AddRazorPages(options =>
            {
                options.Conventions.AuthorizeFolder("/Private", AuthorizationPolicyNames.OnlyAsAdmin);
            });

            services.AddTransient <RecaptchaManager>();

            services.AddSingleton(Program.ActivitySource);
            services.AddTransient <ApplicationContextActivityDecorator>();
            services.AddTransient <ApplicationCache>();
            services.AddTransient <ArticleService>();
            services.AddTransient <CommentService>();
            services.AddTransient <TagService>();
            services.AddTransient <ImageService>();

            services.AddSingleton <IMapper>(x => new Mapper(new MapperConfiguration(configExpression => {
                ApplictionMapperConfig.ConfigureAutomapper(configExpression);
                Data.ApplictionMapperConfig.ConfigureAutomapper(configExpression);
            })));

            if (BlogConfiguration.PersistenceLayer == PersistenceLayerType.MySql)
            {
                services.AddScoped <MySqlConnectionFactory>();
                services.AddScoped <IConnectionFactory, MySqlConnectionFactory>();
                services.AddScoped <ICommentRepository, RelationalDbCommentRepository>();
                services.AddScoped <IImageRepository, RelationalDbImageRepository>();
                services.AddScoped <IArticleRepository, RelationalDbArticleRepository>();
                services.AddScoped <ITagRepository, RelationalDbTagRepository>();
                services.AddScoped <IVisitorRepository, RelationalDbVisitorRepository>();
            }
            else if (BlogConfiguration.PersistenceLayer == PersistenceLayerType.MongoDb)
            {
                services.AddScoped <MongoConnectionFactory>();
                services.AddScoped <IConnectionFactory, MongoConnectionFactory>();
                services.AddScoped <ICommentRepository, MongoDbCommentRepository>();
                services.AddScoped <IImageRepository, MongoDbImageRepository>();
                services.AddScoped <IArticleRepository, MongoDbArticleRepository>();
                services.AddScoped <ITagRepository, MongoDbTagRepository>();
                services.AddScoped <IVisitorRepository, MongoDbVisitorRepository>();
            }
        }