Skip to content

Commit

Permalink
+ (Communication) Added support for separate "API key" and "HTTP webh…
Browse files Browse the repository at this point in the history
…ook signing key" values within Mailgun integration. (Fixes #5694)
  • Loading branch information
jasonhendee committed Dec 13, 2023
1 parent 3b81299 commit bd2e772
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 23 deletions.
97 changes: 83 additions & 14 deletions Rock.Mailgun/Communication/Transport/MailgunHttp.cs
Expand Up @@ -41,16 +41,85 @@ namespace Rock.Communication.Transport
[Export( typeof( TransportComponent ) )]
[ExportMetadata( "ComponentName", "Mailgun HTTP" )]

[TextField( "Base URL", "The API URL provided by Mailgun, keep the default in most cases.", true, @"https://api.mailgun.net/v3", "", 0, "BaseURL" )]
[TextField( "Resource", "The URL part provided by Mailgun, keep the default in most cases.", true, @"{domain}/messages", "", 1, "Resource" )]
[TextField( "Domain", "The email domain (e.g. rocksolidchurchdemo.com).", true, "", "", 2, "Domain" )]
[TextField( "API Key", "The Private API Key provided by Mailgun.", true, "", "", 3, "APIKey" )]
[BooleanField( "Track Opens", "Allow Mailgun to track opens, clicks, and unsubscribes.", true, "", 4, "TrackOpens" )]
[BooleanField( "Replace Unsafe Sender", "Defaults to \"Yes\". If set to \"No\" Mailgun will allow relaying email \"on behalf of\" regardless of the sender's domain. The safe sender list will still be used for adding a \"Sender\" header.", true, "", 5 )]
[IntegerField( "Concurrent Send Workers", "", false, 10, "", 6, key: "MaxParallelization" )]
#region Attributes

[TextField( "Base URL",
Key = AttributeKey.BaseURL,
Description = "The API URL provided by Mailgun, keep the default in most cases.",
DefaultValue = "https://api.mailgun.net/v3",
Order = 0,
IsRequired = true )]

[TextField( "Resource",
Key = AttributeKey.Resource,
Description = "The URL part provided by Mailgun, keep the default in most cases.",
DefaultValue = "{domain}/messages",
Order = 1,
IsRequired = true )]

[TextField( "Domain",
Key = AttributeKey.Domain,
Description = "The email domain (e.g. rocksolidchurchdemo.com). This should match the domain name as listed in your Mailgun account.",
DefaultValue = "",
Order = 2,
IsRequired = true )]

[TextField( "API Key",
Key = AttributeKey.APIKey,
Description = @"The API Key provided by Mailgun. Newly-created Mailgun accounts should use one of the account-wide ""Mailgun API keys"" or domain-specific ""Sending API keys"" for this value. Preexisting Mailgun accounts might refer to this value as the ""Private API key"".",
DefaultValue = "",
Order = 3,
IsRequired = true )]

[TextField( "HTTP Webhook Signing Key",
Key = AttributeKey.HTTPWebhookSigningKey,
Description = "The HTTP Webhook Signing Key provided by Mailgun. Newly-created Mailgun accounts will have separate API and Webhook keys.",
DefaultValue = "",
Order = 4,
IsRequired = true )]

[BooleanField( "Track Opens",
Key = AttributeKey.TrackOpens,
Description = "Allow Mailgun to track opens, clicks, and unsubscribes.",
DefaultBooleanValue = true,
Order = 5,
IsRequired = false )]

[BooleanField( "Replace Unsafe Sender",
Key = AttributeKey.ReplaceUnsafeSender,
Description = @"Defaults to ""Yes"". If set to ""No"" Mailgun will allow relaying email ""on behalf of"" regardless of the sender's domain. The safe sender list will still be used for adding a ""Sender"" header.",
DefaultBooleanValue = true,
Order = 6,
IsRequired = false )]

[IntegerField( "Concurrent Send Workers",
Key = AttributeKey.MaxParallelization,
Description = "The maximum number of emails that will be sent concurrently.",
DefaultIntegerValue = 10,
Order = 7,
IsRequired = false )]

#endregion Attributes

