Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created Secret token validator middleware #1190

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Telegram.Bot.sln
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".devcontainer", ".devcontai
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnumSerializer.Generator", "src\EnumSerializer.Generator\EnumSerializer.Generator.csproj", "{C246B54F-018B-4589-B26D-C38D7846FFF2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telegram.Bot.AspNet", "src\Telegram.Bot.AspNet\Telegram.Bot.AspNet.csproj", "{93755AE6-F438-4D86-9C36-A54A32549F6D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -69,6 +71,10 @@ Global
{C246B54F-018B-4589-B26D-C38D7846FFF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C246B54F-018B-4589-B26D-C38D7846FFF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C246B54F-018B-4589-B26D-C38D7846FFF2}.Release|Any CPU.Build.0 = Release|Any CPU
{93755AE6-F438-4D86-9C36-A54A32549F6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93755AE6-F438-4D86-9C36-A54A32549F6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93755AE6-F438-4D86-9C36-A54A32549F6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93755AE6-F438-4D86-9C36-A54A32549F6D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -81,6 +87,7 @@ Global
{C20A563E-603B-49E8-A954-DB90D4F351DE} = {71662597-40F2-4192-AC4D-5FB9A1F12642}
{4897F36C-2F57-48A7-B425-D8F695E0AC0D} = {C45387C6-C93F-4FD2-84A8-A69CCE93B7EE}
{C246B54F-018B-4589-B26D-C38D7846FFF2} = {838231A4-B081-44B0-AC16-DC4C2FABAE86}
{93755AE6-F438-4D86-9C36-A54A32549F6D} = {838231A4-B081-44B0-AC16-DC4C2FABAE86}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F6A9C4CA-DF26-4772-9119-627935D70E7C}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;

namespace Telegram.Bot.AspNet.Exceptions;

public class InvalidSecretTokenException : Exception
{
public InvalidSecretTokenException(string message) : base(message)
{ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Telegram.Bot.AspNet.Exceptions;
using Telegram.Bot.AspNet.Options;

namespace Telegram.Bot.AspNet.Middlewares.SecretTokenValidator;

public class SecretTokenValidatorMiddleware : IMiddleware
{
private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token";
private readonly TelegramBotClientWebhookOptions _options;

public SecretTokenValidatorMiddleware(IOptions<TelegramBotClientWebhookOptions> options)
{
_options = options.Value;
}

public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
string? secretToken = context.Request.Headers
.FirstOrDefault(x => x.Key == SecretTokenHeader)
.Value
.ToString();

if (string.IsNullOrWhiteSpace(secretToken))
throw new InvalidSecretTokenException($"Header {SecretTokenHeader} not found or empty");

if (secretToken != _options.SecretToken)
throw new InvalidSecretTokenException("Secret token is invalid");

await next(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Builder;

namespace Telegram.Bot.AspNet.Middlewares.SecretTokenValidator;

public static class SecretTokenValidatorMiddlewareExtension
{
public static IApplicationBuilder UseSecretTokenValidator(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SecretTokenValidatorMiddleware>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Telegram.Bot.AspNet.Options;

public class TelegramBotClientWebhookOptions
{
public const string SectionName = "SecretToken";
public string SecretToken { get; set; }
}
19 changes: 19 additions & 0 deletions src/Telegram.Bot.AspNet/Telegram.Bot.AspNet.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<WarningLevel>7</WarningLevel>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<AnalysisLevel>latest-recommended</AnalysisLevel>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Telegram.Bot.AspNet.Exceptions;
using Telegram.Bot.AspNet.Middlewares.SecretTokenValidator;
using Telegram.Bot.AspNet.Options;
using Xunit;

namespace Telegram.Bot.Tests.Unit.Middlewares;

public class SecretTokenValidatorMiddlewareTests
{
private const string SecretTokenHeader = "X-Telegram-Bot-Api-Secret-Token";
private const string SecretToken = "my-secret-token";

[Fact]
public async Task SendAsync_WithValidSecretToken_ShouldReturn404()
{
IHost host = await CreateHost();
TestServer server = host.GetTestServer();
HttpContext context = await server.SendAsync(c => c.Request.Headers.Add(SecretTokenHeader, SecretToken));
Assert.Equal(context.Response.StatusCode, (int)HttpStatusCode.NotFound);
}

[Fact]
public async Task SendAsync_WithValidToken_ToProtectedPath_ShouldReturn404()
{
const string protectedPath = "/telegram";
IHost host = await CreateHostWithProtectedPath(protectedPath);
TestServer server = host.GetTestServer();
HttpContext context = await server.SendAsync(c =>
{
c.Request.Path = protectedPath;
c.Request.Headers.Add(SecretTokenHeader, SecretToken);
});
Assert.Equal(context.Response.StatusCode, (int)HttpStatusCode.NotFound);
}


[Fact]
public async Task SendAsync_WithNoToken_ToUnprotectedPath_ShouldReturn404()
{
const string protectedPath = "/telegram";
const string unprotectedPath = "/path";
IHost host = await CreateHostWithProtectedPath(protectedPath);
TestServer server = host.GetTestServer();
HttpContext context = await server.SendAsync(c => { c.Request.Path = unprotectedPath; });
Assert.Equal(context.Response.StatusCode, (int)HttpStatusCode.NotFound);
}


[Fact]
public async Task SendAsync_WithInvalidSecretToken_ShouldThrow()
{
IHost host = await CreateHost();
TestServer server = host.GetTestServer();

await Assert.ThrowsAsync<InvalidSecretTokenException>(async () => await server.SendAsync(c =>
{
c.Request.Headers.Add(SecretTokenHeader, "invalid-secret-token");
}));
}

[Fact]
public async Task SendAsync_WithEmptySecretToken_ShouldThrow()
{
IHost host = await CreateHost();
TestServer server = host.GetTestServer();

await Assert.ThrowsAsync<InvalidSecretTokenException>(async () => await server.SendAsync(c => { }));
}

private static async Task<IHost> CreateHost()
{
IHost? host = await new HostBuilder()
.ConfigureWebHost(builder =>
{
builder.UseTestServer()
.ConfigureServices(services =>
{
services.Configure<TelegramBotClientWebhookOptions>(options =>
options.SecretToken = SecretToken);
services.AddTransient<SecretTokenValidatorMiddleware>();
})
.Configure(app => app.UseSecretTokenValidator());
}).StartAsync();
return host;
}

private static async Task<IHost> CreateHostWithProtectedPath(string pathToProtect)
{
IHost? host = await new HostBuilder()
.ConfigureWebHost(builder =>
{
builder.UseTestServer()
.ConfigureServices(services =>
{
services.Configure<TelegramBotClientWebhookOptions>(options =>
options.SecretToken = SecretToken);
services.AddTransient<SecretTokenValidatorMiddleware>();
})
.Configure(app =>
app.UseWhen(
c => c.Request.Path.StartsWithSegments(pathToProtect,
StringComparison.InvariantCultureIgnoreCase),
applicationBuilder => applicationBuilder.UseSecretTokenValidator()));
}).StartAsync();
return host;
}
}
2 changes: 2 additions & 0 deletions test/Telegram.Bot.Tests.Unit/Telegram.Bot.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.13" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.utility" Version="2.4.2" />
Expand All @@ -15,6 +16,7 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Telegram.Bot.AspNet\Telegram.Bot.AspNet.csproj" />
<ProjectReference Include="..\..\src\Telegram.Bot\Telegram.Bot.csproj" />
</ItemGroup>
</Project>