From 95e4b3745e0343eb323122b63aed2322b0172ea3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 21:41:36 +0000 Subject: [PATCH 01/11] Bump System.Text.Json in /src/testapps/AspNetFramework Bumps System.Text.Json from 7.0.2 to 8.0.4. --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .../AspNetFramework/AspNetFramework.csproj | 14 ++++++++++---- src/testapps/AspNetFramework/packages.config | 6 +++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/testapps/AspNetFramework/AspNetFramework.csproj b/src/testapps/AspNetFramework/AspNetFramework.csproj index 8d2fc2b..b59f032 100644 --- a/src/testapps/AspNetFramework/AspNetFramework.csproj +++ b/src/testapps/AspNetFramework/AspNetFramework.csproj @@ -42,8 +42,9 @@ 4 - - ..\packages\Microsoft.Bcl.AsyncInterfaces.7.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + ../packages/Microsoft.Bcl.AsyncInterfaces.8.0.0/lib/net462/Microsoft.Bcl.AsyncInterfaces.dll + True ..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.3.6.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll @@ -72,8 +73,13 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - ..\packages\System.Text.Encodings.Web.7.0.0\lib\net462\System.Text.Encodings.Web.dll + + ../packages/System.Text.Encodings.Web.8.0.0/lib/net462/System.Text.Encodings.Web.dll + True + + + ../packages/System.Text.Json.8.0.4/lib/net462/System.Text.Json.dll + True ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll diff --git a/src/testapps/AspNetFramework/packages.config b/src/testapps/AspNetFramework/packages.config index e78ce96..93f6156 100644 --- a/src/testapps/AspNetFramework/packages.config +++ b/src/testapps/AspNetFramework/packages.config @@ -4,7 +4,7 @@ - + @@ -13,8 +13,8 @@ - - + + From 1f155aa13e178bb783a18009b34535e537a58cb2 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:49:21 -0500 Subject: [PATCH 02/11] Update dependencies and target frameworks --- build.ps1 | 1 + .../Twilio.AspNet.Common.csproj | 4 ++-- .../Twilio.AspNet.Core.UnitTests.csproj | 12 +++++----- .../ValidateTwilioRequestTests.cs | 20 +++++++--------- .../Twilio.AspNet.Core.csproj | 22 ++++++++---------- .../Twilio.AspNet.Mvc.UnitTests.csproj | 23 +++++++++++-------- .../Twilio.AspNet.Mvc.csproj | 14 +++++------ version_bump.ps1 | 3 ++- 8 files changed, 47 insertions(+), 52 deletions(-) mode change 100644 => 100755 build.ps1 mode change 100644 => 100755 version_bump.ps1 diff --git a/build.ps1 b/build.ps1 old mode 100644 new mode 100755 index 29ba0bb..5d8c5c2 --- a/build.ps1 +++ b/build.ps1 @@ -1,3 +1,4 @@ +#!/usr/bin/env pwsh $originalLocation = Get-Location function Remove-EntirePath() { diff --git a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj index f59f65b..87a1281 100644 --- a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj +++ b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj @@ -9,7 +9,7 @@ Twilio request classes for handling Twilio webhooks. false Refer to the changelog at https://github.com/twilio-labs/twilio-aspnet/blob/main/CHANGELOG.md - Copyright 2022 (c) Twilio, Inc. All rights reserved. + Copyright 2024 (c) Twilio, Inc. All rights reserved. twilio;twiml;sms;voice;telephony;phone;aspnet Apache-2.0 https://github.com/twilio/twilio-aspnet @@ -30,7 +30,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj index ae6eaf2..ea52a91 100644 --- a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj +++ b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj @@ -1,15 +1,15 @@ - net7.0 + net9.0 false disable - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs b/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs index 35fb00b..da3fa26 100644 --- a/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs @@ -53,13 +53,11 @@ public async Task Validate_Request_Successfully(Type type) { "From", "+1234567890" }, { "Body", "Ahoy!" } }); - c.Request.Headers.Add( - "X-Twilio-Signature", ValidationHelper.CalculateSignature( + c.Request.Headers["X-Twilio-Signature"] = ValidationHelper.CalculateSignature( $"{ValidTwilioOptions.RequestValidation.BaseUrlOverride.TrimEnd('/')}{c.Request.Path}", ValidTwilioOptions.RequestValidation.AuthToken, c.Request.Form - ) - ); + ); }); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); @@ -87,7 +85,7 @@ public async Task Validate_Request_Forbid(Type type) { "From", "+1234567890" }, { "Body", "Ahoy!" } }); - c.Request.Headers.Add("X-Twilio-Signature", "sldflsjf"); + c.Request.Headers["X-Twilio-Signature"] = "sldflsjf"; }); Assert.Equal(StatusCodes.Status403Forbidden, context.Response.StatusCode); @@ -121,13 +119,12 @@ public async Task Validate_Request_With_ReloadOnChange(Type type) { "From", "+1234567890" }, { "Body", "Ahoy!" } }); - c.Request.Headers.Add( - "X-Twilio-Signature", ValidationHelper.CalculateSignature( + c.Request.Headers["X-Twilio-Signature"] = ValidationHelper.CalculateSignature( $"{ValidTwilioOptions.RequestValidation.BaseUrlOverride.TrimEnd('/')}{c.Request.Path}", ValidTwilioOptions.RequestValidation.AuthToken, c.Request.Form ) - ); +; }); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); @@ -158,13 +155,12 @@ public async Task Validate_Request_With_ReloadOnChange(Type type) { "From", "+1234567890" }, { "Body", "Ahoy!" } }); - c.Request.Headers.Add( - "X-Twilio-Signature", ValidationHelper.CalculateSignature( + c.Request.Headers["X-Twilio-Signature"] = ValidationHelper.CalculateSignature( $"{updatedOptions.RequestValidation.BaseUrlOverride.TrimEnd('/')}{c.Request.Path}", updatedOptions.RequestValidation.AuthToken, c.Request.Form ) - ); +; }); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); @@ -190,7 +186,7 @@ public async Task Validate_Request_With_ReloadOnChange(Type type) c.Request.Host = new HostString("localhost"); c.Request.Method = HttpMethods.Post; c.Request.Path = "/sms"; - c.Request.Headers.Add("X-Twilio-Signature", "sdfsjf"); + c.Request.Headers["X-Twilio-Signature"] = "sdfsjf"; }); Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode); diff --git a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj index 426753d..6b6b07e 100644 --- a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj +++ b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj @@ -1,6 +1,6 @@  - net6.0;net7.0 + net7.0;net8.0;net9.0 Library 0.0.0-alpha Twilio.AspNet.Core @@ -9,7 +9,7 @@ Twilio helper library for ASP.NET Core false Refer to the changelog at https://github.com/twilio-labs/twilio-aspnet/blob/main/CHANGELOG.md - Copyright 2022 (c) Twilio, Inc. All rights reserved. + Copyright 2024 (c) Twilio, Inc. All rights reserved. twilio;twiml;sms;voice;telephony;phone;aspnet Apache-2.0 https://github.com/twilio-labs/twilio-aspnet @@ -21,31 +21,27 @@ true true snupkg - $([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net7.0')) true - + - + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + <_Parameter1>Twilio.AspNet.Core.UnitTests - - - \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj index 74ad47f..35c1c04 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj +++ b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj @@ -25,20 +25,23 @@ - - - + + + - - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj index f2403a6..5d517c6 100644 --- a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj +++ b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj @@ -10,7 +10,7 @@ Twilio helper library for ASP.NET MVC on .NET Framework. false Refer to the changelog at https://github.com/twilio-labs/twilio-aspnet/blob/main/CHANGELOG.md - Copyright 2022 (c) Twilio, Inc. All rights reserved. + Copyright 2024 (c) Twilio, Inc. All rights reserved. twilio;twiml;sms;voice;telephony;phone;aspnet Apache-2.0 https://github.com/twilio-labs/twilio-aspnet @@ -29,9 +29,7 @@ - - ..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.1\System.Configuration.dll - + @@ -49,14 +47,14 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all diff --git a/version_bump.ps1 b/version_bump.ps1 old mode 100644 new mode 100755 index 415ecd0..7692645 --- a/version_bump.ps1 +++ b/version_bump.ps1 @@ -1,6 +1,7 @@ +#!/usr/bin/env pwsh function updateStandardCsproj() { Param($inputFileNameRelative, $targetVersion) - + Write-Host "Updating : $inputFileNameRelative" $inputFileName = Join-Path $PSScriptRoot $inputFileNameRelative From 9c138e228980f955a3976b933b9b13b8196c01e3 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:59:51 -0500 Subject: [PATCH 03/11] Update GH actions --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/release.yml | 6 +++--- src/Twilio.AspNet.sln | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d5e5b8..dd0f3b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,12 +42,12 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x + dotnet-version: 9.0.x dotnet-quality: ga - name: Update project versions @@ -80,7 +80,7 @@ jobs: shell: pwsh - name: (Twilio.AspNet.Common) Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Twilio.AspNet.Common NuGet Package path: | @@ -174,7 +174,7 @@ jobs: shell: pwsh - name: (Twilio.AspNet.Mvc) Upload Artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Twilio.AspNet.Mvc NuGet Package path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aea65e4..1c63116 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,17 +33,17 @@ jobs: runs-on: ubuntu-latest needs: [build] steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download Twilio.AspNet.Common NuGet Package with: name: Twilio.AspNet.Common NuGet Package - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download Twilio.AspNet.Core NuGet Package with: name: Twilio.AspNet.Core NuGet Package - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 name: Download Twilio.AspNet.Mvc NuGet Package with: name: Twilio.AspNet.Mvc NuGet Package diff --git a/src/Twilio.AspNet.sln b/src/Twilio.AspNet.sln index e5d2ada..386ed3b 100644 --- a/src/Twilio.AspNet.sln +++ b/src/Twilio.AspNet.sln @@ -31,6 +31,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Twilio.AspNet.Mvc.UnitTests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Twilio.AspNet.Core.UnitTests", "Twilio.AspNet.Core.UnitTests\Twilio.AspNet.Core.UnitTests.csproj", "{B3E732C9-27EF-4E96-B620-5B5DA57D9AD3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{14C361F2-74BC-4D62-AC32-8B640D28A94F}" + ProjectSection(SolutionItems) = preProject + ..\.github\workflows\release.yml = ..\.github\workflows\release.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{5001EA6E-22E1-454C-8746-D76CD562D89B}" + ProjectSection(SolutionItems) = preProject + ..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,4 +74,8 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7D0F9171-129A-4B05-809E-F501DBC23197} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {14C361F2-74BC-4D62-AC32-8B640D28A94F} = {339A75DF-3315-4CF3-9FD9-DAF1B0DB50B2} + {5001EA6E-22E1-454C-8746-D76CD562D89B} = {14C361F2-74BC-4D62-AC32-8B640D28A94F} + EndGlobalSection EndGlobal From 81833ccf4691aceb1443893ac19a38e5923c7514 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Sat, 23 Nov 2024 00:35:22 -0500 Subject: [PATCH 04/11] Use PolySharp to use latest C# features on older .NET version + do recommended refactorings --- src/Twilio.AspNet.Common/SmsRequest.cs | 77 ++-- .../SmsStatusCallbackRequest.cs | 69 ++-- .../StatusCallbackRequest.cs | 27 +- .../Twilio.AspNet.Common.csproj | 5 + src/Twilio.AspNet.Common/TwilioRequest.cs | 113 +++--- src/Twilio.AspNet.Common/VoiceRequest.cs | 281 +++++++------- .../ContextMocks.cs | 24 +- .../RequestValidationHelperTests.cs | 14 +- .../Twilio.AspNet.Core.UnitTests.csproj | 4 + .../TwilioClientOptionsTests.cs | 19 +- .../TwilioRequestValidationOptionsTests.cs | 20 +- ...ValidationDependencyInjectionExtensions.cs | 161 ++++---- .../RequestValidationHelper.cs | 270 +++++++------ src/Twilio.AspNet.Core/TwiMLExtensions.cs | 67 ++-- src/Twilio.AspNet.Core/TwiMLResult.cs | 69 ++-- .../Twilio.AspNet.Core.csproj | 5 + ...ilioClientDependencyInjectionExtensions.cs | 364 +++++++++--------- src/Twilio.AspNet.Core/TwilioController.cs | 73 ++-- .../TwilioControllerExtensions.cs | 77 ++-- src/Twilio.AspNet.Core/TwilioOptions.cs | 59 ++- .../ValidateRequestAttribute.cs | 35 +- .../ValidateTwilioRequestMiddleware.cs | 57 ++- .../ContextMocks.cs | 95 +++-- .../RequestValidationHelperTests.cs | 95 ++--- .../TwiMLResultTests.cs | 113 +++--- .../Twilio.AspNet.Mvc.UnitTests.csproj | 5 + .../RequestValidationHelper.cs | 83 ++-- src/Twilio.AspNet.Mvc/TwiMLResult.cs | 55 ++- .../Twilio.AspNet.Mvc.csproj | 5 + src/Twilio.AspNet.Mvc/TwilioConfiguration.cs | 51 ++- src/Twilio.AspNet.Mvc/TwilioController.cs | 75 ++-- .../TwilioControllerExtensions.cs | 79 ++-- src/Twilio.AspNet.Mvc/TwilioUriHelper.cs | 11 +- .../ValidateRequestAttribute.cs | 105 +++-- 34 files changed, 1324 insertions(+), 1338 deletions(-) diff --git a/src/Twilio.AspNet.Common/SmsRequest.cs b/src/Twilio.AspNet.Common/SmsRequest.cs index b5a5c46..e24f877 100644 --- a/src/Twilio.AspNet.Common/SmsRequest.cs +++ b/src/Twilio.AspNet.Common/SmsRequest.cs @@ -1,49 +1,48 @@ -namespace Twilio.AspNet.Common +namespace Twilio.AspNet.Common; + +/// +/// This class can be used as the parameter on your SMS action. Incoming parameters will be bound here. +/// +/// https://www.twilio.com/docs/messaging/guides/webhook-request +public class SmsRequest : TwilioRequest { /// - /// This class can be used as the parameter on your SMS action. Incoming parameters will be bound here. + /// A 34 character unique identifier for the message. May be used to later retrieve this message from the REST API. /// - /// https://www.twilio.com/docs/messaging/guides/webhook-request - public class SmsRequest : TwilioRequest - { - /// - /// A 34 character unique identifier for the message. May be used to later retrieve this message from the REST API. - /// - public string MessageSid { get; set; } + public string MessageSid { get; set; } - /// - /// Same value as MessageSid. Deprecated and included for backward compatibility. - /// - public string SmsSid { get; set; } + /// + /// Same value as MessageSid. Deprecated and included for backward compatibility. + /// + public string SmsSid { get; set; } - /// - /// The text body of the SMS message. Up to 160 characters long - /// - public string Body { get; set; } + /// + /// The text body of the SMS message. Up to 160 characters long + /// + public string Body { get; set; } - /// - /// The status of the message - /// - public string MessageStatus { get; set; } + /// + /// The status of the message + /// + public string MessageStatus { get; set; } - /// - /// The message OptOut type - /// - public string OptOutType { get; set; } + /// + /// The message OptOut type + /// + public string OptOutType { get; set; } - /// - /// A unique identifier of the messaging service - /// - public string MessagingServiceSid { get; set; } + /// + /// A unique identifier of the messaging service + /// + public string MessagingServiceSid { get; set; } - /// - /// The number of media items associated with your message - /// - public int NumMedia { get; set; } + /// + /// The number of media items associated with your message + /// + public int NumMedia { get; set; } - /// - /// The number of media items associated with a "Click to WhatsApp" advertisement. - /// - public int ReferralNumMedia { get; set; } - } -} + /// + /// The number of media items associated with a "Click to WhatsApp" advertisement. + /// + public int ReferralNumMedia { get; set; } +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs b/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs index 2736dfc..0de580c 100644 --- a/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs +++ b/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs @@ -1,41 +1,40 @@ -namespace Twilio.AspNet.Common +namespace Twilio.AspNet.Common; + +public class SmsStatusCallbackRequest: SmsRequest { - public class SmsStatusCallbackRequest: SmsRequest - { - /// - /// The error code (if any) associated with your message. If your message - /// status is failed or undelivered, the ErrorCode can give you more information - /// about the failure. If the message was delivered successfully, no ErrorCode - /// will be present. Find the possible values here: - /// https://www.twilio.com/docs/sms/api/message-resource#delivery-related-errors - /// - public string ErrorCode { get; set; } + /// + /// The error code (if any) associated with your message. If your message + /// status is failed or undelivered, the ErrorCode can give you more information + /// about the failure. If the message was delivered successfully, no ErrorCode + /// will be present. Find the possible values here: + /// https://www.twilio.com/docs/sms/api/message-resource#delivery-related-errors + /// + public string ErrorCode { get; set; } - /// - /// The Installed Channel SID (found on the Channel detail page) that was - /// used to send this message. Only present if the message was sent using a - /// Channel. - /// - public string ChannelInstallSid { get; set; } + /// + /// The Installed Channel SID (found on the Channel detail page) that was + /// used to send this message. Only present if the message was sent using a + /// Channel. + /// + public string ChannelInstallSid { get; set; } - /// - /// The Error message returned by the underlying Channel if Message delivery - /// failed. Only present if the message was sent using a Channel and message - /// delivery failed. - /// - public string ChannelStatusMessage { get; set; } + /// + /// The Error message returned by the underlying Channel if Message delivery + /// failed. Only present if the message was sent using a Channel and message + /// delivery failed. + /// + public string ChannelStatusMessage { get; set; } - /// - /// Channel specific prefix that allows you to identify which channel this - /// message was sent over. - /// - public string ChannelPrefix { get; set; } + /// + /// Channel specific prefix that allows you to identify which channel this + /// message was sent over. + /// + public string ChannelPrefix { get; set; } - /// - /// Contains post-delivery events. If the Channel supports Read receipts, this - /// parameter will be included with a value of READ after the user has read - /// the message. Currently supported only for WhatsApp. - /// - public string EventType { get; set; } - } + /// + /// Contains post-delivery events. If the Channel supports Read receipts, this + /// parameter will be included with a value of READ after the user has read + /// the message. Currently supported only for WhatsApp. + /// + public string EventType { get; set; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/StatusCallbackRequest.cs b/src/Twilio.AspNet.Common/StatusCallbackRequest.cs index d42edb1..5b387c1 100644 --- a/src/Twilio.AspNet.Common/StatusCallbackRequest.cs +++ b/src/Twilio.AspNet.Common/StatusCallbackRequest.cs @@ -1,18 +1,17 @@ -namespace Twilio.AspNet.Common +namespace Twilio.AspNet.Common; + +/// +/// This class can be used as the parameter on your StatusCallback action. Incoming parameters will be bound here. +/// +/// https://www.twilio.com/docs/voice/twiml#ending-the-call-callback-requests +public class StatusCallbackRequest : VoiceRequest { /// - /// This class can be used as the parameter on your StatusCallback action. Incoming parameters will be bound here. + /// The duration in seconds of the just-completed call. /// - /// https://www.twilio.com/docs/voice/twiml#ending-the-call-callback-requests - public class StatusCallbackRequest : VoiceRequest - { - /// - /// The duration in seconds of the just-completed call. - /// - public float CallDuration { get; set; } + public float CallDuration { get; set; } - public string Called { get; set; } - public string Caller { get; set; } - public float Duration { get; set; } - } -} + public string Called { get; set; } + public string Caller { get; set; } + public float Duration { get; set; } +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj index 87a1281..62ca42c 100644 --- a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj +++ b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj @@ -1,6 +1,7 @@ netstandard2.0 + 13 Library 0.0.0-alpha Twilio.AspNet.Common @@ -34,5 +35,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/TwilioRequest.cs b/src/Twilio.AspNet.Common/TwilioRequest.cs index 6e02eaf..4a9d46b 100644 --- a/src/Twilio.AspNet.Common/TwilioRequest.cs +++ b/src/Twilio.AspNet.Common/TwilioRequest.cs @@ -1,69 +1,68 @@ -namespace Twilio.AspNet.Common -{ +namespace Twilio.AspNet.Common; + +/// +/// Base class for mapping incoming request parameters into a strongly typed object +/// +public abstract class TwilioRequest +{ /// - /// Base class for mapping incoming request parameters into a strongly typed object + /// Your Twilio account id. It is 34 characters long, and always starts with the letters AC /// - public abstract class TwilioRequest - { - /// - /// Your Twilio account id. It is 34 characters long, and always starts with the letters AC - /// - public string AccountSid { get; set; } + public string AccountSid { get; set; } - /// - /// The phone number or client identifier of the party that initiated the call - /// - /// - /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call from a client named 'tommy', the From parameter will be client:tommy. - /// - public string From { get; set; } + /// + /// The phone number or client identifier of the party that initiated the call + /// + /// + /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call from a client named 'tommy', the From parameter will be client:tommy. + /// + public string From { get; set; } - /// - /// The phone number or client identifier of the called party - /// - /// - /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call to a client named 'jenny', the To parameter will be client:jenny. - /// - public string To { get; set; } + /// + /// The phone number or client identifier of the called party + /// + /// + /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call to a client named 'jenny', the To parameter will be client:jenny. + /// + public string To { get; set; } - /// - /// The city of the caller - /// - public string FromCity { get; set; } + /// + /// The city of the caller + /// + public string FromCity { get; set; } - /// - /// The state or province of the caller - /// - public string FromState { get; set; } + /// + /// The state or province of the caller + /// + public string FromState { get; set; } - /// - /// The postal code of the caller - /// - public string FromZip { get; set; } + /// + /// The postal code of the caller + /// + public string FromZip { get; set; } - /// - /// The country of the caller - /// - public string FromCountry { get; set; } + /// + /// The country of the caller + /// + public string FromCountry { get; set; } - /// - /// The city of the called party - /// - public string ToCity { get; set; } + /// + /// The city of the called party + /// + public string ToCity { get; set; } - /// - /// The state or province of the called party - /// - public string ToState { get; set; } + /// + /// The state or province of the called party + /// + public string ToState { get; set; } - /// - /// The postal code of the called party - /// - public string ToZip { get; set; } + /// + /// The postal code of the called party + /// + public string ToZip { get; set; } - /// - /// The country of the called party - /// - public string ToCountry { get; set; } - } -} + /// + /// The country of the called party + /// + public string ToCountry { get; set; } +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/VoiceRequest.cs b/src/Twilio.AspNet.Common/VoiceRequest.cs index fa86399..2b4f682 100644 --- a/src/Twilio.AspNet.Common/VoiceRequest.cs +++ b/src/Twilio.AspNet.Common/VoiceRequest.cs @@ -1,170 +1,169 @@ -namespace Twilio.AspNet.Common +namespace Twilio.AspNet.Common; + +/// +/// This class can be used as the parameter on your voice action. Incoming parameters will be bound here. +/// +/// https://www.twilio.com/docs/usage/webhooks/voice-webhooks +public class VoiceRequest : TwilioRequest { /// - /// This class can be used as the parameter on your voice action. Incoming parameters will be bound here. - /// - /// https://www.twilio.com/docs/usage/webhooks/voice-webhooks - public class VoiceRequest : TwilioRequest - { - /// - /// A unique identifier for this call, generated by Twilio - /// - public string CallSid { get; set; } - - /// - /// A descriptive status for the call. The value is one of queued, ringing, in-progress, completed, busy, failed or no-answer - /// - public string CallStatus { get; set; } - - /// - /// The version of the Twilio API used to handle this call. For incoming calls, this is determined by the API version set on the called number. For outgoing calls, this is the API version used by the outgoing call's REST API request - /// - public string ApiVersion { get; set; } + /// A unique identifier for this call, generated by Twilio + /// + public string CallSid { get; set; } + + /// + /// A descriptive status for the call. The value is one of queued, ringing, in-progress, completed, busy, failed or no-answer + /// + public string CallStatus { get; set; } + + /// + /// The version of the Twilio API used to handle this call. For incoming calls, this is determined by the API version set on the called number. For outgoing calls, this is the API version used by the outgoing call's REST API request + /// + public string ApiVersion { get; set; } - /// - /// Indicates the direction of the call. In most cases this will be inbound, but if you are using Dial it will be outbound-dial - /// - public string Direction { get; set; } - - /// - /// This parameter is set only when Twilio receives a forwarded call, but its value depends on the caller's carrier including information when forwarding. Not all carriers support passing this information - /// - public string ForwardedFrom { get; set; } - - /// - /// This parameter is set when the IncomingPhoneNumber that received the call has had its VoiceCallerIdLookup value set to true. - /// - public string CallerName { get; set; } + /// + /// Indicates the direction of the call. In most cases this will be inbound, but if you are using Dial it will be outbound-dial + /// + public string Direction { get; set; } + + /// + /// This parameter is set only when Twilio receives a forwarded call, but its value depends on the caller's carrier including information when forwarding. Not all carriers support passing this information + /// + public string ForwardedFrom { get; set; } + + /// + /// This parameter is set when the IncomingPhoneNumber that received the call has had its VoiceCallerIdLookup value set to true. + /// + public string CallerName { get; set; } - /// - /// A unique identifier for the call that created this leg. This parameter is not passed if this is the first leg of a call. - /// - public string ParentCallSid { get; set; } + /// + /// A unique identifier for the call that created this leg. This parameter is not passed if this is the first leg of a call. + /// + public string ParentCallSid { get; set; } - /// A token string needed to invoke a forwarded call. - public string CallToken { get; set; } + /// A token string needed to invoke a forwarded call. + public string CallToken { get; set; } - #region Gather & Record Parameters + #region Gather & Record Parameters - /// - /// When used with the Gather verb, the digits the caller pressed, excluding the finishOnKey digit if used. - /// When used with the Record verb, the key (if any) pressed to end the recording or 'hangup' if the caller hung up - /// - public string Digits { get; set; } + /// + /// When used with the Gather verb, the digits the caller pressed, excluding the finishOnKey digit if used. + /// When used with the Record verb, the key (if any) pressed to end the recording or 'hangup' if the caller hung up + /// + public string Digits { get; set; } - /// - /// When used with the Gather verb, the transcribed result of the speech - /// - public string SpeechResult { get; set; } + /// + /// When used with the Gather verb, the transcribed result of the speech + /// + public string SpeechResult { get; set; } - /// - /// When used with the Gather verb, a confidence score between 0.0 and 1.0 respectively. - /// A higher confidence score means a greater likelihood that recognized words are correct. - /// - public float? Confidence { get; set; } + /// + /// When used with the Gather verb, a confidence score between 0.0 and 1.0 respectively. + /// A higher confidence score means a greater likelihood that recognized words are correct. + /// + public float? Confidence { get; set; } - /// - /// The URL of the recorded audio. When the result of a transcription, the URL for the transcription's source recording resource. - /// - public string RecordingUrl { get; set; } - - /// - /// The status of the recording. Possible values are: completed, failed. - /// - public string RecordingStatus { get; set; } - - /// - /// The duration of the recorded audio (in seconds) - /// - public string RecordingDuration { get; set; } - - /// - /// The number of channels in the final recording file as an integer. - /// - public int? RecordingChannels { get; set; } - - /// - /// The source of the recorded audio. - /// - public string RecordingSource { get; set; } - - /// - /// The key used to submit the digits - /// - public string FinishedOnKey { get; set; } + /// + /// The URL of the recorded audio. When the result of a transcription, the URL for the transcription's source recording resource. + /// + public string RecordingUrl { get; set; } + + /// + /// The status of the recording. Possible values are: completed, failed. + /// + public string RecordingStatus { get; set; } + + /// + /// The duration of the recorded audio (in seconds) + /// + public string RecordingDuration { get; set; } + + /// + /// The number of channels in the final recording file as an integer. + /// + public int? RecordingChannels { get; set; } + + /// + /// The source of the recorded audio. + /// + public string RecordingSource { get; set; } + + /// + /// The key used to submit the digits + /// + public string FinishedOnKey { get; set; } - #endregion + #endregion - #region Transcription Parameters + #region Transcription Parameters - /// - /// The unique 34 character ID of the transcription - /// - public string TranscriptionSid { get; set; } + /// + /// The unique 34 character ID of the transcription + /// + public string TranscriptionSid { get; set; } - /// - /// Contains the text of the transcription - /// - public string TranscriptionText { get; set; } + /// + /// Contains the text of the transcription + /// + public string TranscriptionText { get; set; } - /// - /// The status of the transcription attempt: either 'completed' or 'failed' - /// - public string TranscriptionStatus { get; set; } + /// + /// The status of the transcription attempt: either 'completed' or 'failed' + /// + public string TranscriptionStatus { get; set; } - /// - /// The URL for the transcription's REST API resource - /// - public string TranscriptionUrl { get; set; } + /// + /// The URL for the transcription's REST API resource + /// + public string TranscriptionUrl { get; set; } - /// - /// The unique 34 character ID of the recording from which the transcription was generated - /// - public string RecordingSid { get; set; } + /// + /// The unique 34 character ID of the recording from which the transcription was generated + /// + public string RecordingSid { get; set; } - #endregion + #endregion - #region Dial Parameters + #region Dial Parameters - /// - /// The outcome of the Dial attempt. See the DialCallStatus section below for details - /// - public string DialCallStatus { get; set; } + /// + /// The outcome of the Dial attempt. See the DialCallStatus section below for details + /// + public string DialCallStatus { get; set; } - /// - /// The call sid of the new call leg. This parameter is not sent after dialing a conference - /// - public string DialCallSid { get; set; } + /// + /// The call sid of the new call leg. This parameter is not sent after dialing a conference + /// + public string DialCallSid { get; set; } - /// - /// The duration in seconds of the dialed call. This parameter is not sent after dialing a conference - /// - public string DialCallDuration { get; set; } + /// + /// The duration in seconds of the dialed call. This parameter is not sent after dialing a conference + /// + public string DialCallDuration { get; set; } - #endregion + #endregion - #region SIP Parameters + #region SIP Parameters - /// - /// The Twilio SIP Domain to which the INVITE was sent - /// - public string SipDomain { get; set; } + /// + /// The Twilio SIP Domain to which the INVITE was sent + /// + public string SipDomain { get; set; } - /// - /// The username given when authenticating the request, if Credential List is the authentication method. - /// - public string SipUsername { get; set; } + /// + /// The username given when authenticating the request, if Credential List is the authentication method. + /// + public string SipUsername { get; set; } - /// - /// The Call-Id of the incoming INVITE - /// - public string SipCallId { get; set; } + /// + /// The Call-Id of the incoming INVITE + /// + public string SipCallId { get; set; } - /// - /// The IP Address the incoming INVITE came from. - /// - public string SipSourceIp { get; set; } + /// + /// The IP Address the incoming INVITE came from. + /// + public string SipSourceIp { get; set; } - #endregion - } -} + #endregion +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs b/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs index a3bd032..0c2eb26 100644 --- a/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs +++ b/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs @@ -35,29 +35,27 @@ public ContextMocks(string urlOverride, bool isLocal, FormCollection form = null Request.Setup(x => x.Headers).Returns(headers); Request.Setup(x => x.HttpContext).Returns(HttpContext.Object); - var uri = new Uri(ContextMocks.fakeUrl); + var uri = new Uri(FakeUrl); Request.Setup(x => x.QueryString).Returns(new QueryString(uri.Query)); Request.Setup(x => x.Scheme).Returns(uri.Scheme); Request.Setup(x => x.Host).Returns(new HostString(uri.Host)); Request.Setup(x => x.Path).Returns(new PathString(uri.AbsolutePath)); - if (form != null) - { - Request.Setup(x => x.Method).Returns("POST"); - Request.Setup(x => x.Form).Returns(form); - Request.Setup(x => x.ReadFormAsync(new CancellationToken())) - .Returns(() => Task.FromResult((IFormCollection)form)); - Request.Setup(x => x.HasFormContentType).Returns(true); - } + if (form == null) return; + Request.Setup(x => x.Method).Returns("POST"); + Request.Setup(x => x.Form).Returns(form); + Request.Setup(x => x.ReadFormAsync(new CancellationToken())) + .Returns(() => Task.FromResult(form)); + Request.Setup(x => x.HasFormContentType).Returns(true); } - public static string fakeUrl = "https://api.example.com/webhook"; - public static string fakeAuthToken = "thisisafakeauthtoken"; + public const string FakeUrl = "https://api.example.com/webhook"; + public const string FakeAuthToken = "thisisafakeauthtoken"; private static string CalculateSignature(string urlOverride, FormCollection form) => ValidationHelper.CalculateSignature( - string.IsNullOrEmpty(urlOverride) ? fakeUrl : urlOverride, - fakeAuthToken, + string.IsNullOrEmpty(urlOverride) ? FakeUrl : urlOverride, + FakeAuthToken, form ); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs b/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs index 3ba40c2..fc21e29 100644 --- a/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs @@ -30,7 +30,7 @@ public void TestNoLocalDueToProxy() public void TestNoLocal() { var fakeContext = new ContextMocks(true).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token", false); + var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token"); Assert.False(result); } @@ -39,7 +39,7 @@ public void TestNoLocal() public void TestNoForm() { var fakeContext = new ContextMocks(true).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, false); + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken); Assert.True(result); } @@ -52,7 +52,7 @@ public void TestBadForm() contextMocks.Request.Setup(x => x.Method).Returns("POST"); contextMocks.Request.Setup(x => x.Form).Throws(new Exception("poof!")); - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, false); + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken); Assert.True(result); } @@ -62,8 +62,7 @@ public void TestUrlOverrideFail() { var fakeContext = new ContextMocks(true).HttpContext.Object; var result = - RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, "https://example.com/", - false); + RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken, "https://example.com/"); Assert.False(result); } @@ -73,8 +72,7 @@ public void TestUrlOverride() { var fakeContext = new ContextMocks("https://example.com/", true).HttpContext.Object; var result = - RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, "https://example.com/", - false); + RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken, "https://example.com/"); Assert.True(result); } @@ -88,7 +86,7 @@ public void TestForm() { "key2", "value2" } }); var fakeContext = new ContextMocks(true, form).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, false); + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken); Assert.True(result); } diff --git a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj index ea52a91..d20db32 100644 --- a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj +++ b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj @@ -8,6 +8,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs index 34176b1..5d7c150 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs @@ -81,11 +81,10 @@ public void AddTwilioClient_Should_Fallback_To_AuthToken() { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), new KeyValuePair("Twilio:Client:AccountSid", ValidTwilioOptions.Client.AccountSid) - }) + ]) .Build(); serviceCollection.AddSingleton(configuration); @@ -164,11 +163,10 @@ public void AddTwilioClient_AuthToken_Without_Config_Should_Sanitize_Options() { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), - new KeyValuePair("Twilio:Client:AuthToken", "AuthToken"), - }).Build(); + new KeyValuePair("Twilio:Client:AuthToken", "AuthToken") + ]).Build(); serviceCollection.AddSingleton(configuration); serviceCollection.AddTwilioClient(); @@ -187,12 +185,11 @@ public void AddTwilioClient_ApiKey_Without_Config_Should_Sanitize_Options() { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), new KeyValuePair("Twilio:Client:ApiKeySid", "ApiKeySid"), - new KeyValuePair("Twilio:Client:ApiKeySecret", "ApiKeySecret"), - }).Build(); + new KeyValuePair("Twilio:Client:ApiKeySecret", "ApiKeySecret") + ]).Build(); serviceCollection.AddSingleton(configuration); serviceCollection.AddTwilioClient(); diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs index 0bf565e..0ff8cf0 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs @@ -50,8 +50,7 @@ public void AddTwilioRequestValidation_From_Configuration_Should_Match_Configura { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), new KeyValuePair( "Twilio:RequestValidation:AuthToken", ValidTwilioOptions.RequestValidation.AuthToken), @@ -59,7 +58,7 @@ public void AddTwilioRequestValidation_From_Configuration_Should_Match_Configura "Twilio:RequestValidation:BaseUrlOverride", ValidTwilioOptions.RequestValidation.BaseUrlOverride), new KeyValuePair( "Twilio:RequestValidation:AllowLocal", ValidTwilioOptions.RequestValidation.AllowLocal.ToString()) - }) + ]) .Build(); serviceCollection.AddSingleton(configuration); @@ -79,15 +78,14 @@ public void AddTwilioRequestValidation_From_ConfigurationSection_Should_Match_Co { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair( "Twilio:AuthToken", ValidTwilioOptions.RequestValidation.AuthToken), new KeyValuePair( "Twilio:BaseUrlOverride", ValidTwilioOptions.RequestValidation.BaseUrlOverride), new KeyValuePair( "Twilio:AllowLocal", ValidTwilioOptions.RequestValidation.AllowLocal.ToString()) - }) + ]) .Build(); serviceCollection.AddSingleton(configuration); @@ -122,10 +120,9 @@ public void AddTwilioRequestValidation_Should_Fallback_To_AuthToken() { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken) - }) + ]) .Build(); serviceCollection.AddSingleton(configuration); @@ -158,11 +155,10 @@ public void AddTwilioRequestValidation_Without_AuthToken_Should_Throw() { var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() - .AddInMemoryCollection(new[] - { + .AddInMemoryCollection([ new KeyValuePair("Twilio", null), new KeyValuePair("Twilio:RequestValidation:AuthToken", null) - }).Build(); + ]).Build(); serviceCollection.AddSingleton(configuration); serviceCollection.AddTwilioRequestValidation(); diff --git a/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs b/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs index fddf5db..6d527ab 100644 --- a/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs +++ b/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs @@ -3,101 +3,100 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +public static class RequestValidationDependencyInjectionExtensions { - public static class RequestValidationDependencyInjectionExtensions + public static IServiceCollection AddTwilioRequestValidation(this IServiceCollection services) { - public static IServiceCollection AddTwilioRequestValidation(this IServiceCollection services) + var optionsBuilder = services.AddOptions(); + optionsBuilder.Configure((options, config) => { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Configure((options, config) => + var twilioSection = config.GetSection("Twilio"); + if (twilioSection.Exists() == false) { - var twilioSection = config.GetSection("Twilio"); - if (twilioSection.Exists() == false) - { - throw new Exception("Twilio options not configured."); - } + throw new Exception("Twilio options not configured."); + } - var requestValidationSection = config.GetSection("Twilio:RequestValidation"); - requestValidationSection.Bind(options); + var requestValidationSection = config.GetSection("Twilio:RequestValidation"); + requestValidationSection.Bind(options); - var authTokenFallback = twilioSection["AuthToken"]; - if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) - options.AuthToken = authTokenFallback; - }); - optionsBuilder.Services.AddSingleton< - IOptionsChangeTokenSource, - ConfigurationChangeTokenSource - >(); - Sanitize(optionsBuilder); - Validate(optionsBuilder); - return services; - } + var authTokenFallback = twilioSection["AuthToken"]; + if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) + options.AuthToken = authTokenFallback; + }); + optionsBuilder.Services.AddSingleton< + IOptionsChangeTokenSource, + ConfigurationChangeTokenSource + >(); + Sanitize(optionsBuilder); + Validate(optionsBuilder); + return services; + } - public static IServiceCollection AddTwilioRequestValidation( - this IServiceCollection services, - IConfiguration namedConfigurationSection - ) - { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Bind(namedConfigurationSection); - Validate(optionsBuilder); - Sanitize(optionsBuilder); - return services; - } + public static IServiceCollection AddTwilioRequestValidation( + this IServiceCollection services, + IConfiguration namedConfigurationSection + ) + { + var optionsBuilder = services.AddOptions(); + optionsBuilder.Bind(namedConfigurationSection); + Validate(optionsBuilder); + Sanitize(optionsBuilder); + return services; + } - public static IServiceCollection AddTwilioRequestValidation( - this IServiceCollection services, - Action configureOptions - ) - => AddTwilioRequestValidation(services, (_, options) => configureOptions(options)); + public static IServiceCollection AddTwilioRequestValidation( + this IServiceCollection services, + Action configureOptions + ) + => AddTwilioRequestValidation(services, (_, options) => configureOptions(options)); - public static IServiceCollection AddTwilioRequestValidation( - this IServiceCollection services, - Action configureOptions - ) - { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Configure((options, provider) => configureOptions(provider, options)); - Sanitize(optionsBuilder); - Validate(optionsBuilder); - return services; - } + public static IServiceCollection AddTwilioRequestValidation( + this IServiceCollection services, + Action configureOptions + ) + { + var optionsBuilder = services.AddOptions(); + optionsBuilder.Configure((options, provider) => configureOptions(provider, options)); + Sanitize(optionsBuilder); + Validate(optionsBuilder); + return services; + } - public static IServiceCollection AddTwilioRequestValidation( - this IServiceCollection services, - TwilioRequestValidationOptions options - ) + public static IServiceCollection AddTwilioRequestValidation( + this IServiceCollection services, + TwilioRequestValidationOptions options + ) + { + var optionsBuilder = services.AddOptions(); + optionsBuilder.Configure((optionsToConfigure, _) => { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Configure((optionsToConfigure, _) => - { - optionsToConfigure.AuthToken = options.AuthToken; - optionsToConfigure.AllowLocal = options.AllowLocal; - optionsToConfigure.BaseUrlOverride = options.BaseUrlOverride; - }); - Sanitize(optionsBuilder); - Validate(optionsBuilder); - return services; - } + optionsToConfigure.AuthToken = options.AuthToken; + optionsToConfigure.AllowLocal = options.AllowLocal; + optionsToConfigure.BaseUrlOverride = options.BaseUrlOverride; + }); + Sanitize(optionsBuilder); + Validate(optionsBuilder); + return services; + } - private static void Sanitize(OptionsBuilder optionsBuilder) + private static void Sanitize(OptionsBuilder optionsBuilder) + { + optionsBuilder.PostConfigure(options => { - optionsBuilder.PostConfigure(options => - { - if (options.AuthToken == "") options.AuthToken = null; - if (options.BaseUrlOverride == "") options.BaseUrlOverride = null; - if (options.BaseUrlOverride != null) options.BaseUrlOverride = options.BaseUrlOverride.TrimEnd('/'); - }); - } + if (options.AuthToken == "") options.AuthToken = null; + if (options.BaseUrlOverride == "") options.BaseUrlOverride = null; + if (options.BaseUrlOverride != null) options.BaseUrlOverride = options.BaseUrlOverride.TrimEnd('/'); + }); + } - private static void Validate(OptionsBuilder optionsBuilder) - { - optionsBuilder.Validate( - options => string.IsNullOrEmpty(options.AuthToken) == false, - "Twilio:AuthToken or Twilio:RequestValidation:AuthToken option is required." - ); - } + private static void Validate(OptionsBuilder optionsBuilder) + { + optionsBuilder.Validate( + options => string.IsNullOrEmpty(options.AuthToken) == false, + "Twilio:AuthToken or Twilio:RequestValidation:AuthToken option is required." + ); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/RequestValidationHelper.cs b/src/Twilio.AspNet.Core/RequestValidationHelper.cs index 6323a24..6d0dfc2 100644 --- a/src/Twilio.AspNet.Core/RequestValidationHelper.cs +++ b/src/Twilio.AspNet.Core/RequestValidationHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; @@ -8,164 +7,163 @@ using Microsoft.Extensions.Options; using Twilio.Security; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// Class used to validate incoming requests from Twilio using 'Request Validation' as described +/// in the Security section of the Twilio TwiML API documentation. +/// +public static class RequestValidationHelper { /// - /// Class used to validate incoming requests from Twilio using 'Request Validation' as described - /// in the Security section of the Twilio TwiML API documentation. + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute /// - public static class RequestValidationHelper + /// HttpContext to use for validation + internal static async Task IsValidRequestAsync(HttpContext context) { - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - internal static async Task IsValidRequestAsync(HttpContext context) - { - var options = context.RequestServices - .GetRequiredService>().Value; - - var authToken = options.AuthToken; - var baseUrlOverride = options.BaseUrlOverride; - var allowLocal = options.AllowLocal; + var options = context.RequestServices + .GetRequiredService>().Value; - var request = context.Request; + var authToken = options.AuthToken; + var baseUrlOverride = options.BaseUrlOverride; + var allowLocal = options.AllowLocal; - string urlOverride = null; - if (!string.IsNullOrEmpty(baseUrlOverride)) - { - urlOverride = $"{baseUrlOverride}{request.Path}{request.QueryString}"; - } + var request = context.Request; - return await IsValidRequestAsync(context, authToken, urlOverride, allowLocal).ConfigureAwait(false); + string urlOverride = null; + if (!string.IsNullOrEmpty(baseUrlOverride)) + { + urlOverride = $"{baseUrlOverride}{request.Path}{request.QueryString}"; } - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static Task IsValidRequestAsync(HttpContext context, string authToken, bool allowLocal = false) - => IsValidRequestAsync(context, authToken, null, allowLocal); - - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static async Task IsValidRequestAsync( - HttpContext context, - string authToken, - string urlOverride, - bool allowLocal = false - ) - { - if (context.Request.HasFormContentType) - { - // this will load the form async, but then cache is in context.Request.Form which is used later - await context.Request.ReadFormAsync(context.RequestAborted).ConfigureAwait(false); - } + return await IsValidRequestAsync(context, authToken, urlOverride, allowLocal).ConfigureAwait(false); + } + + /// + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute + /// + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static Task IsValidRequestAsync(HttpContext context, string authToken, bool allowLocal = false) + => IsValidRequestAsync(context, authToken, null, allowLocal); - return IsValidRequest(context, authToken, urlOverride, allowLocal); + /// + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute + /// + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static async Task IsValidRequestAsync( + HttpContext context, + string authToken, + string urlOverride, + bool allowLocal = false + ) + { + if (context.Request.HasFormContentType) + { + // this will load the form async, but then cache is in context.Request.Form which is used later + await context.Request.ReadFormAsync(context.RequestAborted).ConfigureAwait(false); } + + return IsValidRequest(context, authToken, urlOverride, allowLocal); + } - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static bool IsValidRequest(HttpContext context, string authToken, bool allowLocal = false) - => IsValidRequest(context, authToken, null, allowLocal); - - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static bool IsValidRequest( - HttpContext context, - string authToken, - string urlOverride, - bool allowLocal = false - ) - { - var request = context.Request; + /// + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute + /// + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static bool IsValidRequest(HttpContext context, string authToken, bool allowLocal = false) + => IsValidRequest(context, authToken, null, allowLocal); - if (allowLocal && IsLocal(request)) - { - return true; - } + /// + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute + /// + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static bool IsValidRequest( + HttpContext context, + string authToken, + string urlOverride, + bool allowLocal = false + ) + { + var request = context.Request; - // validate request - // http://www.twilio.com/docs/security-reliability/security - // Take the full URL of the request, from the protocol (http...) through the end of the query string (everything after the ?) - string fullUrl = string.IsNullOrEmpty(urlOverride) - ? $"{request.Scheme}://{(request.IsHttps ? request.Host.Host : request.Host.ToUriComponent())}{request.Path}{request.QueryString}" - : urlOverride; + if (allowLocal && IsLocal(request)) + { + return true; + } - Dictionary parameters = null; - if (request.HasFormContentType) - { - parameters = request.Form.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); - } + // validate request + // http://www.twilio.com/docs/security-reliability/security + // Take the full URL of the request, from the protocol (http...) through the end of the query string (everything after the ?) + var fullUrl = string.IsNullOrEmpty(urlOverride) + ? $"{request.Scheme}://{(request.IsHttps ? request.Host.Host : request.Host.ToUriComponent())}{request.Path}{request.QueryString}" + : urlOverride; - var validator = new RequestValidator(authToken); - return validator.Validate( - url: fullUrl, - parameters: parameters, - expected: request.Headers["X-Twilio-Signature"] - ); + Dictionary parameters = null; + if (request.HasFormContentType) + { + parameters = request.Form.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); } - private static bool IsLocal(HttpRequest req) + var validator = new RequestValidator(authToken); + return validator.Validate( + url: fullUrl, + parameters: parameters, + expected: request.Headers["X-Twilio-Signature"] + ); + } + + private static bool IsLocal(HttpRequest req) + { + if (req.Headers.ContainsKey("X-Forwarded-For")) { - if (req.Headers.ContainsKey("X-Forwarded-For")) - { - // Assume not local if we're behind a proxy - return false; - } + // Assume not local if we're behind a proxy + return false; + } - var connection = req.HttpContext.Connection; - if (connection.RemoteIpAddress != null) + var connection = req.HttpContext.Connection; + if (connection.RemoteIpAddress != null) + { + if (connection.LocalIpAddress != null) { - if (connection.LocalIpAddress != null) - { - return connection.RemoteIpAddress.Equals(connection.LocalIpAddress); - } - - return IPAddress.IsLoopback(connection.RemoteIpAddress); + return connection.RemoteIpAddress.Equals(connection.LocalIpAddress); } - // for in memory TestServer or when dealing with default connection info - if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null) - { - return true; - } + return IPAddress.IsLoopback(connection.RemoteIpAddress); + } - return false; + // for in memory TestServer or when dealing with default connection info + if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null) + { + return true; } + + return false; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/TwiMLExtensions.cs b/src/Twilio.AspNet.Core/TwiMLExtensions.cs index cf30494..93640e9 100644 --- a/src/Twilio.AspNet.Core/TwiMLExtensions.cs +++ b/src/Twilio.AspNet.Core/TwiMLExtensions.cs @@ -1,42 +1,41 @@ using System.Xml.Linq; using Twilio.TwiML; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +public static class TwiMLExtensions { - public static class TwiMLExtensions - { - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - public static TwiMLResult ToTwiMLResult(this VoiceResponse voiceResponse) - => new TwiMLResult(voiceResponse); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + public static TwiMLResult ToTwiMLResult(this VoiceResponse voiceResponse) + => new(voiceResponse); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// Specifies how to format TwiML - /// - public static TwiMLResult ToTwiMLResult(this VoiceResponse voiceResponse, SaveOptions formattingOptions) - => new TwiMLResult(voiceResponse, formattingOptions); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// Specifies how to format TwiML + /// + public static TwiMLResult ToTwiMLResult(this VoiceResponse voiceResponse, SaveOptions formattingOptions) + => new(voiceResponse, formattingOptions); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - public static TwiMLResult ToTwiMLResult(this MessagingResponse messagingResponse) - => new TwiMLResult(messagingResponse); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + public static TwiMLResult ToTwiMLResult(this MessagingResponse messagingResponse) + => new(messagingResponse); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// Specifies how to format TwiML - /// - public static TwiMLResult ToTwiMLResult(this MessagingResponse messagingResponse, SaveOptions formattingOptions) - => new TwiMLResult(messagingResponse, formattingOptions); - } + /// + /// Returns a properly formatted TwiML response + /// + /// + /// Specifies how to format TwiML + /// + public static TwiMLResult ToTwiMLResult(this MessagingResponse messagingResponse, SaveOptions formattingOptions) + => new(messagingResponse, formattingOptions); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/TwiMLResult.cs b/src/Twilio.AspNet.Core/TwiMLResult.cs index 9f6f648..d365aa0 100644 --- a/src/Twilio.AspNet.Core/TwiMLResult.cs +++ b/src/Twilio.AspNet.Core/TwiMLResult.cs @@ -3,46 +3,45 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// TwiMLResult writes TwiML to the HTTP response body +/// +public partial class TwiMLResult : IActionResult { - /// - /// TwiMLResult writes TwiML to the HTTP response body - /// - public partial class TwiMLResult : IActionResult - { - private readonly TwiML.TwiML twiml; - private readonly SaveOptions formattingOptions; + private readonly TwiML.TwiML _twiml; + private readonly SaveOptions _formattingOptions; - /// The TwiML to respond with - public TwiMLResult(TwiML.TwiML twiml) : this(twiml, SaveOptions.None) - { - } + /// The TwiML to respond with + public TwiMLResult(TwiML.TwiML twiml) : this(twiml, SaveOptions.None) + { + } - /// The TwiML to respond with - /// Specifies how to format TwiML - public TwiMLResult(TwiML.TwiML twiml, SaveOptions formattingOptions) - { - this.twiml = twiml; - this.formattingOptions = formattingOptions; - } + /// The TwiML to respond with + /// Specifies how to format TwiML + public TwiMLResult(TwiML.TwiML twiml, SaveOptions formattingOptions) + { + _twiml = twiml; + _formattingOptions = formattingOptions; + } - public async Task ExecuteResultAsync(ActionContext actionContext) - { - var response = actionContext.HttpContext.Response; - await WriteTwiMLToResponse(response); - } + public async Task ExecuteResultAsync(ActionContext actionContext) + { + var response = actionContext.HttpContext.Response; + await WriteTwiMLToResponse(response); + } - private async Task WriteTwiMLToResponse(HttpResponse response) + private async Task WriteTwiMLToResponse(HttpResponse response) + { + response.ContentType = "application/xml"; + if (_twiml == null) { - response.ContentType = "application/xml"; - if (twiml == null) - { - await response.WriteAsync(""); - return; - } - - var data = twiml.ToString(formattingOptions); - await response.WriteAsync(data); + await response.WriteAsync(""); + return; } + + var data = _twiml.ToString(_formattingOptions); + await response.WriteAsync(data); } -} +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj index 6b6b07e..bea3b92 100644 --- a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj +++ b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj @@ -1,6 +1,7 @@  net7.0;net8.0;net9.0 + 13 Library 0.0.0-alpha Twilio.AspNet.Core @@ -26,6 +27,10 @@ true + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs b/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs index eeaff6e..1bab313 100644 --- a/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs +++ b/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs @@ -6,220 +6,216 @@ using Twilio.Clients; using Twilio.Http; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +public static class TwilioClientDependencyInjectionExtensions { - public static class TwilioClientDependencyInjectionExtensions + internal const string TwilioHttpClientName = "Twilio"; + + public static IServiceCollection AddTwilioClient(this IServiceCollection services) { - internal const string TwilioHttpClientName = "Twilio"; + var optionsBuilder = services.AddOptions(); + ConfigureDefaultOptions(optionsBuilder); + PostConfigure(optionsBuilder); + Validate(optionsBuilder); + AddServices(services); + return services; + } - public static IServiceCollection AddTwilioClient(this IServiceCollection services) - { - var optionsBuilder = services.AddOptions(); - ConfigureDefaultOptions(optionsBuilder); - PostConfigure(optionsBuilder); - Validate(optionsBuilder); - AddServices(services); - return services; - } + public static IServiceCollection AddTwilioClient( + this IServiceCollection services, + IConfiguration namedConfigurationSection + ) + { + var optionsBuilder = services.AddOptions(); + optionsBuilder.Bind(namedConfigurationSection); + PostConfigure(optionsBuilder); + Validate(optionsBuilder); + AddServices(services); + return services; + } - public static IServiceCollection AddTwilioClient( - this IServiceCollection services, - IConfiguration namedConfigurationSection - ) - { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Bind(namedConfigurationSection); - PostConfigure(optionsBuilder); - Validate(optionsBuilder); - AddServices(services); - return services; - } + public static IServiceCollection AddTwilioClient( + this IServiceCollection services, + Action configureOptions + ) + => AddTwilioClient(services, (_, options) => configureOptions(options)); - public static IServiceCollection AddTwilioClient( - this IServiceCollection services, - Action configureOptions - ) - => AddTwilioClient(services, (_, options) => configureOptions(options)); + public static IServiceCollection AddTwilioClient( + this IServiceCollection services, + Action configureOptions + ) + { + var optionsBuilder = services.AddOptions(); + optionsBuilder.Configure((options, provider) => configureOptions(provider, options)); + PostConfigure(optionsBuilder); + Validate(optionsBuilder); + AddServices(services); + return services; + } - public static IServiceCollection AddTwilioClient( - this IServiceCollection services, - Action configureOptions - ) - { - var optionsBuilder = services.AddOptions(); - optionsBuilder.Configure((options, provider) => configureOptions(provider, options)); - PostConfigure(optionsBuilder); - Validate(optionsBuilder); - AddServices(services); - return services; - } + public static IServiceCollection AddTwilioClient( + this IServiceCollection services, + TwilioClientOptions options + ) + { + var optionsBuilder = services.AddOptions(); - public static IServiceCollection AddTwilioClient( - this IServiceCollection services, - TwilioClientOptions options - ) + optionsBuilder.Configure((optionsToConfigure, _) => { - var optionsBuilder = services.AddOptions(); - - optionsBuilder.Configure((optionsToConfigure, _) => - { - optionsToConfigure.AccountSid = options.AccountSid; - optionsToConfigure.AuthToken = options.AuthToken; - optionsToConfigure.ApiKeySid = options.ApiKeySid; - optionsToConfigure.ApiKeySecret = options.ApiKeySecret; - optionsToConfigure.CredentialType = options.CredentialType; - optionsToConfigure.Edge = options.Edge; - optionsToConfigure.Region = options.Region; - optionsToConfigure.LogLevel = options.LogLevel; - }); - PostConfigure(optionsBuilder); - Validate(optionsBuilder); - AddServices(services); - return services; - } + optionsToConfigure.AccountSid = options.AccountSid; + optionsToConfigure.AuthToken = options.AuthToken; + optionsToConfigure.ApiKeySid = options.ApiKeySid; + optionsToConfigure.ApiKeySecret = options.ApiKeySecret; + optionsToConfigure.CredentialType = options.CredentialType; + optionsToConfigure.Edge = options.Edge; + optionsToConfigure.Region = options.Region; + optionsToConfigure.LogLevel = options.LogLevel; + }); + PostConfigure(optionsBuilder); + Validate(optionsBuilder); + AddServices(services); + return services; + } - private static void ConfigureDefaultOptions(OptionsBuilder optionsBuilder) + private static void ConfigureDefaultOptions(OptionsBuilder optionsBuilder) + { + optionsBuilder.Configure((options, config) => { - optionsBuilder.Configure((options, config) => + var twilioSection = config.GetSection("Twilio"); + if (twilioSection.Exists() == false) { - var twilioSection = config.GetSection("Twilio"); - if (twilioSection.Exists() == false) - { - throw new Exception("Twilio options not configured."); - } + throw new Exception("Twilio options not configured."); + } - var clientSection = config.GetSection("Twilio:Client"); - if (clientSection.Exists() == false) - { - throw new Exception("Twilio:Client options not configured."); - } + var clientSection = config.GetSection("Twilio:Client"); + if (clientSection.Exists() == false) + { + throw new Exception("Twilio:Client options not configured."); + } - clientSection.Bind(options); + clientSection.Bind(options); - var authTokenFallback = twilioSection["AuthToken"]; - if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) - options.AuthToken = authTokenFallback; - }); - optionsBuilder.Services.AddSingleton< - IOptionsChangeTokenSource, - ConfigurationChangeTokenSource - >(); - } + var authTokenFallback = twilioSection["AuthToken"]; + if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) + options.AuthToken = authTokenFallback; + }); + optionsBuilder.Services.AddSingleton< + IOptionsChangeTokenSource, + ConfigurationChangeTokenSource + >(); + } - private static void PostConfigure(OptionsBuilder optionsBuilder) - => optionsBuilder - .PostConfigure(Sanitize) - .PostConfigure(ConfigureCredentialType); + private static void PostConfigure(OptionsBuilder optionsBuilder) + => optionsBuilder + .PostConfigure(Sanitize) + .PostConfigure(ConfigureCredentialType); - private static void AddServices(IServiceCollection services) - { - services.AddHttpClient(TwilioHttpClientName) - .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler - { - // same options as the Twilio C# SDK - AllowAutoRedirect = false - }); + private static void AddServices(IServiceCollection services) + { + services.AddHttpClient(TwilioHttpClientName) + .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + // same options as the Twilio C# SDK + AllowAutoRedirect = false + }); - services.AddScoped(CreateTwilioClient); - services.AddScoped(CreateTwilioClient); - } + services.AddScoped(CreateTwilioClient); + services.AddScoped(CreateTwilioClient); + } - private static void Sanitize(TwilioClientOptions options) - { - if (options.AccountSid == "") options.AccountSid = null; - if (options.AuthToken == "") options.AuthToken = null; - if (options.ApiKeySid == "") options.ApiKeySid = null; - if (options.ApiKeySecret == "") options.ApiKeySecret = null; - if (options.Region == "") options.Region = null; - if (options.Edge == "") options.Edge = null; - if (options.LogLevel == "") options.LogLevel = null; - } + private static void Sanitize(TwilioClientOptions options) + { + if (options.AccountSid == "") options.AccountSid = null; + if (options.AuthToken == "") options.AuthToken = null; + if (options.ApiKeySid == "") options.ApiKeySid = null; + if (options.ApiKeySecret == "") options.ApiKeySecret = null; + if (options.Region == "") options.Region = null; + if (options.Edge == "") options.Edge = null; + if (options.LogLevel == "") options.LogLevel = null; + } - private static void Validate(OptionsBuilder optionsBuilder) - { - optionsBuilder.Validate( - options => options.CredentialType != CredentialType.Unspecified, - "Twilio:Client:CredentialType could not be determined. Configure as ApiKey or AuthToken." - ); - optionsBuilder.Validate(options => - { - var isApiKeyConfigured = options.AccountSid != null && - options.ApiKeySid != null && - options.ApiKeySecret != null; - return options.CredentialType != CredentialType.ApiKey || isApiKeyConfigured; - }, "Twilio:Client:{AccountSid|ApiKeySid|ApiKeySecret} options required for CredentialType.ApiKey." - ); - optionsBuilder.Validate(options => + private static void Validate(OptionsBuilder optionsBuilder) + { + optionsBuilder.Validate( + options => options.CredentialType != CredentialType.Unspecified, + "Twilio:Client:CredentialType could not be determined. Configure as ApiKey or AuthToken." + ); + optionsBuilder.Validate(options => + { + var isApiKeyConfigured = options is { AccountSid: not null, ApiKeySid: not null, ApiKeySecret: not null }; + return options.CredentialType != CredentialType.ApiKey || isApiKeyConfigured; + }, "Twilio:Client:{AccountSid|ApiKeySid|ApiKeySecret} options required for CredentialType.ApiKey." + ); + optionsBuilder.Validate(options => + { + var isAuthTokenConfigured = options is { AccountSid: not null, AuthToken: not null }; + if (options.CredentialType == CredentialType.AuthToken && !isAuthTokenConfigured) { - var isAuthTokenConfigured = options.AccountSid != null && - options.AuthToken != null; - if (options.CredentialType == CredentialType.AuthToken && !isAuthTokenConfigured) - { - return false; - } - - return true; - }, "Twilio:Client:{AccountSid|AuthToken} options required for CredentialType.AuthToken." - ); - } + return false; + } - private static void ConfigureCredentialType(TwilioClientOptions options) - { - if (options.CredentialType != CredentialType.Unspecified) return; + return true; + }, "Twilio:Client:{AccountSid|AuthToken} options required for CredentialType.AuthToken." + ); + } - var isApiKeyConfigured = options.AccountSid != null && - options.ApiKeySid != null && - options.ApiKeySecret != null; - var isAuthTokenConfigured = options.AccountSid != null && - options.AuthToken != null; + private static void ConfigureCredentialType(TwilioClientOptions options) + { + if (options.CredentialType != CredentialType.Unspecified) return; - if (isApiKeyConfigured) options.CredentialType = CredentialType.ApiKey; - else if (isAuthTokenConfigured) options.CredentialType = CredentialType.AuthToken; - } + var isApiKeyConfigured = options.AccountSid != null && + options.ApiKeySid != null && + options.ApiKeySecret != null; + var isAuthTokenConfigured = options.AccountSid != null && + options.AuthToken != null; - private static TwilioRestClient CreateTwilioClient(IServiceProvider provider) - { - var httpClient = provider.GetRequiredService().CreateClient(TwilioHttpClientName); - Twilio.Http.HttpClient twilioHttpClient = new SystemNetHttpClient(httpClient); + if (isApiKeyConfigured) options.CredentialType = CredentialType.ApiKey; + else if (isAuthTokenConfigured) options.CredentialType = CredentialType.AuthToken; + } - var options = provider.GetRequiredService>().Value; + private static TwilioRestClient CreateTwilioClient(IServiceProvider provider) + { + var httpClient = provider.GetRequiredService().CreateClient(TwilioHttpClientName); + Http.HttpClient twilioHttpClient = new SystemNetHttpClient(httpClient); - TwilioRestClient client; - switch (options.CredentialType) - { - case CredentialType.ApiKey: - client = new TwilioRestClient( - username: options.ApiKeySid, - password: options.ApiKeySecret, - accountSid: options.AccountSid, - region: options.Region, - httpClient: twilioHttpClient, - edge: options.Edge - ); - break; - case CredentialType.AuthToken: - client = new TwilioRestClient( - username: options.AccountSid, - password: options.AuthToken, - accountSid: options.AccountSid, - region: options.Region, - httpClient: twilioHttpClient, - edge: options.Edge - ); - break; - case CredentialType.Unspecified: - default: - throw new Exception("This code should be unreachable"); - } + var options = provider.GetRequiredService>().Value; - if (options.LogLevel != null) - { - client.LogLevel = options.LogLevel; - } + TwilioRestClient client; + switch (options.CredentialType) + { + case CredentialType.ApiKey: + client = new TwilioRestClient( + username: options.ApiKeySid, + password: options.ApiKeySecret, + accountSid: options.AccountSid, + region: options.Region, + httpClient: twilioHttpClient, + edge: options.Edge + ); + break; + case CredentialType.AuthToken: + client = new TwilioRestClient( + username: options.AccountSid, + password: options.AuthToken, + accountSid: options.AccountSid, + region: options.Region, + httpClient: twilioHttpClient, + edge: options.Edge + ); + break; + case CredentialType.Unspecified: + default: + throw new Exception("This code should be unreachable"); + } - return client; + if (options.LogLevel != null) + { + client.LogLevel = options.LogLevel; } + + return client; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/TwilioController.cs b/src/Twilio.AspNet.Core/TwilioController.cs index f6baea7..029bb23 100644 --- a/src/Twilio.AspNet.Core/TwilioController.cs +++ b/src/Twilio.AspNet.Core/TwilioController.cs @@ -2,47 +2,46 @@ using Microsoft.AspNetCore.Mvc; using Twilio.TwiML; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// Extends the standard base controller to simplify returning a TwiML response +/// +public class TwilioController : ControllerBase { /// - /// Extends the standard base controller to simplify returning a TwiML response + /// Returns a properly formatted TwiML response /// - public class TwilioController : ControllerBase - { - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - [NonAction] - public TwiMLResult TwiML(MessagingResponse response) => new TwiMLResult(response); + /// + /// + [NonAction] + public TwiMLResult TwiML(MessagingResponse response) => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - [NonAction] - public TwiMLResult TwiML(MessagingResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + [NonAction] + public TwiMLResult TwiML(MessagingResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - [NonAction] - public TwiMLResult TwiML(VoiceResponse response) => new TwiMLResult(response); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + [NonAction] + public TwiMLResult TwiML(VoiceResponse response) => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - [NonAction] - public TwiMLResult TwiML(VoiceResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); - } + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + [NonAction] + public TwiMLResult TwiML(VoiceResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/TwilioControllerExtensions.cs b/src/Twilio.AspNet.Core/TwilioControllerExtensions.cs index 894e955..d0fae14 100644 --- a/src/Twilio.AspNet.Core/TwilioControllerExtensions.cs +++ b/src/Twilio.AspNet.Core/TwilioControllerExtensions.cs @@ -2,49 +2,48 @@ using Microsoft.AspNetCore.Mvc; using Twilio.TwiML; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// Adds extension methods to the ControllerBase class for returning TwiML in MVC actions +/// +public static class TwilioControllerExtensions { /// - /// Adds extension methods to the ControllerBase class for returning TwiML in MVC actions + /// Returns a properly formatted TwiML response /// - public static class TwilioControllerExtensions - { - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response) - => new TwiMLResult(response); + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response) - => new TwiMLResult(response); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); - } + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/TwilioOptions.cs b/src/Twilio.AspNet.Core/TwilioOptions.cs index e15b00f..f0a85a3 100644 --- a/src/Twilio.AspNet.Core/TwilioOptions.cs +++ b/src/Twilio.AspNet.Core/TwilioOptions.cs @@ -1,35 +1,34 @@ -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +public class TwilioOptions { - public class TwilioOptions - { - public string AuthToken { get; set; } - public TwilioClientOptions Client { get; set; } - public TwilioRequestValidationOptions RequestValidation { get; set; } - } + public string AuthToken { get; set; } + public TwilioClientOptions Client { get; set; } + public TwilioRequestValidationOptions RequestValidation { get; set; } +} - public class TwilioClientOptions - { - public string AccountSid { get; set; } - public string AuthToken { get; set; } - public string ApiKeySid { get; set; } - public string ApiKeySecret { get; set; } - public CredentialType CredentialType { get; set; } - public string Region { get; set; } - public string Edge { get; set; } - public string LogLevel { get; set; } - } +public class TwilioClientOptions +{ + public string AccountSid { get; set; } + public string AuthToken { get; set; } + public string ApiKeySid { get; set; } + public string ApiKeySecret { get; set; } + public CredentialType CredentialType { get; set; } + public string Region { get; set; } + public string Edge { get; set; } + public string LogLevel { get; set; } +} - public class TwilioRequestValidationOptions - { - public string AuthToken { get; set; } - public bool AllowLocal { get; set; } - public string BaseUrlOverride { get; set; } - } +public class TwilioRequestValidationOptions +{ + public string AuthToken { get; set; } + public bool AllowLocal { get; set; } + public string BaseUrlOverride { get; set; } +} - public enum CredentialType - { - Unspecified, - AuthToken, - ApiKey - } +public enum CredentialType +{ + Unspecified, + AuthToken, + ApiKey } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs b/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs index 5174fbf..3584308 100644 --- a/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs +++ b/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs @@ -4,27 +4,26 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// Represents an attribute that is used to prevent forgery of a request. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] +public class ValidateRequestAttribute : Attribute, IAsyncActionFilter { - /// - /// Represents an attribute that is used to prevent forgery of a request. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] - public class ValidateRequestAttribute : Attribute, IAsyncActionFilter + public async Task OnActionExecutionAsync( + ActionExecutingContext filterContext, + ActionExecutionDelegate next + ) { - public async Task OnActionExecutionAsync( - ActionExecutingContext filterContext, - ActionExecutionDelegate next - ) + var context = filterContext.HttpContext; + if (await RequestValidationHelper.IsValidRequestAsync(context).ConfigureAwait(false)) { - var context = filterContext.HttpContext; - if (await RequestValidationHelper.IsValidRequestAsync(context).ConfigureAwait(false)) - { - await next(); - return; - } - - filterContext.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden); + await next(); + return; } + + filterContext.Result = new StatusCodeResult((int)HttpStatusCode.Forbidden); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs b/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs index 2417856..b30145d 100644 --- a/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs +++ b/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs @@ -3,40 +3,39 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -namespace Twilio.AspNet.Core +namespace Twilio.AspNet.Core; + +/// +/// Validates that incoming HTTP request originates from Twilio. +/// +public class ValidateTwilioRequestMiddleware { - /// - /// Validates that incoming HTTP request originates from Twilio. - /// - public class ValidateTwilioRequestMiddleware - { - private readonly RequestDelegate next; + private readonly RequestDelegate _next; - public ValidateTwilioRequestMiddleware(RequestDelegate next) - { - this.next = next; - } + public ValidateTwilioRequestMiddleware(RequestDelegate next) + { + _next = next; + } - public async Task InvokeAsync(HttpContext context) + public async Task InvokeAsync(HttpContext context) + { + if (await RequestValidationHelper.IsValidRequestAsync(context).ConfigureAwait(false)) { - if (await RequestValidationHelper.IsValidRequestAsync(context).ConfigureAwait(false)) - { - await next(context); - return; - } - - context.Response.StatusCode = (int)HttpStatusCode.Forbidden; + await _next(context); + return; } + + context.Response.StatusCode = (int)HttpStatusCode.Forbidden; } +} - public static class ValidateTwilioRequestMiddlewareExtensions - { - /// - /// Validates that incoming HTTP request originates from Twilio. - /// - /// - /// - public static IApplicationBuilder UseTwilioRequestValidation(this IApplicationBuilder builder) - => builder.UseMiddleware(); - } +public static class ValidateTwilioRequestMiddlewareExtensions +{ + /// + /// Validates that incoming HTTP request originates from Twilio. + /// + /// + /// + public static IApplicationBuilder UseTwilioRequestValidation(this IApplicationBuilder builder) + => builder.UseMiddleware(); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs b/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs index 5a8bb9f..8b7ee93 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs +++ b/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs @@ -7,67 +7,66 @@ using System.Web.Mvc; using Twilio.TwiML; -namespace Twilio.AspNet.Mvc.UnitTests +namespace Twilio.AspNet.Mvc.UnitTests; + +public class ContextMocks { - public class ContextMocks - { - public Moq.Mock HttpContext { get; set; } - public Moq.Mock Request { get; set; } - public Moq.Mock Response { get; set; } - public Moq.Mock ControllerContext { get; set; } + public Moq.Mock HttpContext { get; set; } + public Moq.Mock Request { get; set; } + public Moq.Mock Response { get; set; } + public Moq.Mock ControllerContext { get; set; } - public ContextMocks(bool isLocal, NameValueCollection form = null) : this("", isLocal, form) - { - } + public ContextMocks(bool isLocal, NameValueCollection form = null) : this("", isLocal, form) + { + } - public ContextMocks(string urlOverride, bool isLocal, NameValueCollection form = null) - { - var headers = new NameValueCollection(); - headers.Add("X-Twilio-Signature", CalculateSignature(urlOverride, form)); + public ContextMocks(string urlOverride, bool isLocal, NameValueCollection form = null) + { + var headers = new NameValueCollection(); + headers.Add("X-Twilio-Signature", CalculateSignature(urlOverride, form)); - HttpContext = new Moq.Mock(); - Request = new Moq.Mock(); - Response = new Moq.Mock(); - ControllerContext = new Moq.Mock(); + HttpContext = new Moq.Mock(); + Request = new Moq.Mock(); + Response = new Moq.Mock(); + ControllerContext = new Moq.Mock(); - HttpContext.Setup(x => x.Request).Returns(Request.Object); - HttpContext.Setup(x => x.Response).Returns(Response.Object); - ControllerContext.Setup(x => x.HttpContext).Returns(HttpContext.Object); + HttpContext.Setup(x => x.Request).Returns(Request.Object); + HttpContext.Setup(x => x.Response).Returns(Response.Object); + ControllerContext.Setup(x => x.HttpContext).Returns(HttpContext.Object); - Request.Setup(x => x.IsLocal).Returns(isLocal); - Request.Setup(x => x.Headers).Returns(headers); - Request.Setup(x => x.Url).Returns(new Uri(ContextMocks.fakeUrl)); - if (form != null) - { - Request.Setup(x => x.HttpMethod).Returns("POST"); - Request.Setup(x => x.Form).Returns(form); - } - - Response.Setup(x => x.Output).Returns(new Utf8StringWriter()); + Request.Setup(x => x.IsLocal).Returns(isLocal); + Request.Setup(x => x.Headers).Returns(headers); + Request.Setup(x => x.Url).Returns(new Uri(FakeUrl)); + if (form != null) + { + Request.Setup(x => x.HttpMethod).Returns("POST"); + Request.Setup(x => x.Form).Returns(form); } + + Response.Setup(x => x.Output).Returns(new Utf8StringWriter()); + } - public static string fakeUrl = "https://api.example.com/webhook"; - public static string fakeAuthToken = "thisisafakeauthtoken"; + public const string FakeUrl = "https://api.example.com/webhook"; + public const string FakeAuthToken = "thisisafakeauthtoken"; - private string CalculateSignature(string urlOverride, NameValueCollection form) - { - var value = new StringBuilder(); - value.Append(string.IsNullOrEmpty(urlOverride) ? ContextMocks.fakeUrl : urlOverride); + private string CalculateSignature(string urlOverride, NameValueCollection form) + { + var value = new StringBuilder(); + value.Append(string.IsNullOrEmpty(urlOverride) ? FakeUrl : urlOverride); - if (form != null) + if (form != null) + { + var sortedKeys = form.AllKeys.OrderBy(k => k, StringComparer.Ordinal).ToList(); + foreach (var key in sortedKeys) { - var sortedKeys = form.AllKeys.OrderBy(k => k, StringComparer.Ordinal).ToList(); - foreach (var key in sortedKeys) - { - value.Append(key); - value.Append(form[key]); - } + value.Append(key); + value.Append(form[key]); } + } - var sha1 = new HMACSHA1(Encoding.UTF8.GetBytes(ContextMocks.fakeAuthToken)); - var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(value.ToString())); + var sha1 = new HMACSHA1(Encoding.UTF8.GetBytes(FakeAuthToken)); + var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(value.ToString())); - return Convert.ToBase64String(hash); - } + return Convert.ToBase64String(hash); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc.UnitTests/RequestValidationHelperTests.cs b/src/Twilio.AspNet.Mvc.UnitTests/RequestValidationHelperTests.cs index 8610945..1f6b9f4 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/RequestValidationHelperTests.cs +++ b/src/Twilio.AspNet.Mvc.UnitTests/RequestValidationHelperTests.cs @@ -1,66 +1,67 @@ using System.Collections.Specialized; using Xunit; -namespace Twilio.AspNet.Mvc.UnitTests +namespace Twilio.AspNet.Mvc.UnitTests; + +public class RequestValidationHelperTests { - public class RequestValidationHelperTests + [Fact] + public void TestLocal() { - [Fact] - public void TestLocal() - { - var fakeContext = (new ContextMocks(true)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token", true); + var fakeContext = new ContextMocks(true).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token", true); - Assert.True(result); - } + Assert.True(result); + } - [Fact] - public void TestNoLocal() - { - var fakeContext = (new ContextMocks(true)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token", false); + [Fact] + public void TestNoLocal() + { + var fakeContext = new ContextMocks(true).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, "bad-token"); - Assert.False(result); - } + Assert.False(result); + } - [Fact] - public void TestNoForm() - { - var fakeContext = (new ContextMocks(true)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, false); + [Fact] + public void TestNoForm() + { + var fakeContext = new ContextMocks(true).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken); - Assert.True(result); - } + Assert.True(result); + } - [Fact] - public void TestUrlOverrideFail() - { - var fakeContext = (new ContextMocks(true)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, "https://example.com/", false); + [Fact] + public void TestUrlOverrideFail() + { + var fakeContext = new ContextMocks(true).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken, "https://example.com/"); - Assert.False(result); - } + Assert.False(result); + } - [Fact] - public void TestUrlOverride() - { - var fakeContext = (new ContextMocks("https://example.com/", true)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, "https://example.com/", false); + [Fact] + public void TestUrlOverride() + { + var fakeContext = new ContextMocks("https://example.com/", true).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken, "https://example.com/"); - Assert.True(result); - } + Assert.True(result); + } - [Fact] - public void TestForm() + [Fact] + public void TestForm() + { + var form = new NameValueCollection { - var form = new NameValueCollection(); - form.Add("key1", "value1"); - form.Add("key2", "value2"); - var fakeContext = (new ContextMocks(true, form)).HttpContext.Object; - var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.fakeAuthToken, false); + { "key1", "value1" }, + { "key2", "value2" } + }; + var fakeContext = new ContextMocks(true, form).HttpContext.Object; + var result = RequestValidationHelper.IsValidRequest(fakeContext, ContextMocks.FakeAuthToken); - Assert.True(result); - } + Assert.True(result); } -} +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs b/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs index 91ae7a8..278133c 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs +++ b/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs @@ -4,74 +4,73 @@ using Twilio.TwiML; using Xunit; -namespace Twilio.AspNet.Mvc.UnitTests +namespace Twilio.AspNet.Mvc.UnitTests; + +public class TwiMLResultTest { - public class TwiMLResultTest - { - private readonly ContextMocks mocks = new ContextMocks(true); - private static readonly string NewLine = Environment.NewLine; + private readonly ContextMocks _mocks = new(true); + private static readonly string NewLine = Environment.NewLine; - [Fact] - public void TestVoiceResponse() - { - var response = new VoiceResponse().Say("Ahoy!"); + [Fact] + public void TestVoiceResponse() + { + var response = new VoiceResponse().Say("Ahoy!"); - var result = new TwiMLResult(response); - result.ExecuteResult(mocks.ControllerContext.Object); + var result = new TwiMLResult(response); + result.ExecuteResult(_mocks.ControllerContext.Object); - Assert.Equal($"{NewLine}" + - $"{NewLine}" + - $" Ahoy!{NewLine}" + - "", - mocks.Response.Object.Output.ToString() - ); - } + Assert.Equal($"{NewLine}" + + $"{NewLine}" + + $" Ahoy!{NewLine}" + + "", + _mocks.Response.Object.Output.ToString() + ); + } - [Fact] - public void TestVoiceResponseUnformatted() - { - var response = new VoiceResponse().Say("Ahoy!"); + [Fact] + public void TestVoiceResponseUnformatted() + { + var response = new VoiceResponse().Say("Ahoy!"); - var result = new TwiMLResult(response, SaveOptions.DisableFormatting); - result.ExecuteResult(mocks.ControllerContext.Object); + var result = new TwiMLResult(response, SaveOptions.DisableFormatting); + result.ExecuteResult(_mocks.ControllerContext.Object); - Assert.Equal("" + - "" + - "Ahoy!" + - "", - mocks.Response.Object.Output.ToString() - ); - } + Assert.Equal("" + + "" + + "Ahoy!" + + "", + _mocks.Response.Object.Output.ToString() + ); + } - [Fact] - public void TestVoiceResponseUnformattedUtf16() - { - // string writer has Utf16 encoding - mocks.Response.Setup(r => r.Output).Returns(new StringWriter()); - var response = new VoiceResponse().Say("Ahoy!"); + [Fact] + public void TestVoiceResponseUnformattedUtf16() + { + // string writer has Utf16 encoding + _mocks.Response.Setup(r => r.Output).Returns(new StringWriter()); + var response = new VoiceResponse().Say("Ahoy!"); - var result = new TwiMLResult(response, SaveOptions.DisableFormatting); - result.ExecuteResult(mocks.ControllerContext.Object); + var result = new TwiMLResult(response, SaveOptions.DisableFormatting); + result.ExecuteResult(_mocks.ControllerContext.Object); - Assert.Equal("" + - "" + - "Ahoy!" + - "", - mocks.Response.Object.Output.ToString() - ); - } + Assert.Equal("" + + "" + + "Ahoy!" + + "", + _mocks.Response.Object.Output.ToString() + ); + } - [Fact] - public void TestNullTwiml() - { - var result = new TwiMLResult(null); - result.ExecuteResult(mocks.ControllerContext.Object); + [Fact] + public void TestNullTwiml() + { + var result = new TwiMLResult(null); + result.ExecuteResult(_mocks.ControllerContext.Object); - Assert.Equal( - "", - mocks.Response.Object.Output.ToString() - ); - mocks.Response.Object.Output.Dispose(); - } + Assert.Equal( + "", + _mocks.Response.Object.Output.ToString() + ); + _mocks.Response.Object.Output.Dispose(); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj index 35c1c04..b872770 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj +++ b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj @@ -3,6 +3,7 @@ Library net471 + 13 @@ -26,6 +27,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs b/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs index 577bf5a..6cc2908 100644 --- a/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs +++ b/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs @@ -3,52 +3,51 @@ using System.Web; using Twilio.Security; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +/// +/// Class used to validate incoming requests from Twilio using 'Request Validation' as described +/// in the Security section of the Twilio TwiML API documentation. +/// +public static class RequestValidationHelper { /// - /// Class used to validate incoming requests from Twilio using 'Request Validation' as described - /// in the Security section of the Twilio TwiML API documentation. + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute /// - public static class RequestValidationHelper - { - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static bool IsValidRequest(HttpContextBase context, string authToken, bool allowLocal = false) - => IsValidRequest(context, authToken, null, allowLocal); + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static bool IsValidRequest(HttpContextBase context, string authToken, bool allowLocal = false) + => IsValidRequest(context, authToken, null, allowLocal); - /// - /// Performs request validation using the current HTTP context passed in manually or from - /// the ASP.NET MVC ValidateRequestAttribute - /// - /// HttpContext to use for validation - /// AuthToken for the account used to sign the request - /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) - /// - /// Skip validation for local requests. - /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. - /// - public static bool IsValidRequest(HttpContextBase context, string authToken, string urlOverride, bool allowLocal = false) + /// + /// Performs request validation using the current HTTP context passed in manually or from + /// the ASP.NET MVC ValidateRequestAttribute + /// + /// HttpContext to use for validation + /// AuthToken for the account used to sign the request + /// The URL to use for validation, if different from Request.Url (sometimes needed if web site is behind a proxy or load-balancer) + /// + /// Skip validation for local requests. + /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. + /// + public static bool IsValidRequest(HttpContextBase context, string authToken, string urlOverride, bool allowLocal = false) + { + if (allowLocal && context.Request.IsLocal && !context.Request.Headers.AllKeys.Contains("X-Forwarded-For")) { - if (allowLocal && context.Request.IsLocal && !context.Request.Headers.AllKeys.Contains("X-Forwarded-For")) - { - return true; - } - - var fullUrl = string.IsNullOrEmpty(urlOverride) ? context.Request.Url?.AbsoluteUri : urlOverride; - var validator = new RequestValidator(authToken); - return validator.Validate( - url: fullUrl, - parameters: context.Request?.Form ?? new NameValueCollection(), - expected: context.Request?.Headers["X-Twilio-Signature"] - ); + return true; } + + var fullUrl = string.IsNullOrEmpty(urlOverride) ? context.Request.Url?.AbsoluteUri : urlOverride; + var validator = new RequestValidator(authToken); + return validator.Validate( + url: fullUrl, + parameters: context.Request?.Form ?? new NameValueCollection(), + expected: context.Request?.Headers["X-Twilio-Signature"] + ); } -} +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/TwiMLResult.cs b/src/Twilio.AspNet.Mvc/TwiMLResult.cs index 76296bd..4ff313f 100644 --- a/src/Twilio.AspNet.Mvc/TwiMLResult.cs +++ b/src/Twilio.AspNet.Mvc/TwiMLResult.cs @@ -1,42 +1,41 @@ using System.Web.Mvc; using System.Xml.Linq; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +public class TwiMLResult : ActionResult { - public class TwiMLResult : ActionResult + private readonly SaveOptions _formattingOptions; + private readonly TwiML.TwiML _dataTwiml; + + public TwiMLResult(TwiML.TwiML response) : this(response, SaveOptions.None) { - private readonly SaveOptions formattingOptions; - private readonly TwiML.TwiML dataTwiml; + } - public TwiMLResult(TwiML.TwiML response) : this(response, SaveOptions.None) - { - } + public TwiMLResult(TwiML.TwiML response, SaveOptions formattingOptions) + { + _dataTwiml = response; + _formattingOptions = formattingOptions; + } - public TwiMLResult(TwiML.TwiML response, SaveOptions formattingOptions) + public override void ExecuteResult(ControllerContext controllerContext) + { + var response = controllerContext.HttpContext.Response; + var encoding = response.Output.Encoding.BodyName; + response.ContentType = "application/xml"; + + if (_dataTwiml == null) { - this.dataTwiml = response; - this.formattingOptions = formattingOptions; + response.Output.Write($""); + return; } - public override void ExecuteResult(ControllerContext controllerContext) + var twimlString = _dataTwiml.ToString(_formattingOptions); + if (encoding != "utf-8") { - var response = controllerContext.HttpContext.Response; - var encoding = response.Output.Encoding.BodyName; - response.ContentType = "application/xml"; - - if (dataTwiml == null) - { - response.Output.Write($""); - return; - } - - var twimlString = dataTwiml.ToString(formattingOptions); - if (encoding != "utf-8") - { - twimlString = twimlString.Replace("utf-8", encoding); - } - - response.Output.Write(twimlString); + twimlString = twimlString.Replace("utf-8", encoding); } + + response.Output.Write(twimlString); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj index 5d517c6..f3f3f67 100644 --- a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj +++ b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj @@ -3,6 +3,7 @@ Library net462 + 13 0.0.0-alpha Twilio.AspNet.Mvc 0.0.0-alpha @@ -47,6 +48,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Twilio.AspNet.Mvc/TwilioConfiguration.cs b/src/Twilio.AspNet.Mvc/TwilioConfiguration.cs index af4b365..35468f9 100644 --- a/src/Twilio.AspNet.Mvc/TwilioConfiguration.cs +++ b/src/Twilio.AspNet.Mvc/TwilioConfiguration.cs @@ -1,36 +1,35 @@ using System.Configuration; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +public class RequestValidationConfigurationSection : ConfigurationSection { - public class RequestValidationConfigurationSection : ConfigurationSection + [ConfigurationProperty("authToken")] + public string AuthToken { - [ConfigurationProperty("authToken")] - public string AuthToken - { - get => (string)this["authToken"]; - set => this["authToken"] = value; - } - - [ConfigurationProperty("baseUrlOverride")] - public string BaseUrlOverride - { - get => (string)this["baseUrlOverride"]; - set => this["baseUrlOverride"] = value; - } - - [ConfigurationProperty("allowLocal")] - public bool AllowLocal - { - get => (bool)this["allowLocal"]; - set => this["allowLocal"] = value; - } + get => (string)this["authToken"]; + set => this["authToken"] = value; } - public class TwilioSectionGroup : ConfigurationSectionGroup + [ConfigurationProperty("baseUrlOverride")] + public string BaseUrlOverride { + get => (string)this["baseUrlOverride"]; + set => this["baseUrlOverride"] = value; + } - [ConfigurationProperty("requestValidation", IsRequired = false)] - public RequestValidationConfigurationSection RequestValidation - => (RequestValidationConfigurationSection)Sections["requestValidation"]; + [ConfigurationProperty("allowLocal")] + public bool AllowLocal + { + get => (bool)this["allowLocal"]; + set => this["allowLocal"] = value; } +} + +public class TwilioSectionGroup : ConfigurationSectionGroup +{ + + [ConfigurationProperty("requestValidation", IsRequired = false)] + public RequestValidationConfigurationSection RequestValidation + => (RequestValidationConfigurationSection)Sections["requestValidation"]; } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/TwilioController.cs b/src/Twilio.AspNet.Mvc/TwilioController.cs index 6a90921..305ae78 100644 --- a/src/Twilio.AspNet.Mvc/TwilioController.cs +++ b/src/Twilio.AspNet.Mvc/TwilioController.cs @@ -2,45 +2,44 @@ using System.Xml.Linq; using Twilio.TwiML; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +/// +/// Extends the standard base controller to simplify returning a TwiML response +/// +public class TwilioController : Controller { - /// - /// Extends the standard base controller to simplify returning a TwiML response - /// - public class TwilioController : Controller - { - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - public TwiMLResult TwiML(MessagingResponse response) - => new TwiMLResult(response); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + public TwiMLResult TwiML(MessagingResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public TwiMLResult TwiML(MessagingResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + public TwiMLResult TwiML(MessagingResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - public TwiMLResult TwiML(VoiceResponse response) - => new TwiMLResult(response); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + public TwiMLResult TwiML(VoiceResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public TwiMLResult TwiML(VoiceResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); - } -} + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + public TwiMLResult TwiML(VoiceResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/TwilioControllerExtensions.cs b/src/Twilio.AspNet.Mvc/TwilioControllerExtensions.cs index 06bc669..7b9b914 100644 --- a/src/Twilio.AspNet.Mvc/TwilioControllerExtensions.cs +++ b/src/Twilio.AspNet.Mvc/TwilioControllerExtensions.cs @@ -2,49 +2,48 @@ using System.Xml.Linq; using Twilio.TwiML; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +/// +/// Adds extension methods to the ControllerBase class for returning TwiML in MVC actions +/// +public static class TwilioControllerExtensions { /// - /// Adds extension methods to the ControllerBase class for returning TwiML in MVC actions + /// Returns a properly formatted TwiML response /// - public static class TwilioControllerExtensions - { - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response) - => new TwiMLResult(response); + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, MessagingResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response) - => new TwiMLResult(response); + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response) + => new(response); - /// - /// Returns a properly formatted TwiML response - /// - /// - /// - /// - /// - public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response, SaveOptions formattingOptions) - => new TwiMLResult(response, formattingOptions); - } -} + /// + /// Returns a properly formatted TwiML response + /// + /// + /// + /// + /// + public static TwiMLResult TwiML(this ControllerBase controller, VoiceResponse response, SaveOptions formattingOptions) + => new(response, formattingOptions); +} \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs b/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs index d022f6f..5202034 100644 --- a/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs +++ b/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs @@ -1,11 +1,10 @@ using System; using System.Web.Mvc; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +public static class TwilioUriHelper { - public static class TwilioUriHelper - { - public static Uri ActionUri(this UrlHelper helper, string actionName, string controllerName) - => new Uri(helper.Action(actionName, controllerName), UriKind.Relative); - } + public static Uri ActionUri(this UrlHelper helper, string actionName, string controllerName) => + new(helper.Action(actionName, controllerName), UriKind.Relative); } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs b/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs index d4ce5f4..f19afda 100644 --- a/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs +++ b/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs @@ -3,71 +3,70 @@ using System.Net; using System.Web.Mvc; -namespace Twilio.AspNet.Mvc +namespace Twilio.AspNet.Mvc; + +/// +/// Represents an attribute that is used to prevent forgery of a request. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Module)] +public class ValidateRequestAttribute : ActionFilterAttribute { + protected internal string AuthToken { get; set; } + protected internal string BaseUrlOverride { get; set; } + protected internal bool AllowLocal { get; set; } + /// - /// Represents an attribute that is used to prevent forgery of a request. + /// Initializes a new instance of the ValidateRequestAttribute class. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Module)] - public class ValidateRequestAttribute : ActionFilterAttribute + public ValidateRequestAttribute() { - protected internal string AuthToken { get; set; } - protected internal string BaseUrlOverride { get; set; } - protected internal bool AllowLocal { get; set; } + ConfigureProperties(); + } - /// - /// Initializes a new instance of the ValidateRequestAttribute class. - /// - public ValidateRequestAttribute() - { - ConfigureProperties(); - } + /// + /// Configures the properties of the attribute. + /// + /// + /// This method exists so ValidateRequestAttribute can be inherited from + /// and ConfigureProperties can be overridden. + /// + /// + protected virtual void ConfigureProperties() + { + var requestValidationConfiguration = + ConfigurationManager.GetSection("twilio/requestValidation") as RequestValidationConfigurationSection; + var appSettings = ConfigurationManager.AppSettings; - /// - /// Configures the properties of the attribute. - /// - /// - /// This method exists so ValidateRequestAttribute can be inherited from - /// and ConfigureProperties can be overridden. - /// - /// - protected virtual void ConfigureProperties() - { - var requestValidationConfiguration = - ConfigurationManager.GetSection("twilio/requestValidation") as RequestValidationConfigurationSection; - var appSettings = ConfigurationManager.AppSettings; + AuthToken = appSettings["twilio:requestValidation:authToken"] + ?? appSettings["twilio:authToken"] + ?? requestValidationConfiguration?.AuthToken + ?? throw new Exception("Twilio Auth Token not configured"); - AuthToken = appSettings["twilio:requestValidation:authToken"] - ?? appSettings["twilio:authToken"] - ?? requestValidationConfiguration?.AuthToken - ?? throw new Exception("Twilio Auth Token not configured"); + BaseUrlOverride = (appSettings["twilio:requestValidation:baseUrlOverride"] + ?? requestValidationConfiguration?.BaseUrlOverride) + ?.TrimEnd('/'); - BaseUrlOverride = (appSettings["twilio:requestValidation:baseUrlOverride"] - ?? requestValidationConfiguration?.BaseUrlOverride) - ?.TrimEnd('/'); + var allowLocalAppSetting = appSettings["twilio:requestValidation:allowLocal"]; + AllowLocal = allowLocalAppSetting != null + ? bool.Parse(allowLocalAppSetting) + : requestValidationConfiguration?.AllowLocal + ?? false; + } - var allowLocalAppSetting = appSettings["twilio:requestValidation:allowLocal"]; - AllowLocal = allowLocalAppSetting != null - ? bool.Parse(allowLocalAppSetting) - : requestValidationConfiguration?.AllowLocal - ?? false; + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + var httpContext = filterContext.HttpContext; + string urlOverride = null; + if (BaseUrlOverride != null) + { + urlOverride = $"{BaseUrlOverride}{httpContext.Request.Path}{httpContext.Request.QueryString}"; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + if (!RequestValidationHelper.IsValidRequest(filterContext.HttpContext, AuthToken, urlOverride, AllowLocal)) { - var httpContext = filterContext.HttpContext; - string urlOverride = null; - if (BaseUrlOverride != null) - { - urlOverride = $"{BaseUrlOverride}{httpContext.Request.Path}{httpContext.Request.QueryString}"; - } - - if (!RequestValidationHelper.IsValidRequest(filterContext.HttpContext, AuthToken, urlOverride, AllowLocal)) - { - filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); - } - - base.OnActionExecuting(filterContext); + filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); } + + base.OnActionExecuting(filterContext); } } \ No newline at end of file From c6777ea53df567ee890037d161bea2e63578e75c Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:51:36 -0500 Subject: [PATCH 05/11] Add nullable & implicit usings --- README.md | 2 +- src/Twilio.AspNet.Common/SmsRequest.cs | 15 +++--- .../SmsStatusCallbackRequest.cs | 10 ++-- .../StatusCallbackRequest.cs | 4 +- .../Twilio.AspNet.Common.csproj | 3 ++ src/Twilio.AspNet.Common/TwilioRequest.cs | 22 ++++---- src/Twilio.AspNet.Common/VoiceRequest.cs | 54 +++++++++---------- .../ContextMocks.cs | 11 ++-- .../Extensions.cs | 3 -- .../MinimalApiTwiMLResultTests.cs | 4 +- .../RequestValidationHelperTests.cs | 2 - .../Twilio.AspNet.Core.UnitTests.csproj | 3 +- .../TwilioClientOptionsTests.cs | 19 +++---- .../TwilioClientTests.cs | 28 +++++----- .../TwilioControllerExtensionTests.cs | 4 +- .../TwilioControllerTests.cs | 4 +- .../TwilioRequestValidationOptionsTests.cs | 20 +++---- .../ValidateTwilioRequestTests.cs | 6 +-- .../ValidationHelper.cs | 6 +-- .../MinimalApiTwiMLResult.cs | 1 - ...ValidationDependencyInjectionExtensions.cs | 5 +- .../RequestValidationHelper.cs | 21 ++++---- src/Twilio.AspNet.Core/TwiMLResult.cs | 5 +- .../Twilio.AspNet.Core.csproj | 4 ++ ...ilioClientDependencyInjectionExtensions.cs | 19 +++---- src/Twilio.AspNet.Core/TwilioOptions.cs | 26 ++++----- .../ValidateRequestAttribute.cs | 4 +- .../ValidateTwilioRequestFilter.cs | 3 +- .../ValidateTwilioRequestMiddleware.cs | 1 - .../ContextMocks.cs | 12 ++--- .../TwiMLResultTests.cs | 6 +-- .../Twilio.AspNet.Mvc.UnitTests.csproj | 3 ++ .../RequestValidationHelper.cs | 7 ++- src/Twilio.AspNet.Mvc/TwiMLResult.cs | 2 +- .../Twilio.AspNet.Mvc.csproj | 4 ++ src/Twilio.AspNet.Mvc/TwilioUriHelper.cs | 3 +- .../ValidateRequestAttribute.cs | 15 +++--- 37 files changed, 164 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 1a49fd8..6c9c249 100644 --- a/README.md +++ b/README.md @@ -552,7 +552,7 @@ bool IsValidTwilioRequest(HttpContext httpContext) ?.Value ?? throw new Exception("TwilioRequestValidationOptions missing."); string? urlOverride = null; - if (options.BaseUrlOverride != null) + if (options.BaseUrlOverride is not null) { var request = httpContext.Request; urlOverride = $"{options.BaseUrlOverride.TrimEnd('/')}{request.Path}{request.QueryString}"; diff --git a/src/Twilio.AspNet.Common/SmsRequest.cs b/src/Twilio.AspNet.Common/SmsRequest.cs index e24f877..cf5b47c 100644 --- a/src/Twilio.AspNet.Common/SmsRequest.cs +++ b/src/Twilio.AspNet.Common/SmsRequest.cs @@ -9,32 +9,33 @@ public class SmsRequest : TwilioRequest /// /// A 34 character unique identifier for the message. May be used to later retrieve this message from the REST API. /// - public string MessageSid { get; set; } - + public string MessageSid { get; set; } = null!; + /// /// Same value as MessageSid. Deprecated and included for backward compatibility. /// - public string SmsSid { get; set; } + [Obsolete("Use MessageSid instead")] + public string SmsSid { get; set; }= null!; /// /// The text body of the SMS message. Up to 160 characters long /// - public string Body { get; set; } + public string Body { get; set; }= null!; /// /// The status of the message /// - public string MessageStatus { get; set; } + public string MessageStatus { get; set; }= null!; /// /// The message OptOut type /// - public string OptOutType { get; set; } + public string? OptOutType { get; set; } /// /// A unique identifier of the messaging service /// - public string MessagingServiceSid { get; set; } + public string? MessagingServiceSid { get; set; } /// /// The number of media items associated with your message diff --git a/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs b/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs index 0de580c..e1b89a6 100644 --- a/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs +++ b/src/Twilio.AspNet.Common/SmsStatusCallbackRequest.cs @@ -9,32 +9,32 @@ public class SmsStatusCallbackRequest: SmsRequest /// will be present. Find the possible values here: /// https://www.twilio.com/docs/sms/api/message-resource#delivery-related-errors /// - public string ErrorCode { get; set; } + public string? ErrorCode { get; set; } /// /// The Installed Channel SID (found on the Channel detail page) that was /// used to send this message. Only present if the message was sent using a /// Channel. /// - public string ChannelInstallSid { get; set; } + public string? ChannelInstallSid { get; set; } /// /// The Error message returned by the underlying Channel if Message delivery /// failed. Only present if the message was sent using a Channel and message /// delivery failed. /// - public string ChannelStatusMessage { get; set; } + public string? ChannelStatusMessage { get; set; } /// /// Channel specific prefix that allows you to identify which channel this /// message was sent over. /// - public string ChannelPrefix { get; set; } + public string? ChannelPrefix { get; set; } /// /// Contains post-delivery events. If the Channel supports Read receipts, this /// parameter will be included with a value of READ after the user has read /// the message. Currently supported only for WhatsApp. /// - public string EventType { get; set; } + public string? EventType { get; set; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/StatusCallbackRequest.cs b/src/Twilio.AspNet.Common/StatusCallbackRequest.cs index 5b387c1..d54b6d0 100644 --- a/src/Twilio.AspNet.Common/StatusCallbackRequest.cs +++ b/src/Twilio.AspNet.Common/StatusCallbackRequest.cs @@ -11,7 +11,7 @@ public class StatusCallbackRequest : VoiceRequest /// public float CallDuration { get; set; } - public string Called { get; set; } - public string Caller { get; set; } + public string? Called { get; set; } + public string? Caller { get; set; } public float Duration { get; set; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj index 62ca42c..65e4604 100644 --- a/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj +++ b/src/Twilio.AspNet.Common/Twilio.AspNet.Common.csproj @@ -2,6 +2,8 @@ netstandard2.0 13 + enable + enable Library 0.0.0-alpha Twilio.AspNet.Common @@ -15,6 +17,7 @@ Apache-2.0 https://github.com/twilio/twilio-aspnet https://github.com/twilio-labs/twilio-aspnet.git + git https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/twilio-icon-64x64.png icon.png README.md diff --git a/src/Twilio.AspNet.Common/TwilioRequest.cs b/src/Twilio.AspNet.Common/TwilioRequest.cs index 4a9d46b..dd6efe8 100644 --- a/src/Twilio.AspNet.Common/TwilioRequest.cs +++ b/src/Twilio.AspNet.Common/TwilioRequest.cs @@ -8,7 +8,7 @@ public abstract class TwilioRequest /// /// Your Twilio account id. It is 34 characters long, and always starts with the letters AC /// - public string AccountSid { get; set; } + public string AccountSid { get; set; } = null!; /// /// The phone number or client identifier of the party that initiated the call @@ -16,7 +16,7 @@ public abstract class TwilioRequest /// /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call from a client named 'tommy', the From parameter will be client:tommy. /// - public string From { get; set; } + public string From { get; set; } = null!; /// /// The phone number or client identifier of the called party @@ -24,45 +24,45 @@ public abstract class TwilioRequest /// /// Phone numbers are formatted with a '+' and country code, e.g. +16175551212 (E.164 format). Client identifiers begin with the client: URI scheme; for example, for a call to a client named 'jenny', the To parameter will be client:jenny. /// - public string To { get; set; } + public string To { get; set; } = null!; /// /// The city of the caller /// - public string FromCity { get; set; } + public string? FromCity { get; set; } /// /// The state or province of the caller /// - public string FromState { get; set; } + public string? FromState { get; set; } /// /// The postal code of the caller /// - public string FromZip { get; set; } + public string? FromZip { get; set; } /// /// The country of the caller /// - public string FromCountry { get; set; } + public string? FromCountry { get; set; } /// /// The city of the called party /// - public string ToCity { get; set; } + public string? ToCity { get; set; } /// /// The state or province of the called party /// - public string ToState { get; set; } + public string? ToState { get; set; } /// /// The postal code of the called party /// - public string ToZip { get; set; } + public string? ToZip { get; set; } /// /// The country of the called party /// - public string ToCountry { get; set; } + public string? ToCountry { get; set; } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Common/VoiceRequest.cs b/src/Twilio.AspNet.Common/VoiceRequest.cs index 2b4f682..bd5e6e1 100644 --- a/src/Twilio.AspNet.Common/VoiceRequest.cs +++ b/src/Twilio.AspNet.Common/VoiceRequest.cs @@ -9,40 +9,40 @@ public class VoiceRequest : TwilioRequest /// /// A unique identifier for this call, generated by Twilio /// - public string CallSid { get; set; } + public string CallSid { get; set; } = null!; /// /// A descriptive status for the call. The value is one of queued, ringing, in-progress, completed, busy, failed or no-answer /// - public string CallStatus { get; set; } + public string CallStatus { get; set; } = null!; /// /// The version of the Twilio API used to handle this call. For incoming calls, this is determined by the API version set on the called number. For outgoing calls, this is the API version used by the outgoing call's REST API request /// - public string ApiVersion { get; set; } + public string ApiVersion { get; set; } = null!; /// /// Indicates the direction of the call. In most cases this will be inbound, but if you are using Dial it will be outbound-dial /// - public string Direction { get; set; } + public string Direction { get; set; } = null!; /// /// This parameter is set only when Twilio receives a forwarded call, but its value depends on the caller's carrier including information when forwarding. Not all carriers support passing this information /// - public string ForwardedFrom { get; set; } + public string? ForwardedFrom { get; set; } /// /// This parameter is set when the IncomingPhoneNumber that received the call has had its VoiceCallerIdLookup value set to true. /// - public string CallerName { get; set; } + public string? CallerName { get; set; } /// /// A unique identifier for the call that created this leg. This parameter is not passed if this is the first leg of a call. /// - public string ParentCallSid { get; set; } + public string? ParentCallSid { get; set; } /// A token string needed to invoke a forwarded call. - public string CallToken { get; set; } + public string? CallToken { get; set; } #region Gather & Record Parameters @@ -50,12 +50,12 @@ public class VoiceRequest : TwilioRequest /// When used with the Gather verb, the digits the caller pressed, excluding the finishOnKey digit if used. /// When used with the Record verb, the key (if any) pressed to end the recording or 'hangup' if the caller hung up /// - public string Digits { get; set; } + public string? Digits { get; set; } /// /// When used with the Gather verb, the transcribed result of the speech /// - public string SpeechResult { get; set; } + public string? SpeechResult { get; set; } /// /// When used with the Gather verb, a confidence score between 0.0 and 1.0 respectively. @@ -66,17 +66,17 @@ public class VoiceRequest : TwilioRequest /// /// The URL of the recorded audio. When the result of a transcription, the URL for the transcription's source recording resource. /// - public string RecordingUrl { get; set; } + public string? RecordingUrl { get; set; } /// /// The status of the recording. Possible values are: completed, failed. /// - public string RecordingStatus { get; set; } + public string? RecordingStatus { get; set; } /// /// The duration of the recorded audio (in seconds) /// - public string RecordingDuration { get; set; } + public string? RecordingDuration { get; set; } /// /// The number of channels in the final recording file as an integer. @@ -86,12 +86,12 @@ public class VoiceRequest : TwilioRequest /// /// The source of the recorded audio. /// - public string RecordingSource { get; set; } + public string? RecordingSource { get; set; } /// /// The key used to submit the digits /// - public string FinishedOnKey { get; set; } + public string? FinishedOnKey { get; set; } #endregion @@ -100,27 +100,27 @@ public class VoiceRequest : TwilioRequest /// /// The unique 34 character ID of the transcription /// - public string TranscriptionSid { get; set; } + public string? TranscriptionSid { get; set; } /// /// Contains the text of the transcription /// - public string TranscriptionText { get; set; } + public string? TranscriptionText { get; set; } /// /// The status of the transcription attempt: either 'completed' or 'failed' /// - public string TranscriptionStatus { get; set; } + public string? TranscriptionStatus { get; set; } /// /// The URL for the transcription's REST API resource /// - public string TranscriptionUrl { get; set; } + public string? TranscriptionUrl { get; set; } /// /// The unique 34 character ID of the recording from which the transcription was generated /// - public string RecordingSid { get; set; } + public string? RecordingSid { get; set; } #endregion @@ -129,17 +129,17 @@ public class VoiceRequest : TwilioRequest /// /// The outcome of the Dial attempt. See the DialCallStatus section below for details /// - public string DialCallStatus { get; set; } + public string? DialCallStatus { get; set; } /// /// The call sid of the new call leg. This parameter is not sent after dialing a conference /// - public string DialCallSid { get; set; } + public string? DialCallSid { get; set; } /// /// The duration in seconds of the dialed call. This parameter is not sent after dialing a conference /// - public string DialCallDuration { get; set; } + public string? DialCallDuration { get; set; } #endregion @@ -148,22 +148,22 @@ public class VoiceRequest : TwilioRequest /// /// The Twilio SIP Domain to which the INVITE was sent /// - public string SipDomain { get; set; } + public string? SipDomain { get; set; } /// /// The username given when authenticating the request, if Credential List is the authentication method. /// - public string SipUsername { get; set; } + public string? SipUsername { get; set; } /// /// The Call-Id of the incoming INVITE /// - public string SipCallId { get; set; } + public string? SipCallId { get; set; } /// /// The IP Address the incoming INVITE came from. /// - public string SipSourceIp { get; set; } + public string? SipSourceIp { get; set; } #endregion } \ No newline at end of file diff --git a/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs b/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs index 0c2eb26..dca22f0 100644 --- a/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs +++ b/src/Twilio.AspNet.Core.UnitTests/ContextMocks.cs @@ -1,7 +1,4 @@ -using System; using System.Net; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Http; namespace Twilio.AspNet.Core.UnitTests; @@ -11,12 +8,12 @@ public class ContextMocks public Moq.Mock HttpContext { get; set; } public Moq.Mock Request { get; set; } - public ContextMocks(bool isLocal, FormCollection form = null, bool isProxied = false) : this("", isLocal, form, + public ContextMocks(bool isLocal, FormCollection? form = null, bool isProxied = false) : this("", isLocal, form, isProxied) { } - public ContextMocks(string urlOverride, bool isLocal, FormCollection form = null, bool isProxied = false) + public ContextMocks(string urlOverride, bool isLocal, FormCollection? form = null, bool isProxied = false) { var headers = new HeaderDictionary(); headers.Add("X-Twilio-Signature", CalculateSignature(urlOverride, form)); @@ -41,7 +38,7 @@ public ContextMocks(string urlOverride, bool isLocal, FormCollection form = null Request.Setup(x => x.Host).Returns(new HostString(uri.Host)); Request.Setup(x => x.Path).Returns(new PathString(uri.AbsolutePath)); - if (form == null) return; + if (form is null) return; Request.Setup(x => x.Method).Returns("POST"); Request.Setup(x => x.Form).Returns(form); Request.Setup(x => x.ReadFormAsync(new CancellationToken())) @@ -52,7 +49,7 @@ public ContextMocks(string urlOverride, bool isLocal, FormCollection form = null public const string FakeUrl = "https://api.example.com/webhook"; public const string FakeAuthToken = "thisisafakeauthtoken"; - private static string CalculateSignature(string urlOverride, FormCollection form) + private static string CalculateSignature(string? urlOverride, FormCollection? form) => ValidationHelper.CalculateSignature( string.IsNullOrEmpty(urlOverride) ? FakeUrl : urlOverride, FakeAuthToken, diff --git a/src/Twilio.AspNet.Core.UnitTests/Extensions.cs b/src/Twilio.AspNet.Core.UnitTests/Extensions.cs index 6afe54c..3365a41 100644 --- a/src/Twilio.AspNet.Core.UnitTests/Extensions.cs +++ b/src/Twilio.AspNet.Core.UnitTests/Extensions.cs @@ -1,6 +1,3 @@ -using System; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Options; namespace Twilio.AspNet.Core.UnitTests; diff --git a/src/Twilio.AspNet.Core.UnitTests/MinimalApiTwiMLResultTests.cs b/src/Twilio.AspNet.Core.UnitTests/MinimalApiTwiMLResultTests.cs index 134b55c..3d904e2 100644 --- a/src/Twilio.AspNet.Core.UnitTests/MinimalApiTwiMLResultTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/MinimalApiTwiMLResultTests.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Twilio.TwiML; using Xunit; diff --git a/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs b/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs index fc21e29..bdb5cb3 100644 --- a/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/RequestValidationHelperTests.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; using Xunit; diff --git a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj index d20db32..5622eeb 100644 --- a/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj +++ b/src/Twilio.AspNet.Core.UnitTests/Twilio.AspNet.Core.UnitTests.csproj @@ -2,7 +2,8 @@ net9.0 false - disable + enable + enable diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs index 5d7c150..2f9a898 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioClientOptionsTests.cs @@ -1,8 +1,5 @@ -using System.Collections.Generic; -using System.IO; using System.Text; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -35,7 +32,7 @@ public void AddTwilioClient_With_Callback_Should_Match_Configuration() serviceCollection.AddSingleton(BuildEmptyConfiguration()); serviceCollection.AddTwilioClient((_, options) => { - var client = ValidTwilioOptions.Client; + var client = ValidTwilioOptions.Client ?? throw new Exception("Client options not configured."); options.AccountSid = client.AccountSid; options.AuthToken = client.AuthToken; options.ApiKeySid = client.ApiKeySid; @@ -82,8 +79,8 @@ public void AddTwilioClient_Should_Fallback_To_AuthToken() var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), - new KeyValuePair("Twilio:Client:AccountSid", ValidTwilioOptions.Client.AccountSid) + new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), + new KeyValuePair("Twilio:Client:AccountSid", ValidTwilioOptions.Client.AccountSid) ]) .Build(); @@ -164,8 +161,8 @@ public void AddTwilioClient_AuthToken_Without_Config_Should_Sanitize_Options() var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), - new KeyValuePair("Twilio:Client:AuthToken", "AuthToken") + new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), + new KeyValuePair("Twilio:Client:AuthToken", "AuthToken") ]).Build(); serviceCollection.AddSingleton(configuration); serviceCollection.AddTwilioClient(); @@ -186,9 +183,9 @@ public void AddTwilioClient_ApiKey_Without_Config_Should_Sanitize_Options() var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), - new KeyValuePair("Twilio:Client:ApiKeySid", "ApiKeySid"), - new KeyValuePair("Twilio:Client:ApiKeySecret", "ApiKeySecret") + new KeyValuePair("Twilio:Client:AccountSid", "AccountSid"), + new KeyValuePair("Twilio:Client:ApiKeySid", "ApiKeySid"), + new KeyValuePair("Twilio:Client:ApiKeySecret", "ApiKeySecret") ]).Build(); serviceCollection.AddSingleton(configuration); serviceCollection.AddTwilioClient(); diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioClientTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioClientTests.cs index 151dbf9..d133b73 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioClientTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioClientTests.cs @@ -1,9 +1,6 @@ -using System; -using System.IO; using System.Reflection; using System.Text; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -66,8 +63,8 @@ public void AddTwilioClient_With_ValidOptions_Should_AddTwilioClient() using var scope = host.Services.CreateScope(); var twilioRestClients = new[] { - scope.ServiceProvider.GetService(), - (TwilioRestClient)scope.ServiceProvider.GetService() + scope.ServiceProvider.GetRequiredService(), + (TwilioRestClient)scope.ServiceProvider.GetRequiredService() }; foreach (var client in twilioRestClients) { @@ -82,15 +79,16 @@ public void AddTwilioClient_With_ApiKeyOptions_Should_Match_Properties() using var scope = host.Services.CreateScope(); var client = scope.ServiceProvider.GetRequiredService(); + Assert.NotNull(ApiKeyTwilioOptions.Client); Assert.Equal(ApiKeyTwilioOptions.Client.Region, client.Region); Assert.Equal(ApiKeyTwilioOptions.Client.Edge, client.Edge); Assert.Equal(ApiKeyTwilioOptions.Client.AccountSid, client.AccountSid); Assert.Equal(ApiKeyTwilioOptions.Client.LogLevel, client.LogLevel); Assert.Equal(ApiKeyTwilioOptions.Client.ApiKeySid, - typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); Assert.Equal(ApiKeyTwilioOptions.Client.ApiKeySecret, - typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); } @@ -101,15 +99,16 @@ public void AddTwilioClient_With_AuthTokenOptions_Should_Match_Properties() using var scope = host.Services.CreateScope(); var client = scope.ServiceProvider.GetRequiredService(); + Assert.NotNull(AuthTokenTwilioOptions.Client); Assert.Equal(AuthTokenTwilioOptions.Client.Region, client.Region); Assert.Equal(AuthTokenTwilioOptions.Client.Edge, client.Edge); Assert.Equal(AuthTokenTwilioOptions.Client.AccountSid, client.AccountSid); Assert.Equal(AuthTokenTwilioOptions.Client.LogLevel, client.LogLevel); Assert.Equal(AuthTokenTwilioOptions.Client.AccountSid, - typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); Assert.Equal(AuthTokenTwilioOptions.Client.AuthToken, - typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); } @@ -167,15 +166,16 @@ public async Task AddTwilioClient_Should_Use_Reloaded_Configuration() { var client = scope.ServiceProvider.GetRequiredService(); + Assert.NotNull(ValidTwilioOptions.Client); Assert.Equal(ValidTwilioOptions.Client.Region, client.Region); Assert.Equal(ValidTwilioOptions.Client.Edge, client.Edge); Assert.Equal(ValidTwilioOptions.Client.AccountSid, client.AccountSid); Assert.Equal(ValidTwilioOptions.Client.LogLevel, client.LogLevel); Assert.Equal(ValidTwilioOptions.Client.ApiKeySid, - typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); Assert.Equal(ValidTwilioOptions.Client.ApiKeySecret, - typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); } @@ -211,10 +211,10 @@ public async Task AddTwilioClient_Should_Use_Reloaded_Configuration() Assert.Equal(updatedOptions.Client.AccountSid, client.AccountSid); Assert.Equal(updatedOptions.Client.LogLevel, client.LogLevel); Assert.Equal(updatedOptions.Client.AccountSid, - typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_username", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); Assert.Equal(updatedOptions.Client.AuthToken, - typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance)! + typeof(TwilioRestClient).GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(client)); } } @@ -228,7 +228,7 @@ public void AddTwilioClient_Should_Add_Named_HttpClient() var twilioRestClient = scope.ServiceProvider.GetService(); var actualHttpClient = (System.Net.Http.HttpClient)typeof(SystemNetHttpClient) - .GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance)! + .GetField("_httpClient", BindingFlags.NonPublic | BindingFlags.Instance) .GetValue(twilioRestClient.HttpClient); Assert.NotNull(actualHttpClient); diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioControllerExtensionTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioControllerExtensionTests.cs index 78ad6e7..5d0cf0d 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioControllerExtensionTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioControllerExtensionTests.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Threading.Tasks; -using System.Xml.Linq; +using System.Xml.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioControllerTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioControllerTests.cs index bacdfee..c9e7418 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioControllerTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioControllerTests.cs @@ -1,6 +1,4 @@ -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; diff --git a/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs b/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs index 0ff8cf0..a58780a 100644 --- a/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/TwilioRequestValidationOptionsTests.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -51,12 +47,12 @@ public void AddTwilioRequestValidation_From_Configuration_Should_Match_Configura var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), - new KeyValuePair( + new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken), + new KeyValuePair( "Twilio:RequestValidation:AuthToken", ValidTwilioOptions.RequestValidation.AuthToken), - new KeyValuePair( + new KeyValuePair( "Twilio:RequestValidation:BaseUrlOverride", ValidTwilioOptions.RequestValidation.BaseUrlOverride), - new KeyValuePair( + new KeyValuePair( "Twilio:RequestValidation:AllowLocal", ValidTwilioOptions.RequestValidation.AllowLocal.ToString()) ]) .Build(); @@ -121,7 +117,7 @@ public void AddTwilioRequestValidation_Should_Fallback_To_AuthToken() var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken) + new KeyValuePair("Twilio:AuthToken", ValidTwilioOptions.AuthToken) ]) .Build(); @@ -156,8 +152,8 @@ public void AddTwilioRequestValidation_Without_AuthToken_Should_Throw() var serviceCollection = new ServiceCollection(); var configuration = new ConfigurationBuilder() .AddInMemoryCollection([ - new KeyValuePair("Twilio", null), - new KeyValuePair("Twilio:RequestValidation:AuthToken", null) + new KeyValuePair("Twilio", null), + new KeyValuePair("Twilio:RequestValidation:AuthToken", null) ]).Build(); serviceCollection.AddSingleton(configuration); diff --git a/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs b/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs index da3fa26..45e1540 100644 --- a/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs +++ b/src/Twilio.AspNet.Core.UnitTests/ValidateTwilioRequestTests.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; +using System.Text; using System.Text.Json; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; diff --git a/src/Twilio.AspNet.Core.UnitTests/ValidationHelper.cs b/src/Twilio.AspNet.Core.UnitTests/ValidationHelper.cs index 757b981..abdb606 100644 --- a/src/Twilio.AspNet.Core.UnitTests/ValidationHelper.cs +++ b/src/Twilio.AspNet.Core.UnitTests/ValidationHelper.cs @@ -1,5 +1,3 @@ -using System; -using System.Linq; using System.Security.Cryptography; using System.Text; using Microsoft.AspNetCore.Http; @@ -8,10 +6,10 @@ namespace Twilio.AspNet.Core.UnitTests; internal static class ValidationHelper { - internal static string CalculateSignature(string url, string authToken, IFormCollection form) + internal static string CalculateSignature(string url, string authToken, IFormCollection? form) { var value = new StringBuilder(url); - if (form != null) + if (form is not null) { var sortedKeys = form.Keys.OrderBy(k => k, StringComparer.Ordinal).ToList(); foreach (var key in sortedKeys) diff --git a/src/Twilio.AspNet.Core/MinimalApiTwiMLResult.cs b/src/Twilio.AspNet.Core/MinimalApiTwiMLResult.cs index 6b5e9f7..07d5b18 100644 --- a/src/Twilio.AspNet.Core/MinimalApiTwiMLResult.cs +++ b/src/Twilio.AspNet.Core/MinimalApiTwiMLResult.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.AspNetCore.Http; diff --git a/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs b/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs index 6d527ab..54a99a8 100644 --- a/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs +++ b/src/Twilio.AspNet.Core/RequestValidationDependencyInjectionExtensions.cs @@ -1,4 +1,3 @@ -using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -86,9 +85,9 @@ private static void Sanitize(OptionsBuilder opti { optionsBuilder.PostConfigure(options => { - if (options.AuthToken == "") options.AuthToken = null; + if (options.AuthToken == "") options.AuthToken = null!; if (options.BaseUrlOverride == "") options.BaseUrlOverride = null; - if (options.BaseUrlOverride != null) options.BaseUrlOverride = options.BaseUrlOverride.TrimEnd('/'); + if (options.BaseUrlOverride is not null) options.BaseUrlOverride = options.BaseUrlOverride.TrimEnd('/'); }); } diff --git a/src/Twilio.AspNet.Core/RequestValidationHelper.cs b/src/Twilio.AspNet.Core/RequestValidationHelper.cs index 6d0dfc2..2709bf5 100644 --- a/src/Twilio.AspNet.Core/RequestValidationHelper.cs +++ b/src/Twilio.AspNet.Core/RequestValidationHelper.cs @@ -1,7 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading.Tasks; +using System.Net; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -27,11 +24,11 @@ internal static async Task IsValidRequestAsync(HttpContext context) var authToken = options.AuthToken; var baseUrlOverride = options.BaseUrlOverride; - var allowLocal = options.AllowLocal; + var allowLocal = options.AllowLocal ?? false; var request = context.Request; - string urlOverride = null; + string? urlOverride = null; if (!string.IsNullOrEmpty(baseUrlOverride)) { urlOverride = $"{baseUrlOverride}{request.Path}{request.QueryString}"; @@ -67,7 +64,7 @@ public static Task IsValidRequestAsync(HttpContext context, string authTok public static async Task IsValidRequestAsync( HttpContext context, string authToken, - string urlOverride, + string? urlOverride, bool allowLocal = false ) { @@ -107,7 +104,7 @@ public static bool IsValidRequest(HttpContext context, string authToken, bool al public static bool IsValidRequest( HttpContext context, string authToken, - string urlOverride, + string? urlOverride, bool allowLocal = false ) { @@ -125,7 +122,7 @@ public static bool IsValidRequest( ? $"{request.Scheme}://{(request.IsHttps ? request.Host.Host : request.Host.ToUriComponent())}{request.Path}{request.QueryString}" : urlOverride; - Dictionary parameters = null; + Dictionary? parameters = null; if (request.HasFormContentType) { parameters = request.Form.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); @@ -148,9 +145,9 @@ private static bool IsLocal(HttpRequest req) } var connection = req.HttpContext.Connection; - if (connection.RemoteIpAddress != null) + if (connection.RemoteIpAddress is not null) { - if (connection.LocalIpAddress != null) + if (connection.LocalIpAddress is not null) { return connection.RemoteIpAddress.Equals(connection.LocalIpAddress); } @@ -159,7 +156,7 @@ private static bool IsLocal(HttpRequest req) } // for in memory TestServer or when dealing with default connection info - if (connection.RemoteIpAddress == null && connection.LocalIpAddress == null) + if (connection.RemoteIpAddress is null && connection.LocalIpAddress is null) { return true; } diff --git a/src/Twilio.AspNet.Core/TwiMLResult.cs b/src/Twilio.AspNet.Core/TwiMLResult.cs index d365aa0..e88ea39 100644 --- a/src/Twilio.AspNet.Core/TwiMLResult.cs +++ b/src/Twilio.AspNet.Core/TwiMLResult.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using System.Xml.Linq; +using System.Xml.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -35,7 +34,7 @@ public async Task ExecuteResultAsync(ActionContext actionContext) private async Task WriteTwiMLToResponse(HttpResponse response) { response.ContentType = "application/xml"; - if (_twiml == null) + if (_twiml is null) { await response.WriteAsync(""); return; diff --git a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj index bea3b92..7d56dae 100644 --- a/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj +++ b/src/Twilio.AspNet.Core/Twilio.AspNet.Core.csproj @@ -2,11 +2,14 @@ net7.0;net8.0;net9.0 13 + enable + enable Library 0.0.0-alpha Twilio.AspNet.Core 0.0.0-alpha Twilio Labs + Twilio helper library for ASP.NET Core Twilio helper library for ASP.NET Core false Refer to the changelog at https://github.com/twilio-labs/twilio-aspnet/blob/main/CHANGELOG.md @@ -15,6 +18,7 @@ Apache-2.0 https://github.com/twilio-labs/twilio-aspnet https://github.com/twilio-labs/twilio-aspnet.git + git https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/twilio-icon-64x64.png icon.png README.md diff --git a/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs b/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs index 1bab313..0f64aeb 100644 --- a/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs +++ b/src/Twilio.AspNet.Core/TwilioClientDependencyInjectionExtensions.cs @@ -1,5 +1,3 @@ -using System; -using System.Net.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -89,7 +87,7 @@ private static void ConfigureDefaultOptions(OptionsBuilder { throw new Exception("Twilio options not configured."); } - + var clientSection = config.GetSection("Twilio:Client"); if (clientSection.Exists() == false) { @@ -99,7 +97,7 @@ private static void ConfigureDefaultOptions(OptionsBuilder clientSection.Bind(options); var authTokenFallback = twilioSection["AuthToken"]; - if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) + if (string.IsNullOrEmpty(options.AuthToken) && !string.IsNullOrEmpty(authTokenFallback)) options.AuthToken = authTokenFallback; }); optionsBuilder.Services.AddSingleton< @@ -128,7 +126,7 @@ private static void AddServices(IServiceCollection services) private static void Sanitize(TwilioClientOptions options) { - if (options.AccountSid == "") options.AccountSid = null; + if (options.AccountSid == "") options.AccountSid = null!; if (options.AuthToken == "") options.AuthToken = null; if (options.ApiKeySid == "") options.ApiKeySid = null; if (options.ApiKeySecret == "") options.ApiKeySecret = null; @@ -136,7 +134,7 @@ private static void Sanitize(TwilioClientOptions options) if (options.Edge == "") options.Edge = null; if (options.LogLevel == "") options.LogLevel = null; } - + private static void Validate(OptionsBuilder optionsBuilder) { optionsBuilder.Validate( @@ -166,11 +164,8 @@ private static void ConfigureCredentialType(TwilioClientOptions options) { if (options.CredentialType != CredentialType.Unspecified) return; - var isApiKeyConfigured = options.AccountSid != null && - options.ApiKeySid != null && - options.ApiKeySecret != null; - var isAuthTokenConfigured = options.AccountSid != null && - options.AuthToken != null; + var isApiKeyConfigured = options is { AccountSid: not null, ApiKeySid: not null, ApiKeySecret: not null }; + var isAuthTokenConfigured = options is { AccountSid: not null, AuthToken: not null }; if (isApiKeyConfigured) options.CredentialType = CredentialType.ApiKey; else if (isAuthTokenConfigured) options.CredentialType = CredentialType.AuthToken; @@ -211,7 +206,7 @@ private static TwilioRestClient CreateTwilioClient(IServiceProvider provider) throw new Exception("This code should be unreachable"); } - if (options.LogLevel != null) + if (options.LogLevel is not null) { client.LogLevel = options.LogLevel; } diff --git a/src/Twilio.AspNet.Core/TwilioOptions.cs b/src/Twilio.AspNet.Core/TwilioOptions.cs index f0a85a3..fd8057d 100644 --- a/src/Twilio.AspNet.Core/TwilioOptions.cs +++ b/src/Twilio.AspNet.Core/TwilioOptions.cs @@ -2,28 +2,28 @@ namespace Twilio.AspNet.Core; public class TwilioOptions { - public string AuthToken { get; set; } - public TwilioClientOptions Client { get; set; } - public TwilioRequestValidationOptions RequestValidation { get; set; } + public string? AuthToken { get; set; } + public TwilioClientOptions? Client { get; set; } + public TwilioRequestValidationOptions? RequestValidation { get; set; } } public class TwilioClientOptions { - public string AccountSid { get; set; } - public string AuthToken { get; set; } - public string ApiKeySid { get; set; } - public string ApiKeySecret { get; set; } + public string AccountSid { get; set; } = null!; + public string? AuthToken { get; set; } + public string? ApiKeySid { get; set; } + public string? ApiKeySecret { get; set; } public CredentialType CredentialType { get; set; } - public string Region { get; set; } - public string Edge { get; set; } - public string LogLevel { get; set; } + public string? Region { get; set; } + public string? Edge { get; set; } + public string? LogLevel { get; set; } } public class TwilioRequestValidationOptions { - public string AuthToken { get; set; } - public bool AllowLocal { get; set; } - public string BaseUrlOverride { get; set; } + public string AuthToken { get; set; } = null!; + public bool? AllowLocal { get; set; } + public string? BaseUrlOverride { get; set; } } public enum CredentialType diff --git a/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs b/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs index 3584308..aad0836 100644 --- a/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs +++ b/src/Twilio.AspNet.Core/ValidateRequestAttribute.cs @@ -1,6 +1,4 @@ -using System; -using System.Net; -using System.Threading.Tasks; +using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; diff --git a/src/Twilio.AspNet.Core/ValidateTwilioRequestFilter.cs b/src/Twilio.AspNet.Core/ValidateTwilioRequestFilter.cs index 9659b00..b4be3a4 100644 --- a/src/Twilio.AspNet.Core/ValidateTwilioRequestFilter.cs +++ b/src/Twilio.AspNet.Core/ValidateTwilioRequestFilter.cs @@ -1,5 +1,4 @@ using System.Net; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -11,7 +10,7 @@ namespace Twilio.AspNet.Core; /// public class ValidateTwilioRequestFilter : IEndpointFilter { - public async ValueTask InvokeAsync( + public async ValueTask InvokeAsync( EndpointFilterInvocationContext efiContext, EndpointFilterDelegate next ) diff --git a/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs b/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs index b30145d..9cf7df5 100644 --- a/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs +++ b/src/Twilio.AspNet.Core/ValidateTwilioRequestMiddleware.cs @@ -1,5 +1,4 @@ using System.Net; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; diff --git a/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs b/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs index 8b7ee93..8b7866e 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs +++ b/src/Twilio.AspNet.Mvc.UnitTests/ContextMocks.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Specialized; -using System.Linq; using System.Security.Cryptography; using System.Text; using System.Web; @@ -16,11 +14,11 @@ public class ContextMocks public Moq.Mock Response { get; set; } public Moq.Mock ControllerContext { get; set; } - public ContextMocks(bool isLocal, NameValueCollection form = null) : this("", isLocal, form) + public ContextMocks(bool isLocal, NameValueCollection? form = null) : this("", isLocal, form) { } - public ContextMocks(string urlOverride, bool isLocal, NameValueCollection form = null) + public ContextMocks(string urlOverride, bool isLocal, NameValueCollection? form = null) { var headers = new NameValueCollection(); headers.Add("X-Twilio-Signature", CalculateSignature(urlOverride, form)); @@ -37,7 +35,7 @@ public ContextMocks(string urlOverride, bool isLocal, NameValueCollection form = Request.Setup(x => x.IsLocal).Returns(isLocal); Request.Setup(x => x.Headers).Returns(headers); Request.Setup(x => x.Url).Returns(new Uri(FakeUrl)); - if (form != null) + if (form is not null) { Request.Setup(x => x.HttpMethod).Returns("POST"); Request.Setup(x => x.Form).Returns(form); @@ -49,12 +47,12 @@ public ContextMocks(string urlOverride, bool isLocal, NameValueCollection form = public const string FakeUrl = "https://api.example.com/webhook"; public const string FakeAuthToken = "thisisafakeauthtoken"; - private string CalculateSignature(string urlOverride, NameValueCollection form) + private string CalculateSignature(string? urlOverride, NameValueCollection? form) { var value = new StringBuilder(); value.Append(string.IsNullOrEmpty(urlOverride) ? FakeUrl : urlOverride); - if (form != null) + if (form is not null) { var sortedKeys = form.AllKeys.OrderBy(k => k, StringComparer.Ordinal).ToList(); foreach (var key in sortedKeys) diff --git a/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs b/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs index 278133c..82dcd6e 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs +++ b/src/Twilio.AspNet.Mvc.UnitTests/TwiMLResultTests.cs @@ -1,6 +1,4 @@ -using System; -using System.IO; -using System.Xml.Linq; +using System.Xml.Linq; using Twilio.TwiML; using Xunit; @@ -64,7 +62,7 @@ public void TestVoiceResponseUnformattedUtf16() [Fact] public void TestNullTwiml() { - var result = new TwiMLResult(null); + var result = new TwiMLResult(null!); result.ExecuteResult(_mocks.ControllerContext.Object); Assert.Equal( diff --git a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj index b872770..66516de 100644 --- a/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj +++ b/src/Twilio.AspNet.Mvc.UnitTests/Twilio.AspNet.Mvc.UnitTests.csproj @@ -3,7 +3,10 @@ Library net471 + false 13 + enable + enable diff --git a/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs b/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs index 6cc2908..c8a0a6c 100644 --- a/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs +++ b/src/Twilio.AspNet.Mvc/RequestValidationHelper.cs @@ -1,5 +1,4 @@ using System.Collections.Specialized; -using System.Linq; using System.Web; using Twilio.Security; @@ -35,7 +34,7 @@ public static bool IsValidRequest(HttpContextBase context, string authToken, boo /// Skip validation for local requests. /// ⚠️ Only use this during development, as this will make your application vulnerable to Server-Side Request Forgery. /// - public static bool IsValidRequest(HttpContextBase context, string authToken, string urlOverride, bool allowLocal = false) + public static bool IsValidRequest(HttpContextBase context, string authToken, string? urlOverride, bool allowLocal = false) { if (allowLocal && context.Request.IsLocal && !context.Request.Headers.AllKeys.Contains("X-Forwarded-For")) { @@ -46,8 +45,8 @@ public static bool IsValidRequest(HttpContextBase context, string authToken, str var validator = new RequestValidator(authToken); return validator.Validate( url: fullUrl, - parameters: context.Request?.Form ?? new NameValueCollection(), - expected: context.Request?.Headers["X-Twilio-Signature"] + parameters: context.Request.Form ?? new NameValueCollection(), + expected: context.Request.Headers["X-Twilio-Signature"] ); } } \ No newline at end of file diff --git a/src/Twilio.AspNet.Mvc/TwiMLResult.cs b/src/Twilio.AspNet.Mvc/TwiMLResult.cs index 4ff313f..7d297ff 100644 --- a/src/Twilio.AspNet.Mvc/TwiMLResult.cs +++ b/src/Twilio.AspNet.Mvc/TwiMLResult.cs @@ -24,7 +24,7 @@ public override void ExecuteResult(ControllerContext controllerContext) var encoding = response.Output.Encoding.BodyName; response.ContentType = "application/xml"; - if (_dataTwiml == null) + if (_dataTwiml is null) { response.Output.Write($""); return; diff --git a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj index f3f3f67..3d5f583 100644 --- a/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj +++ b/src/Twilio.AspNet.Mvc/Twilio.AspNet.Mvc.csproj @@ -4,10 +4,13 @@ Library net462 13 + enable + enable 0.0.0-alpha Twilio.AspNet.Mvc 0.0.0-alpha Twilio Labs + Twilio helper library for ASP.NET MVC Twilio helper library for ASP.NET MVC on .NET Framework. false Refer to the changelog at https://github.com/twilio-labs/twilio-aspnet/blob/main/CHANGELOG.md @@ -16,6 +19,7 @@ Apache-2.0 https://github.com/twilio-labs/twilio-aspnet https://github.com/twilio-labs/twilio-aspnet.git + git https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/twilio-icon-64x64.png icon.png README.md diff --git a/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs b/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs index 5202034..4e0d219 100644 --- a/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs +++ b/src/Twilio.AspNet.Mvc/TwilioUriHelper.cs @@ -1,5 +1,4 @@ -using System; -using System.Web.Mvc; +using System.Web.Mvc; namespace Twilio.AspNet.Mvc; diff --git a/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs b/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs index f19afda..b4494f1 100644 --- a/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs +++ b/src/Twilio.AspNet.Mvc/ValidateRequestAttribute.cs @@ -1,5 +1,4 @@ -using System; -using System.Configuration; +using System.Configuration; using System.Net; using System.Web.Mvc; @@ -11,8 +10,8 @@ namespace Twilio.AspNet.Mvc; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Module)] public class ValidateRequestAttribute : ActionFilterAttribute { - protected internal string AuthToken { get; set; } - protected internal string BaseUrlOverride { get; set; } + protected internal string? AuthToken { get; set; } + protected internal string? BaseUrlOverride { get; set; } protected internal bool AllowLocal { get; set; } /// @@ -47,7 +46,7 @@ protected virtual void ConfigureProperties() ?.TrimEnd('/'); var allowLocalAppSetting = appSettings["twilio:requestValidation:allowLocal"]; - AllowLocal = allowLocalAppSetting != null + AllowLocal = allowLocalAppSetting is not null ? bool.Parse(allowLocalAppSetting) : requestValidationConfiguration?.AllowLocal ?? false; @@ -56,13 +55,13 @@ protected virtual void ConfigureProperties() public override void OnActionExecuting(ActionExecutingContext filterContext) { var httpContext = filterContext.HttpContext; - string urlOverride = null; - if (BaseUrlOverride != null) + string? urlOverride = null; + if (BaseUrlOverride is not null) { urlOverride = $"{BaseUrlOverride}{httpContext.Request.Path}{httpContext.Request.QueryString}"; } - if (!RequestValidationHelper.IsValidRequest(filterContext.HttpContext, AuthToken, urlOverride, AllowLocal)) + if (!RequestValidationHelper.IsValidRequest(filterContext.HttpContext, AuthToken!, urlOverride, AllowLocal)) { filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); } From 19d8f0909f0d7d175b3d8bf2a6578433d4f2b7a2 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:58:48 -0500 Subject: [PATCH 06/11] Add missing permissions --- .github/workflows/ci.yml | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd0f3b4..efc432a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: default: 0.0.0-alpha required: false type: string - + push: branches: [ "main" ] paths: @@ -30,16 +30,18 @@ on: description: 'The version of the library to use when compiling and packaging.' required: true -env: +env: CI: true DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_NOLOGO: true - + jobs: build: name: Build, test, and pack runs-on: windows-latest permissions: + contents: read + actions: read checks: write steps: - uses: actions/checkout@v4 @@ -62,18 +64,18 @@ jobs: } } shell: pwsh - + # Build and pack Twilio.AspNet.Common - name: (Twilio.AspNet.Common) Restore run: dotnet restore working-directory: src/Twilio.AspNet.Common/ shell: pwsh - + - name: (Twilio.AspNet.Common) Build run: dotnet build --no-restore --configuration Release working-directory: src/Twilio.AspNet.Common/ shell: pwsh - + - name: (Twilio.AspNet.Common) Pack run: dotnet pack -c Release -o ..\..\ working-directory: src/Twilio.AspNet.Common/ @@ -86,28 +88,28 @@ jobs: path: | Twilio.AspNet.Common.${{ inputs.libraryVersion || '0.0.0-alpha' }}.nupkg Twilio.AspNet.Common.${{ inputs.libraryVersion || '0.0.0-alpha' }}.snupkg - + # Build, test, and pack Twilio.AspNet.Core - name: (Twilio.AspNet.Core) Restore run: dotnet restore working-directory: src/Twilio.AspNet.Core/ shell: pwsh - + - name: (Twilio.AspNet.Core) Build run: dotnet build --no-restore --configuration Release working-directory: src/Twilio.AspNet.Core/ shell: pwsh - + - name: (Twilio.AspNet.Core.UnitTests) Restore run: dotnet restore working-directory: src/Twilio.AspNet.Core.UnitTests/ shell: pwsh - + - name: (Twilio.AspNet.Core.UnitTests) Build run: dotnet build --no-restore working-directory: src/Twilio.AspNet.Core.UnitTests/ shell: pwsh - + - name: (Twilio.AspNet.Core.UnitTests) Test run: dotnet test --no-build --logger trx working-directory: src/Twilio.AspNet.Core.UnitTests/ @@ -120,7 +122,7 @@ jobs: name: Twilio.AspNet.Core.UnitTests path: src/Twilio.AspNet.Core.UnitTests/TestResults/*.trx reporter: dotnet-trx - + - name: (Twilio.AspNet.Core) Pack run: dotnet pack -c Release -o ..\..\ working-directory: src/Twilio.AspNet.Core/ @@ -133,33 +135,33 @@ jobs: path: | Twilio.AspNet.Core.${{ inputs.libraryVersion || '0.0.0-alpha' }}.nupkg Twilio.AspNet.Core.${{ inputs.libraryVersion || '0.0.0-alpha' }}.snupkg - + # Build, test, and pack Twilio.AspNet.Mvc - name: (Twilio.AspNet.Mvc) Restore run: dotnet restore working-directory: src/Twilio.AspNet.Mvc/ shell: pwsh - + - name: (Twilio.AspNet.Mvc) Build run: dotnet build --no-restore --configuration Release working-directory: src/Twilio.AspNet.Mvc/ shell: pwsh - + - name: (Twilio.AspNet.Mvc.UnitTests) Restore run: dotnet restore working-directory: src/Twilio.AspNet.Mvc.UnitTests/ shell: pwsh - + - name: (Twilio.AspNet.Mvc.UnitTests) Build run: dotnet build --no-restore working-directory: src/Twilio.AspNet.Mvc.UnitTests/ shell: pwsh - + - name: (Twilio.AspNet.Mvc.UnitTests) Test run: dotnet test --no-build --logger trx working-directory: src/Twilio.AspNet.Mvc.UnitTests/ shell: pwsh - + - name: (Twilio.AspNet.Mvc.UnitTests) Report Tests uses: dorny/test-reporter@v1 if: success() || failure() # run this step even if previous step failed @@ -167,12 +169,12 @@ jobs: name: Twilio.AspNet.Mvc.UnitTests path: src/Twilio.AspNet.Mvc.UnitTests/TestResults/*.trx reporter: dotnet-trx - + - name: (Twilio.AspNet.Mvc) Pack run: dotnet pack -c Release -o ..\..\ working-directory: src/Twilio.AspNet.Mvc/ shell: pwsh - + - name: (Twilio.AspNet.Mvc) Upload Artifacts uses: actions/upload-artifact@v4 with: From 51fe300d81bec49e0b098b7e25d9a0b357480826 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:14:20 -0500 Subject: [PATCH 07/11] Remove test reporter from GHA CI --- .github/workflows/ci.yml | 24 ++++-------------------- src/Twilio.AspNet.sln | 12 ++++++++++++ 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d5e5b8..60f425f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,20 +109,12 @@ jobs: shell: pwsh - name: (Twilio.AspNet.Core.UnitTests) Test - run: dotnet test --no-build --logger trx + run: dotnet test --no-build --no-restore working-directory: src/Twilio.AspNet.Core.UnitTests/ shell: pwsh - - name: (Twilio.AspNet.Core.UnitTests) Report Tests - uses: dorny/test-reporter@v1 - if: success() || failure() # run this step even if previous step failed - with: - name: Twilio.AspNet.Core.UnitTests - path: src/Twilio.AspNet.Core.UnitTests/TestResults/*.trx - reporter: dotnet-trx - - name: (Twilio.AspNet.Core) Pack - run: dotnet pack -c Release -o ..\..\ + run: dotnet pack --no-build --no-restore -c Release -o ..\..\ working-directory: src/Twilio.AspNet.Core/ shell: pwsh @@ -156,20 +148,12 @@ jobs: shell: pwsh - name: (Twilio.AspNet.Mvc.UnitTests) Test - run: dotnet test --no-build --logger trx + run: dotnet test --no-build --no-restore working-directory: src/Twilio.AspNet.Mvc.UnitTests/ shell: pwsh - - name: (Twilio.AspNet.Mvc.UnitTests) Report Tests - uses: dorny/test-reporter@v1 - if: success() || failure() # run this step even if previous step failed - with: - name: Twilio.AspNet.Mvc.UnitTests - path: src/Twilio.AspNet.Mvc.UnitTests/TestResults/*.trx - reporter: dotnet-trx - - name: (Twilio.AspNet.Mvc) Pack - run: dotnet pack -c Release -o ..\..\ + run: dotnet pack --no-build --no-restore -c Release -o ..\..\ working-directory: src/Twilio.AspNet.Mvc/ shell: pwsh diff --git a/src/Twilio.AspNet.sln b/src/Twilio.AspNet.sln index e5d2ada..38324f7 100644 --- a/src/Twilio.AspNet.sln +++ b/src/Twilio.AspNet.sln @@ -31,6 +31,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Twilio.AspNet.Mvc.UnitTests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Twilio.AspNet.Core.UnitTests", "Twilio.AspNet.Core.UnitTests\Twilio.AspNet.Core.UnitTests.csproj", "{B3E732C9-27EF-4E96-B620-5B5DA57D9AD3}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{9B3D65CE-AF46-4122-885D-1CFE42D5EAFB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{4CAACCDD-6731-4617-9DE4-B6495B96DE08}" + ProjectSection(SolutionItems) = preProject + ..\.github\workflows\ci.yml = ..\.github\workflows\ci.yml + ..\.github\workflows\release.yml = ..\.github\workflows\release.yml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -64,4 +72,8 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7D0F9171-129A-4B05-809E-F501DBC23197} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9B3D65CE-AF46-4122-885D-1CFE42D5EAFB} = {339A75DF-3315-4CF3-9FD9-DAF1B0DB50B2} + {4CAACCDD-6731-4617-9DE4-B6495B96DE08} = {9B3D65CE-AF46-4122-885D-1CFE42D5EAFB} + EndGlobalSection EndGlobal From 8fa65cfd4bf82aac99a81bcf4acd5efb59bd8b85 Mon Sep 17 00:00:00 2001 From: Niels Swimberghe <3382717+Swimburger@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:22:42 -0500 Subject: [PATCH 08/11] Use .NET 9 in CI --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c020d1..28b9727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,13 +42,12 @@ jobs: permissions: checks: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: - dotnet-version: 7.0.x - dotnet-quality: ga + dotnet-version: 9.x - name: Update project versions run: | From 35d6502ecf39093797b76350df5154883211d0b7 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Tue, 3 Dec 2024 16:47:17 +0000 Subject: [PATCH 09/11] fix: src/testapps/AspNetFramework/packages.config to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-DOTNET-SYSTEMTEXTJSON-8168848 --- src/testapps/AspNetFramework/packages.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testapps/AspNetFramework/packages.config b/src/testapps/AspNetFramework/packages.config index 93f6156..e3c6e4d 100644 --- a/src/testapps/AspNetFramework/packages.config +++ b/src/testapps/AspNetFramework/packages.config @@ -14,7 +14,7 @@ - + From 569a2a80f5836aef848b6a8baee3dfb4247a888c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:48:01 +0000 Subject: [PATCH 10/11] Bump System.Text.Json in /src/testapps/AspNetFramework Bumps [System.Text.Json](https://github.com/dotnet/runtime) from 8.0.4 to 8.0.5. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.4...v8.0.5) --- updated-dependencies: - dependency-name: System.Text.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- src/testapps/AspNetFramework/AspNetFramework.csproj | 10 +++++----- src/testapps/AspNetFramework/Web.config | 10 +++++----- src/testapps/AspNetFramework/packages.config | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/testapps/AspNetFramework/AspNetFramework.csproj b/src/testapps/AspNetFramework/AspNetFramework.csproj index b59f032..da3c569 100644 --- a/src/testapps/AspNetFramework/AspNetFramework.csproj +++ b/src/testapps/AspNetFramework/AspNetFramework.csproj @@ -42,8 +42,8 @@ 4 - - ../packages/Microsoft.Bcl.AsyncInterfaces.8.0.0/lib/net462/Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.AsyncInterfaces.9.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll True @@ -74,11 +74,11 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - ../packages/System.Text.Encodings.Web.8.0.0/lib/net462/System.Text.Encodings.Web.dll + ..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll True - - ../packages/System.Text.Json.8.0.4/lib/net462/System.Text.Json.dll + + ..\packages\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll True diff --git a/src/testapps/AspNetFramework/Web.config b/src/testapps/AspNetFramework/Web.config index 770e76c..8857f51 100644 --- a/src/testapps/AspNetFramework/Web.config +++ b/src/testapps/AspNetFramework/Web.config @@ -45,15 +45,15 @@ - + - + - + @@ -73,7 +73,7 @@ - + @@ -85,7 +85,7 @@ - + diff --git a/src/testapps/AspNetFramework/packages.config b/src/testapps/AspNetFramework/packages.config index 93f6156..710c3ec 100644 --- a/src/testapps/AspNetFramework/packages.config +++ b/src/testapps/AspNetFramework/packages.config @@ -4,7 +4,7 @@ - + @@ -14,7 +14,7 @@ - + From 7e4cc1426eb43428db37b3ca5ab2adc7816470fd Mon Sep 17 00:00:00 2001 From: Amanda Lange Date: Wed, 4 Dec 2024 11:43:25 -0500 Subject: [PATCH 11/11] Updating build action --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1c63116..98b9d4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ env: jobs: build: uses: ./.github/workflows/ci.yml - if: contains('["Swimburger","dprothero"]', github.actor) + if: contains('["Swimburger","dprothero","AJLange"]', github.actor) name: Build, test, and pack permissions: checks: write @@ -29,7 +29,7 @@ jobs: secrets: inherit release: - if: contains('["Swimburger","dprothero"]', github.actor) + if: contains('["Swimburger","dprothero","AJLange"]', github.actor) runs-on: ubuntu-latest needs: [build] steps: