Skip to content

Commit

Permalink
Entra/Library Updates (#38532)
Browse files Browse the repository at this point in the history
* Marked empty/null actions as failed

* replaced tabs with spaces

* Updated spacing

* Replaced tab with spaces

* Updates based on feedback

* Updated Validation Logic

* WIP Added test cases for OneOrMoreRequiredAttribute

* Added test case for EnumberableItemsNotNull Attribute

* Feedback updates

* Updates based on PR feedback

* Sort 'using' lists and removed unapplicable data annotation

* Removed xUnit reference now that we are using NUnit tests

* removed Update-Snippets.ps1 changes and will rebase

* Added null check and test for response before marking as failed

* Updated exception to validation exception and corrected test

* Changed back to argument null exception

* Added response validation exception type

* Update sdk/entra/Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents/src/AuthenticationEventResource.resx

Adding a missing period

Co-authored-by: Jesse Squire <[email protected]>

* Added period

* Added not null check for source field in payload

* Changed source attribute to required

* Adding necessary updates to merge with azure main

* Added validation class

* added logic to throw RequestValidationException

* Replaced tabs with spaces

* Added pull request template

* Added existing text to PR template

* Removed pr template

* Reverted IsMsaPassThroughEnabled and ran scripts to pass build merge

* Added comment

* Updated changelog

* added more context to changelog file

* Revert changes

* ODataType property in Request is now required

* Add bug fix description to changelog

* Corrected format

* Add metrics to header

* Reverting code that was changed by accident

* passing inner exception to RequestValidationException

* Modify version semantics

* Revert version name

* ChangeLog

* Changes to metrics

* Change the metrics format string to match the general guidelines for azure sdk

* Sorted usings

* ran the scripts

* Updated Errors and added unit tests

* Used newtonsoft to parse json and updated unit tests

* Updated changelog

* Updated error for  invalid json characters

* Updated exception identifier

* Replaced data with sanitized values

* Updated GUIDs, IP address and emails

* Removed newtonsoft dependencies and used system.text.json instead

* Removed newtonsoft dependency from AuthenticationEventDataTests

* Changing access level on AuthEventResponseHandler

* Change the which assembly is used for metrics

* Ran script

* Use type to look up assembly

* Updated TestHelpers newtonsoft dependencies to stj

* Removed usings

* Using assembly name instead of assembly itself

* Modify the tests to check only for value exsistence and not actual value

* Adding using for JsonDocument and Used JsonObject

* Changing metrics to a singleton pattern and modifing the tests to trim

* Ran Scripts

* Added braces

* Reverted test project changes as newtonsoft linq is currently needed

* removed unnecessary comment and line

* replaced string quotes with string.empty

* Simplified isJson method and reverted project change

* Updated IsJson boolean method to ValidateJson void

* Updated spacing

* Updated Json error string for empty payload

* Update error string

* Added structure for Exception classes

* Made classes internal

* Update class to remove Old exceptions

---------

Co-authored-by: Jesse Squire <[email protected]>
Co-authored-by: Harman Dhunna🐳 <[email protected]>
Co-authored-by: HarmanDhunna <[email protected]>
  • Loading branch information
4 people authored Nov 9, 2023
1 parent ac80145 commit 41e5fd3
Show file tree
Hide file tree
Showing 34 changed files with 776 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@

## 1.0.0-beta.4 (Unreleased)

### Features

- Added metrics to header. Will be used to track the number of requests and responses for each action.

### Bugs Fixed

- Updated ODataType signature
- Empty or null response actions will throw a bad response
- Made the source field in the request a required field
- Updated ODataType signature. This was done to make sure the contracts were consistent across all services.
- Empty or null response actions will throw a bad response. There should be at lease one action passed as this is the main purpose of the SDK library.
- Made the source field in the request a required field.
- Made the request validation errors return 500. This way, we can identify that 500 errors as internal and should be marked as failures whereas response object errors should return 400s since they are customer input errors and should be identified as CallerErrors in our service.
- Made the ODataType field in the request a required field.
- Made the errors for JSON payload more descriptive when an invalid character is passed.
- Added JsonDocument TryParse check to validate payload is JSON format.

## 1.0.0-beta.3 (2022-12-13)

Expand All @@ -17,7 +25,7 @@
- Added createdDateTime to AuthenticationEventContextUser
- Added new request status type for validation failure.
- Validation Errors raise 500 response.
- Added CustomAuthenticaionExtensionId to Data.
- Added CustomAuthenticationExtensionId to Data.
- Removed AuthenticationEventsId from Data.

## 1.0.0-beta.2 (2022-11-08)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal AuthenticationEventMetadataAttribute() { }
}
public partial class AuthenticationEventResponseHandler : Microsoft.Azure.WebJobs.Host.Bindings.IValueBinder, Microsoft.Azure.WebJobs.Host.Bindings.IValueProvider
{
public AuthenticationEventResponseHandler() { }
internal AuthenticationEventResponseHandler() { }
public Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventRequestBase Request { get { throw null; } }
public Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventResponse Response { get { throw null; } }
public System.Type Type { get { throw null; } }
Expand All @@ -34,6 +34,16 @@ public enum EventDefinition
[Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.AuthenticationEventMetadataAttribute(typeof(Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.TokenIssuanceStart.TokenIssuanceStartRequest), "microsoft.graph.authenticationEvent.TokenIssuanceStart", "TokenIssuanceStart", "CloudEventActionableTemplate.json")]
TokenIssuanceStart = 0,
}
public sealed partial class EventTriggerMetrics
{
internal EventTriggerMetrics() { }
public const string MetricsHeader = "User-Agent";
public const string ProductName = "AuthenticationEvents";
public static string Framework { get { throw null; } }
public static Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.EventTriggerMetrics Instance { get { throw null; } }
public static string Platform { get { throw null; } }
public static string ProductVersion { get { throw null; } }
}
public enum EventType
{
OnTokenIssuanceStart = 0,
Expand All @@ -45,11 +55,6 @@ public enum RequestStatusType
Successful = 2,
ValidationError = 3,
}
public partial class ResponseValidationException : System.Exception
{
public ResponseValidationException(string message) { }
public ResponseValidationException(string message, System.Exception innerException) { }
}
}
namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework
{
Expand Down Expand Up @@ -128,6 +133,7 @@ protected CloudEventData() { }
public abstract partial class CloudEventRequest<TResponse, TData> : Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventRequest<TResponse, TData> where TResponse : Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventResponse, new() where TData : Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.CloudEventData
{
internal CloudEventRequest() { }
[System.ComponentModel.DataAnnotations.RequiredAttribute]
[System.Text.Json.Serialization.JsonPropertyNameAttribute("oDataType")]
public string ODataType { get { throw null; } set { } }
[System.ComponentModel.DataAnnotations.RequiredAttribute]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
Expand All @@ -14,8 +9,14 @@
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Azure.WebJobs.Host.Listeners;
using Microsoft.Azure.WebJobs.Host.Protocols;
using Microsoft.Azure.WebJobs.Host.Triggers;
using static Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.EmptyResponse;
using AuthenticationEventMetadata = Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework.AuthenticationEventMetadata;

Expand Down Expand Up @@ -84,7 +85,8 @@ private static IReadOnlyDictionary<string, Type> GetBindingDataContract(Paramete
public async Task<ITriggerData> BindAsync(object value, ValueBindingContext context)
{
var request = (HttpRequestMessage)value;
AuthenticationEventResponseHandler eventResponseHandler = (AuthenticationEventResponseHandler)request.Properties[AuthenticationEventResponseHandler.EventResponseProperty];
AuthenticationEventResponseHandler eventResponseHandler =
(AuthenticationEventResponseHandler)request.Properties[AuthenticationEventResponseHandler.EventResponseProperty];
try
{
if (request == null)
Expand All @@ -97,7 +99,15 @@ public async Task<ITriggerData> BindAsync(object value, ValueBindingContext cont
AuthenticationEventMetadata eventMetadata = GetEventAndValidateSchema(payload);

eventResponseHandler.Request = GetRequestForEvent(request, payload, eventMetadata, Claims);
return new TriggerData(new AuthenticationEventValueBinder(eventResponseHandler.Request, _authEventTriggerAttr), GetBindingData(context, value, eventResponseHandler))

return new TriggerData(
new AuthenticationEventValueBinder(
eventResponseHandler.Request,
_authEventTriggerAttr),
GetBindingData(
context,
value,
eventResponseHandler))
{
ReturnValueProvider = eventResponseHandler
};
Expand All @@ -118,7 +128,12 @@ public async Task<ITriggerData> BindAsync(object value, ValueBindingContext cont
/// <returns>A TriggerData Object with the failed event request based on the event. With the related request status set.</returns>
/// <seealso cref="TriggerData" />
/// <seealso cref="AuthenticationEventResponseHandler" />
private TriggerData GetFaultyRequest(ValueBindingContext context, object value, HttpRequestMessage request, AuthenticationEventResponseHandler eventResponseHandler, Exception ex)
private TriggerData GetFaultyRequest(
ValueBindingContext context,
object value,
HttpRequestMessage request,
AuthenticationEventResponseHandler eventResponseHandler,
Exception ex)
{
eventResponseHandler.Request = _parameterInfo.ParameterType == typeof(string) ? new EmptyRequest(request) : AuthenticationEventMetadata.CreateEventRequest(request, _parameterInfo.ParameterType, null);
eventResponseHandler.Request.StatusMessage = ex.Message;
Expand All @@ -127,6 +142,7 @@ private TriggerData GetFaultyRequest(ValueBindingContext context, object value,
{
UnauthorizedAccessException => RequestStatusType.TokenInvalid,
ValidationException => RequestStatusType.ValidationError,
AuthenticationEventTriggerRequestValidationException => RequestStatusType.ValidationError,
_ => RequestStatusType.Failed,
};

Expand Down Expand Up @@ -228,11 +244,15 @@ private async Task<Dictionary<string, string>> GetClaimsAndValidateRequest(HttpR
/// <returns>The Event Metadata object.</returns>
/// <exception cref="AggregateException">Aggregates all the schema validation exceptions.</exception>
/// <exception cref="Exception">IF the event cannot be determined or if the object model event differs from the requested event on the incoming payload.</exception>
private static AuthenticationEventMetadata GetEventAndValidateSchema(string body)
internal static AuthenticationEventMetadata GetEventAndValidateSchema(string body)
{
if (!Helpers.IsJson(body))
try
{
Helpers.ValidateJson(body);
}
catch (JsonException ex)
{
throw new InvalidDataException();
throw new AuthenticationEventTriggerRequestValidationException($"{AuthenticationEventResource.Ex_Invalid_JsonPayload}: {ex.Message}", ex.InnerException);
}

return AuthenticationEventMetadataLoader.GetEventMetadata(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ public async Task<HttpResponseMessage> ConvertAsync(HttpRequestMessage input, Ca
};

FunctionResult result = await listener.Value.FunctionExecutor.TryExecuteAsync(triggerData, cancellationToken).ConfigureAwait(false);
return result.Succeeded ? (HttpResponseMessage)eventsResponseHandler.Response : Helpers.HttpErrorResponse(result.Exception);

return result.Succeeded ?
eventsResponseHandler.Response :
Helpers.HttpErrorResponse(result.Exception);
}
catch (Exception ex)
{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@
<data name="Ex_Context_Null" xml:space="preserve">
<value>ListenerFactoryContext is null</value>
</data>
<data name="Ex_Empty_Json" xml:space="preserve">
<value>JSON is null or empty.</value>
</data>
<data name="Ex_Event_Missing" xml:space="preserve">
<value>Cannot determine the event from payload, please check that the incoming payload is a valid JSON string and contains the event type.</value>
</data>
Expand Down Expand Up @@ -153,14 +156,14 @@
<data name="Ex_Invalid_JsonPath" xml:space="preserve">
<value>Cannot find json path set value</value>
</data>
<data name="Ex_Invalid_Payload" xml:space="preserve">
<value>Invalid Payload detected.</value>
<data name="Ex_Invalid_JsonPayload" xml:space="preserve">
<value>Invalid Json Payload</value>
</data>
<data name="Ex_Invalid_Response" xml:space="preserve">
<value>Response validation failed, see inner exceptions.</value>
</data>
<data name="Ex_Invalid_Return" xml:space="preserve">
<value>Return type is invalid, please return either an AuthEventResponse, HttpResponse, HttpResponseMessage or string in your function return.</value>
<value>Return type is invalid, please return either an AuthEventResponse, HttpResponse, HttpResponseMessage or string in your function return</value>
</data>
<data name="Ex_Invalid_SchemaVersion" xml:space="preserve">
<value>Invalid version on Schema</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
using Microsoft.Azure.WebJobs.Host.Bindings;
using System;
using System.Buffers;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents.Framework;
using Microsoft.Azure.WebJobs.Host.Bindings;

namespace Microsoft.Azure.WebJobs.Extensions.AuthenticationEvents
{
Expand All @@ -24,9 +23,26 @@ public class AuthenticationEventResponseHandler : IValueBinder
/// <summary>The response property.</summary>
internal const string EventResponseProperty = "$event$response";

private AuthenticationEventResponse _response;

/// <summary>Gets or sets the action result.</summary>
/// <value>The action result.</value>
public AuthenticationEventResponse Response { get; internal set; }
public AuthenticationEventResponse Response
{
get => _response;
private set
{
if (value != null)
{
_response = value;

// Set metrics on the headers for the response
EventTriggerMetrics.Instance.SetMetricHeaders(_response);
}
}
}

internal AuthenticationEventResponseHandler() { }

/// <summary>Gets the type.</summary>
/// <value>The type.</value>
Expand Down Expand Up @@ -57,7 +73,7 @@ public Task SetValueAsync(object result, CancellationToken cancellationToken)
{
if (result == null)
{
throw new ResponseValidationException(AuthenticationEventResource.Ex_Invalid_Return);
throw new AuthenticationEventTriggerResponseValidationException(AuthenticationEventResource.Ex_Invalid_Return);
}

if (result is AuthenticationEventResponse action)
Expand All @@ -69,7 +85,7 @@ public Task SetValueAsync(object result, CancellationToken cancellationToken)
AuthenticationEventResponse response = Request.GetResponseObject();
if (response == null)
{
throw new InvalidOperationException(AuthenticationEventResource.Ex_Missing_Request_Response);
throw new AuthenticationEventTriggerRequestValidationException(AuthenticationEventResource.Ex_Missing_Request_Response);
}

Response = GetActionResult(result, response);
Expand Down Expand Up @@ -209,9 +225,16 @@ internal static AuthenticationEventJsonElement GetJsonObjectFromStream(Stream st

internal static AuthenticationEventJsonElement GetJsonObjectFromString(string result)
{
return !Helpers.IsJson(result)
? throw new InvalidCastException(AuthenticationEventResource.Ex_Invalid_Return)
: new AuthenticationEventJsonElement(result);
try
{
Helpers.ValidateJson(result);
}
catch (JsonException ex)
{
throw new AuthenticationEventTriggerResponseValidationException($"{AuthenticationEventResource.Ex_Invalid_Return}: {ex.Message}", ex.InnerException);
}

return new AuthenticationEventJsonElement(result);
}

internal static AuthenticationEventResponse GetAuthEventFromJObject(AuthenticationEventJsonElement result, AuthenticationEventResponse response)
Expand Down
Loading

0 comments on commit 41e5fd3

Please sign in to comment.