Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c75a340
Main Library passing.
ShaneMicro Aug 10, 2022
c7f6e8a
Tests Passing
ShaneMicro Aug 11, 2022
56be904
Merge branch 'main' into authentication-events
ShaneMicro Aug 11, 2022
874aef9
Removed unused packages.
ShaneMicro Aug 12, 2022
69e83b4
Fixed Link issues.
ShaneMicro Aug 12, 2022
0ae0802
Fixed README.md
ShaneMicro Aug 12, 2022
31974ec
Fixed README.md 2
ShaneMicro Aug 12, 2022
2c4f0e8
Review fix 1
ShaneMicro Aug 12, 2022
c1b9335
Address Review 2
ShaneMicro Aug 12, 2022
7b99127
Review 3
ShaneMicro Aug 12, 2022
2822bc7
Review 4
ShaneMicro Aug 16, 2022
ffff48e
Fix spelling.
ShaneMicro Aug 17, 2022
59cdb5e
Fixed path in ci.yml
ShaneMicro Aug 17, 2022
6606605
Arch Review 5
ShaneMicro Aug 18, 2022
bf8070a
Added API
ShaneMicro Aug 18, 2022
2bdc652
Arch Review 6
ShaneMicro Aug 18, 2022
de2c8a0
Removed dependencies on Identity Protocol OpenAPI Libs
ShaneMicro Sep 7, 2022
0952a5a
Clean up
ShaneMicro Sep 7, 2022
c01d392
Clean up ci.yml
ShaneMicro Sep 7, 2022
25be7d5
Build fix
ShaneMicro Sep 8, 2022
cbb0382
Merge branch 'Azure:main' into authentication-events
ShaneMicro Sep 8, 2022
3ed2368
Merge branch 'main' into authentication-events
ShaneMicro Sep 8, 2022
49b3caa
Merge branch 'authentication-events' of https://github.com/ShaneMicro…
ShaneMicro Sep 8, 2022
1dbe88b
Merge branch 'main' into authentication-events
ShaneMicro Sep 9, 2022
468e9e5
Updated ci
ShaneMicro Sep 12, 2022
c315ef1
Refactor folder name
ShaneMicro Sep 12, 2022
681fe3b
Update readme
ShaneMicro Sep 12, 2022
77bb144
clean up
ShaneMicro Sep 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Release History

## 1.0.0-beta.1 (Unreleased)
- The initial beta release

### Features Added

### Breaking Changes

### Bugs Fixed

### Other Changes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="$(MSBuildProjectName.StartsWith('Microsoft.Azure.WebJobs.')) and $(MSBuildProjectName.EndsWith('.AuthenticationEvents'))">
<IsClientLibrary>true</IsClientLibrary>
</PropertyGroup>

<PropertyGroup Condition="$(MSBuildProjectName.StartsWith('Microsoft.Azure.WebJobs.')) and $(MSBuildProjectName.EndsWith('.Tests'))">
<IsTestProject>true</IsTestProject>
<ImportDefaultReferences>false</ImportDefaultReferences>
</PropertyGroup>

<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory).., Directory.Build.props))\Directory.Build.props" />

</Project>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32630.192
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FD6F52B7-03AC-4EBB-A79A-3F02F8782E5C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FAB04E28-402B-4039-8F7A-027CE8E0E343}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents", "src\Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.csproj", "{BB38310C-9B9A-436E-B100-7739ABDBB75A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{40B476A8-C30C-46BB-97DC-1AD8F405B529}"
ProjectSection(SolutionItems) = preProject
..\..\..\eng\images\azureicon.png = ..\..\..\eng\images\azureicon.png
CHANGELOG.md = CHANGELOG.md
..\ci.yml = ..\ci.yml
Directory.Build.props = Directory.Build.props
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Tests", "tests\Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Tests.csproj", "{0373F757-E998-4C45-9118-C264231E2944}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
TriggerRelease|Any CPU = TriggerRelease|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.Release|Any CPU.Build.0 = Release|Any CPU
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.TriggerRelease|Any CPU.ActiveCfg = Release|Any CPU
{BB38310C-9B9A-436E-B100-7739ABDBB75A}.TriggerRelease|Any CPU.Build.0 = Release|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.Release|Any CPU.Build.0 = Release|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.TriggerRelease|Any CPU.ActiveCfg = Release|Any CPU
{0373F757-E998-4C45-9118-C264231E2944}.TriggerRelease|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{BB38310C-9B9A-436E-B100-7739ABDBB75A} = {FD6F52B7-03AC-4EBB-A79A-3F02F8782E5C}
{0373F757-E998-4C45-9118-C264231E2944} = {FAB04E28-402B-4039-8F7A-027CE8E0E343}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21348D28-E450-49EE-AEA4-ADD8168DD20B}
EndGlobalSection
EndGlobal

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.WebJobs.Host.Triggers;
using System.Reflection;
using System.Threading.Tasks;

namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
{
/// <summary>The main trigger binding provider class.<br /></summary>
/// <seealso cref="ITriggerBindingProvider" />
internal class AuthenticationEventBindingProvider : ITriggerBindingProvider
{
private readonly AuthenticationEventConfigProvider _config;
/// <summary>Initializes a new instance of the <see cref="AuthenticationEventBindingProvider" /> class.</summary>
/// <param name="configProvider">The configuration provider.</param>
internal AuthenticationEventBindingProvider(AuthenticationEventConfigProvider configProvider)
{
_config = configProvider;
}

/// <summary>This is called from the WebJobs framework, where we look for our attribute and create and new instance of our trigger binding.</summary>
/// <param name="context">The context that we get from the framework.</param>
/// <returns>A new instance of EventsTriggerBinding.</returns>
/// <exception cref="System.MissingFieldException">Is thrown when we cannot find the correct event definition attribute attached to the trigger attribute.</exception>
/// <exception cref="System.InvalidOperationException">Is thrown when the object model is out of event sync or the wrong parameter for the event is specified on the function signature. </exception>
/// <seealso cref="AuthenticationEventBinding" />
public Task<ITriggerBinding> TryCreateAsync(TriggerBindingProviderContext context)
{
AuthenticationEventsTriggerAttribute attribute = context.Parameter.GetCustomAttribute<AuthenticationEventsTriggerAttribute>(false);
if (attribute == null)
{
return Task.FromResult<ITriggerBinding>(null);
}

attribute.IsParameterString = context.Parameter.ParameterType == typeof(string);

return Task.FromResult<ITriggerBinding>(new AuthenticationEventBinding(attribute, _config, context.Parameter));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.WebJobs.Description;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
{
/// <summary>The main configuration provider, this also handles the initial HTTP requests and response via IAsyncConverter.</summary>
/// <seealso cref="HttpRequestMessage" />
/// <seealso cref="HttpResponseMessage" />
[Extension("CustomAuthenticationExtension", "CustomAuthenticationExtension")]
internal class AuthenticationEventConfigProvider : IExtensionConfigProvider, IAsyncConverter<HttpRequestMessage, HttpResponseMessage>
{
private readonly ILogger _logger;
private Uri _base_uri;

/// <summary>The listeners that are attached to the functions that implement the AuthenticationEventTriggerAttribute.</summary>
public Dictionary<string, AuthenticationEventListener> Listeners { get; } = new Dictionary<string, AuthenticationEventListener>();

/// <summary>Initializes a new instance of the <see cref="AuthenticationEventConfigProvider" /> class.</summary>
/// <param name="loggerFactory">The logger factory from the WebJobs framework.</param>
public AuthenticationEventConfigProvider(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AuthenticationEventConfigProvider>();
}

/// <summary>Initializes the specified configuration.
/// This is where we create the main HTTP Listener endpoint and bind the trigger. (For all intents and purposes this is not obsolete).</summary>
/// <param name="context">The context we get from the Webjobs framework.</param>
[Obsolete("Is not obsolete marked by webjobs team, but chatted and this is correct. It is not being deprecated")]
public void Initialize(ExtensionConfigContext context)
{
context.AddBindingRule<AuthenticationEventsTriggerAttribute>()
.BindToTrigger(new AuthenticationEventBindingProvider(this));

_base_uri = context.GetWebhookHandler();
//LogInformation(string.Format(AuthenticationEventResource.Log_EventHandler_Url, Uri));
}

internal void LogInformation(string message)
{
Console.WriteLine(message);
_logger.LogInformation(message);
}

internal void DisplayAzureFunctionInfoToConsole(string functionName)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
if (Listeners.Count == 1)
{
Console.WriteLine();
Console.WriteLine(AuthenticationEventResource.Out_Console_Seperator);
}
Console.Write(AuthenticationEventResource.Out_Console_FunctName);
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine(functionName);
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.Write(AuthenticationEventResource.Out_Console_FunctUrl);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"{_base_uri}&functionName={functionName}");
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine(AuthenticationEventResource.Out_Console_Seperator);
Console.ResetColor();
}

/// <summary>The main entry point when we get an HTTP post.</summary>
/// <param name="input">The incoming HTTP request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The HTTP response that is build based on the events request's response.<br /></returns>
/// <exception cref="MissingFieldException">Is thrown if the function parameter is not in the incoming query string.</exception>
/// <exception cref="MissingMethodException">Is thrown if the incoming function parameter is not associated to a trigger listener.</exception>
/// <exception cref="InvalidOperationException">Is thrown when the incoming request method is NOT 'POST'.</exception>
public async Task<HttpResponseMessage> ConvertAsync(HttpRequestMessage input, CancellationToken cancellationToken)
{
try
{
NameValueCollection queryStringParmeters = HttpUtility.ParseQueryString(input.RequestUri.Query);

if (input.Method != HttpMethod.Post)
{
throw new InvalidOperationException("Method can only be post.");
}

//We find the attached listener assigned to the function and execute it.
var functionName = queryStringParmeters["function"] ?? queryStringParmeters["functionName"];
if (string.IsNullOrEmpty(functionName))
{
throw new MissingFieldException(string.Format(CultureInfo.CurrentCulture, AuthenticationEventResource.Ex_Missing_Function, string.Join(", ", Listeners.Select(l => l.Key))));
}

KeyValuePair<string, AuthenticationEventListener> listener = Listeners.FirstOrDefault(l => l.Key.Equals(functionName, StringComparison.OrdinalIgnoreCase));
if (listener.Key == null)
{
throw new MissingMethodException(string.Format(CultureInfo.CurrentCulture, AuthenticationEventResource.Ex_Invalid_Function, functionName, string.Join(", ", Listeners.Select(l => l.Key))));
}

//We create an event response handler and attach it to the income HTTP message, then on the trigger we set the function response
//in the event response handler and after the executor calls the functions we have reference to the function response.
AuthenticationEventResponseHandler eventsResponseHandler = new AuthenticationEventResponseHandler();
input.Properties.Add(AuthenticationEventResponseHandler.EventResponseProperty, eventsResponseHandler);

TriggeredFunctionData triggerData = new TriggeredFunctionData()
{
TriggerValue = input
};

FunctionResult result = await listener.Value.FunctionExecutor.TryExecuteAsync(triggerData, cancellationToken).ConfigureAwait(false);
return result.Succeeded ? (HttpResponseMessage)eventsResponseHandler.Response : Helpers.HttpErrorResponse(result.Exception);
}
catch (Exception ex)
{
return Helpers.HttpErrorResponse(ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.WebJobs.Host.Executors;
using Microsoft.Azure.WebJobs.Host.Listeners;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
{
/// <summary>This class is attached to each function that calls into our authentication event trigger.</summary>
internal class AuthenticationEventListener : IListener
{
/// <summary>Gets or sets the function executor.</summary>
/// <value>The function executor that would execute the attached function.</value>
/// <seealso cref="ITriggeredFunctionExecutor" />
internal ITriggeredFunctionExecutor FunctionExecutor { get; private set; }

/// <summary>Gets or sets the attribute.</summary>
/// <value>The event trigger attribute assigned to the function that the listener is attached to.</value>
internal AuthenticationEventsTriggerAttribute Attribute { get; set; }

/// <summary>Initializes a new instance of the <see cref="AuthenticationEventListener" /> class.</summary>
/// <param name="executor">The executor.</param>
/// <param name="attribute">The attribute to assign to the listener.</param>
internal AuthenticationEventListener(ITriggeredFunctionExecutor executor, AuthenticationEventsTriggerAttribute attribute)
{
FunctionExecutor = executor;
Attribute = attribute;
}

/// <summary>Cancels this instance.</summary>
public void Cancel()
{ }

/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
GC.SuppressFinalize(this);
}

/// <summary>Starts the asynchronous listener, we do not do anything here as all we need is the reference to the executor.</summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task flagged as completed with the value as true.</returns>
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

/// <summary>Stops the asynchronous listener, we do not do anything here as all we need is the reference to the executor.</summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task flagged as completed with the value true.</returns>
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
using System;

namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
{
/// <summary>EventMetadata enum attribute that controls the related request object, schemas and json payloads</summary>
/// <seealso cref="AuthenticationEventRequest{T, K}" />
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class AuthenticationEventMetadataAttribute : Attribute
{
/// <summary>Gets or sets the type of the request.</summary>
/// <value>The type of the request.
/// Which is must inherit EventRequest</value>
/// <seealso cref="AuthenticationEventRequest{T, K}" />
internal Type RequestType { get; set; }
/// <summary>Gets or sets the request schema.
/// The name of the schema file in the event folder.</summary>
/// <value>The request schema.</value>

/// <summary>Gets or sets the event namespace.</summary>
/// <value>The event namespace.</value>
public string EventNamespace { get; set; }

/// <summary>Gets or sets the response template.
/// This with be the base response Json template file located in the event folder</summary>
/// <value>The response template.</value>
internal string ResponseTemplate { get; set; }
/// <summary>Gets or sets the EventIdentifer.</summary>
/// <value>The Event Identifier.</value>
internal string EventIdentifier { get; set; }

/// <summary>Initializes a new instance of the <see cref="AuthenticationEventMetadataAttribute" /> class.</summary>
/// <param name="requestType">Type of the request.</param>
/// <param name="eventIdentifier">The event identifier.</param>
/// <param name="eventNamespace">The name space related to the event</param>
/// <param name="responseTemplate">The response template.
/// Defaulted to ResponseTemplate.json</param>
/// <exception cref="Exception">If the requestType in not of type EventRequest</exception>
internal AuthenticationEventMetadataAttribute(Type requestType, string eventIdentifier, string eventNamespace, string responseTemplate = "ResponseTemplate.json")
{
if (!typeof(AuthenticationEventRequestBase).IsAssignableFrom(requestType))
{
throw new Exception(AuthenticationEventResource.Ex_Invalid_EventType);
}

RequestType = requestType;
EventNamespace = eventNamespace;
ResponseTemplate = responseTemplate;
EventIdentifier = eventIdentifier;
}
}
}
Loading