Skip to content

Commit

Permalink
fix: draft security scheme reference proxy design pattern
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Biret <vibiret@microsoft.com>
  • Loading branch information
baywet committed Jan 29, 2025
1 parent 21253f6 commit 1bd2624
Show file tree
Hide file tree
Showing 26 changed files with 174 additions and 241 deletions.
50 changes: 50 additions & 0 deletions src/Microsoft.OpenApi/Models/Interfaces/IOpenApiSecurityScheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Interfaces;

namespace Microsoft.OpenApi.Models.Interfaces;

/// <summary>
/// Defines the base properties for the security scheme object.
/// This interface is provided for type assertions but should not be implemented by package consumers beyond automatic mocking.
/// </summary>
public interface IOpenApiSecurityScheme : IOpenApiDescribedElement, IOpenApiSerializable, IOpenApiReadOnlyExtensible
{
/// <summary>
/// REQUIRED. The type of the security scheme. Valid values are "apiKey", "http", "oauth2", "openIdConnect".
/// </summary>
public SecuritySchemeType? Type { get; }

/// <summary>
/// REQUIRED. The name of the header, query or cookie parameter to be used.
/// </summary>
public string Name { get; }

/// <summary>
/// REQUIRED. The location of the API key. Valid values are "query", "header" or "cookie".
/// </summary>
public ParameterLocation? In { get; }

/// <summary>
/// REQUIRED. The name of the HTTP Authorization scheme to be used
/// in the Authorization header as defined in RFC7235.
/// </summary>
public string Scheme { get; }

/// <summary>
/// A hint to the client to identify how the bearer token is formatted.
/// Bearer tokens are usually generated by an authorization server,
/// so this information is primarily for documentation purposes.
/// </summary>
public string BearerFormat { get; }

/// <summary>
/// REQUIRED. An object containing configuration information for the flow types supported.
/// </summary>
public OpenApiOAuthFlows Flows { get; }

/// <summary>
/// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values.
/// </summary>
public Uri OpenIdConnectUrl { get; }
}
8 changes: 4 additions & 4 deletions src/Microsoft.OpenApi/Models/OpenApiComponents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ public class OpenApiComponents : IOpenApiSerializable, IOpenApiExtensible
public IDictionary<string, IOpenApiHeader>? Headers { get; set; } = new Dictionary<string, IOpenApiHeader>();

/// <summary>
/// An object to hold reusable <see cref="OpenApiSecurityScheme"/> Objects.
/// An object to hold reusable <see cref="IOpenApiSecurityScheme"/> Objects.
/// </summary>
public IDictionary<string, OpenApiSecurityScheme>? SecuritySchemes { get; set; } =
new Dictionary<string, OpenApiSecurityScheme>();
public IDictionary<string, IOpenApiSecurityScheme>? SecuritySchemes { get; set; } =
new Dictionary<string, IOpenApiSecurityScheme>();

