Skip to content

Commit

Permalink
Merge branch 'release/1.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
belidzs committed Jun 19, 2020
2 parents 81449ef + 1aa40cc commit dec01f0
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 41 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[*.cs]

# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none

# SA1309: Field names should not begin with underscore
dotnet_diagnostic.SA1309.severity = none

# SA1623: Property summary documentation should match accessors
dotnet_diagnostic.SA1623.severity = none

# SA1642: Constructor summary documentation should begin with standard text
dotnet_diagnostic.SA1642.severity = none
34 changes: 17 additions & 17 deletions Mailgun.Models.SignedEvent.Tests/MailgunSignatureTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ namespace Mailgun.Models.SignedEvent.Tests
/// </summary>
public class MailgunSignatureTests
{
private DateTime originalTimestamp;
private TimeSpan timeSinceOriginalTimestamp;
private MailgunSignature validSignature;
private string apiKey;
private DateTime _originalTimestamp;
private TimeSpan _timeSinceOriginalTimestamp;
private MailgunSignature _validSignature;
private string _apiKey;

/// <summary>
/// Sets up test fixture with a know valid signature.
/// </summary>
[SetUp]
public void Setup()
{
this.originalTimestamp = new DateTime(2020, 6, 18, 11, 55, 0).ToUniversalTime();
var originalTimestampAsUnixEpoch = (this.originalTimestamp - DateTime.UnixEpoch).TotalSeconds.ToString();
this.timeSinceOriginalTimestamp = DateTime.UtcNow - this.originalTimestamp;
this.apiKey = "ffffffffffffffffffffffffffffffff-ffffffff-ffffffff";
_originalTimestamp = new DateTime(2020, 6, 18, 11, 55, 0).ToUniversalTime();
var originalTimestampAsUnixEpoch = (_originalTimestamp - DateTime.UnixEpoch).TotalSeconds.ToString();
_timeSinceOriginalTimestamp = DateTime.UtcNow - _originalTimestamp;
_apiKey = "ffffffffffffffffffffffffffffffff-ffffffff-ffffffff";

this.validSignature = new MailgunSignature()
_validSignature = new MailgunSignature()
{
Signature = "de4b938580bb4d84f710cbb8bfa7d224bb2262c8f644f558c2901c1ae645bb03",
Token = "ffffffffffffffffffffffffffffffffffffffffffffffffff",
Expand All @@ -43,7 +43,7 @@ public void Setup()
[Test]
public void Valid()
{
Assert.That(this.validSignature.IsValid(this.apiKey, this.timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.True);
Assert.That(_validSignature.IsValid(_apiKey, _timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.True);
}

/// <summary>
Expand All @@ -52,7 +52,7 @@ public void Valid()
[Test]
public void TooOld()
{
Assert.That(this.validSignature.IsValid(this.apiKey, this.timeSinceOriginalTimestamp - new TimeSpan(0, 1, 0)), Is.False);
Assert.That(_validSignature.IsValid(_apiKey, _timeSinceOriginalTimestamp - new TimeSpan(0, 1, 0)), Is.False);
}

/// <summary>
Expand All @@ -61,9 +61,9 @@ public void TooOld()
[Test]
public void BadSignature()
{
this.validSignature.Signature += "x";
_validSignature.Signature += "x";

Assert.That(this.validSignature.IsValid(this.apiKey, this.timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
Assert.That(_validSignature.IsValid(_apiKey, _timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
}

/// <summary>
Expand All @@ -72,9 +72,9 @@ public void BadSignature()
[Test]
public void BadToken()
{
this.validSignature.Token += "x";
_validSignature.Token += "x";

Assert.That(this.validSignature.IsValid(this.apiKey, this.timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
Assert.That(_validSignature.IsValid(_apiKey, _timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
}

/// <summary>
Expand All @@ -83,9 +83,9 @@ public void BadToken()
[Test]
public void BadApiKey()
{
this.apiKey += "x";
_apiKey += "x";

Assert.That(this.validSignature.IsValid(this.apiKey, this.timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
Assert.That(_validSignature.IsValid(_apiKey, _timeSinceOriginalTimestamp + new TimeSpan(0, 1, 0)), Is.False);
}
}
}
7 changes: 6 additions & 1 deletion Mailgun.Models.SignedEvent.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ VisualStudioVersion = 16.0.30204.135
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailgun.Models.SignedEvent", "Mailgun.Models.SignedEvent\Mailgun.Models.SignedEvent.csproj", "{1BEB07E0-C800-45A1-990E-5F64962B8C9E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mailgun.Models.SignedEvent.Tests", "Mailgun.Models.SignedEvent.Tests\Mailgun.Models.SignedEvent.Tests.csproj", "{8E4EF695-2A4F-4FF9-913D-541F4AF7F841}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mailgun.Models.SignedEvent.Tests", "Mailgun.Models.SignedEvent.Tests\Mailgun.Models.SignedEvent.Tests.csproj", "{8E4EF695-2A4F-4FF9-913D-541F4AF7F841}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{662C3973-3245-4C91-B884-315F126B1B3A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
35 changes: 35 additions & 0 deletions Mailgun.Models.SignedEvent/IMailgunSignature.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// <copyright file="IMailgunSignature.cs" company="Balazs Keresztury">
// Copyright (c) Balazs Keresztury. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// </copyright>

using System;

namespace Mailgun.Models.SignedEvent
{
public interface IMailgunSignature
{
string Signature { get; set; }

string Timestamp { get; set; }

string Token { get; set; }

/// <summary>
/// Checks if the signature is valid based on the provided API key with a maximum allowed time skew of 10 minutes.
/// https://documentation.mailgun.com/en/latest/user_manual.html#webhooks.
/// </summary>
/// <param name="apiKey">malgun API key.</param>
/// <returns>True if the signature is valid.</returns>
bool IsValid(string apiKey);

/// <summary>
/// Checks if the signature is valid based on the provided API key.
/// https://documentation.mailgun.com/en/latest/user_manual.html#webhooks.
/// </summary>
/// <param name="apiKey">mailgun API key.</param>
/// <param name="tolerance">Maximum acceptable time difference between the creation of the signature and now.</param>
/// <returns>True if the signature is valid.</returns>
bool IsValid(string apiKey, TimeSpan tolerance);
}
}
6 changes: 3 additions & 3 deletions Mailgun.Models.SignedEvent/Mailgun.Models.SignedEvent.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.3;net20</TargetFrameworks>
<TargetFrameworks>netstandard1.3;net45</TargetFrameworks>
<Authors>Balazs Keresztury</Authors>
<Copyright>Copyright (c) 2020 Balazs Keresztury</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Version>1.0.0</Version>
<Version>1.0.1</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/belidzs/Mailgun.Models.SignedEvent</PackageProjectUrl>
<RepositoryUrl>https://github.com/belidzs/Mailgun.Models.SignedEvent</RepositoryUrl>
Expand All @@ -28,6 +28,6 @@ This library can be used as a data model for the deserialization of this data wi
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
</ItemGroup>

</Project>
29 changes: 11 additions & 18 deletions Mailgun.Models.SignedEvent/MailgunSignature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,28 @@
// </copyright>

using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;

namespace Mailgun.Models.SignedEvent
{
public class MailgunSignature
public class MailgunSignature : IMailgunSignature
{
[Required]
public string Timestamp { get; set; }

[Required]
public string Token { get; set; }

[Required]
public string Signature { get; set; }

/// <summary>
/// Checks if the signature is valid based on the provided API key.
/// https://documentation.mailgun.com/en/latest/user_manual.html#webhooks.
/// </summary>
/// <param name="apiKey">mailgun API key.</param>
/// <param name="tolerance">Maximum acceptable time difference between the creation of the signature and now.</param>
/// <returns>True if the signature is valid.</returns>
/// <inheritdoc />
public bool IsValid(string apiKey, TimeSpan tolerance)
{
// parse timestamp as a DateTime object
var timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(Convert.ToDouble(this.Timestamp));
var timestamp = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(Convert.ToDouble(Timestamp));
if (DateTime.UtcNow - timestamp > tolerance)
{
// if the signature was made too long ago, return false
Expand All @@ -39,7 +37,7 @@ public bool IsValid(string apiKey, TimeSpan tolerance)
{
Key = Encoding.ASCII.GetBytes(apiKey),
};
var calculated_signature = hasher.ComputeHash(Encoding.ASCII.GetBytes(this.Timestamp + this.Token));
var calculated_signature = hasher.ComputeHash(Encoding.ASCII.GetBytes(Timestamp + Token));

// convert calculated signature to hexdigest format
string hash_hex = string.Empty;
Expand All @@ -49,18 +47,13 @@ public bool IsValid(string apiKey, TimeSpan tolerance)
}

// compare
return this.Signature.Equals(hash_hex);
return Signature.Equals(hash_hex);
}

/// <summary>
/// Checks if the signature is valid based on the provided API key with a maximum allowed time skew of 10 minutes.
/// https://documentation.mailgun.com/en/latest/user_manual.html#webhooks.
/// </summary>
/// <param name="apiKey">malgun API key.</param>
/// <returns>True if the signature is valid.</returns>
/// <inheritdoc />
public bool IsValid(string apiKey)
{
return this.IsValid(apiKey, new TimeSpan(0, 10, 0));
return IsValid(apiKey, new TimeSpan(0, 10, 0));
}
}
}
4 changes: 4 additions & 0 deletions Mailgun.Models.SignedEvent/SignedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// </copyright>

using System.ComponentModel.DataAnnotations;

namespace Mailgun.Models.SignedEvent
{
public class SignedEvent
{
[Required]
public MailgunSignature Signature { get; set; }

[Required]
public MailgunEvent EventData { get; set; }
}
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

Implements data model for incoming Mailgun events to use with your custom Mailgun webhooks.

This library can be used as a data model for the deserialization of this data with any JSON serializer of your choice. It even provides a handy function to [verify its cryptographic signature](https://documentation.mailgun.com/en/latest/user_manual.html#webhooks).
This library can be used as the data model for the deserialization process with basically any JSON deserializer library of your choice. It even provides a handy function to [verify its cryptographic signature](https://documentation.mailgun.com/en/latest/user_manual.html#webhooks).

Since it targets .NET Standard 1.6 it is [compatible](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) with a wide variety of platforms (such as .NET Framework 4.6.1, .NET Core 1.0 and newer).
Since it targets [.NET Standard](https://docs.microsoft.com/en-us/dotnet/standard/net-standard) 1.3 and .NET Framework 4.5 it is compatible with a wide variety of platforms.

## Background

Expand Down

0 comments on commit dec01f0

Please sign in to comment.