├── .gitignore ├── LICENSE.md ├── README.md ├── assets ├── logo-wide.png └── logo.png └── src ├── AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup ├── AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup.csproj └── MyStartup.cs ├── AzureFunctionsV2.HttpExtensions.Examples.Authorization ├── .gitignore ├── AuthorizedFuncs.cs ├── AzureFunctionsV2.HttpExtensions.Examples.Authorization.csproj ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── README.md └── host.json ├── AzureFunctionsV2.HttpExtensions.Examples.FunctionApp ├── .gitignore ├── AzureFunctionsV2.HttpExtensions.Examples.FunctionApp.csproj ├── BodyParameters.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── FormParameters.cs ├── HeaderParameters.cs ├── QueryParameters.cs ├── README.md └── host.json ├── AzureFunctionsV2.HttpExtensions.Fody ├── AzureFunctionsV2.HttpExtensions.Fody.csproj ├── AzureFunctionsV2.HttpExtensions.props ├── FunctionAsyncStateMachineMoveNextFinder.cs ├── Helpers.cs └── ModuleWeaver.cs ├── AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup ├── AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup.csproj └── Startup.cs ├── AzureFunctionsV2.HttpExtensions.Tests.FunctionApp ├── .gitignore ├── AuthTests.cs ├── AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.csproj ├── FodyTests.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── HeaderTests.cs ├── QueryParameterTests.cs ├── TestResultSets.cs └── host.json ├── AzureFunctionsV2.HttpExtensions.Tests ├── Authentication │ ├── ApikeyAuthenticatorTests.cs │ ├── AuthorizedFunctionDiscovererTests.cs │ ├── BasicAuthenticatorTests.cs │ ├── HttpAuthorizationFilterTests.cs │ ├── JwtAuthenticatorTests.cs │ └── OAuth2AuthenticatorTests.cs ├── AzureFunctionsV2.HttpExtensions.Tests.csproj ├── Helpers │ └── MockedFunctionRequestContext.cs ├── HttpRequestMetadataStorageFilterTests.cs ├── Infrastructure │ ├── DefaultHttpExceptionHandlerTests.cs │ ├── HttpExtensionsExceptionFilterTests.cs │ ├── HttpParamAssignmentFilterTests.cs │ ├── HttpParamValueDeserializerTests.cs │ └── HttpRequestStoreTests.cs └── Mocks │ ├── MockHttpContext.cs │ ├── MockHttpRequest.cs │ └── MockHttpResponse.cs ├── AzureFunctionsV2.HttpExtensions.sln └── AzureFunctionsV2.HttpExtensions ├── Annotations ├── HttpBodyAttribute.cs ├── HttpFormAttribute.cs ├── HttpHeaderAttribute.cs ├── HttpQueryAttribute.cs ├── HttpSourceAttribute.cs └── HttpTokenAttribute.cs ├── Authorization ├── ApiKeyAuthenticationParameters.cs ├── ApiKeyAuthenticator.cs ├── AuthorizedFunctionDiscoverer.cs ├── BasicAuthenticationParameters.cs ├── BasicAuthenticator.cs ├── HttpAuthenticationOptions.cs ├── HttpAuthorizationFilter.cs ├── HttpAuthorizeAttribute.cs ├── IApiKeyAuthenticator.cs ├── IAuthorizedFunctionDiscoverer.cs ├── IBasicAuthenticator.cs ├── IJwtAuthenticator.cs ├── IOAuth2Authenticator.cs ├── JwtAuthenticationParameters.cs ├── JwtAuthenticator.cs ├── OAuth2AuthenticationParameters.cs ├── OAuth2Authenticator.cs └── OpenIdConnectJwtValidationParameters.cs ├── AzureFunctionsV2.HttpExtensions.csproj ├── AzureFunctionsV2.HttpExtensions.nuspec ├── Exceptions ├── HttpAuthenticationException.cs ├── HttpAuthorizationException.cs ├── HttpExtensionsException.cs ├── ParameterFormatConversionException.cs └── ParameterRequiredException.cs ├── Extensions └── HttpAttributeExtensionsConfigProvider.cs ├── ExtensionsStartup.cs ├── IL ├── AssemblyUtils.cs ├── IILFunctionExceptionHandler.cs └── ILFunctionExceptionHandler.cs ├── Infrastructure ├── AttributedParameter.cs ├── DefaultHttpExceptionHandler.cs ├── ExtensionRegistration.cs ├── HttpExtensionsExceptionFilter.cs ├── HttpParam.cs ├── HttpParamAssignmentFilter.cs ├── HttpParamConverter.cs ├── HttpRequestMetadataStorageFilter.cs ├── HttpRequestStore.cs ├── HttpUser.cs ├── HttpUserConverter.cs ├── IHttpExceptionHandler.cs ├── IHttpParam.cs ├── IHttpParamValueDeserializer.cs └── IHttpRequestStore.cs ├── Utils └── HttpContextExtensions.cs └── nuget-pack.bat /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jussi Saarivirta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Azure Functions HTTP Extensions 2 | 3 | [![Wiki](https://img.shields.io/badge/docs-in%20wiki-green.svg?style=flat)](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki) 4 | [![Nuget](https://img.shields.io/nuget/v/AzureFunctionsV2.HttpExtensions.svg)](https://www.nuget.org/packages/AzureFunctionsV2.HttpExtensions/) 5 | 6 | ![Logo](assets/logo.png) 7 | 8 | This C# library extends the Azure Functions HTTP Trigger and adds useful extensions to 9 | make working with HTTP requests more fluent. It allows you to 10 | add HTTP parameters from headers, query parameters, body and form fields directly 11 | to the function signature. It also adds some boilerplate code to take advantage of 12 | Function Filters in v2 Functions, allowing you to specify cross-cutting Exception 13 | handling tasks combined with an overridable error response formatter. 14 | 15 | __Also see the [NSwag.AzureFunctionsV2 project](https://github.com/Jusas/NSwag.AzureFunctionsV2)__ which is 16 | a Swagger Generator supplementing NSwag, built specifically to produce Swagger JSON from Azure Function Apps and __fully 17 | supports the HttpExtensions project__, allowing you to pretty much use a convenient syntax with 18 | HTTP request parameters defined in the Function signature and getting the full benefits of an automatic 19 | Swagger JSON generation from the Function App assembly. These two projects combined make 20 | Azure Functions a different experience, and building APIs with Functions is now less of a hassle. 21 | 22 | ## Features 23 | 24 | - Enables you to define Function signatures similar to ASP.NET Controller conventions 25 | - Automatically deserializes objects, lists and arrays, supporting JSON and XML content out of the box 26 | - Provides basic input validation via JSON deserializer 27 | - Provides JWT, Basic, ApiKey and OAuth2 authentication/authorization via attributes, which is also customizable 28 | - Provides an exception filter, allowing more control over responses 29 | - Allows overriding default deserialization methods and exception handling with custom behaviour 30 | 31 | ### Example usage 32 | 33 | ```C# 34 | [HttpAuthorize(Scheme.Jwt)] 35 | [FunctionName("TestFunction")] 36 | public static async Task TestFunction ( 37 | [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "mymethod/{somestring}")] HttpRequest req, 38 | string somestring, 39 | [HttpQuery(Required = true)]HttpParam stringParam, 40 | [HttpQuery]HttpParam> intParams, 41 | [HttpQuery]HttpParam enumParam, 42 | [HttpBody]HttpParam myBodyObject, 43 | [HttpHeader(Name = "x-my-header")]HttpParam customHeader, 44 | [HttpToken]HttpUser user, 45 | ILogger log) 46 | { 47 | string blah = stringParam; // implicit operator 48 | 49 | foreach(var x in intParams.Value) { 50 | // ... 51 | } 52 | 53 | return new OkObjectResult(""); 54 | } 55 | ``` 56 | 57 | See the [wiki](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki) for details on 58 | the attributes and parameters. 59 | 60 | ### Error responses 61 | 62 | Assuming we're using the default implementation of __IHttpExceptionHandler__ and the above function was called with improper values in intParams, the function returns 400, with JSON content: 63 | 64 | ``` 65 | HTTP/1.1 400 Bad Request 66 | Date: Sun, 13 Jan 2019 16:12:30 GMT 67 | Content-Type: application/json 68 | Server: Kestrel 69 | Transfer-Encoding: chunked 70 | 71 | { 72 | "message": "Failed to assign parameter 'intParams' value", 73 | "parameter": "intParams" 74 | } 75 | ``` 76 | 77 | This functionality is provided by the exception filter. 78 | __Note: the exception filter is an optional feature and needs to be enabled manually__. Read more 79 | about this in the [wiki](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki/Exceptions) 80 | 81 | Further examples can be found from the example project __AzureFunctionsV2.HttpExtensions.Examples.FunctionApp__ and documentation on exception 82 | handling from the [wiki](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki). 83 | 84 | 85 | 86 | ## Documentation 87 | 88 | The project comes with an example project in the sources [AzureFunctionsV2.HttpExtensions.Examples.FunctionApp](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/tree/master/src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp) as well as with some [wiki](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki) documentation. Please check the wiki 89 | before opening an issue. 90 | 91 | 92 | ## How was this made? 93 | 94 | By using Binding attributes it's possible to create Function parameters 95 | that act as placeholders for the HttpRequest parameters, which are then assigned 96 | the proper values just before running the Function using Function Filters. 97 | 98 | The caveat is that it's necessary to use a container (HttpParam<>) for the parameters 99 | because the parameter binding happens before we can get access to the HttpRequest. The 100 | values get bound kind of too early for this to work, but we save the day by having 101 | access to both the placeholder containers and the HttpRequest upon the Function Filter 102 | running phase, which gets run just before the function gets called so we may assign 103 | the values to each HttpParam's Value property there. 104 | 105 | It's worth noticing that the Function Filters are a somewhat new feature and have been marked as obsolete - _however they're not obsolete, they have only been marked obsolete due to the team not having fully finished the features to consider them complete._ (see https://github.com/Azure/azure-webjobs-sdk/issues/1284) 106 | 107 | Also it's worth saying that the Azure Functions Exception Filter has no control over the return value of the Function 108 | and therefore a different approach has been taken in this library to tackle this issue. 109 | Read more about it in the [wiki](https://github.com/Jusas/AzureFunctionsV2.HttpExtensions/wiki/Exceptions). -------------------------------------------------------------------------------- /assets/logo-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusas/AzureFunctionsV2.HttpExtensions/796f18b9f5bff22646e4d9d50d4c0359c4e2c0bd/assets/logo-wide.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusas/AzureFunctionsV2.HttpExtensions/796f18b9f5bff22646e4d9d50d4c0359c4e2c0bd/assets/logo.png -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup/AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup/MyStartup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using AzureFunctionsV2.HttpExtensions.Authorization; 5 | using AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Hosting; 8 | using Microsoft.Extensions.DependencyInjection; 9 | 10 | [assembly: WebJobsStartup(typeof(MyStartup), "MyStartup")] 11 | 12 | namespace AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup 13 | { 14 | public class MyStartup : IWebJobsStartup 15 | { 16 | public void Configure(IWebJobsBuilder builder) 17 | { 18 | builder.Services.Configure(options => 19 | { 20 | options.ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 21 | { 22 | ApiKeyVerifier = async (s, request) => s == "key" ? true : false, 23 | HeaderName = "x-apikey" 24 | }; 25 | options.BasicAuthentication = new BasicAuthenticationParameters() 26 | { 27 | ValidCredentials = new Dictionary() { { "admin", "admin" } } 28 | }; 29 | options.JwtAuthentication = new JwtAuthenticationParameters() 30 | { 31 | TokenValidationParameters = new OpenIdConnectJwtValidationParameters() 32 | { 33 | OpenIdConnectConfigurationUrl = 34 | "https://jusas-tests.eu.auth0.com/.well-known/openid-configuration", 35 | ValidAudiences = new List() 36 | {"XLjNBiBCx3_CZUAK3gagLSC_PPQjBDzB"}, 37 | ValidateIssuerSigningKey = true, 38 | NameClaimType = ClaimTypes.NameIdentifier 39 | }, 40 | CustomAuthorizationFilter = async (principal, token, attributes) => { } 41 | }; 42 | }); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/AuthorizedFuncs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using AzureFunctionsV2.HttpExtensions.Annotations; 5 | using AzureFunctionsV2.HttpExtensions.Authorization; 6 | using AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup; 7 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.AspNetCore.Mvc.Formatters; 13 | using Microsoft.Extensions.Logging; 14 | using Newtonsoft.Json; 15 | 16 | namespace AzureFunctionsV2.HttpExtensions.Examples.Authorization 17 | { 18 | public static class AuthorizedFuncs 19 | { 20 | [FunctionName("BasicAuthenticatedFunc")] 21 | [HttpAuthorize(Scheme.Basic)] 22 | public static async Task BasicAuthenticatedFunc( 23 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 24 | ILogger log) 25 | { 26 | return new OkObjectResult($"Welcome anonymous, you are authorized!"); 27 | } 28 | 29 | [FunctionName("ApiKeyAuthenticatedFunc")] 30 | [HttpAuthorize(Scheme.HeaderApiKey)] 31 | public static async Task ApiKeyAuthenticatedFunc( 32 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 33 | ILogger log) 34 | { 35 | return new OkObjectResult($"Welcome anonymous, you are authorized!"); 36 | } 37 | 38 | [FunctionName("JwtAuthenticatedFunc")] 39 | [HttpAuthorize(Scheme.Jwt)] 40 | public static async Task JwtAuthenticatedFunc( 41 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 42 | ILogger log, 43 | [HttpToken]HttpUser user) 44 | { 45 | return new OkObjectResult($"Welcome {user.ClaimsPrincipal.Identity.Name}, you are authorized!"); 46 | } 47 | 48 | [FunctionName("JwtLoginRedirect")] 49 | public static async Task JwtLoginRedirect( 50 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "login")] HttpRequest req, 51 | ILogger log) 52 | { 53 | var nonce = new Random().Next(); 54 | return new ContentResult() 55 | { 56 | Content = 57 | $"Login", 58 | ContentType = "text/html", 59 | StatusCode = 200 60 | }; 61 | } 62 | 63 | 64 | [FunctionName("JwtLoginCallback")] 65 | public static async Task JwtLoginCallback( 66 | [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "callback")] HttpRequest req, 67 | ILogger log) 68 | { 69 | return new ContentResult() 70 | { 71 | Content = 72 | "
", 73 | ContentType = "text/html", 74 | StatusCode = 200 75 | }; 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/AzureFunctionsV2.HttpExtensions.Examples.Authorization.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | PreserveNewest 23 | Never 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/README.md: -------------------------------------------------------------------------------- 1 | # Authentication/authorization example 2 | 3 | This example project contains Functions with different authentication schemes applied to them 4 | and serves as an example of how to set them up. 5 | 6 | The actual setup code is in another project, __AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup__. 7 | This is because the Functions runtime has a problem detecting Startup classes inside the main assembly. 8 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.Authorization/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | PreserveNewest 22 | Never 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/BodyParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Examples.FunctionApp 15 | { 16 | public static class BodyParameters 17 | { 18 | public class MyObject 19 | { 20 | public string Name { get; set; } 21 | public bool Boolean { get; set; } 22 | public int[] Numbers { get; set; } 23 | } 24 | 25 | public class MyObjectRequired 26 | { 27 | [JsonRequired] 28 | public string Name { get; set; } 29 | public bool Boolean { get; set; } 30 | public int[] Numbers { get; set; } 31 | } 32 | 33 | /* 34 | POST http://localhost:7071/api/post-object 35 | Content-Type: application/json 36 | 37 | { 38 | "name": "John", 39 | "boolean": true, 40 | "numbers": [1,2,3] 41 | } 42 | 43 | ### 44 | 45 | POST http://localhost:7071/api/post-object 46 | Content-Type: application/xml 47 | 48 | 49 | true 50 | John 51 | 52 | 1 53 | 2 54 | 3 55 | 56 | 57 | */ 58 | /// 59 | /// Standard HTTP POST with a body that needs to be deserialized into an object. 60 | /// The default deserialization to objects supports these content types: 61 | /// - application/json 62 | /// - application/xml 63 | /// 64 | /// 65 | /// 66 | /// 67 | /// 68 | [FunctionName("BodyParametersDemo1")] 69 | public static async Task PostObject( 70 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = "post-object")] HttpRequest req, 71 | [HttpBody]HttpParam bodyData, 72 | ILogger log) 73 | { 74 | log.LogInformation($"Object received: {JsonConvert.SerializeObject(bodyData.Value)}"); 75 | return new OkObjectResult("see the log"); 76 | } 77 | 78 | /// 79 | /// Basically the same as above, but with some requirements: 80 | /// - Body is not allowed to be empty (Required=true) 81 | /// - The MyObjectRequired class' Name property has a JsonRequired attribute. 82 | /// Since we use JSON deserializer, an exception will be thrown if the required 83 | /// field is not set, and the method will return 400. 84 | /// 85 | /// 86 | /// 87 | /// 88 | /// 89 | [FunctionName("BodyParametersDemo2")] 90 | public static async Task PostObject2( 91 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = "post-object-required")] HttpRequest req, 92 | [HttpBody(Required = true)]HttpParam bodyData, 93 | ILogger log) 94 | { 95 | log.LogInformation($"Object received: {JsonConvert.SerializeObject(bodyData.Value)}"); 96 | return new OkObjectResult("see the log"); 97 | } 98 | 99 | 100 | 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/FormParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Examples.FunctionApp 15 | { 16 | public static class FormParameters 17 | { 18 | public enum SomeEnum 19 | { 20 | One, 21 | Ten, 22 | All 23 | } 24 | 25 | public class SomeClass 26 | { 27 | public string Name { get; set; } 28 | public bool Bool { get; set; } 29 | } 30 | 31 | /* 32 | POST http://localhost:7071/api/form-basics 33 | Content-Type: application/x-www-form-urlencoded 34 | 35 | someString=hello&someObject=%7B%22Name%22%3A%22John%22%2C%22Bool%22%3A%22true%22%7D&someInteger=123&stringList=5&stringList=6&stringList=7&enumArray=0&enumArray=1 36 | 37 | */ 38 | /// 39 | /// The very basics of using the HttpFormAttribute and showing how it works 40 | /// deserialization-wise. 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// 46 | /// 47 | /// 48 | /// 49 | /// 50 | [FunctionName("FormParametersDemo1")] 51 | public static async Task FormParametersDemo1( 52 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = "form-basics")] HttpRequest req, 53 | [HttpForm]HttpParam someString, 54 | [HttpForm]HttpParam someObject, 55 | [HttpForm(Required = true, Name = "someInteger")]HttpParam integer, 56 | [HttpForm]HttpParam> stringList, 57 | [HttpForm]HttpParam enumArray, 58 | ILogger log) 59 | { 60 | log.LogInformation($"someString: {someString}"); 61 | log.LogInformation($"someObject: {JsonConvert.SerializeObject(someObject.Value)}"); 62 | log.LogInformation($"integer: {integer}"); 63 | log.LogInformation($"stringList: {JsonConvert.SerializeObject(stringList.Value)}"); 64 | log.LogInformation($"enumArray: {JsonConvert.SerializeObject(enumArray.Value)}"); 65 | return new OkObjectResult("see the log"); 66 | } 67 | 68 | /* 69 | POST http://localhost:7071/api/form-upload 70 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW 71 | 72 | ------WebKitFormBoundary7MA4YWxkTrZu0gW 73 | Content-Disposition: form-data; name="someString" 74 | 75 | hello 76 | 77 | ------WebKitFormBoundary7MA4YWxkTrZu0gW 78 | Content-Disposition: form-data; name="image"; filename="test.jpg" 79 | Content-Type: image/jpeg 80 | MIME-Version: 1.0 81 | 82 | < c:/temp/temp.jpg 83 | ------WebKitFormBoundary7MA4YWxkTrZu0gW-- 84 | 85 | */ 86 | /// 87 | /// An example of a file upload. A typical multipart/form-data scenario. 88 | /// 89 | /// 90 | /// 91 | /// 92 | /// 93 | /// 94 | [FunctionName("FormParametersDemo2")] 95 | public static async Task FormParametersDemo2( 96 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = "form-upload")] HttpRequest req, 97 | [HttpForm]HttpParam someString, 98 | [HttpForm(Name = "image")]HttpParam file, 99 | ILogger log) 100 | { 101 | log.LogInformation($"someString: {someString}"); 102 | log.LogInformation($"File information: name: {file.Value?.Name}, fileName: {file.Value?.FileName}, size: {file.Value?.Length}"); 103 | return new OkObjectResult("see the log"); 104 | } 105 | 106 | /* 107 | POST http://localhost:7071/api/form-upload-multi 108 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW 109 | 110 | ------WebKitFormBoundary7MA4YWxkTrZu0gW 111 | Content-Disposition: form-data; name="someString" 112 | 113 | hello 114 | 115 | ------WebKitFormBoundary7MA4YWxkTrZu0gW 116 | Content-Disposition: form-data; name="image1"; filename="test.jpg" 117 | Content-Type: image/jpeg 118 | MIME-Version: 1.0 119 | 120 | < c:/temp/temp.jpg 121 | ------WebKitFormBoundary7MA4YWxkTrZu0gW 122 | Content-Disposition: form-data; name="image2"; filename="test.jpg" 123 | Content-Type: image/jpeg 124 | MIME-Version: 1.0 125 | 126 | < c:/temp/temp.jpg 127 | ------WebKitFormBoundary7MA4YWxkTrZu0gW-- 128 | */ 129 | /// 130 | /// Multi-file upload when there's a varying number of files. 131 | /// This is not really different from accessing req.Form.Files, except that it's in the 132 | /// Function signature, allowing some code analysis with reflection. 133 | /// 134 | /// 135 | /// 136 | /// 137 | /// 138 | /// 139 | [FunctionName("FormParametersDemo3")] 140 | public static async Task FormParametersDemo3( 141 | [HttpTrigger(AuthorizationLevel.Function, "post", Route = "form-upload-multi")] HttpRequest req, 142 | [HttpForm]HttpParam someString, 143 | [HttpForm]HttpParam files, 144 | ILogger log) 145 | { 146 | log.LogInformation($"someString: {someString}"); 147 | foreach (var file in files.Value) 148 | { 149 | log.LogInformation($"File information: name: {file.Name}, fileName: {file.FileName}, size: {file.Length}"); 150 | } 151 | 152 | return new OkObjectResult("see the log"); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/HeaderParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Examples.FunctionApp 15 | { 16 | public static class HeaderParameters 17 | { 18 | 19 | /* 20 | GET http://localhost:7071/api/header-basics 21 | x-my-header: hello world 22 | x-my-json-header: {"name": "John"} 23 | 24 | */ 25 | /// 26 | /// The basics of headers. 27 | /// Headers support strings, as the headers normally are strings, but 28 | /// also deserialization of JSON objects (excluding root level arrays) 29 | /// are supported as shown below. 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | [FunctionName("HeaderParametersDemo1")] 36 | public static async Task HeaderParametersDemo1( 37 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "header-basics")] HttpRequest req, 38 | [HttpHeader(Name = "x-my-header")]HttpParam myBasicHeader, 39 | [HttpHeader(Name = "x-my-json-header")]HttpParam myJsonHeader, 40 | ILogger log) 41 | { 42 | log.LogInformation($"x-my-header: {myBasicHeader}"); 43 | log.LogInformation($"x-my-json-header: {JsonConvert.SerializeObject(myJsonHeader.Value)}"); 44 | return new OkObjectResult("see the log"); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/QueryParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Examples.FunctionApp 15 | { 16 | public static class QueryParameters 17 | { 18 | /* 19 | GET http://localhost:7071/api/query-basics?someString=hello 20 | &anotherString=world&myObject={"Name": "John"} 21 | &numberArray=1&numberArray=2 22 | &stringList=foo&stringList=bar 23 | */ 24 | /// 25 | /// The basics of HttpQueryAttribute usage. 26 | /// This is somewhat self explanatory. Objects are deserialized, and 27 | /// application/xml and application/json are supported with default settings 28 | /// out of the box. The same applies to Arrays and Lists. 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | /// 38 | [FunctionName("QueryParametersDemo1")] 39 | public static async Task QueryParametersDemo1( 40 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = "query-basics")] HttpRequest req, 41 | [HttpQuery(Required = true)]HttpParam someString, 42 | [HttpQuery(Name = "anotherString")]HttpParam yetAnother, 43 | [HttpQuery]HttpParam myObject, 44 | [HttpQuery]HttpParam numberArray, 45 | [HttpQuery]HttpParam> stringList, 46 | ILogger log) 47 | { 48 | log.LogInformation($"someString: {someString}"); 49 | log.LogInformation($"anotherString: {yetAnother}"); 50 | log.LogInformation($"myObject: {JsonConvert.SerializeObject(myObject.Value)}"); 51 | log.LogInformation($"numberArray: {JsonConvert.SerializeObject(numberArray.Value)}"); 52 | log.LogInformation($"stringList: {JsonConvert.SerializeObject(stringList.Value)}"); 53 | return new OkObjectResult("see the log"); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/README.md: -------------------------------------------------------------------------------- 1 | # Usage examples 2 | 3 | This project contains some usage examples on the HttpParam and the related attributes. 4 | The examples are split to separate classes by attribute (HttpBody, HttpForm, HttpHeader and HttpForm). 5 | Each class has one or more functions that demonstrate their usage, and also HTTP request examples are included 6 | in the comments. -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Examples.FunctionApp/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Fody/AzureFunctionsV2.HttpExtensions.Fody.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 1.3.0 6 | Jussi Saarivirta 7 | AzureFunctionsV2.HttpExtensions 8 | Fody Weaver for AzureFunctionsV2.HttpExtensions 9 | (c) 2019 Jussi Saarivirta 10 | https://aka.ms/deprecateLicenseUrl 11 | https://github.com/Jusas/AzureFunctionsV2.HttpExtensions 12 | https://raw.githubusercontent.com/Jusas/AzureFunctionsV2.HttpExtensions/master/assets/logo.png 13 | https://github.com/Jusas/AzureFunctionsV2.HttpExtensions 14 | Git 15 | Azure Functions 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Fody/AzureFunctionsV2.HttpExtensions.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandardweaver 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Fody/FunctionAsyncStateMachineMoveNextFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Mono.Cecil; 6 | using Mono.Cecil.Cil; 7 | 8 | namespace AzureFunctionsV2.HttpExtensions.Fody 9 | { 10 | public static class FunctionAsyncStateMachineMoveNextFinder 11 | { 12 | 13 | public class AsyncStateMachineContext 14 | { 15 | public MethodDefinition CompilerGeneratedMoveNext { get; set; } 16 | public FieldDefinition HttpRequestFieldDefinition { get; set; } 17 | public string SourceFunctionName { get; set; } 18 | public TypeDefinition CompilerGeneratedStateMachineType { get; set; } 19 | } 20 | 21 | /// 22 | /// Find MoveNext() methods from compiler generated async state machines that belong 23 | /// to an Azure Function (static methods in static classes, having FunctionNameAttribute 24 | /// and HttpRequest parameter in the method signature. 25 | /// 26 | /// 27 | /// 28 | /// 29 | public static List Find(ModuleDefinition moduleDefinition, 30 | Action log) 31 | { 32 | // Find static class functions, that have a specific attribute. 33 | // Try to get its System.Runtime.CompilerServices.AsyncStateMachineAttribute, and get its Type. 34 | // Get its MoveNext(), and then proceed as with AsyncErrorHandler. 35 | List functionMethodDefinitions = 36 | new List(); 37 | 38 | List temp = new List(); 39 | foreach (var typeDefinition in moduleDefinition.Types) 40 | { 41 | if (typeDefinition.IsAbstract && typeDefinition.IsClass && typeDefinition.IsSealed) 42 | { 43 | var functionLikeMethods = typeDefinition.Methods.Where(m => 44 | m.HasCustomAttributes && 45 | m.CustomAttributes.Any(a => a.AttributeType.Name == "FunctionNameAttribute") && 46 | m.CustomAttributes.Any(a => a.AttributeType.Name == "AsyncStateMachineAttribute") && 47 | m.Parameters.Any(p => p.ParameterType.Name == "HttpRequest" && p.CustomAttributes.Any(ca => ca.AttributeType.Name == "HttpTriggerAttribute"))); 48 | if (functionLikeMethods.Any()) 49 | temp.AddRange(functionLikeMethods); 50 | } 51 | } 52 | 53 | foreach (var functionMethod in temp) 54 | { 55 | 56 | log("Found Function " + functionMethod.Name); 57 | var compilerGeneratedStateMachineType = functionMethod.Body.Variables.FirstOrDefault(v => 58 | v.VariableType.Resolve().Interfaces.Any(i => i.InterfaceType.Name == "IAsyncStateMachine")) 59 | ?.VariableType; 60 | 61 | log(" - Corresponding compiler generated state machine: " + compilerGeneratedStateMachineType.FullName); 62 | var allModuleTypes = 63 | moduleDefinition.Types.Concat(moduleDefinition.Types.SelectMany(t => t.NestedTypes)); 64 | 65 | var matchingStateMachineType = allModuleTypes.First( 66 | t => t.FullName == compilerGeneratedStateMachineType.FullName); 67 | var moveNextMethod = matchingStateMachineType.Methods.First(m => m.Name == "MoveNext"); 68 | 69 | // If the request is never used in the Function body, it will be optimized out in Release builds. 70 | // Therefore if it's missing, we must add it as a field manually because our functionality depends on it. 71 | var httpRequestFieldInStateMachineType = matchingStateMachineType.Fields.FirstOrDefault(f => f.FieldType.Name == "HttpRequest"); 72 | if (httpRequestFieldInStateMachineType == null) 73 | { 74 | log(" - No HttpRequest field present in the state machine even though it is in the signature; most likely optimized out. Inserting it in."); 75 | 76 | var httpRequestParam = functionMethod.Parameters.First(p => p.ParameterType.Name == "HttpRequest"); 77 | var httpRequestFieldDef = new FieldDefinition(httpRequestParam.Name, FieldAttributes.Public, httpRequestParam.ParameterType); 78 | httpRequestFieldInStateMachineType = httpRequestFieldDef; 79 | matchingStateMachineType.Fields.Add(httpRequestFieldDef); 80 | 81 | var ctorParamIndex = functionMethod.Parameters.IndexOf(httpRequestParam); 82 | // log(" - Index of ctor HttpRequest param is " + ctorParamIndex); 83 | 84 | var localStateMachineVar = functionMethod.Body.Variables.First(v => v.VariableType == matchingStateMachineType); 85 | var localStateMachineVarIndex = functionMethod.Body.Variables.IndexOf(localStateMachineVar); 86 | var instructionIndex = 0; 87 | var newInstructions = new[] 88 | { 89 | // load the address of the local that holds the state machine instance. 90 | functionMethod.Body.GetILProcessor().Create(OpCodes.Ldloca, localStateMachineVarIndex), 91 | functionMethod.Body.GetILProcessor().Create(OpCodes.Ldarg, ctorParamIndex), 92 | Instruction.Create(OpCodes.Stfld, httpRequestFieldDef) 93 | }; 94 | foreach (var instruction in newInstructions) 95 | { 96 | functionMethod.Body.Instructions.Insert(instructionIndex, instruction); 97 | instructionIndex++; 98 | } 99 | 100 | } 101 | 102 | // functionMethodDefinitions.Add((moveNextMethod, httpRequestFieldInStateMachineType, functionMethod.Name)); 103 | functionMethodDefinitions.Add(new AsyncStateMachineContext() 104 | { 105 | CompilerGeneratedMoveNext = moveNextMethod, 106 | SourceFunctionName = functionMethod.Name, 107 | HttpRequestFieldDefinition = httpRequestFieldInStateMachineType, 108 | CompilerGeneratedStateMachineType = matchingStateMachineType 109 | }); 110 | 111 | } 112 | 113 | log("Discovery completed."); 114 | 115 | return functionMethodDefinitions; 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Fody/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Mono.Cecil; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Fody 7 | { 8 | public static class Helpers 9 | { 10 | 11 | public static TypeReference MakeGenericType(TypeReference self, params TypeReference[] arguments) 12 | { 13 | if (self.GenericParameters.Count != arguments.Length) 14 | throw new ArgumentException(); 15 | 16 | var instance = new GenericInstanceType(self); 17 | foreach (var argument in arguments) 18 | instance.GenericArguments.Add(argument); 19 | 20 | return instance; 21 | } 22 | 23 | public static MethodReference MakeHostInstanceGeneric(MethodReference self, params TypeReference[] arguments) 24 | { 25 | var reference = new MethodReference(self.Name, self.ReturnType, MakeGenericType(self.DeclaringType, arguments)) 26 | { 27 | HasThis = self.HasThis, 28 | ExplicitThis = self.ExplicitThis, 29 | CallingConvention = self.CallingConvention 30 | }; 31 | 32 | foreach (var parameter in self.Parameters) 33 | reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); 34 | 35 | foreach (var genericParameter in self.GenericParameters) 36 | reference.GenericParameters.Add(new GenericParameter(genericParameter.Name, reference)); 37 | 38 | return reference; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Security.Claims; 3 | using AzureFunctionsV2.HttpExtensions.Authorization; 4 | using AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup; 5 | using Microsoft.Azure.WebJobs; 6 | using Microsoft.Azure.WebJobs.Hosting; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | [assembly: WebJobsStartup(typeof(Startup), "MyStartup")] 10 | 11 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup 12 | { 13 | // See: https://github.com/Azure/Azure-Functions/issues/972 14 | // This is why the startup class is now in a separate assembly. 15 | public class Startup : IWebJobsStartup 16 | { 17 | public void Configure(IWebJobsBuilder builder) 18 | { 19 | // To replace default implementations: 20 | // builder.Services.Replace(ServiceDescriptor.Singleton()); 21 | // builder.Services.Replace(ServiceDescriptor.Singleton()); 22 | 23 | // Registering OIDC token validation parameters for the JWT authentication. 24 | builder.Services.Configure(options => 25 | { 26 | options.ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 27 | { 28 | ApiKeyVerifier = async (s, request) => s == "key" ? true : false, 29 | HeaderName = "x-apikey", 30 | QueryParameterName = "apikey" 31 | }; 32 | options.BasicAuthentication = new BasicAuthenticationParameters() 33 | { 34 | ValidCredentials = new Dictionary() {{"admin", "admin"}} 35 | }; 36 | options.JwtAuthentication = new JwtAuthenticationParameters() 37 | { 38 | TokenValidationParameters = new OpenIdConnectJwtValidationParameters() 39 | { 40 | OpenIdConnectConfigurationUrl = 41 | "https://jusas-tests.eu.auth0.com/.well-known/openid-configuration", 42 | ValidAudiences = new List() 43 | {"http://localhost:7071/", "XLjNBiBCx3_CZUAK3gagLSC_PPQjBDzB"}, 44 | ValidateIssuerSigningKey = true, 45 | NameClaimType = ClaimTypes.NameIdentifier 46 | }, 47 | CustomAuthorizationFilter = async (principal, token, attributes) => { } 48 | }; 49 | }); 50 | 51 | // Alternatively, if there is no OIDC endpoint and I want to define the public certificate and do things manually: 52 | /* 53 | builder.Services.Configure(options => 54 | { 55 | string publicCert = @"my-base64-encoded-certificate"; 56 | var x509cert = new X509Certificate2(Convert.FromBase64String(publicCert)); 57 | SecurityKey sk = new X509SecurityKey(x509cert); 58 | sk.KeyId = x509cert.Thumbprint; 59 | options.TokenValidationParameters = new TokenValidationParameters() 60 | { 61 | ValidIssuers = new List() { "https://my-issuer" }, 62 | ValidAudiences = new List() { "my-audience" }, 63 | IssuerSigningKeys = new List() { sk }, 64 | ValidateIssuerSigningKey = true, 65 | NameClaimType = ClaimTypes.NameIdentifier 66 | }; 67 | }); 68 | */ 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/AuthTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Authorization; 7 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.Azure.WebJobs; 11 | using Microsoft.Azure.WebJobs.Extensions.Http; 12 | using Microsoft.Extensions.Logging; 13 | using Newtonsoft.Json; 14 | 15 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp 16 | { 17 | public static class AuthTests 18 | { 19 | /// 20 | /// Function with HttpAuthorize, Jwt. 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | [FunctionName("AuthTest1")] 27 | [HttpAuthorize(Scheme.Jwt)] 28 | public static async Task AuthTest1( 29 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 30 | [HttpToken]HttpUser user, 31 | ILogger log) 32 | { 33 | return new OkResult(); 34 | } 35 | 36 | /// 37 | /// Authorization with Basic Auth. 38 | /// 39 | /// 40 | /// 41 | /// 42 | /// 43 | [FunctionName("AuthTest2")] 44 | [HttpAuthorize(Scheme.Basic)] 45 | public static async Task AuthTest2( 46 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 47 | [HttpToken]HttpUser user, 48 | ILogger log) 49 | { 50 | return new OkObjectResult("ok"); 51 | } 52 | 53 | 54 | 55 | /// 56 | /// Function without HttpAuthorize. 57 | /// 58 | /// 59 | /// 60 | /// 61 | /// 62 | [FunctionName("AuthTest3")] 63 | public static async Task AuthTest3( 64 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 65 | [HttpToken]HttpUser user, 66 | ILogger log) 67 | { 68 | return new OkObjectResult("ok"); 69 | } 70 | 71 | /// 72 | /// Authorization with OAuth2 token 73 | /// 74 | /// 75 | /// 76 | /// 77 | /// 78 | [FunctionName("AuthTest4")] 79 | [HttpAuthorize(Scheme.OAuth2)] 80 | public static async Task AuthTest4( 81 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 82 | [HttpToken]HttpUser user, 83 | ILogger log) 84 | { 85 | return new OkObjectResult("ok"); 86 | } 87 | 88 | /// 89 | /// Authorization with HeaderApiKey 90 | /// 91 | /// 92 | /// 93 | /// 94 | /// 95 | [FunctionName("AuthTest5")] 96 | [HttpAuthorize(Scheme.HeaderApiKey)] 97 | public static async Task AuthTest5( 98 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 99 | [HttpToken]HttpUser user, 100 | ILogger log) 101 | { 102 | return new OkObjectResult("ok"); 103 | } 104 | 105 | /// 106 | /// Authorization with QueryApiKey 107 | /// 108 | /// 109 | /// 110 | /// 111 | /// 112 | [FunctionName("AuthTest6")] 113 | [HttpAuthorize(Scheme.QueryApiKey)] 114 | public static async Task AuthTest6( 115 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 116 | [HttpToken]HttpUser user, 117 | ILogger log) 118 | { 119 | return new OkObjectResult("ok"); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | v2 5 | 6 | 7 | 8 | all 9 | runtime; build; native; contentfiles; analyzers 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | Never 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/FodyTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Azure.WebJobs; 10 | using Microsoft.Azure.WebJobs.Extensions.Http; 11 | using Microsoft.Extensions.Logging; 12 | 13 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp 14 | { 15 | public static class FodyTests 16 | { 17 | [FunctionName("FodyTest1")] 18 | public static async Task FodyTest1( 19 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 20 | ILogger log) 21 | { 22 | throw new Exception("foo"); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/HeaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Azure.WebJobs; 9 | using Microsoft.Azure.WebJobs.Extensions.Http; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp 15 | { 16 | public static class HeaderTests 17 | { 18 | [FunctionName("HeaderTestFunction-Primitives")] 19 | public static async Task HeaderTest_Primitives( 20 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 21 | [HttpHeader("x-my-string-header")]HttpParam stringHeader, 22 | [HttpHeader("x-my-int-header")]HttpParam intHeader, 23 | [HttpHeader("x-my-enum-header")]HttpParam enumHeader, 24 | ILogger log) 25 | { 26 | return new OkObjectResult(new HeaderTestResultSet() 27 | { 28 | EnumParam = enumHeader, 29 | IntParam = intHeader, 30 | StringParam = stringHeader 31 | }); 32 | } 33 | 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/QueryParameterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Annotations; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Azure.WebJobs; 9 | using Microsoft.Azure.WebJobs.Extensions.Http; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.Extensions.Logging; 12 | using Newtonsoft.Json; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp 15 | { 16 | public static class QueryParameterTests 17 | { 18 | [FunctionName("QueryParameterTestFunction-Basic")] 19 | public static async Task QueryParameterTestFunction_Basic( 20 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 21 | [HttpQuery(Required = true)]HttpParam stringParam, 22 | [HttpQuery]HttpParam intParam, 23 | [HttpQuery]HttpParam enumParam, 24 | ILogger log) 25 | { 26 | return new OkObjectResult(new QueryTestResultSet() 27 | { 28 | EnumParam = enumParam, 29 | IntParam = intParam, 30 | StringParam = stringParam 31 | }); 32 | } 33 | 34 | [FunctionName("QueryParameterTestFunction-Arrays")] 35 | public static async Task QueryParameterTestFunction_Arrays( 36 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 37 | [HttpQuery(Required = true)]HttpParam stringArray, 38 | [HttpQuery]HttpParam intArray, 39 | [HttpQuery]HttpParam enumArray, 40 | ILogger log) 41 | { 42 | return new OkObjectResult(new QueryArrayTestResultSet() 43 | { 44 | EnumArray = enumArray, 45 | IntArray = intArray, 46 | StringArray = stringArray 47 | }); 48 | } 49 | 50 | [FunctionName("QueryParameterTestFunction-Lists")] 51 | public static async Task QueryParameterTestFunction_Lists( 52 | [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest req, 53 | [HttpQuery(Required = true)]HttpParam> stringList, 54 | [HttpQuery]HttpParam> intList, 55 | [HttpQuery]HttpParam> enumList, 56 | ILogger log) 57 | { 58 | return new OkObjectResult(new QueryListTestResultSet() 59 | { 60 | EnumList = enumList, 61 | IntList = intList, 62 | StringList = stringList 63 | }); 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/TestResultSets.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Tests.FunctionApp 6 | { 7 | public enum TestEnum 8 | { 9 | Zero, 10 | One, 11 | Two, 12 | Three 13 | } 14 | 15 | public class HeaderTestResultSet 16 | { 17 | public string StringParam { get; set; } 18 | public int IntParam { get; set; } 19 | public TestEnum EnumParam { get; set; } 20 | } 21 | 22 | public class QueryTestResultSet 23 | { 24 | public string StringParam { get; set; } 25 | public int IntParam { get; set; } 26 | public TestEnum EnumParam { get; set; } 27 | } 28 | 29 | public class QueryArrayTestResultSet 30 | { 31 | public string[] StringArray { get; set; } 32 | public int[] IntArray { get; set; } 33 | public TestEnum[] EnumArray { get; set; } 34 | } 35 | 36 | public class QueryListTestResultSet 37 | { 38 | public List StringList { get; set; } 39 | public List IntList { get; set; } 40 | public List EnumList { get; set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests.FunctionApp/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0" 3 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/ApikeyAuthenticatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Authorization; 7 | using AzureFunctionsV2.HttpExtensions.Tests.Mocks; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Http.Internal; 10 | using Microsoft.Extensions.Options; 11 | using Microsoft.Extensions.Primitives; 12 | using Microsoft.IdentityModel.Protocols; 13 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 14 | using Microsoft.IdentityModel.Tokens; 15 | using Moq; 16 | using Xunit; 17 | 18 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 19 | { 20 | public class ApiKeyAuthenticatorTests 21 | { 22 | [Fact] 23 | public async Task Should_throw_without_valid_configuration() 24 | { 25 | // Arrange 26 | 27 | // Act 28 | 29 | // Assert 30 | await Assert.ThrowsAsync(async () => await (new ApiKeyAuthenticator(null).Authenticate(null))); 31 | } 32 | 33 | [Fact] 34 | public async Task Should_authenticate_successfully_using_header() 35 | { 36 | // Arrange 37 | var config = new Mock>(); 38 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 39 | { 40 | ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 41 | { 42 | ApiKeyVerifier = async (key, request) => key == "foo", 43 | HeaderName = "x-key" 44 | } 45 | }); 46 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 47 | httpRequest.HeaderDictionary = new HeaderDictionary() {{"x-key", "foo"}}; 48 | 49 | // Act 50 | var apiKeyAuthenticator = new ApiKeyAuthenticator(config.Object); 51 | var result = await apiKeyAuthenticator.Authenticate(httpRequest); 52 | 53 | // Assert 54 | Assert.True(result); 55 | } 56 | 57 | [Fact] 58 | public async Task Should_authenticate_successfully_using_query_param() 59 | { 60 | // Arrange 61 | var config = new Mock>(); 62 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 63 | { 64 | ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 65 | { 66 | ApiKeyVerifier = async (key, request) => key == "foo", 67 | QueryParameterName = "key" 68 | } 69 | }); 70 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 71 | httpRequest.Query = new QueryCollection(new Dictionary() {{"key", "foo"}}); 72 | 73 | // Act 74 | var apiKeyAuthenticator = new ApiKeyAuthenticator(config.Object); 75 | var result = await apiKeyAuthenticator.Authenticate(httpRequest); 76 | 77 | // Assert 78 | Assert.True(result); 79 | } 80 | 81 | [Fact] 82 | public async Task Should_authenticate_successfully_using_query_param_when_both_header_and_queryparam_are_configured() 83 | { 84 | // Arrange 85 | var config = new Mock>(); 86 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 87 | { 88 | ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 89 | { 90 | ApiKeyVerifier = async (key, request) => key == "foo", 91 | QueryParameterName = "key", 92 | HeaderName = "x-key" 93 | } 94 | }); 95 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 96 | httpRequest.HeaderDictionary = new HeaderDictionary(); 97 | httpRequest.Query = new QueryCollection(new Dictionary() { { "key", "foo" } }); 98 | 99 | // Act 100 | var apiKeyAuthenticator = new ApiKeyAuthenticator(config.Object); 101 | var result = await apiKeyAuthenticator.Authenticate(httpRequest); 102 | 103 | // Assert 104 | Assert.True(result); 105 | } 106 | 107 | [Fact] 108 | public async Task Should_fail_authentication_with_wrong_key_using_header() 109 | { 110 | // Arrange 111 | var config = new Mock>(); 112 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 113 | { 114 | ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 115 | { 116 | ApiKeyVerifier = async (key, request) => key == "foo", 117 | HeaderName = "x-key" 118 | } 119 | }); 120 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 121 | httpRequest.HeaderDictionary = new HeaderDictionary() { { "x-key", "x" } }; 122 | 123 | // Act 124 | var basicAuthenticator = new ApiKeyAuthenticator(config.Object); 125 | var result = await basicAuthenticator.Authenticate(httpRequest); 126 | 127 | // Assert 128 | Assert.False(result); 129 | } 130 | 131 | [Fact] 132 | public async Task Should_fail_authentication_when_not_providing_credentials() 133 | { 134 | // Arrange 135 | var config = new Mock>(); 136 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 137 | { 138 | ApiKeyAuthentication = new ApiKeyAuthenticationParameters() 139 | { 140 | ApiKeyVerifier = async (key, request) => key == "foo", 141 | HeaderName = "x-key" 142 | } 143 | }); 144 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 145 | httpRequest.Query = new QueryCollection(); 146 | httpRequest.HeaderDictionary = new HeaderDictionary(); 147 | 148 | // Act 149 | var basicAuthenticator = new ApiKeyAuthenticator(config.Object); 150 | var result = await basicAuthenticator.Authenticate(httpRequest); 151 | 152 | // Assert 153 | Assert.False(result); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/AuthorizedFunctionDiscovererTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFunctionsV2.HttpExtensions.Authorization; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 7 | { 8 | public class AuthorizedFunctionDiscovererTests 9 | { 10 | [Fact] 11 | public async Task Should_find_functions_with_HttpAuthorizeAttribute() 12 | { 13 | // Arrange 14 | var discoverer = new AuthorizedFunctionDiscoverer(); 15 | 16 | // Act 17 | var functions = discoverer.GetFunctions(); 18 | 19 | // Assert 20 | functions.Keys.Should().Contain(new[] {"AuthTest1", "AuthTest2", "AuthTest4", "AuthTest5", "AuthTest6"}); 21 | } 22 | 23 | [Fact] 24 | public async Task Should_not_include_functions_without_HttpAuthorizeAttribute() 25 | { 26 | // Arrange 27 | var discoverer = new AuthorizedFunctionDiscoverer(); 28 | 29 | // Act 30 | var functions = discoverer.GetFunctions(); 31 | 32 | // Assert 33 | functions.Keys.Should().NotContain(new[] { "AuthTest3", "HeaderTestFunction-Primitives", "QueryParameterTestFunction-Basic" }); 34 | } 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/BasicAuthenticatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Authorization; 7 | using Microsoft.Extensions.Options; 8 | using Microsoft.IdentityModel.Protocols; 9 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 10 | using Microsoft.IdentityModel.Tokens; 11 | using Moq; 12 | using Xunit; 13 | 14 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 15 | { 16 | public class BasicAuthenticatorTests 17 | { 18 | [Fact] 19 | public async Task Should_throw_without_valid_configuration() 20 | { 21 | // Arrange 22 | 23 | // Act 24 | 25 | // Assert 26 | await Assert.ThrowsAsync(async () => await (new BasicAuthenticator(null).Authenticate("foo"))); 27 | } 28 | 29 | [Fact] 30 | public async Task Should_authenticate_successfully() 31 | { 32 | // Arrange 33 | var config = new Mock>(); 34 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 35 | { 36 | BasicAuthentication = new BasicAuthenticationParameters() 37 | { 38 | ValidCredentials = new Dictionary() { 39 | {"user", "pass"} 40 | } 41 | } 42 | }); 43 | 44 | // Act 45 | var basicAuthenticator = new BasicAuthenticator(config.Object); 46 | var result = await basicAuthenticator.Authenticate("Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("user:pass"))); 47 | 48 | // Assert 49 | Assert.True(result); 50 | } 51 | 52 | [Fact] 53 | public async Task Should_fail_authentication() 54 | { 55 | // Arrange 56 | var config = new Mock>(); 57 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 58 | { 59 | BasicAuthentication = new BasicAuthenticationParameters() 60 | { 61 | ValidCredentials = new Dictionary() { 62 | {"user", "pass"} 63 | } 64 | } 65 | }); 66 | 67 | // Act 68 | var basicAuthenticator = new BasicAuthenticator(config.Object); 69 | var result = await basicAuthenticator.Authenticate("Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("foo:bar"))); 70 | 71 | // Assert 72 | Assert.False(result); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/HttpAuthorizationFilterTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IdentityModel.Tokens.Jwt; 3 | using System.Reflection; 4 | using System.Security.Claims; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using AzureFunctionsV2.HttpExtensions.Authorization; 8 | using AzureFunctionsV2.HttpExtensions.Exceptions; 9 | using AzureFunctionsV2.HttpExtensions.Tests.Helpers; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.Extensions.Options; 12 | using Microsoft.Extensions.Primitives; 13 | using Moq; 14 | using Xunit; 15 | 16 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 17 | { 18 | public class HttpAuthorizationFilterTests 19 | { 20 | 21 | [Fact] 22 | public async Task Should_run_JwtAuthenticator_code_for_Function_which_has_HttpAuthorizeAttribute_Jwt_and_succeed() 23 | { 24 | // Arrange 25 | var jwtAuthenticator = new Mock(); 26 | jwtAuthenticator.Setup(x => x.Authenticate(It.IsAny())).ReturnsAsync(() => 27 | { 28 | return (new ClaimsPrincipal(), new JwtSecurityToken()); 29 | }); 30 | var discoverer = new Mock(); 31 | var method = new Mock(); 32 | discoverer.Setup(x => x.GetFunctions()).Returns( 33 | new Dictionary)>() 34 | { 35 | {"func", (method.Object, new List() {new HttpAuthorizeAttribute(Scheme.Jwt)})} 36 | }); 37 | 38 | var config = new Mock>(); 39 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 40 | { 41 | JwtAuthentication = new JwtAuthenticationParameters() 42 | { 43 | TokenValidationParameters = new OpenIdConnectJwtValidationParameters() 44 | { 45 | OpenIdConnectConfigurationUrl = "http://foo.bar" 46 | } 47 | } 48 | }); 49 | 50 | var authFilter = new HttpAuthorizationFilter(discoverer.Object, config.Object, jwtAuthenticator.Object, null, null, null); 51 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 52 | mockedFunctionRequestContext.HttpRequest.HeaderDictionary = new HeaderDictionary( 53 | new Dictionary() {{"Authorization", new StringValues("Bearer foo") }}); 54 | var userParam = mockedFunctionRequestContext.AddUserParam("user"); 55 | mockedFunctionRequestContext.CreateFunctionExecutingContextWithJustName("func"); 56 | 57 | // Act 58 | await authFilter.OnExecutingAsync(mockedFunctionRequestContext.FunctionExecutingContext, CancellationToken.None); 59 | 60 | // Assert 61 | Assert.NotNull(userParam.ClaimsPrincipal); 62 | } 63 | 64 | 65 | [Fact] 66 | public async Task Should_run_custom_authorization_method_with_Jwt_auth() 67 | { 68 | // Arrange 69 | var jwtAuthenticator = new Mock(); 70 | jwtAuthenticator.Setup(x => x.Authenticate(It.IsAny())).ReturnsAsync(() => 71 | { 72 | return (new ClaimsPrincipal(new ClaimsIdentity(new List() { new Claim("myClaim", "myValue") })), new JwtSecurityToken()); 73 | }); 74 | var discoverer = new Mock(); 75 | var method = new Mock(); 76 | discoverer.Setup(x => x.GetFunctions()).Returns( 77 | new Dictionary)>() 78 | { 79 | {"func", (method.Object, new List() {new HttpAuthorizeAttribute(Scheme.Jwt)})} 80 | }); 81 | 82 | 83 | var options = new Mock>(); 84 | bool wasRun = false; 85 | options.SetupGet(x => x.Value).Returns(new HttpAuthenticationOptions() 86 | { 87 | JwtAuthentication = new JwtAuthenticationParameters() 88 | { 89 | CustomAuthorizationFilter = async (principal, token, attributes) => wasRun = true 90 | } 91 | }); 92 | var authFilter = new HttpAuthorizationFilter(discoverer.Object, options.Object, jwtAuthenticator.Object, null, null, null); 93 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 94 | mockedFunctionRequestContext.HttpRequest.HeaderDictionary = new HeaderDictionary( 95 | new Dictionary() { { "Authorization", new StringValues("Bearer foo") } }); 96 | var userParam = mockedFunctionRequestContext.AddUserParam("user"); 97 | mockedFunctionRequestContext.CreateFunctionExecutingContextWithJustName("func"); 98 | 99 | // Act 100 | await authFilter.OnExecutingAsync(mockedFunctionRequestContext.FunctionExecutingContext, CancellationToken.None); 101 | Assert.True(wasRun); 102 | } 103 | 104 | [Fact] 105 | public async Task Should_not_run_any_auth_code_for_Function_which_does_not_have_HttpAuthorizeAttribute() 106 | { 107 | // Arrange 108 | var discoverer = new Mock(); 109 | discoverer.Setup(x => x.GetFunctions()).Returns( 110 | new Dictionary)>()); 111 | 112 | var authFilter = new HttpAuthorizationFilter(discoverer.Object, null, null, null, null, null); 113 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 114 | mockedFunctionRequestContext.HttpRequest.HeaderDictionary = new HeaderDictionary(); 115 | var userParam = mockedFunctionRequestContext.AddUserParam("user"); 116 | mockedFunctionRequestContext.CreateFunctionExecutingContextWithJustName("func"); 117 | 118 | // Act 119 | await authFilter.OnExecutingAsync(mockedFunctionRequestContext.FunctionExecutingContext, CancellationToken.None); 120 | 121 | // Assert 122 | Assert.Null(userParam.ClaimsPrincipal); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/JwtAuthenticatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using AzureFunctionsV2.HttpExtensions.Authorization; 5 | using Microsoft.Extensions.Options; 6 | using Microsoft.IdentityModel.Protocols; 7 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 8 | using Microsoft.IdentityModel.Tokens; 9 | using Moq; 10 | using Xunit; 11 | 12 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 13 | { 14 | public class JwtAuthenticatorTests 15 | { 16 | [Fact] 17 | public async Task Should_throw_without_valid_configuration() 18 | { 19 | // Arrange 20 | 21 | // Act 22 | 23 | // Assert 24 | await Assert.ThrowsAsync(async () => await (new JwtAuthenticator(null, null, null).Authenticate("foo"))); 25 | } 26 | 27 | [Fact] 28 | public async Task 29 | Should_get_and_use_config_from_OIDC_endpoint_when_OpenIdConnectJwtValidationParameters_is_used() 30 | { 31 | // Arrange 32 | var config = new Mock>(); 33 | var optionsObject = new HttpAuthenticationOptions() 34 | { 35 | JwtAuthentication = new JwtAuthenticationParameters() 36 | { 37 | TokenValidationParameters = new OpenIdConnectJwtValidationParameters() 38 | { 39 | OpenIdConnectConfigurationUrl = "http://foo.bar" 40 | } 41 | } 42 | }; 43 | config.SetupGet(opts => opts.Value).Returns(optionsObject); 44 | var tokenValidator = new Mock(); 45 | var configManager = new Mock>(); 46 | configManager.Setup(x => x.GetConfigurationAsync(It.IsAny())) 47 | .ReturnsAsync(new OpenIdConnectConfiguration()); 48 | 49 | // Act 50 | var jwtAuthenticator = new JwtAuthenticator(config.Object, tokenValidator.Object, configManager.Object); 51 | await jwtAuthenticator.Authenticate("Bearer foo"); 52 | 53 | // Assert 54 | SecurityToken validatedToken; 55 | tokenValidator.Verify( 56 | x => x.ValidateToken("foo", optionsObject.JwtAuthentication.TokenValidationParameters, out validatedToken), 57 | Times.Once); 58 | configManager.Verify(x => x.GetConfigurationAsync(It.IsAny()), Times.Once); 59 | } 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Authentication/OAuth2AuthenticatorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using AzureFunctionsV2.HttpExtensions.Authorization; 8 | using AzureFunctionsV2.HttpExtensions.Exceptions; 9 | using AzureFunctionsV2.HttpExtensions.Tests.Mocks; 10 | using Microsoft.Extensions.Options; 11 | using Microsoft.IdentityModel.Protocols; 12 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 13 | using Microsoft.IdentityModel.Tokens; 14 | using Moq; 15 | using Xunit; 16 | 17 | namespace AzureFunctionsV2.HttpExtensions.Tests.Authentication 18 | { 19 | public class OAuth2AuthenticatorTests 20 | { 21 | [Fact] 22 | public async Task Should_throw_without_valid_configuration() 23 | { 24 | // Arrange 25 | 26 | // Act 27 | 28 | // Assert 29 | await Assert.ThrowsAsync(async () => await (new OAuth2Authenticator(null).AuthenticateAndAuthorize("", null, null))); 30 | } 31 | 32 | [Fact] 33 | public async Task Should_throw_with_non_bearer_tokens() 34 | { 35 | // Arrange 36 | var config = new Mock>(); 37 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 38 | { 39 | OAuth2Authentication = new OAuth2AuthenticationParameters() 40 | { 41 | CustomAuthorizationFilter = async (token, request, attributes) => new ClaimsPrincipal() 42 | } 43 | }); 44 | // Act 45 | 46 | // Assert 47 | await Assert.ThrowsAsync(async () => await (new OAuth2Authenticator(config.Object).AuthenticateAndAuthorize("foo", null, null))); 48 | } 49 | 50 | [Fact] 51 | public async Task Should_authenticate_successfully() 52 | { 53 | // Arrange 54 | var config = new Mock>(); 55 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 56 | { 57 | OAuth2Authentication = new OAuth2AuthenticationParameters() 58 | { 59 | CustomAuthorizationFilter = async (token, request, attributes) => new ClaimsPrincipal() 60 | } 61 | }); 62 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 63 | var authAttributes = new List() {new HttpAuthorizeAttribute(Scheme.OAuth2)}; 64 | 65 | // Act 66 | var oauth2Authenticator = new OAuth2Authenticator(config.Object); 67 | var result = await oauth2Authenticator.AuthenticateAndAuthorize("Bearer foo", httpRequest, authAttributes); 68 | 69 | // Assert 70 | Assert.NotNull(result); 71 | } 72 | 73 | [Fact] 74 | public async Task Should_fail_authentication() 75 | { 76 | // Arrange 77 | var config = new Mock>(); 78 | config.SetupGet(opts => opts.Value).Returns(new HttpAuthenticationOptions() 79 | { 80 | OAuth2Authentication = new OAuth2AuthenticationParameters() 81 | { 82 | CustomAuthorizationFilter = async (token, request, attributes) => throw new HttpAuthorizationException("unauthorized") 83 | } 84 | }); 85 | var httpRequest = new MockHttpRequest(new MockHttpContext()); 86 | var authAttributes = new List() { new HttpAuthorizeAttribute(Scheme.OAuth2) }; 87 | 88 | // Act 89 | var oauth2Authenticator = new OAuth2Authenticator(config.Object); 90 | await Assert.ThrowsAsync(async () => await oauth2Authenticator.AuthenticateAndAuthorize("Bearer foo", httpRequest, authAttributes)); 91 | 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/AzureFunctionsV2.HttpExtensions.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Helpers/MockedFunctionRequestContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.ExceptionServices; 4 | using AzureFunctionsV2.HttpExtensions.Annotations; 5 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 6 | using AzureFunctionsV2.HttpExtensions.Tests.Mocks; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Extensions.Logging; 9 | using Moq; 10 | 11 | namespace AzureFunctionsV2.HttpExtensions.Tests.Helpers 12 | { 13 | public class MockedFunctionRequestContext 14 | { 15 | public Mock MockedLogger { get; set; } 16 | public Guid FunctionContextId { get; set; } 17 | public IHttpParam HttpParam { get; set; } 18 | public MockHttpContext HttpContext { get; set; } 19 | public MockHttpRequest HttpRequest { get; set; } 20 | public MockHttpResponse HttpResponse { get; set; } 21 | public FunctionExecutingContext FunctionExecutingContext { get; set; } 22 | public FunctionExceptionContext FunctionExceptionContext { get; set; } 23 | public Dictionary ArgumentsDictionary { get; set; } = new Dictionary(); 24 | public Mock RequestStoreMock { get; set; } 25 | 26 | public MockedFunctionRequestContext() 27 | { 28 | MockedLogger = new Mock(); 29 | FunctionContextId = Guid.NewGuid(); 30 | HttpContext = new MockHttpContext(); 31 | HttpRequest = new MockHttpRequest(HttpContext); 32 | HttpResponse = new MockHttpResponse(HttpContext); 33 | RequestStoreMock = new Mock(); 34 | RequestStoreMock.Setup(x => x.Get(FunctionContextId)).Returns(HttpRequest); 35 | ArgumentsDictionary.Add("httptrigger_request", HttpRequest); 36 | HttpRequest.ContentType = "application/json"; 37 | } 38 | 39 | public void CreateFunctionExecutingContextWithJustName(string functionName) 40 | { 41 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, new Dictionary(), 42 | FunctionContextId, functionName, MockedLogger.Object); 43 | } 44 | 45 | public HttpUser AddUserParam(string argumentName) 46 | { 47 | var arg = new HttpUser(); 48 | ArgumentsDictionary.Add(argumentName, arg); 49 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, 50 | new Dictionary(), FunctionContextId, "func", MockedLogger.Object); 51 | return arg; 52 | } 53 | 54 | public HttpParam AddFormHttpParam(string argumentName, string alias = null, bool required = false) 55 | { 56 | var attribute = new HttpFormAttribute(alias); 57 | attribute.Required = required; 58 | var arg = new HttpParam() { HttpExtensionAttribute = attribute }; 59 | ArgumentsDictionary.Add(argumentName, arg); 60 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, 61 | new Dictionary(), FunctionContextId, "func", MockedLogger.Object); 62 | return arg; 63 | } 64 | 65 | 66 | public HttpParam AddBodyHttpParam(string argumentName, bool required = false) 67 | { 68 | var attribute = new HttpBodyAttribute(); 69 | attribute.Required = required; 70 | var arg = new HttpParam() { HttpExtensionAttribute = attribute }; 71 | ArgumentsDictionary.Add(argumentName, arg); 72 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, 73 | new Dictionary(), FunctionContextId, "func", MockedLogger.Object); 74 | return arg; 75 | } 76 | 77 | public HttpParam AddHeaderHttpParam(string argumentName, string alias = null, bool required = false) 78 | { 79 | var attribute = new HttpHeaderAttribute(alias ?? argumentName); 80 | attribute.Required = required; 81 | var arg = new HttpParam() { HttpExtensionAttribute = attribute }; 82 | ArgumentsDictionary.Add(argumentName, arg); 83 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, 84 | new Dictionary(), FunctionContextId, "func", MockedLogger.Object); 85 | return arg; 86 | } 87 | 88 | public HttpParam AddQueryHttpParam(string argumentName, string alias = null, bool required = false) 89 | { 90 | var attribute = new HttpQueryAttribute(alias); 91 | attribute.Required = required; 92 | var arg = new HttpParam() { HttpExtensionAttribute = attribute }; 93 | ArgumentsDictionary.Add(argumentName, arg); 94 | FunctionExecutingContext = new FunctionExecutingContext(ArgumentsDictionary, 95 | new Dictionary(), FunctionContextId, "func", MockedLogger.Object); 96 | return arg; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/HttpRequestMetadataStorageFilterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using AzureFunctionsV2.HttpExtensions.Tests.Helpers; 8 | using AzureFunctionsV2.HttpExtensions.Utils; 9 | using FluentAssertions; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.Azure.WebJobs.Host; 12 | using Microsoft.Azure.WebJobs.Host.Executors; 13 | using Microsoft.Extensions.Logging; 14 | using Moq; 15 | using Xunit; 16 | 17 | namespace AzureFunctionsV2.HttpExtensions.Tests 18 | { 19 | public class HttpRequestMetadataStorageFilterTests 20 | { 21 | [Fact] 22 | public async Task Should_add_HttpRequest_and_FunctionExecutingContext_to_request_store_after_function_invocation() 23 | { 24 | // Arrange 25 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 26 | mockedFunctionRequestContext.CreateFunctionExecutingContextWithJustName("func"); 27 | 28 | var metadataStorageFilter = new HttpRequestMetadataStorageFilter(mockedFunctionRequestContext.RequestStoreMock.Object, null); 29 | 30 | // Act 31 | await metadataStorageFilter.OnExecutingAsync(mockedFunctionRequestContext.FunctionExecutingContext, new CancellationToken()); 32 | 33 | // Assert 34 | mockedFunctionRequestContext.RequestStoreMock.Verify(x => x.Set(mockedFunctionRequestContext.FunctionContextId, 35 | mockedFunctionRequestContext.HttpRequest), Times.Once); 36 | mockedFunctionRequestContext.HttpContext.GetStoredFunctionExecutingContext().Should().NotBeNull(); 37 | } 38 | 39 | [Fact] 40 | public async Task Should_remove_HttpRequest_from_request_store_after_function_invocation() 41 | { 42 | // Arrange 43 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 44 | var executedContext = new FunctionExecutedContext(new Dictionary(), new Dictionary(), 45 | Guid.Empty, "func", new Mock().Object, new FunctionResult(true)); 46 | var metadataStorageFilter = new HttpRequestMetadataStorageFilter(mockedFunctionRequestContext.RequestStoreMock.Object, null); 47 | 48 | // Act 49 | await metadataStorageFilter.OnExecutedAsync(executedContext, new CancellationToken()); 50 | 51 | // Assert 52 | mockedFunctionRequestContext.RequestStoreMock.Verify(x => x.Remove(Guid.Empty), Times.Once); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Infrastructure/DefaultHttpExceptionHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using AzureFunctionsV2.HttpExtensions.Exceptions; 5 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 6 | using AzureFunctionsV2.HttpExtensions.Tests.Helpers; 7 | using FluentAssertions; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Newtonsoft.Json; 10 | using Xunit; 11 | 12 | namespace AzureFunctionsV2.HttpExtensions.Tests.Infrastructure 13 | { 14 | [CollectionDefinition("Non-Parallel DefaultHttpExceptionHandlerTests", DisableParallelization = true)] 15 | public class DefaultHttpExceptionHandlerTestsCollectionDefinition 16 | { 17 | } 18 | 19 | [Collection("Non-Parallel DefaultHttpExceptionHandlerTests")] 20 | public class DefaultHttpExceptionHandlerTests 21 | { 22 | [Fact] 23 | public async Task Should_output_BadRequest_400_on_HttpExtensionsException() 24 | { 25 | // Arrange 26 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 27 | var exception = new ParameterFormatConversionException("Test exception", null, "x", mockedFunctionRequestContext.HttpContext); 28 | var handler = new DefaultHttpExceptionHandler(); 29 | DefaultHttpExceptionHandler.OutputRecursiveExceptionMessages = false; 30 | 31 | // Act 32 | var result = await handler.HandleException(mockedFunctionRequestContext.FunctionExecutingContext, 33 | mockedFunctionRequestContext.HttpContext.Request, exception); 34 | 35 | // Assert 36 | var objectResult = result as ObjectResult; 37 | Assert.NotNull(objectResult); 38 | Assert.Equal(400, objectResult.StatusCode); 39 | } 40 | 41 | [Fact] 42 | public async Task Should_output_InternalError_500_on_general_exceptions_and_output_recursive_exception_message() 43 | { 44 | // Arrange 45 | var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 46 | var exception = new Exception("outer", new Exception("inner")); 47 | var handler = new DefaultHttpExceptionHandler(); 48 | DefaultHttpExceptionHandler.OutputRecursiveExceptionMessages = true; 49 | 50 | // Act 51 | var result = await handler.HandleException(mockedFunctionRequestContext.FunctionExecutingContext, 52 | mockedFunctionRequestContext.HttpContext.Request, exception); 53 | 54 | // Assert 55 | var objectResult = result as ObjectResult; 56 | Assert.NotNull(objectResult); 57 | Assert.Equal(500, objectResult.StatusCode); 58 | JsonConvert.SerializeObject(objectResult.Value).Should().Contain("inner").And.Contain("outer"); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Infrastructure/HttpExtensionsExceptionFilterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.ExceptionServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 7 | using AzureFunctionsV2.HttpExtensions.Tests.Helpers; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.Azure.WebJobs.Host; 10 | using Moq; 11 | using Xunit; 12 | 13 | namespace AzureFunctionsV2.HttpExtensions.Tests.Infrastructure 14 | { 15 | public class HttpExtensionsExceptionFilterTests 16 | { 17 | //[Fact] 18 | //public async Task Should_call_http_exception_handler_upon_exception() 19 | //{ 20 | // // Arrange 21 | // var mockedFunctionRequestContext = new MockedFunctionRequestContext(); 22 | // mockedFunctionRequestContext.RequestStoreMock.Setup(x => x.Get(Guid.Empty)) 23 | // .Returns(mockedFunctionRequestContext.HttpRequest); 24 | // var exceptionHandlerMock = new Mock(); 25 | // var exceptionFilter = new HttpExtensionsExceptionFilter( 26 | // mockedFunctionRequestContext.RequestStoreMock.Object, 27 | // exceptionHandlerMock.Object); 28 | // var exception = new Exception("test"); 29 | // var exceptionContext = new FunctionExceptionContext(Guid.Empty, "func", 30 | // mockedFunctionRequestContext.MockedLogger.Object, ExceptionDispatchInfo.Capture(exception), 31 | // new Dictionary()); 32 | 33 | // // Act 34 | // await exceptionFilter.OnExceptionAsync(exceptionContext, new CancellationToken()); 35 | 36 | // // Assert 37 | // exceptionHandlerMock.Verify(x => x.HandleException( 38 | // It.Is((value) => value == exceptionContext), 39 | // It.Is((value) => value == mockedFunctionRequestContext.HttpContext)), Times.Once); 40 | 41 | //} 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Infrastructure/HttpRequestStoreTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 3 | using AzureFunctionsV2.HttpExtensions.Tests.Mocks; 4 | using Xunit; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Tests.Infrastructure 7 | { 8 | public class HttpRequestStoreTests 9 | { 10 | [Fact] 11 | public void Should_store_and_return_and_delete_request() 12 | { 13 | // Arrange 14 | var requestStore = new HttpRequestStore(); 15 | Guid guid = Guid.NewGuid(); 16 | 17 | // Act 18 | var newRequest = new MockHttpRequest(null); 19 | requestStore.Set(guid, newRequest); 20 | var returnedRequest = requestStore.Get(guid); 21 | requestStore.Remove(guid); 22 | var shouldBeNull = requestStore.Get(guid); 23 | 24 | // Assert 25 | Assert.Equal(newRequest, returnedRequest); 26 | Assert.Null(shouldBeNull); 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Mocks/MockHttpContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Threading; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Http.Authentication; 7 | using Microsoft.AspNetCore.Http.Features; 8 | 9 | namespace AzureFunctionsV2.HttpExtensions.Tests.Mocks 10 | { 11 | public class MockHttpContext : HttpContext 12 | { 13 | 14 | 15 | public override void Abort() 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | 20 | public override IFeatureCollection Features { get; } 21 | private HttpRequest _request; 22 | public override HttpRequest Request => _request; 23 | public void SetRequest(HttpRequest r) => _request = r; 24 | private HttpResponse _response; 25 | public override HttpResponse Response => _response; 26 | public void SetResponse(HttpResponse r) => _response = r; 27 | public override ConnectionInfo Connection { get; } 28 | public override WebSocketManager WebSockets { get; } 29 | public override AuthenticationManager Authentication { get; } 30 | public override ClaimsPrincipal User { get; set; } 31 | public override IDictionary Items { get; set; } 32 | public override IServiceProvider RequestServices { get; set; } 33 | public override CancellationToken RequestAborted { get; set; } 34 | public override string TraceIdentifier { get; set; } 35 | public override ISession Session { get; set; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Mocks/MockHttpRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Tests.Mocks 8 | { 9 | public class MockHttpRequest : HttpRequest 10 | { 11 | public MockHttpRequest(MockHttpContext httpContext) 12 | { 13 | HttpContext = httpContext; 14 | httpContext?.SetRequest(this); 15 | } 16 | 17 | public override async Task ReadFormAsync(CancellationToken cancellationToken = new CancellationToken()) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | 22 | public override HttpContext HttpContext { get; } 23 | public override string Method { get; set; } 24 | public override string Scheme { get; set; } 25 | public override bool IsHttps { get; set; } 26 | public override HostString Host { get; set; } 27 | public override PathString PathBase { get; set; } 28 | public override PathString Path { get; set; } 29 | public override QueryString QueryString { get; set; } 30 | public override IQueryCollection Query { get; set; } 31 | public override string Protocol { get; set; } 32 | public override IHeaderDictionary Headers => HeaderDictionary; 33 | public IHeaderDictionary HeaderDictionary; 34 | public override IRequestCookieCollection Cookies { get; set; } 35 | public override long? ContentLength { get; set; } 36 | public override string ContentType { get; set; } 37 | public override Stream Body { get; set; } 38 | public override bool HasFormContentType { get; } 39 | public override IFormCollection Form { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.Tests/Mocks/MockHttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Tests.Mocks 7 | { 8 | public class MockHttpResponse : HttpResponse 9 | { 10 | 11 | public MockHttpResponse(MockHttpContext httpContext) 12 | { 13 | HttpContext = httpContext; 14 | httpContext?.SetResponse(this); 15 | Body = new MemoryStream(); 16 | HeaderDictionary = new HeaderDictionary(); 17 | } 18 | 19 | public override void OnStarting(Func callback, object state) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | 24 | public override void OnCompleted(Func callback, object state) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | 29 | public override void Redirect(string location, bool permanent) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public override HttpContext HttpContext { get; } 35 | public override int StatusCode { get; set; } 36 | public override IHeaderDictionary Headers => HeaderDictionary; 37 | public IHeaderDictionary HeaderDictionary; 38 | public override Stream Body { get; set; } 39 | public override long? ContentLength { get; set; } 40 | public override string ContentType { get; set; } 41 | public override IResponseCookies Cookies { get; } 42 | public override bool HasStarted { get; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions", "AzureFunctionsV2.HttpExtensions\AzureFunctionsV2.HttpExtensions.csproj", "{EAB9AE22-87E9-457F-A57F-324783A1A551}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01 Core", "01 Core", "{42FF5004-68FF-49DA-A9E0-499102050829}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02 Tests", "02 Tests", "{5191C2F7-F6CD-4B75-B835-425CA894BD88}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Tests.FunctionApp", "AzureFunctionsV2.HttpExtensions.Tests.FunctionApp\AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.csproj", "{2A5B60E8-F3C2-486D-BCD8-09910D08AC30}" 13 | ProjectSection(ProjectDependencies) = postProject 14 | {9D8768F6-D017-4440-9A47-B0EF92FD068A} = {9D8768F6-D017-4440-9A47-B0EF92FD068A} 15 | EndProjectSection 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Tests", "AzureFunctionsV2.HttpExtensions.Tests\AzureFunctionsV2.HttpExtensions.Tests.csproj", "{790D3638-1356-4135-A4CC-3B987B0F2009}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup", "AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup\AzureFunctionsV2.HttpExtensions.Tests.FunctionApp.Startup.csproj", "{24B510EE-69A6-4404-B30E-F95EF640E4C2}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03 Examples", "03 Examples", "{FF3921F3-F04E-42B2-A8D0-F757051DEB76}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Examples.FunctionApp", "AzureFunctionsV2.HttpExtensions.Examples.FunctionApp\AzureFunctionsV2.HttpExtensions.Examples.FunctionApp.csproj", "{B584E208-9F6A-47A7-996A-07F818A373C9}" 24 | ProjectSection(ProjectDependencies) = postProject 25 | {9D8768F6-D017-4440-9A47-B0EF92FD068A} = {9D8768F6-D017-4440-9A47-B0EF92FD068A} 26 | EndProjectSection 27 | EndProject 28 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Examples.Authorization", "AzureFunctionsV2.HttpExtensions.Examples.Authorization\AzureFunctionsV2.HttpExtensions.Examples.Authorization.csproj", "{736F4CC3-6600-4B16-AF85-9A06A35D207D}" 29 | ProjectSection(ProjectDependencies) = postProject 30 | {9D8768F6-D017-4440-9A47-B0EF92FD068A} = {9D8768F6-D017-4440-9A47-B0EF92FD068A} 31 | EndProjectSection 32 | EndProject 33 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup", "AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup\AzureFunctionsV2.HttpExtensions.Examples.Authorization.Startup.csproj", "{8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332}" 34 | EndProject 35 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureFunctionsV2.HttpExtensions.Fody", "AzureFunctionsV2.HttpExtensions.Fody\AzureFunctionsV2.HttpExtensions.Fody.csproj", "{9D8768F6-D017-4440-9A47-B0EF92FD068A}" 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {EAB9AE22-87E9-457F-A57F-324783A1A551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {EAB9AE22-87E9-457F-A57F-324783A1A551}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {EAB9AE22-87E9-457F-A57F-324783A1A551}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {EAB9AE22-87E9-457F-A57F-324783A1A551}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {2A5B60E8-F3C2-486D-BCD8-09910D08AC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {2A5B60E8-F3C2-486D-BCD8-09910D08AC30}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {2A5B60E8-F3C2-486D-BCD8-09910D08AC30}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {2A5B60E8-F3C2-486D-BCD8-09910D08AC30}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {790D3638-1356-4135-A4CC-3B987B0F2009}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {790D3638-1356-4135-A4CC-3B987B0F2009}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {790D3638-1356-4135-A4CC-3B987B0F2009}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {790D3638-1356-4135-A4CC-3B987B0F2009}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {24B510EE-69A6-4404-B30E-F95EF640E4C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {24B510EE-69A6-4404-B30E-F95EF640E4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {24B510EE-69A6-4404-B30E-F95EF640E4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {24B510EE-69A6-4404-B30E-F95EF640E4C2}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {B584E208-9F6A-47A7-996A-07F818A373C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {B584E208-9F6A-47A7-996A-07F818A373C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {B584E208-9F6A-47A7-996A-07F818A373C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {B584E208-9F6A-47A7-996A-07F818A373C9}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {736F4CC3-6600-4B16-AF85-9A06A35D207D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {736F4CC3-6600-4B16-AF85-9A06A35D207D}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {736F4CC3-6600-4B16-AF85-9A06A35D207D}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {736F4CC3-6600-4B16-AF85-9A06A35D207D}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {9D8768F6-D017-4440-9A47-B0EF92FD068A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {9D8768F6-D017-4440-9A47-B0EF92FD068A}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {9D8768F6-D017-4440-9A47-B0EF92FD068A}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {9D8768F6-D017-4440-9A47-B0EF92FD068A}.Release|Any CPU.Build.0 = Release|Any CPU 75 | EndGlobalSection 76 | GlobalSection(SolutionProperties) = preSolution 77 | HideSolutionNode = FALSE 78 | EndGlobalSection 79 | GlobalSection(NestedProjects) = preSolution 80 | {EAB9AE22-87E9-457F-A57F-324783A1A551} = {42FF5004-68FF-49DA-A9E0-499102050829} 81 | {2A5B60E8-F3C2-486D-BCD8-09910D08AC30} = {5191C2F7-F6CD-4B75-B835-425CA894BD88} 82 | {790D3638-1356-4135-A4CC-3B987B0F2009} = {5191C2F7-F6CD-4B75-B835-425CA894BD88} 83 | {24B510EE-69A6-4404-B30E-F95EF640E4C2} = {5191C2F7-F6CD-4B75-B835-425CA894BD88} 84 | {B584E208-9F6A-47A7-996A-07F818A373C9} = {FF3921F3-F04E-42B2-A8D0-F757051DEB76} 85 | {736F4CC3-6600-4B16-AF85-9A06A35D207D} = {FF3921F3-F04E-42B2-A8D0-F757051DEB76} 86 | {8B5E8D25-81FE-4B15-A0D1-4B9B7DD14332} = {FF3921F3-F04E-42B2-A8D0-F757051DEB76} 87 | {9D8768F6-D017-4440-9A47-B0EF92FD068A} = {42FF5004-68FF-49DA-A9E0-499102050829} 88 | EndGlobalSection 89 | GlobalSection(ExtensibilityGlobals) = postSolution 90 | SolutionGuid = {F6DF2335-7459-442B-84FE-E0605494D34F} 91 | EndGlobalSection 92 | EndGlobal 93 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpBodyAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Exceptions; 3 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Azure.WebJobs.Description; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Annotations 8 | { 9 | /// 10 | /// Binding attribute that indicates that the parameter value should 11 | /// be read from . Apply to 12 | /// type parameters in the Azure Function signature. 13 | /// 14 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 15 | [Binding] 16 | public class HttpBodyAttribute : HttpSourceAttribute 17 | { 18 | /// 19 | /// Indicates that this is a required field. Required fields are checked upon assignment 20 | /// and will be thrown if the required field is 21 | /// not present or is null/empty. 22 | /// 23 | public bool Required { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpFormAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Exceptions; 3 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Azure.WebJobs.Description; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Annotations 8 | { 9 | /// 10 | /// Binding attribute that indicates that the parameter value should 11 | /// be read from . Apply to 12 | /// type parameters in the Azure Function signature. 13 | /// 14 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 15 | [Binding] 16 | public class HttpFormAttribute : HttpSourceAttribute 17 | { 18 | public HttpFormAttribute() 19 | { 20 | 21 | } 22 | 23 | public HttpFormAttribute(string formFieldName) 24 | { 25 | Name = formFieldName; 26 | } 27 | 28 | /// 29 | /// The name of the form field parameter in the request, if not given it is assumed it 30 | /// matches the function parameter name. 31 | /// 32 | public string Name { get; set; } 33 | 34 | /// 35 | /// Indicates that this is a required field. Required fields are checked upon assignment 36 | /// and will be thrown if the required field is 37 | /// not present or is null/empty. 38 | /// 39 | public bool Required { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpHeaderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Exceptions; 3 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Azure.WebJobs.Description; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Annotations 8 | { 9 | /// 10 | /// Binding attribute that indicates that the parameter value should 11 | /// be read from . Apply to 12 | /// type parameters in the Azure Function signature. 13 | /// 14 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 15 | [Binding] 16 | public class HttpHeaderAttribute : HttpSourceAttribute 17 | { 18 | public HttpHeaderAttribute() 19 | { 20 | 21 | } 22 | 23 | public HttpHeaderAttribute(string headerName) 24 | { 25 | Name = headerName; 26 | } 27 | 28 | /// 29 | /// The name of the header in the request, if not given it is assumed it 30 | /// matches the function parameter name. 31 | /// 32 | public string Name { get; set; } 33 | 34 | /// 35 | /// Indicates that this is a required field. Required fields are checked upon assignment 36 | /// and will be thrown if the required field is 37 | /// not present or is null/empty. 38 | /// 39 | public bool Required { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpQueryAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Exceptions; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Azure.WebJobs.Description; 5 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Annotations 8 | { 9 | /// 10 | /// Binding attribute that indicates that the parameter value should 11 | /// be read from . Apply to 12 | /// type parameters in the Azure Function signature. 13 | /// 14 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 15 | [Binding] 16 | public class HttpQueryAttribute : HttpSourceAttribute 17 | { 18 | public HttpQueryAttribute() 19 | { 20 | 21 | } 22 | 23 | public HttpQueryAttribute(string queryParameterName) 24 | { 25 | Name = queryParameterName; 26 | } 27 | 28 | /// 29 | /// The name of the query parameter in the request, if not given it is assumed it 30 | /// matches the function parameter name. 31 | /// 32 | public string Name { get; set; } 33 | 34 | /// 35 | /// Indicates that this is a required field. Required fields are checked upon assignment 36 | /// and will be thrown if the required field is 37 | /// not present or is null/empty. 38 | /// 39 | public bool Required { get; set; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpSourceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Annotations 5 | { 6 | /// 7 | /// The parent class for all custom HttpRequest source attributes. 8 | /// Those attributes signify from what data source the parameter () value 9 | /// should be assigned from. 10 | /// 11 | public abstract class HttpSourceAttribute : Attribute 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Annotations/HttpTokenAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 3 | using Microsoft.Azure.WebJobs.Description; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Annotations 6 | { 7 | /// 8 | /// Indicates that the value is populated from the JSON Web Token or oAuth2 token. Only valid for 9 | /// type method parameter. 10 | /// 11 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 12 | [Binding] 13 | public class HttpTokenAttribute : Attribute 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/ApiKeyAuthenticationParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Authorization 8 | { 9 | public class ApiKeyAuthenticationParameters 10 | { 11 | public string QueryParameterName { get; set; } 12 | public string HeaderName { get; set; } 13 | public Func> ApiKeyVerifier { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/ApiKeyAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Authorization 7 | { 8 | /// 9 | /// The API key authenticator. 10 | /// Contains the actual authentication logic. 11 | /// 12 | public class ApiKeyAuthenticator : IApiKeyAuthenticator 13 | { 14 | private readonly ApiKeyAuthenticationParameters _authenticationParameters; 15 | 16 | public ApiKeyAuthenticator(IOptions authOptions) 17 | { 18 | _authenticationParameters = authOptions?.Value?.ApiKeyAuthentication; 19 | } 20 | 21 | public async Task Authenticate(HttpRequest request) 22 | { 23 | if(_authenticationParameters?.ApiKeyVerifier == null) 24 | throw new InvalidOperationException("ApiKeyAuthenticationParameters ApiKeyVerifier has not been configured"); 25 | 26 | if(string.IsNullOrEmpty(_authenticationParameters.HeaderName) && string.IsNullOrEmpty(_authenticationParameters.QueryParameterName)) 27 | throw new InvalidOperationException("ApiKeyAuthenticationParameters HeaderName and QueryParameterName have neither been configured"); 28 | 29 | var apiKeyQueryFieldName = _authenticationParameters.QueryParameterName; 30 | var apiKeyHeaderName = _authenticationParameters.HeaderName; 31 | 32 | if (!string.IsNullOrEmpty(apiKeyHeaderName)) 33 | { 34 | if (request.Headers.ContainsKey(apiKeyHeaderName)) 35 | return await _authenticationParameters.ApiKeyVerifier(request.Headers[apiKeyHeaderName], request); 36 | } 37 | 38 | if (!string.IsNullOrEmpty(apiKeyQueryFieldName)) 39 | { 40 | if (request.Query.ContainsKey(apiKeyQueryFieldName)) 41 | return await _authenticationParameters.ApiKeyVerifier(request.Query[apiKeyQueryFieldName], request); 42 | } 43 | 44 | return false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/AuthorizedFunctionDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Authorization 8 | { 9 | /// 10 | /// Implementation of the authorized Function discoverer. 11 | /// 12 | public class AuthorizedFunctionDiscoverer : IAuthorizedFunctionDiscoverer 13 | { 14 | /// 15 | /// Finds Functions that have the attached. 16 | /// Scans assemblies that have been loaded, specifically the ones that refer to this 17 | /// assembly, and then looks for the attribute in static class static methods. 18 | /// 19 | /// A Dictionary of function name and tuple of the corresponding MethodInfo and list of HttpAuthorizeAttributes. 20 | public Dictionary)> GetFunctions() 21 | { 22 | // Find functions from the assemblies. Criteria: 23 | // - member of static class 24 | // - member has a parameter with HttpRequest (with HttpTrigger attribute) in its signature 25 | // - member has FunctionNameAttribute (optional, take the name from it if it has) 26 | // - member has HttpAuthorizeAttribute 27 | 28 | var candidateAssemblies = AppDomain.CurrentDomain.GetAssemblies() 29 | .Where(p => !p.IsDynamic) 30 | .Where(a => a.GetReferencedAssemblies() 31 | .Any(r => r.Name == Assembly.GetAssembly(GetType()).GetName().Name)); 32 | 33 | var functions = new Dictionary)>(); 34 | foreach (var candidateAssembly in candidateAssemblies) 35 | { 36 | var asmFunctionMethodsWithAuth = candidateAssembly.ExportedTypes 37 | .Where(x => x.IsAbstract && x.IsSealed && x.IsClass) 38 | .SelectMany(x => x.GetMethods(BindingFlags.Static | BindingFlags.Public)) 39 | .Where(m => 40 | m.GetParameters().Any(p => 41 | p.ParameterType == typeof(HttpRequest) && 42 | p.GetCustomAttributes().Any(a => a.GetType().Name == "HttpTriggerAttribute") 43 | ) && 44 | m.GetCustomAttributes().Any() 45 | ); 46 | foreach (var method in asmFunctionMethodsWithAuth) 47 | { 48 | var methodFunctionName = method.Name; 49 | var functionNameAttribute = method.GetCustomAttributes() 50 | .FirstOrDefault(a => a.GetType().Name == "FunctionNameAttribute"); 51 | if (functionNameAttribute != null) 52 | { 53 | var propInfo = functionNameAttribute.GetType().GetProperty("Name"); 54 | methodFunctionName = propInfo.GetValue(functionNameAttribute) as string ?? method.Name; 55 | } 56 | 57 | var authorizeAttributes = method.GetCustomAttributes().ToList(); 58 | functions.Add(methodFunctionName, (method, authorizeAttributes)); 59 | } 60 | } 61 | 62 | return functions; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/BasicAuthenticationParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Authorization 6 | { 7 | /// 8 | /// BasicAuth parameters. 9 | /// 10 | public class BasicAuthenticationParameters 11 | { 12 | /// 13 | /// A list of valid username, password pairs. 14 | /// 15 | public Dictionary ValidCredentials { get; set; } = new Dictionary(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/BasicAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Exceptions; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace AzureFunctionsV2.HttpExtensions.Authorization 9 | { 10 | /// 11 | /// The Basic authenticator. 12 | /// Contains the actual authentication logic. 13 | /// 14 | public class BasicAuthenticator : IBasicAuthenticator 15 | { 16 | private readonly BasicAuthenticationParameters _authenticationParameters; 17 | 18 | public BasicAuthenticator(IOptions authOptions) 19 | { 20 | _authenticationParameters = authOptions?.Value?.BasicAuthentication; 21 | } 22 | 23 | public async Task Authenticate(string authorizationHeader) 24 | { 25 | if(_authenticationParameters == null) 26 | throw new InvalidOperationException("BasicAuthenticationParameters have not been configured"); 27 | 28 | if(!authorizationHeader.StartsWith("Basic ")) 29 | throw new HttpAuthenticationException("Expected a basic auth token"); 30 | 31 | authorizationHeader = authorizationHeader.Substring(6); 32 | 33 | byte[] credentialBytes; 34 | try 35 | { 36 | credentialBytes = Convert.FromBase64String(authorizationHeader); 37 | } 38 | catch (Exception e) 39 | { 40 | throw new HttpAuthenticationException("Failed to read the authorization header from base64 string", e); 41 | } 42 | 43 | // Assume UTF-8. 44 | var credentials = Encoding.UTF8.GetString(credentialBytes); 45 | var usernamePassword = credentials.Split(':'); 46 | if(usernamePassword.Length != 2) 47 | throw new HttpAuthenticationException("Invalid credentials format, expected base64 encoded username:password"); 48 | 49 | return _authenticationParameters.ValidCredentials.Any(c => 50 | c.Key.Equals(usernamePassword[0], StringComparison.OrdinalIgnoreCase) && 51 | c.Value.Equals(usernamePassword[1], StringComparison.OrdinalIgnoreCase)); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/HttpAuthenticationOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Exceptions; 6 | using Microsoft.IdentityModel.Tokens; 7 | 8 | namespace AzureFunctionsV2.HttpExtensions.Authorization 9 | { 10 | /// 11 | /// Authentication options. 12 | /// Contains the options set for all different available types of authentication. 13 | /// 14 | public class HttpAuthenticationOptions 15 | { 16 | /// 17 | /// API key based authentication parameters. 18 | /// 19 | public ApiKeyAuthenticationParameters ApiKeyAuthentication { get; set; } 20 | 21 | /// 22 | /// Basic auth parameters. 23 | /// 24 | public BasicAuthenticationParameters BasicAuthentication { get; set; } 25 | 26 | /// 27 | /// OAuth2 based authentication parameters. Note: if you're configuring JWT based OAuth2 authentication, 28 | /// configure the JwtAuthentication property instead. 29 | /// 30 | public OAuth2AuthenticationParameters OAuth2Authentication { get; set; } 31 | 32 | /// 33 | /// JWT (JSON Web Token) based authentication parameters. 34 | /// 35 | public JwtAuthenticationParameters JwtAuthentication { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/HttpAuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Authorization 6 | { 7 | public enum Scheme 8 | { 9 | Basic, 10 | OAuth2, 11 | Jwt, 12 | HeaderApiKey, 13 | QueryApiKey 14 | } 15 | 16 | /// 17 | /// Attribute that indicates that the Function requires authentication/authorization. 18 | /// 19 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 20 | public class HttpAuthorizeAttribute : Attribute 21 | { 22 | public HttpAuthorizeAttribute(Scheme scheme) 23 | { 24 | Scheme = scheme; 25 | } 26 | 27 | public HttpAuthorizeAttribute() 28 | { 29 | } 30 | 31 | /// 32 | /// The required authentication scheme. 33 | /// 34 | public Scheme Scheme { get; set; } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/IApiKeyAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Authorization 8 | { 9 | /// 10 | /// Interface for the API Key Authenticator. 11 | /// 12 | public interface IApiKeyAuthenticator 13 | { 14 | /// 15 | /// The authentication method. 16 | /// 17 | /// 18 | /// False if authentication failed/unauthorized, true if authentication succeeded. 19 | Task Authenticate(HttpRequest request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/IAuthorizedFunctionDiscoverer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Authorization 5 | { 6 | /// 7 | /// Interface for the Function discoverer that returns a set of 8 | /// (, ), ie. all 9 | /// the Functions that bear the . 10 | /// 11 | public interface IAuthorizedFunctionDiscoverer 12 | { 13 | Dictionary)> GetFunctions(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/IBasicAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System.Threading.Tasks; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Authorization 6 | { 7 | /// 8 | /// Interface for the Basic Authenticator. 9 | /// 10 | public interface IBasicAuthenticator 11 | { 12 | /// 13 | /// The authentication method. 14 | /// 15 | /// False if authentication failed/unauthorized, true if authentication succeeded. 16 | Task Authenticate(string authorizationHeader); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/IJwtAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using AzureFunctionsV2.HttpExtensions.Exceptions; 5 | using Microsoft.IdentityModel.Tokens; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Authorization 8 | { 9 | /// 10 | /// The interface for a JWT Authenticator. 11 | /// The JWT Authenticator verifies that the JWT contents are valid and returns 12 | /// claims and the validated token. 13 | /// 14 | public interface IJwtAuthenticator 15 | { 16 | /// 17 | /// The authentication method. 18 | /// 19 | /// JWT token to validate. 20 | /// The ClaimsPrincipal and SecurityToken resulting from the operation if it succeeded. 21 | /// 22 | /// 23 | Task<(ClaimsPrincipal claimsPrincipal, SecurityToken validatedToken)> Authenticate(string jwtToken); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/IOAuth2Authenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | 8 | namespace AzureFunctionsV2.HttpExtensions.Authorization 9 | { 10 | /// 11 | /// The interface for the OAuth2 authenticator. 12 | /// 13 | public interface IOAuth2Authenticator 14 | { 15 | /// 16 | /// Authenticates and authorizes the user. 17 | /// 18 | /// Note: requires the OAuth2AuthenticationParameters.CustomAuthorizationFilter set. 19 | /// Otherwise the implementation should throw, as the CustomAuthorizationFilter is mandatory 20 | /// from the point of authentication and authorization since the tokens can be anything. 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// The resolved ClaimsPrincipal. 27 | Task AuthenticateAndAuthorize(string token, HttpRequest request, IList attributes); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/JwtAuthenticationParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Exceptions; 7 | using Microsoft.IdentityModel.Tokens; 8 | 9 | namespace AzureFunctionsV2.HttpExtensions.Authorization 10 | { 11 | public class JwtAuthenticationParameters 12 | { 13 | /// 14 | /// Token validation parameters. These are used when validating JSON Web Tokens. 15 | /// 16 | /// In practice you'll need to provide IssuerSigningKey(s), ValidIssuer(s) and ValidAudience(s). 17 | /// 18 | /// 19 | /// If you wish to use a OIDC conformant endpoint to populate the configuration (eg. Auth0), use 20 | /// the class. 21 | /// 22 | /// 23 | public TokenValidationParameters TokenValidationParameters { get; set; } 24 | 25 | /// 26 | /// A custom authorization filter. If provided, it will be used to authorize the user. 27 | /// Otherwise there will be no authorization applied, only authentication. 28 | /// This function should throw a 29 | /// if the authorization fails. 30 | /// 31 | public Func, Task> CustomAuthorizationFilter { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/JwtAuthenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Exceptions; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.IdentityModel.Protocols; 8 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 9 | using Microsoft.IdentityModel.Tokens; 10 | 11 | namespace AzureFunctionsV2.HttpExtensions.Authorization 12 | { 13 | /// 14 | /// JWT Authenticator implementation. 15 | /// Basically validates the JWT using the given validation parameters. 16 | /// 17 | public class JwtAuthenticator : IJwtAuthenticator 18 | { 19 | private readonly TokenValidationParameters _jwtValidationParameters; 20 | private readonly IConfigurationManager _manager; 21 | private readonly ISecurityTokenValidator _handler; 22 | 23 | public JwtAuthenticator(IOptions config, 24 | ISecurityTokenValidator jwtSecurityTokenHandler, 25 | IConfigurationManager configurationManager) 26 | { 27 | _jwtValidationParameters = config?.Value?.JwtAuthentication?.TokenValidationParameters; 28 | _handler = jwtSecurityTokenHandler; 29 | _manager = configurationManager; 30 | } 31 | 32 | /// 33 | /// Validates the token. 34 | /// Throws an exception if validation fails, otherwise returns the resolved 35 | /// and the validated token . 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | public async Task<(ClaimsPrincipal claimsPrincipal, SecurityToken validatedToken)> Authenticate(string jwtToken) 42 | { 43 | if(_jwtValidationParameters == null) 44 | throw new InvalidOperationException("JwtAuthenticatorOptions have not been configured"); 45 | 46 | if (!jwtToken.StartsWith("Bearer ")) 47 | throw new HttpAuthenticationException("Expected Bearer token"); 48 | jwtToken = jwtToken.Substring(7); 49 | 50 | try 51 | { 52 | if (_jwtValidationParameters is OpenIdConnectJwtValidationParameters oidcParams && 53 | _jwtValidationParameters.IssuerSigningKeys == null) 54 | { 55 | var config = await _manager.GetConfigurationAsync(CancellationToken.None); 56 | oidcParams.ValidIssuer = config.Issuer; 57 | oidcParams.IssuerSigningKeys = config.SigningKeys; 58 | } 59 | 60 | var claimsPrincipal = _handler.ValidateToken(jwtToken, _jwtValidationParameters, out var validatedToken); 61 | return (claimsPrincipal, validatedToken); 62 | } 63 | catch (Exception e) 64 | { 65 | throw new HttpAuthenticationException("Token validation failed", e); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/OAuth2AuthenticationParameters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Exceptions; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.IdentityModel.Tokens; 9 | 10 | namespace AzureFunctionsV2.HttpExtensions.Authorization 11 | { 12 | public class OAuth2AuthenticationParameters 13 | { 14 | /// 15 | /// An authorization method that must validate the token, given the HttpRequest and the authorization 16 | /// attributes. Returns the resolved ClaimsPrincipal, or must throw an exception if authorization failed 17 | /// or user was unauthorized. 18 | /// 19 | /// The authorization token 20 | /// The HTTP request 21 | /// The Azure Function authorization attributes 22 | /// The user ClaimsPrincipal 23 | /// 24 | /// 25 | public delegate Task AuthorizeMethod(string token, HttpRequest request, 26 | IList authorizeAttributes); 27 | 28 | /// 29 | /// The authorization method that authenticates and authorizes the user. 30 | /// Returns the resolved ClaimsPrincipal, or must throw an exception if authorization failed 31 | /// or user was unauthorized. 32 | /// 33 | public AuthorizeMethod CustomAuthorizationFilter { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/OAuth2Authenticator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Security.Claims; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using AzureFunctionsV2.HttpExtensions.Exceptions; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace AzureFunctionsV2.HttpExtensions.Authorization 11 | { 12 | /// 13 | /// OAuth2 authenticator. 14 | /// 15 | public class OAuth2Authenticator : IOAuth2Authenticator 16 | { 17 | private readonly OAuth2AuthenticationParameters _authenticationParameters; 18 | 19 | public OAuth2Authenticator(IOptions httpAuthOptions) 20 | { 21 | _authenticationParameters = httpAuthOptions?.Value?.OAuth2Authentication; 22 | } 23 | 24 | public async Task AuthenticateAndAuthorize(string token, HttpRequest request, IList attributes) 25 | { 26 | if (_authenticationParameters?.CustomAuthorizationFilter == null) 27 | throw new InvalidOperationException("OAuth2AuthenticationParameters have not been configured"); 28 | 29 | if (!token.StartsWith("Bearer ")) 30 | throw new HttpAuthenticationException("Expected Bearer token in Authorization header"); 31 | token = token.Substring(7); 32 | 33 | return await _authenticationParameters.CustomAuthorizationFilter(token, request, attributes); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Authorization/OpenIdConnectJwtValidationParameters.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IdentityModel.Tokens; 2 | 3 | namespace AzureFunctionsV2.HttpExtensions.Authorization 4 | { 5 | /// 6 | /// TokenValidationParameters for using OpenIdConnect (OIDC) configuration. 7 | /// Use this with Auth0 for example. 8 | /// 9 | public class OpenIdConnectJwtValidationParameters : TokenValidationParameters 10 | { 11 | /// 12 | /// The configuration URL where the configuration will be fetched from. 13 | /// 14 | public string OpenIdConnectConfigurationUrl { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/AzureFunctionsV2.HttpExtensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | Jussi Saarivirta 7 | Improves the Azure Functions HTTP Trigger experience by extending the infrastructure around it, allowing to define request parameters in the function signature and implementing boilerplate like exception filters. 8 | (c) 2019 Jussi Saarivirta 9 | https://aka.ms/deprecateLicenseUrl 10 | https://github.com/Jusas/AzureFunctionsV2.HttpExtensions 11 | https://raw.githubusercontent.com/Jusas/AzureFunctionsV2.HttpExtensions/master/assets/logo.png 12 | https://github.com/Jusas/AzureFunctionsV2.HttpExtensions 13 | Git 14 | Azure Functions 15 | true 16 | 1.3.0 17 | 1.3.0.0 18 | 19 | 20 | 21 | H:\dev\AzureFunctionsV2.HttpExtensions\src\AzureFunctionsV2.HttpExtensions\AzureFunctionsV2.HttpExtensions.xml 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/AzureFunctionsV2.HttpExtensions.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | AzureFunctionsV2.HttpExtensions 5 | 1.3.1 6 | Jussi Saarivirta 7 | Jussi Saarivirta 8 | true 9 | 10 | MIT 11 | https://github.com/Jusas/AzureFunctionsV2.HttpExtensions 12 | https://raw.githubusercontent.com/Jusas/AzureFunctionsV2.HttpExtensions/master/assets/logo.png 13 | Improves the Azure Functions HTTP Trigger experience by extending the infrastructure around it, allowing to define request parameters in the function signature and implementing boilerplate like exception filters and adds different authentication schemes. 14 | (c) 2019 Jussi Saarivirta 15 | Azure Functions 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Exceptions/HttpAuthenticationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AzureFunctionsV2.HttpExtensions.Exceptions 4 | { 5 | /// 6 | /// The exception is thrown when authentication checks fail and authentication is required. 7 | /// 8 | public class HttpAuthenticationException : Exception 9 | { 10 | public HttpAuthenticationException(string message, Exception inner = null) : base(message, inner) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Exceptions/HttpAuthorizationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AzureFunctionsV2.HttpExtensions.Exceptions 4 | { 5 | /// 6 | /// The exception is thrown when authorization checks fail and authorization is required. 7 | /// 8 | public class HttpAuthorizationException : Exception 9 | { 10 | public HttpAuthorizationException(string message, Exception inner = null) : base(message, inner) 11 | { 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Exceptions/HttpExtensionsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Exceptions 5 | { 6 | /// 7 | /// The parent class for custom exceptions. 8 | /// 9 | public abstract class HttpExtensionsException : Exception 10 | { 11 | public HttpContext HttpContext { get; } 12 | public string ParameterName { get; } 13 | 14 | protected HttpExtensionsException(string message, Exception inner, string parameterName, HttpContext requestHttpContext) : base(message, inner) 15 | { 16 | HttpContext = requestHttpContext; 17 | ParameterName = parameterName; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Exceptions/ParameterFormatConversionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Exceptions 6 | { 7 | /// 8 | /// The exception is thrown when there was an error converting values when trying to assign 9 | /// values to parameters. 10 | /// 11 | public class ParameterFormatConversionException : HttpExtensionsException 12 | { 13 | public ParameterFormatConversionException(string message, Exception inner, string parameterName, HttpContext requestHttpContext) : base(message, inner, parameterName, requestHttpContext) 14 | { 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Exceptions/ParameterRequiredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Annotations; 3 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace AzureFunctionsV2.HttpExtensions.Exceptions 7 | { 8 | /// 9 | /// The exception is thrown when a is marked as required by a 10 | /// but the parameter was not present in the request or was null/empty. 11 | /// 12 | public class ParameterRequiredException : HttpExtensionsException 13 | { 14 | public ParameterRequiredException(string message, Exception inner, string parameterName, HttpContext requestHttpContext) : base(message, inner, parameterName, requestHttpContext) 15 | { 16 | 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Extensions/HttpAttributeExtensionsConfigProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using AzureFunctionsV2.HttpExtensions.Annotations; 3 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 4 | using Microsoft.Azure.WebJobs.Host.Bindings; 5 | using Microsoft.Azure.WebJobs.Host.Config; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Extensions 8 | { 9 | /// 10 | /// The config provider that registers the different bindings. 11 | /// 12 | public class HttpAttributeExtensionsConfigProvider : IExtensionConfigProvider 13 | { 14 | public HttpAttributeExtensionsConfigProvider() 15 | { 16 | 17 | } 18 | 19 | public void Initialize(ExtensionConfigContext context) 20 | { 21 | var ruleFromBody = context.AddBindingRule(); 22 | var ruleFromForm = context.AddBindingRule(); 23 | var ruleFromHeader = context.AddBindingRule(); 24 | var ruleFromQuery = context.AddBindingRule(); 25 | var ruleFromAuth = context.AddBindingRule(); 26 | 27 | ruleFromBody.BindToInput(BodyPlaceholder); 28 | ruleFromForm.BindToInput(FormPlaceholder); 29 | ruleFromHeader.BindToInput(HeaderPlaceholder); 30 | ruleFromQuery.BindToInput(QueryPlaceholder); 31 | ruleFromAuth.BindToInput(AuthPlaceholder); 32 | 33 | context.AddOpenConverter>(typeof(HttpParamConverter<>)); 34 | context.AddConverter(new HttpUserConverter()); 35 | } 36 | 37 | private async Task BodyPlaceholder(HttpBodyAttribute attribute, ValueBindingContext ctx) => new AttributedParameter(attribute); 38 | private async Task FormPlaceholder(HttpFormAttribute attribute, ValueBindingContext ctx) => new AttributedParameter(attribute); 39 | private async Task HeaderPlaceholder(HttpHeaderAttribute attribute, ValueBindingContext ctx) => new AttributedParameter(attribute); 40 | private async Task QueryPlaceholder(HttpQueryAttribute attribute, ValueBindingContext ctx) => new AttributedParameter(attribute); 41 | private async Task AuthPlaceholder(HttpTokenAttribute attribute, ValueBindingContext ctx) => new AttributedParameter(attribute); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/ExtensionsStartup.cs: -------------------------------------------------------------------------------- 1 | using System.IdentityModel.Tokens.Jwt; 2 | using AzureFunctionsV2.HttpExtensions; 3 | using AzureFunctionsV2.HttpExtensions.Authorization; 4 | using AzureFunctionsV2.HttpExtensions.IL; 5 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 6 | using Microsoft.Azure.WebJobs; 7 | using Microsoft.Azure.WebJobs.Host; 8 | using Microsoft.Azure.WebJobs.Hosting; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Options; 11 | using Microsoft.IdentityModel.Protocols; 12 | using Microsoft.IdentityModel.Protocols.OpenIdConnect; 13 | 14 | [assembly: WebJobsStartup(typeof(ExtensionsStartup), "AzureFunctionsV2HttpExtensionsStartup")] 15 | 16 | namespace AzureFunctionsV2.HttpExtensions 17 | { 18 | public class ExtensionsStartup : IWebJobsStartup 19 | { 20 | public void Configure(IWebJobsBuilder builder) 21 | { 22 | builder.AddAzureFunctionHttpExtensions(); 23 | 24 | builder.Services.AddSingleton(); 25 | builder.Services.AddSingleton(); 26 | builder.Services.AddSingleton(); 27 | 28 | builder.Services.AddSingleton(); 29 | DefaultHttpExceptionHandler.OutputRecursiveExceptionMessages = true; 30 | 31 | builder.Services.AddSingleton(); 32 | 33 | builder.Services.AddSingleton(); 34 | builder.Services.AddSingleton(); 35 | 36 | builder.Services.AddSingleton(provider => 37 | { 38 | var options = provider.GetService>(); 39 | var configManager = 40 | options?.Value?.JwtAuthentication?.TokenValidationParameters is OpenIdConnectJwtValidationParameters oidcParams 41 | ? new ConfigurationManager( 42 | oidcParams.OpenIdConnectConfigurationUrl, new OpenIdConnectConfigurationRetriever()) 43 | : null; 44 | return new JwtAuthenticator(options, new JwtSecurityTokenHandler(), configManager); 45 | }); 46 | builder.Services.AddSingleton(); 47 | builder.Services.AddSingleton(); 48 | builder.Services.AddSingleton(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/IL/AssemblyUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.IL 8 | { 9 | public static class AssemblyUtils 10 | { 11 | 12 | private static bool? _isIlModified; 13 | 14 | public static bool IsILModified() 15 | { 16 | if (_isIlModified == null) 17 | { 18 | var referringAssemblies = AppDomain.CurrentDomain.GetAssemblies() 19 | .Where(p => !p.IsDynamic) 20 | .Where(a => a.GetReferencedAssemblies() 21 | .Any(r => r.Name == Assembly.GetAssembly(typeof(AssemblyUtils)).GetName().Name)); 22 | 23 | var fodyMarkers = referringAssemblies.Select(a => a.GetType("ProcessedByFody")) 24 | .Where(a => a != null) 25 | .Distinct(); 26 | var httpExtensionMarker = fodyMarkers.FirstOrDefault( 27 | t => t.IsClass && 28 | t.GetFields(BindingFlags.NonPublic | BindingFlags.Static) 29 | .Any(f => f.Name == "AzureFunctionsV2HttpExtensions")); 30 | 31 | if (httpExtensionMarker != null) 32 | _isIlModified = true; 33 | else 34 | _isIlModified = false; 35 | } 36 | 37 | return _isIlModified.Value; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/IL/IILFunctionExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | namespace AzureFunctionsV2.HttpExtensions.IL 2 | { 3 | public interface IILFunctionExceptionHandler 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/IL/ILFunctionExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Web.Http; 4 | using AzureFunctionsV2.HttpExtensions.Infrastructure; 5 | using AzureFunctionsV2.HttpExtensions.Utils; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | 9 | namespace AzureFunctionsV2.HttpExtensions.IL 10 | { 11 | /// 12 | /// The exception handler methods whose invocation is injected to the IL assembly code 13 | /// after compilation (to each Function and its compiler generated async state machine method). 14 | /// 15 | public class ILFunctionExceptionHandler : IILFunctionExceptionHandler 16 | { 17 | 18 | private static IHttpExceptionHandler _httpExceptionHandler; 19 | 20 | public ILFunctionExceptionHandler(IHttpExceptionHandler httpExceptionHandler) 21 | { 22 | _httpExceptionHandler = httpExceptionHandler; 23 | } 24 | 25 | /// 26 | /// A method that simply looks for stored exceptions from the request's HttpContext 27 | /// and throws the first one found if one exists. 28 | /// 29 | /// This method is called at the beginning of each HTTP triggered Function; its job 30 | /// is to rethrow the exceptions captured inside the Function Filters because they 31 | /// can't throw immediately, otherwise it'd cause an unhandled exception and we'd 32 | /// lose control of the Function return value. 33 | /// 34 | /// 35 | /// 36 | public static void RethrowStoredException(HttpRequest request) 37 | { 38 | // Simply rethrow a stored exception if one exists. 39 | var exception = request.HttpContext.GetStoredExceptions().FirstOrDefault(); 40 | if (exception != null) 41 | { 42 | throw exception; 43 | } 44 | } 45 | 46 | /// 47 | /// A method that is called in the async state machine's MoveNext() catch block. 48 | /// This effectively replaces the compiler generated SetException() call, 49 | /// and enables us to swallow and handle the exception and return a valid value 50 | /// from the Function - enabling us to return custom error results. 51 | /// 52 | /// The thrown exception 53 | /// 54 | /// Either InternalServerErrorResult, or any IActionResult if the _httpExceptionHandler is defined. 55 | /// By default it should be set to . 56 | public static IActionResult HandleExceptionAndReturnResult(Exception exception, HttpRequest request) 57 | { 58 | if (_httpExceptionHandler != null) 59 | { 60 | var awaitable = _httpExceptionHandler.HandleException( 61 | request.HttpContext.GetStoredFunctionExecutingContext(), 62 | request, exception); 63 | return awaitable.Result; 64 | } 65 | 66 | return new InternalServerErrorResult(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/AttributedParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Annotations; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 5 | { 6 | /// 7 | /// A placeholder class for temporarily storing for instances 8 | /// that get instantiated a bit later once the gets invoked. 9 | /// 10 | public class AttributedParameter 11 | { 12 | public Attribute SourceAttribute { get; set; } 13 | 14 | public AttributedParameter(Attribute sourceAttribute) 15 | { 16 | SourceAttribute = sourceAttribute; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/DefaultHttpExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using AzureFunctionsV2.HttpExtensions.Exceptions; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Azure.WebJobs.Host; 9 | using Microsoft.Extensions.Logging; 10 | using Newtonsoft.Json; 11 | 12 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 13 | { 14 | public class DefaultHttpExceptionHandler : IHttpExceptionHandler 15 | { 16 | public static bool OutputRecursiveExceptionMessages { get; set; } 17 | 18 | public DefaultHttpExceptionHandler() 19 | { 20 | } 21 | 22 | protected virtual string GetExceptionMessageRecursive(Exception outermostException) 23 | { 24 | var messages = new List(); 25 | var exception = outermostException; 26 | while (exception != null) 27 | { 28 | messages.Add(exception.Message); 29 | exception = exception.InnerException; 30 | } 31 | 32 | return string.Join("; ", messages); 33 | } 34 | 35 | public virtual async Task HandleException(FunctionExecutingContext functionExecutingContext, HttpRequest request, Exception exception) 36 | { 37 | functionExecutingContext?.Logger.LogError(exception, GetExceptionMessageRecursive(exception)); 38 | 39 | var errorObject = new Dictionary(); 40 | 41 | var httpExtensionsException = exception as HttpExtensionsException 42 | ?? exception.InnerException as HttpExtensionsException; 43 | 44 | if (httpExtensionsException is ParameterFormatConversionException || 45 | httpExtensionsException is ParameterRequiredException) 46 | { 47 | var response = new BadRequestObjectResult(errorObject); 48 | errorObject.Add("message", OutputRecursiveExceptionMessages 49 | ? GetExceptionMessageRecursive(httpExtensionsException) 50 | : httpExtensionsException.Message); 51 | errorObject.Add("parameter", httpExtensionsException.ParameterName); 52 | return response; 53 | } 54 | 55 | var httpAuthenticationException = exception as HttpAuthenticationException 56 | ?? exception.InnerException as HttpAuthenticationException; 57 | 58 | if (httpAuthenticationException != null) 59 | { 60 | var response = new ObjectResult(errorObject); 61 | response.StatusCode = 401; 62 | errorObject.Add("message", OutputRecursiveExceptionMessages 63 | ? GetExceptionMessageRecursive(httpAuthenticationException) 64 | : httpAuthenticationException.Message); 65 | return response; 66 | } 67 | 68 | var httpAuthorizationException = exception as HttpAuthorizationException 69 | ?? exception.InnerException as HttpAuthorizationException; 70 | 71 | if (httpAuthorizationException != null) 72 | { 73 | var response = new ObjectResult(errorObject); 74 | response.StatusCode = 403; 75 | errorObject.Add("message", OutputRecursiveExceptionMessages 76 | ? GetExceptionMessageRecursive(httpAuthorizationException) 77 | : httpAuthorizationException.Message); 78 | return response; 79 | } 80 | 81 | var defaultResponse = new ObjectResult(errorObject); 82 | defaultResponse.StatusCode = 500; 83 | errorObject.Add("message", OutputRecursiveExceptionMessages 84 | ? GetExceptionMessageRecursive(exception) 85 | : exception.Message); 86 | return defaultResponse; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/ExtensionRegistration.cs: -------------------------------------------------------------------------------- 1 | using AzureFunctionsV2.HttpExtensions.Extensions; 2 | using Microsoft.Azure.WebJobs; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 5 | { 6 | public static class ExtensionRegistration 7 | { 8 | public static IWebJobsBuilder AddAzureFunctionHttpExtensions(this IWebJobsBuilder builder) 9 | { 10 | builder.AddExtension(); 11 | return builder; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpExtensionsExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Azure.WebJobs.Host; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 8 | { 9 | /// 10 | /// Exception filter to enable customizing responses when the Function throws 11 | /// an exception. Invokes the registered implementation. 12 | /// 13 | [Obsolete("Not in intended use; unable to set the return value of the HttpResponse here, using IL code injection with Fody Weaver instead.")] 14 | public class HttpExtensionsExceptionFilter : IFunctionExceptionFilter 15 | { 16 | private static IHttpExceptionHandler _httpExceptionHandler; 17 | private readonly IHttpRequestStore _httpRequestStore; 18 | 19 | public HttpExtensionsExceptionFilter(IHttpRequestStore httpRequestStore, IHttpExceptionHandler httpExceptionHandler) 20 | { 21 | _httpRequestStore = httpRequestStore; 22 | _httpExceptionHandler = httpExceptionHandler; 23 | } 24 | 25 | public async Task OnExceptionAsync(FunctionExceptionContext exceptionContext, CancellationToken cancellationToken) 26 | { 27 | //exceptionContext.Logger.LogError(exceptionContext.Exception.ToString()); 28 | //var httpContext = _httpRequestStore.Get(exceptionContext.FunctionInstanceId)?.HttpContext; 29 | //if(_httpExceptionHandler != null) 30 | // await _httpExceptionHandler.HandleException(exceptionContext, httpContext); 31 | //_httpRequestStore.Remove(exceptionContext.FunctionInstanceId); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpParam.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AzureFunctionsV2.HttpExtensions.Annotations; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 6 | { 7 | /// 8 | /// 9 | /// The class that defines a Function parameter whose value is coming from a HTTP request. 10 | /// Together with implementations of they define parameters 11 | /// whose values can be retrieved from the trigger's . 12 | /// 13 | /// 14 | /// Should always be used together with the attributes. 15 | /// 16 | /// 17 | /// 18 | public class HttpParam : IHttpParam 19 | { 20 | /// 21 | /// Reference to the implementation that this parameter 22 | /// has. 23 | /// 24 | public Attribute HttpExtensionAttribute { get; set; } 25 | 26 | /// 27 | /// The value retrieved and deserialized from the HTTP request. 28 | /// 29 | public T Value { get; set; } 30 | 31 | public static implicit operator T(HttpParam param) 32 | { 33 | return param.Value; 34 | } 35 | 36 | public override string ToString() 37 | { 38 | return Value?.ToString() ?? "null"; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpParamConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.WebJobs; 2 | 3 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 4 | { 5 | /// 6 | /// The converter that transforms the temporary AttributeParameters into HttpParams. 7 | /// 8 | /// 9 | public class HttpParamConverter : IConverter> 10 | { 11 | public HttpParam Convert(AttributedParameter input) 12 | { 13 | return new HttpParam 14 | { 15 | HttpExtensionAttribute = input.SourceAttribute 16 | }; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpRequestMetadataStorageFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using AzureFunctionsV2.HttpExtensions.IL; 8 | using AzureFunctionsV2.HttpExtensions.Utils; 9 | using Microsoft.AspNetCore.Http; 10 | using Microsoft.Azure.WebJobs.Host; 11 | 12 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 13 | { 14 | /// 15 | /// This is a FunctionFilter whose whole purpose is to store capture some metadata from 16 | /// the request for the duration of the function invocation for other components to use. 17 | /// 18 | public class HttpRequestMetadataStorageFilter : IFunctionInvocationFilter 19 | { 20 | private IHttpRequestStore _httpRequestStore; 21 | 22 | public HttpRequestMetadataStorageFilter(IHttpRequestStore httpRequestStore, 23 | IILFunctionExceptionHandler ilFunctionExceptionHandler /* just so that ILFunctionExceptionHandler gets initialized */) 24 | { 25 | _httpRequestStore = httpRequestStore; 26 | } 27 | 28 | public async Task OnExecutingAsync(FunctionExecutingContext executingContext, 29 | CancellationToken cancellationToken) 30 | { 31 | if (executingContext.Arguments.Values.Where(x => x != null).FirstOrDefault( 32 | x => typeof(HttpRequest).IsAssignableFrom(x.GetType())) is HttpRequest httpRequest) 33 | { 34 | _httpRequestStore.Set(executingContext.FunctionInstanceId, httpRequest); 35 | httpRequest.HttpContext.StoreFunctionExecutingContext(executingContext); 36 | } 37 | } 38 | 39 | public async Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken) 40 | { 41 | _httpRequestStore.Remove(executedContext.FunctionInstanceId); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpRequestStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Microsoft.AspNetCore.Http; 4 | 5 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 6 | { 7 | /// 8 | /// Implementation of the . 9 | /// Stores HttpRequests in a . 10 | /// 11 | public class HttpRequestStore : IHttpRequestStore 12 | { 13 | private readonly ConcurrentDictionary _httpRequests = new ConcurrentDictionary(); 14 | 15 | public void Set(Guid functionInvocationId, HttpRequest httpRequest) 16 | { 17 | if (_httpRequests.ContainsKey(functionInvocationId)) 18 | return; 19 | _httpRequests.AddOrUpdate(functionInvocationId, httpRequest, (guid, request) => request); 20 | } 21 | 22 | public void Remove(Guid functionInvocationId) 23 | { 24 | if (_httpRequests.ContainsKey(functionInvocationId)) 25 | { 26 | _httpRequests.TryRemove(functionInvocationId, out _); 27 | } 28 | } 29 | 30 | public HttpRequest Get(Guid functionInvocationId) 31 | { 32 | HttpRequest r = null; 33 | if (_httpRequests.ContainsKey(functionInvocationId)) 34 | { 35 | _httpRequests.TryGetValue(functionInvocationId, out r); 36 | } 37 | return r; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpUser.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Claims; 2 | using AzureFunctionsV2.HttpExtensions.Annotations; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 5 | { 6 | /// 7 | /// The class that defines a User parameter coming from a JSON Web Token. It is 8 | /// a value holder whose primary job is to hold the ClaimsPrincipal of the user 9 | /// that was resolved from the JWT. 10 | /// 11 | /// This class should always have the binding attribute applied to it 12 | /// in the Function signature. 13 | /// 14 | /// 15 | public class HttpUser 16 | { 17 | /// 18 | /// The claims of the user, originating from the JSON Web Token. 19 | /// 20 | public ClaimsPrincipal ClaimsPrincipal { get; set; } 21 | 22 | /// 23 | /// Implicit operator for easier assignment. 24 | /// 25 | /// 26 | public static implicit operator ClaimsPrincipal(HttpUser user) 27 | { 28 | return user.ClaimsPrincipal; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/HttpUserConverter.cs: -------------------------------------------------------------------------------- 1 | using AzureFunctionsV2.HttpExtensions.Authorization; 2 | using Microsoft.Azure.WebJobs; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 5 | { 6 | /// 7 | /// The converter that transforms the temporary AttributeParameters into HttpUser. 8 | /// The HttpUser.ClaimsPrincipal will be later filled in the . 9 | /// 10 | public class HttpUserConverter : IConverter 11 | { 12 | public HttpUser Convert(AttributedParameter input) 13 | { 14 | return new HttpUser(); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/IHttpExceptionHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Azure.WebJobs.Host; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 8 | { 9 | /// 10 | /// 11 | public interface IHttpExceptionHandler 12 | { 13 | /// 14 | /// Handles the exception. 15 | /// 16 | /// 17 | Task HandleException(FunctionExecutingContext functionExecutingContext, HttpRequest request, Exception exception); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/IHttpParam.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 4 | { 5 | /// 6 | /// Interface for without generics. 7 | /// 8 | public interface IHttpParam 9 | { 10 | /// 11 | /// The that has been assigned 12 | /// to this HttpParam. 13 | /// 14 | Attribute HttpExtensionAttribute { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/IHttpParamValueDeserializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 8 | { 9 | /// 10 | /// A custom deserializer interface that allows fully customized deserializing of HTTP parameters. 11 | /// The implementation of this interface must be registered to Services at startup. 12 | /// 13 | public interface IHttpParamValueDeserializer 14 | { 15 | /// 16 | /// This method should deserialize the body from Stream to the target type. 17 | /// 18 | /// The request body 19 | /// The target value type 20 | /// The Function name whose parameters are being assigned 21 | /// The source HttpRequest 22 | /// The deserialization operation result 23 | Task DeserializeBodyParameter(Stream body, Type httpParamValueType, string functionName, 24 | HttpRequest request); 25 | 26 | /// 27 | /// This method should deserialize a header value to the target type. 28 | /// 29 | /// The header name 30 | /// The header source value 31 | /// The target value type 32 | /// The Function name whose parameters are being assigned 33 | /// The source HttpRequest 34 | /// The deserialization operation result 35 | Task DeserializeHeaderParameter(string headerName, StringValues headerValue, Type httpParamValueType, 36 | string functionName, HttpRequest request); 37 | 38 | /// 39 | /// This method should deserialize a form parameter value to the target type. 40 | /// 41 | /// The form parameter name 42 | /// The form parameter source value 43 | /// The target value type 44 | /// The Function name whose parameters are being assigned 45 | /// The source HttpRequest 46 | /// The deserialization operation result 47 | Task DeserializeFormParameter(string formParameterName, StringValues formParameterValue, Type httpParamValueType, 48 | string functionName, HttpRequest request); 49 | 50 | /// 51 | /// This method should deserialize a query parameter value to the target type. 52 | /// 53 | /// The query parameter name 54 | /// The query parameter source value 55 | /// The target value type 56 | /// The Function name whose parameters are being assigned 57 | /// The source HttpRequest 58 | /// The deserialization operation result 59 | Task DeserializeQueryParameter(string queryParameterName, StringValues queryParameterValue, Type httpParamValueType, 60 | string functionName, HttpRequest request); 61 | 62 | /// 63 | /// This method should deserialize a form file parameter value to the target type. 64 | /// 65 | /// The form file parameter name 66 | /// The IFormFile object that contains the data 67 | /// The target value type 68 | /// The Function name whose parameters are being assigned 69 | /// The source HttpRequest 70 | /// The deserialization operation result 71 | Task DeserializeFormFile(string fileParameterName, IFormFile formFile, 72 | Type httpParamValueType, string functionName, HttpRequest request); 73 | } 74 | 75 | /// 76 | /// The deserialization result object. 77 | /// 78 | public class DeserializerResult 79 | { 80 | /// 81 | /// Returns a DeserializerResult where DidSerialize is false. 82 | /// 83 | public static DeserializerResult DidNotDeserialize => new DeserializerResult(false); 84 | 85 | public DeserializerResult(bool didDeserialize, object result) 86 | { 87 | DidDeserialize = didDeserialize; 88 | Result = result; 89 | } 90 | 91 | public DeserializerResult(bool didDeserialize) 92 | { 93 | DidDeserialize = didDeserialize; 94 | } 95 | 96 | /// 97 | /// Set to true if this deserializer did or wants to handle deserialization, 98 | /// false if we should still proceed with default serialization after this deserializer was run. 99 | /// 100 | public bool DidDeserialize { get; } 101 | 102 | /// 103 | /// The resulting deserialized object. 104 | /// 105 | public object Result { get; } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Infrastructure/IHttpRequestStore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | 4 | namespace AzureFunctionsV2.HttpExtensions.Infrastructure 5 | { 6 | /// 7 | /// A service that stores HttpRequests from HttpTriggers for cross-code access. 8 | /// The exception filter for example does not get the HttpRequest when it's invoked, and needs 9 | /// to use this service in order to gain access to it. 10 | /// Get/Set/Remove is performed based on the function invocation id. 11 | /// 12 | public interface IHttpRequestStore 13 | { 14 | void Set(Guid functionInvocationId, HttpRequest httpRequest); 15 | HttpRequest Get(Guid functionInvocationId); 16 | void Remove(Guid functionInvocationId); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/Utils/HttpContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Azure.WebJobs.Host; 6 | 7 | namespace AzureFunctionsV2.HttpExtensions.Utils 8 | { 9 | /// 10 | /// Some extensions for HttpContext, used by HttpExtensions. 11 | /// 12 | public static class HttpContextExtensions 13 | { 14 | private static readonly string ExceptionListKey = "HttpExtensionsStoredExceptions"; 15 | private static readonly string FunctionExecutingContextKey = "HttpExtensionsFunctionExecutingContext"; 16 | 17 | public static void StoreExceptionItem(this HttpContext context, Exception e) 18 | { 19 | if(context.Items == null) 20 | context.Items = new Dictionary(); 21 | 22 | if (!context.Items.ContainsKey(ExceptionListKey)) 23 | { 24 | context.Items.Add(ExceptionListKey, new List() { e }); 25 | } 26 | else 27 | { 28 | var exceptionList = context.Items[ExceptionListKey] as List; 29 | if(exceptionList == null) 30 | throw new InvalidOperationException($"Expected HttpContext.Items['{ExceptionListKey}'] to be a List, " + 31 | $"but was a {context.Items[ExceptionListKey].GetType().Name}"); 32 | exceptionList.Add(e); 33 | } 34 | } 35 | 36 | public static List GetStoredExceptions(this HttpContext context) 37 | { 38 | if (context.Items != null && context.Items.ContainsKey(ExceptionListKey) && context.Items[ExceptionListKey] is List el) 39 | { 40 | return el; 41 | } 42 | 43 | return new List(); 44 | } 45 | 46 | public static void StoreFunctionExecutingContext(this HttpContext context, FunctionExecutingContext functionExecutingContext) 47 | { 48 | if(context.Items == null) 49 | context.Items = new Dictionary(); 50 | 51 | if (context.Items.ContainsKey(FunctionExecutingContextKey)) 52 | context.Items[FunctionExecutingContextKey] = functionExecutingContext; 53 | else 54 | context.Items.Add(FunctionExecutingContextKey, functionExecutingContext); 55 | } 56 | 57 | public static FunctionExecutingContext GetStoredFunctionExecutingContext(this HttpContext context) 58 | { 59 | if(context.Items != null && context.Items.ContainsKey(FunctionExecutingContextKey)) 60 | return context.Items[FunctionExecutingContextKey] as FunctionExecutingContext; 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/AzureFunctionsV2.HttpExtensions/nuget-pack.bat: -------------------------------------------------------------------------------- 1 | nuget pack AzureFunctionsV2.HttpExtensions.csproj -IncludeReferencedProjects -Symbols --------------------------------------------------------------------------------