Skip to content
Merged
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
74 changes: 67 additions & 7 deletions WebPush.Test/WebPushClientTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -36,6 +37,35 @@ public void InitializeTest()
client = new WebPushClient(httpMessageHandlerMock.ToHttpClient());
}

[TestMethod]
public void TestBogusEndpoint()
{
var subscription = new PushSubscription("this is not a valid endpoint", TestPublicKey, TestPrivateKey);
Assert.ThrowsExactly<ArgumentException>(() => client.GenerateRequestDetails(subscription, @"test payload"));
}


[TestMethod]
[DataRow(TestPublicKey, "")]
[DataRow("", TestPrivateKey)]
[DataRow("", "")]
public void TestMissingAuthWithPayload(string publicKey, string privateKey)
{
var subscription = new PushSubscription(TestFcmEndpoint, publicKey, privateKey);
Assert.ThrowsExactly<ArgumentException>(() => client.GenerateRequestDetails(subscription, @"test payload"));
}

[TestMethod]
[DataRow(TestPublicKey, "")]
[DataRow("", TestPrivateKey)]
[DataRow("", "")]
public void TestMissingAuthWithoutPayload(string publicKey, string privateKey)
{
var subscription = new PushSubscription(TestFcmEndpoint, publicKey, privateKey);
var message = client.GenerateRequestDetails(subscription, null);
Assert.IsNotNull(message);
}

[TestMethod]
public void TestSetTopic()
{
Expand All @@ -45,12 +75,28 @@ public void TestSetTopic()
}

[TestMethod]
public void TestSetTopicFailures()
[DataRow("failing topic #3")]
[DataRow("")]
[DataRow("a123456789012345678901234567890toolong")]
public void TestSetTopicFailures(string topic)
{
var subscription = new PushSubscription(TestGcmEndpoint, TestPublicKey, TestPrivateKey);
Assert.ThrowsExactly<ArgumentException>(() => client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { Topic = "failing topic #3" }));
Assert.ThrowsExactly<ArgumentException>(() => client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { Topic = topic, }));
}

[TestMethod]
public void TestExtraHeaders()
{
var subscription = new PushSubscription(TestGcmEndpoint, TestPublicKey, TestPrivateKey);
Dictionary<string, object> extraHeaders = [];
extraHeaders["DEBUG-VERBOSE"] = true;
var message = client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { ExtraHeaders = extraHeaders, });
var checkHeader = message.Headers.GetValues(@"DEBUG-VERBOSE").First();
Assert.IsNotNull(checkHeader);
Assert.AreEqual(@"True", checkHeader);
}



[TestMethod]
public void TestSetUrgency()
Expand All @@ -61,15 +107,29 @@ public void TestSetUrgency()
}

[TestMethod]
public void TestSetContentEncoding()
[DataRow(ContentEncoding.Aes128gcm, "aes128gcm")]
[DataRow(ContentEncoding.Aesgcm, "aesgcm")]
public void TestSetContentEncoding(ContentEncoding encoding, string encodingHeaderValue)
{
var subscription = new PushSubscription(TestGcmEndpoint, TestPublicKey, TestPrivateKey);
var messageAes128gcm = client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { ContentEncoding = ContentEncoding.Aes128gcm });
Assert.AreEqual(@"aes128gcm", messageAes128gcm.Content.Headers.ContentEncoding.First());
var messageAesgcm = client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { ContentEncoding = ContentEncoding.Aesgcm });
Assert.AreEqual(@"aesgcm", messageAesgcm.Content.Headers.ContentEncoding.First());
var message = client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { ContentEncoding = encoding, });
Assert.AreEqual(encodingHeaderValue, message.Content.Headers.ContentEncoding.First());
}

[TestMethod]
[DataRow(ContentEncoding.Aes128gcm, "aes128gcm")]
[DataRow(ContentEncoding.Aesgcm, "aesgcm")]
public void TestSetContentEncodingWithVapid(ContentEncoding encoding, string encodingHeaderValue)
{
client.SetVapidDetails(TestSubject, TestPublicKey, TestPrivateKey);
var subscription = new PushSubscription(TestGcmEndpoint, TestPublicKey, TestPrivateKey);
var message = client.GenerateRequestDetails(subscription, @"test payload", new WebPushOptions { ContentEncoding = encoding, });
Assert.AreEqual(encodingHeaderValue, message.Content.Headers.ContentEncoding.First());
// var authorizationHeader = message.Headers.GetValues(@"Authorization").First();
// Assert.StartsWith(@"vapid ", authorizationHeader);
}



[TestMethod]
public void TestSetVapidDetails()
Expand Down
4 changes: 1 addition & 3 deletions WebPush/Model/EncryptionResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace WebPush.Model;

// @LogicSoftware
// Originally From: https://github.com/LogicSoftware/WebPushEncryption/blob/master/src/EncryptionResult.cs
public class EncryptionResult
public sealed class EncryptionResult
{
public required byte[] PublicKey { get; set; }
public required byte[] Payload { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Model/InvalidEncryptionDetailsException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace WebPush.Model;

public class InvalidEncryptionDetailsException : Exception
public sealed class InvalidEncryptionDetailsException : Exception
{
public InvalidEncryptionDetailsException(string message, PushSubscription pushSubscription)
: base(message)
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Model/PushSubscription.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace WebPush;

public class PushSubscription
public sealed class PushSubscription
{
public PushSubscription(string endpoint, string p256dh, string auth)
{
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Model/VapidDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace WebPush;

public class VapidDetails
public sealed class VapidDetails
{
/// <param name="subject">This should be a URL or a 'mailto:' email address</param>
/// <param name="publicKey">The VAPID public key as a base64 encoded string</param>
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Model/WebPushException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace WebPush.Model;

public class WebPushException : Exception
public sealed class WebPushException : Exception
{
public WebPushException(string message, PushSubscription pushSubscription, HttpResponseMessage responseMessage) : base(message)
{
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Model/WebPushOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace WebPush;

public class WebPushOptions
public sealed class WebPushOptions
{
public VapidDetails? VapidDetails { get; set; }
public const int DefaultTtl = 2419200; // default is 4 weeks
Expand Down
2 changes: 1 addition & 1 deletion WebPush/Util/EnumHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace WebPush.Util;

public static partial class EnumHelper
internal static partial class EnumHelper
{

public static string ToKebabCaseLower<T>(this T val) where T : Enum
Expand Down
14 changes: 14 additions & 0 deletions WebPush/VapidHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ public static VapidDetails GenerateVapidKeys()
return new VapidDetails("", keys.GetEncodedPublicKey(), keys.GetEncodedPrivateKey());
}

/// <summary>
/// This method takes the required VAPID parameters and returns the required
/// header to be added to a Web Push Protocol Request.
/// </summary>
/// <param name="audience">This must be the origin of the push service.</param>
/// <param name="vapid">The VAPID details</param>
/// <param name="contentEncoding">The content encoding (defaults to Aes128gcm)</param>
/// <returns>A dictionary of header key/value pairs.</returns>
public static Dictionary<string, string> GetVapidHeaders(string audience, VapidDetails vapid, ContentEncoding contentEncoding = ContentEncoding.Aes128gcm)
{
return GetVapidHeaders(audience, vapid.Subject, vapid.PublicKey, vapid.PrivateKey, vapid.Expiration, contentEncoding);
}

/// <summary>
/// This method takes the required VAPID parameters and returns the required
/// header to be added to a Web Push Protocol Request.
Expand All @@ -27,6 +40,7 @@ public static VapidDetails GenerateVapidKeys()
/// <param name="publicKey">The VAPID public key as a base64 encoded string</param>
/// <param name="privateKey">The VAPID private key as a base64 encoded string</param>
/// <param name="expiration">The expiration of the VAPID JWT.</param>
/// <param name="contentEncoding">The content encoding (defaults to Aes128gcm)</param>
/// <returns>A dictionary of header key/value pairs.</returns>
public static Dictionary<string, string> GetVapidHeaders(string audience, string subject, string publicKey, string privateKey, DateTime? expiration = null, ContentEncoding contentEncoding = ContentEncoding.Aes128gcm)
{
Expand Down
10 changes: 2 additions & 8 deletions WebPush/WebPushClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
string.IsNullOrEmpty(subscription.P256DH)))
{
throw new ArgumentException(
@"To send a message with a payload, the subscription must have 'auth' and 'p256dh' keys.", nameof(subscription));
@"Unable to send a message with payload to this subscription since it doesn't have the required encryption key", nameof(subscription));
}

if (options is not null)
Expand Down Expand Up @@ -145,12 +145,6 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
var contentEncoding = options?.ContentEncoding ?? WebPushOptions.DefaultContentEncoding;
if (!string.IsNullOrEmpty(payload))
{
if (string.IsNullOrEmpty(subscription.P256DH) || string.IsNullOrEmpty(subscription.Auth))
{
throw new ArgumentException(
@"Unable to send a message with payload to this subscription since it doesn't have the required encryption key", nameof(subscription));
}

var encryptedPayload = EncryptPayload(subscription, payload);

request.Content = new ByteArrayContent(encryptedPayload.Payload);
Expand All @@ -174,7 +168,7 @@ public HttpRequestMessage GenerateRequestDetails(PushSubscription subscription,
{
var uri = new Uri(subscription.Endpoint);
var audience = uri.Scheme + @"://" + uri.Host;
var vapidHeaders = VapidHelper.GetVapidHeaders(audience, vapidDetails.Subject, vapidDetails.PublicKey, vapidDetails.PrivateKey, vapidDetails.Expiration, contentEncoding);
var vapidHeaders = VapidHelper.GetVapidHeaders(audience, vapidDetails, contentEncoding);
request.Headers.Add(@"Authorization", vapidHeaders["Authorization"]);
if (contentEncoding == ContentEncoding.Aesgcm)
{
Expand Down
Loading