[Rock.SystemGuid.EntityTypeGuid( "35E39CA7-9383-421C-BBFA-0A6CC7AF1BAC")]
public class MailgunHttp : EmailTransportComponent, IAsyncTransport
{
#region Keys

private static class AttributeKey
{
public const string BaseURL = "BaseURL";
public const string Resource = "Resource";
public const string Domain = "Domain";
public const string APIKey = "APIKey";
public const string HTTPWebhookSigningKey = "HTTPWebhookSigningKey";
public const string TrackOpens = "TrackOpens";
public const string ReplaceUnsafeSender = "ReplaceUnsafeSender";
public const string MaxParallelization = "MaxParallelization";
}

#endregion Keys

/// <summary>
/// Gets the response returned from the Mailgun API REST call.
/// </summary>
Expand All @@ -68,7 +137,7 @@ public class MailgunHttp : EmailTransportComponent, IAsyncTransport
/// </value>
public override bool CanTrackOpens
{
get { return GetAttributeValue( "TrackOpens" ).AsBoolean( true ); }
get { return GetAttributeValue( AttributeKey.TrackOpens ).AsBoolean( true ); }
}

/// <summary>
Expand All @@ -81,7 +150,7 @@ public int MaxParallelization
{
get
{
return GetAttributeValue( "MaxParallelization" ).AsIntegerOrNull() ?? 10;
return GetAttributeValue( AttributeKey.MaxParallelization ).AsIntegerOrNull() ?? 10;
}
}

Expand Down Expand Up @@ -127,8 +196,8 @@ protected override async Task<EmailSendResponse> SendEmailAsync( RockEmailMessag

var restClient = new RestClient
{
BaseUrl = new Uri( GetAttributeValue( "BaseURL" ) ),
Authenticator = new HttpBasicAuthenticator( "api", GetAttributeValue( "APIKey" ) )
BaseUrl = new Uri( GetAttributeValue( AttributeKey.BaseURL ) ),
Authenticator = new HttpBasicAuthenticator( "api", GetAttributeValue( AttributeKey.APIKey ) )
};

var retriableStatusCode = new List<HttpStatusCode>()
Expand Down Expand Up @@ -165,7 +234,7 @@ protected override async Task<EmailSendResponse> SendEmailAsync( RockEmailMessag
/// <returns></returns>
protected override SafeSenderResult CheckSafeSender( List<string> toEmailAddresses, MailAddress fromEmail, string organizationEmail )
{
if ( GetAttributeValue( "ReplaceUnsafeSender" ).AsBoolean( true ) )
if ( GetAttributeValue( AttributeKey.ReplaceUnsafeSender ).AsBoolean( true ) )
{
return base.CheckSafeSender( toEmailAddresses, fromEmail, organizationEmail );
}
Expand Down Expand Up @@ -195,8 +264,8 @@ private void AddAdditionalHeaders( RestRequest restRequest, Dictionary<string, s

private RestRequest GetRestRequestFromRockEmailMessage( RockEmailMessage rockEmailMessage )
{
var restRequest = new RestRequest( GetAttributeValue( "Resource" ), Method.POST );
restRequest.AddParameter( "domain", GetAttributeValue( "Domain" ), ParameterType.UrlSegment );
var restRequest = new RestRequest( GetAttributeValue( AttributeKey.Resource ), Method.POST );
restRequest.AddParameter( "domain", GetAttributeValue( AttributeKey.Domain ), ParameterType.UrlSegment );

// To
rockEmailMessage.GetRecipients().ForEach( r => restRequest.AddParameter( "to", new MailAddress( r.To, r.Name ).ToString() ) );
Expand Down
176 changes: 176 additions & 0 deletions Rock/Plugin/HotFixes/192_MailgunCopyApiKeyToHttpWebhookSigningKey.cs
@@ -0,0 +1,176 @@
// <copyright>
// Copyright by the Spark Development Network
//
// Licensed under the Rock Community License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.rockrms.com/license
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>

namespace Rock.Plugin.HotFixes
{
/// <summary>
/// Plug-in migration
/// </summary>
/// <seealso cref="Rock.Plugin.Migration" />
[MigrationNumber( 192, "1.14.3" )]
public class MailgunCopyApiKeyToHttpWebhookSigningKey : Migration
{
/// <summary>
/// Operations to be performed during the upgrade process.
/// </summary>
public override void Up()
{
CopyApiKeyToHttpWebhookSigningKey();
}

/// <summary>
/// Operations to be performed during the downgrade process.
/// </summary>
public override void Down()
{
// Down migrations are not yet supported in plug-in migrations.
}

/// <summary>
/// Copies the Mailgun "APIKey" AttributeValue (if present) to a
/// newly-created "HTTPWebhookSigningKey" AttributeValue.
/// </summary>
private void CopyApiKeyToHttpWebhookSigningKey()
{
Sql( @"
DECLARE @EntityTypeId [int] = (SELECT [Id] FROM [EntityType] WHERE [Name] = 'Rock.Communication.Transport.MailgunHttp');
DECLARE @FieldTypeId [int] = (SELECT [Id] FROM [FieldType] WHERE [Guid] = '9C204CD0-1233-41C5-818A-C5DA439445AA'); -- Text
-- Attempt to find the existing APIKey Attribute and AttributeValue.
DECLARE @ApiKeyAttrId [int] = (
SELECT TOP 1 [Id]
FROM [Attribute]
WHERE [EntityTypeId] = @EntityTypeId
AND [FieldTypeId] = @FieldTypeId
AND [Key] = 'APIKey'
);
DECLARE @ApiKeyAttrValue [nvarchar](max) = (
SELECT TOP 1 [Value]
FROM [AttributeValue]
WHERE [AttributeId] = @ApiKeyAttrId
);
-- Only attempt to set the HTTPWebhookSigningKey value if the APIKey value is actually set.
IF @ApiKeyAttrValue IS NOT NULL AND @ApiKeyAttrValue <> ''
BEGIN
-- Ensure the HTTPWebhookSigningKey Attribute exists; create it if not.
DECLARE @WebhookKeyAttrKey [nvarchar](21) = 'HTTPWebhookSigningKey';
DECLARE @WebhookKeyAttrId [int] = (
SELECT [Id]
FROM [Attribute]
WHERE [EntityTypeId] = @EntityTypeId
AND [FieldTypeId] = @FieldTypeId
AND [Key] = @WebhookKeyAttrKey
);
DECLARE @Now [datetime] = (SELECT GETDATE());
IF @WebhookKeyAttrId IS NULL
BEGIN
INSERT INTO [Attribute]
(
[IsSystem]
, [FieldTypeId]
, [EntityTypeId]
, [EntityTypeQualifierColumn]
, [EntityTypeQualifierValue]
, [Key]
, [Name]
, [Description]
, [Order]
, [IsGridColumn]
, [DefaultValue]
, [IsMultiValue]
, [IsRequired]
, [Guid]
, [CreatedDateTime]
, [ModifiedDateTime]
, [IconCssClass]
, [AbbreviatedName]
, [IsDefaultPersistedValueDirty]
)
VALUES
(
0
, @FieldTypeId
, @EntityTypeId
, ''
, ''
, @WebhookKeyAttrKey
, 'HTTP Webhook Signing Key'
, 'The HTTP Webhook Signing Key provided by Mailgun. Newly-created Mailgun accounts will have separate API and Webhook keys.'
, 4
, 0
, ''
, 0
, 1
, NEWID()
, @Now
, @Now
, ''
, 'HTTP Webhook Signing Key'
, 0
);
SET @WebhookKeyAttrId = (SELECT @@IDENTITY);
END
-- If the HTTPWebhookSigningKey happens to already have a value, don't overwrite it.
DECLARE @WebhookKeyAttrValueId [int] = (
SELECT TOP 1 [Id]
FROM [AttributeValue]
WHERE [AttributeId] = @WebhookKeyAttrId
);
DECLARE @WebhookKeyAttrValue [nvarchar](max) = (SELECT [Value] FROM [AttributeValue] WHERE [Id] = @WebhookKeyAttrValueId);
IF @WebhookKeyAttrValueId IS NULL
BEGIN
-- AttributeValue didn't already exist; create it.
INSERT INTO [AttributeValue]
(
[IsSystem]
, [AttributeId]
, [EntityId]
, [Value]
, [Guid]
, [CreatedDateTime]
, [ModifiedDateTime]
)
VALUES
(
0
, @WebhookKeyAttrId
, 0
, @ApiKeyAttrValue
, NEWID()
, @Now
, @Now
);
END
ELSE IF @WebhookKeyAttrValue IS NULL OR @WebhookKeyAttrValue = ''
BEGIN
-- AttributeValue already existed without a value; update it.
UPDATE [AttributeValue]
SET [Value] = @ApiKeyAttrValue
, [ModifiedDateTime]= @Now
WHERE [Id] = @WebhookKeyAttrValueId;
END
END" );
}
}
}
1 change: 1 addition & 0 deletions Rock/Rock.csproj
Expand Up @@ -998,6 +998,7 @@
<Compile Include="Plugin\HotFixes\166_MigrationRollupsForV14_2_0.cs" />
<Compile Include="Plugin\HotFixes\165_EnableTextToGiveMultiAcct.cs" />
<Compile Include="Plugin\HotFixes\159_AddStaffFormsWorkflowCategory.cs" />
<Compile Include="Plugin\HotFixes\192_MailgunCopyApiKeyToHttpWebhookSigningKey.cs" />
<Compile Include="Plugin\HotFixes\HotFixMigrationResource.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
Expand Down

0 comments on commit bd2e772

Please sign in to comment.