/// <summary>
/// An object to hold reusable <see cref="IOpenApiLink"/> Objects.
Expand Down Expand Up @@ -91,7 +91,7 @@ public OpenApiComponents(OpenApiComponents? components)
Examples = components?.Examples != null ? new Dictionary<string, IOpenApiExample>(components.Examples) : null;
RequestBodies = components?.RequestBodies != null ? new Dictionary<string, IOpenApiRequestBody>(components.RequestBodies) : null;
Headers = components?.Headers != null ? new Dictionary<string, IOpenApiHeader>(components.Headers) : null;
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, OpenApiSecurityScheme>(components.SecuritySchemes) : null;
SecuritySchemes = components?.SecuritySchemes != null ? new Dictionary<string, IOpenApiSecurityScheme>(components.SecuritySchemes) : null;
Links = components?.Links != null ? new Dictionary<string, IOpenApiLink>(components.Links) : null;
Callbacks = components?.Callbacks != null ? new Dictionary<string, IOpenApiCallback>(components.Callbacks) : null;
PathItems = components?.PathItems != null ? new Dictionary<string, IOpenApiPathItem>(components.PathItems) : null;
Expand Down
2 changes: 1 addition & 1 deletion src/Microsoft.OpenApi/Models/OpenApiDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ public bool AddComponent<T>(string id, T componentToRegister)
Components.Headers.Add(id, openApiHeader);
break;
case OpenApiSecurityScheme openApiSecurityScheme:
Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();
Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
Components.SecuritySchemes.Add(id, openApiSecurityScheme);
break;
default:
Expand Down
33 changes: 19 additions & 14 deletions src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models
Expand All @@ -16,7 +18,7 @@ namespace Microsoft.OpenApi.Models
/// then the value is a list of scope names required for the execution.
/// For other security scheme types, the array MUST be empty.
/// </summary>
public class OpenApiSecurityRequirement : Dictionary<OpenApiSecurityScheme, IList<string>>,
public class OpenApiSecurityRequirement : Dictionary<IOpenApiSecurityScheme, IList<string>>,
IOpenApiSerializable
{
/// <summary>
Expand Down Expand Up @@ -59,15 +61,15 @@ private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOp
var securityScheme = securitySchemeAndScopesValuePair.Key;
var scopes = securitySchemeAndScopesValuePair.Value;

if (securityScheme.Reference == null)
if (securityScheme is not OpenApiSecuritySchemeReference schemeReference || schemeReference.Reference is null)
{
// Reaching this point means the reference to a specific OpenApiSecurityScheme fails.
// We are not able to serialize this SecurityScheme/Scopes key value pair since we do not know what
// string to output.
continue;
}

writer.WritePropertyName(securityScheme.Reference.ReferenceV3);
writer.WritePropertyName(schemeReference.Reference.ReferenceV3);

writer.WriteStartArray();

Expand Down Expand Up @@ -96,7 +98,7 @@ public void SerializeAsV2(IOpenApiWriter writer)
var securityScheme = securitySchemeAndScopesValuePair.Key;
var scopes = securitySchemeAndScopesValuePair.Value;

if (securityScheme.Reference == null)
if (securityScheme is not OpenApiSecuritySchemeReference schemeReference || schemeReference.Reference is null)
{
// Reaching this point means the reference to a specific OpenApiSecurityScheme fails.
// We are not able to serialize this SecurityScheme/Scopes key value pair since we do not know what
Expand All @@ -123,12 +125,12 @@ public void SerializeAsV2(IOpenApiWriter writer)
/// Comparer for OpenApiSecurityScheme that only considers the Id in the Reference
/// (i.e. the string that will actually be displayed in the written document)
/// </summary>
private class OpenApiSecuritySchemeReferenceEqualityComparer : IEqualityComparer<OpenApiSecurityScheme>
private sealed class OpenApiSecuritySchemeReferenceEqualityComparer : IEqualityComparer<IOpenApiSecurityScheme>
{
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
public bool Equals(OpenApiSecurityScheme x, OpenApiSecurityScheme y)
public bool Equals(IOpenApiSecurityScheme x, IOpenApiSecurityScheme y)
{
if (x == null && y == null)
{
Expand All @@ -140,20 +142,23 @@ public bool Equals(OpenApiSecurityScheme x, OpenApiSecurityScheme y)
return false;
}

if (x.Reference == null || y.Reference == null)
{
return false;
}

return x.Reference.Id == y.Reference.Id;
return GetHashCode(x) == GetHashCode(y);
}

/// <summary>
/// Returns a hash code for the specified object.
/// </summary>
public int GetHashCode(OpenApiSecurityScheme obj)
public int GetHashCode(IOpenApiSecurityScheme obj)
{
return obj?.Reference?.Id == null ? 0 : obj.Reference.Id.GetHashCode();
if (obj is null)
{
return 0;
}
else if (obj is OpenApiSecuritySchemeReference reference)
{
return string.IsNullOrEmpty(reference?.Reference?.Id) ? 0 : reference.Reference.Id.GetHashCode();
}
return obj.GetHashCode();
}
}
}
Expand Down
85 changes: 27 additions & 58 deletions src/Microsoft.OpenApi/Models/OpenApiSecurityScheme.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,54 @@
using System.Collections.Generic;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Interfaces;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Writers;

namespace Microsoft.OpenApi.Models
{
/// <summary>
/// Security Scheme Object.
/// </summary>
public class OpenApiSecurityScheme : IOpenApiReferenceable, IOpenApiExtensible
public class OpenApiSecurityScheme : IOpenApiExtensible, IOpenApiReferenceable, IOpenApiSecurityScheme
{
/// <summary>
/// REQUIRED. The type of the security scheme. Valid values are "apiKey", "http", "oauth2", "openIdConnect".
/// </summary>
public virtual SecuritySchemeType? Type { get; set; }

/// <summary>
/// A short description for security scheme. CommonMark syntax MAY be used for rich text representation.
/// </summary>
public virtual string Description { get; set; }

/// <summary>
/// REQUIRED. The name of the header, query or cookie parameter to be used.
/// </summary>
public virtual string Name { get; set; }
/// <inheritdoc/>
public SecuritySchemeType? Type { get; set; }

/// <summary>
/// REQUIRED. The location of the API key. Valid values are "query", "header" or "cookie".
/// </summary>
public virtual ParameterLocation? In { get; set; }
/// <inheritdoc/>
public string Description { get; set; }

/// <summary>
/// REQUIRED. The name of the HTTP Authorization scheme to be used
/// in the Authorization header as defined in RFC7235.
/// </summary>
public virtual string Scheme { get; set; }
/// <inheritdoc/>
public string Name { get; set; }

/// <summary>
/// A hint to the client to identify how the bearer token is formatted.
/// Bearer tokens are usually generated by an authorization server,
/// so this information is primarily for documentation purposes.
/// </summary>
public virtual string BearerFormat { get; set; }
/// <inheritdoc/>
public ParameterLocation? In { get; set; }

/// <summary>
/// REQUIRED. An object containing configuration information for the flow types supported.
/// </summary>
public virtual OpenApiOAuthFlows Flows { get; set; }
/// <inheritdoc/>
public string Scheme { get; set; }

/// <summary>
/// REQUIRED. OpenId Connect URL to discover OAuth2 configuration values.
/// </summary>
public virtual Uri OpenIdConnectUrl { get; set; }
/// <inheritdoc/>
public string BearerFormat { get; set; }

/// <summary>
/// Specification Extensions.
/// </summary>
public virtual IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();
/// <inheritdoc/>
public OpenApiOAuthFlows Flows { get; set; }

/// <summary>
/// Indicates if object is populated with data or is just a reference to the data
/// </summary>
public bool UnresolvedReference { get; set; }
/// <inheritdoc/>
public Uri OpenIdConnectUrl { get; set; }

/// <summary>
/// Reference object.
/// </summary>
public OpenApiReference Reference { get; set; }
/// <inheritdoc/>
public IDictionary<string, IOpenApiExtension> Extensions { get; set; } = new Dictionary<string, IOpenApiExtension>();

/// <summary>
/// Parameterless constructor
/// </summary>
public OpenApiSecurityScheme() { }

/// <summary>
/// Initializes a copy of <see cref="OpenApiSecurityScheme"/> object
/// Initializes a copy of <see cref="IOpenApiSecurityScheme"/> object
/// </summary>
public OpenApiSecurityScheme(OpenApiSecurityScheme securityScheme)
public OpenApiSecurityScheme(IOpenApiSecurityScheme securityScheme)
{
Utils.CheckArgumentNull(securityScheme);
Type = securityScheme?.Type;
Description = securityScheme?.Description ?? Description;
Name = securityScheme?.Name ?? Name;
Expand All @@ -91,27 +62,25 @@ public OpenApiSecurityScheme(OpenApiSecurityScheme securityScheme)
Flows = securityScheme?.Flows != null ? new(securityScheme?.Flows) : null;
OpenIdConnectUrl = securityScheme?.OpenIdConnectUrl != null ? new Uri(securityScheme.OpenIdConnectUrl.OriginalString, UriKind.RelativeOrAbsolute) : null;
Extensions = securityScheme?.Extensions != null ? new Dictionary<string, IOpenApiExtension>(securityScheme.Extensions) : null;
UnresolvedReference = securityScheme?.UnresolvedReference ?? UnresolvedReference;
Reference = securityScheme?.Reference != null ? new(securityScheme?.Reference) : null;
}

/// <summary>
/// Serialize <see cref="OpenApiSecurityScheme"/> to Open Api v3.1
/// </summary>
public virtual void SerializeAsV31(IOpenApiWriter writer)
public void SerializeAsV31(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_1, (writer, element) => element.SerializeAsV31(writer));
}

/// <summary>
/// Serialize <see cref="OpenApiSecurityScheme"/> to Open Api v3.0
/// </summary>
public virtual void SerializeAsV3(IOpenApiWriter writer)
public void SerializeAsV3(IOpenApiWriter writer)
{
SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer));
}

internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version,
Action<IOpenApiWriter, IOpenApiSerializable> callback)
{
Utils.CheckArgumentNull(writer);
Expand Down Expand Up @@ -161,7 +130,7 @@ internal virtual void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersio
/// <summary>
/// Serialize <see cref="OpenApiSecurityScheme"/> to Open Api v2.0
/// </summary>
public virtual void SerializeAsV2(IOpenApiWriter writer)
public void SerializeAsV2(IOpenApiWriter writer)
{
Utils.CheckArgumentNull(writer);

Expand Down
Loading

0 comments on commit 1bd2624

Please sign in to comment.