Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/security scheme reference #2107

Merged
merged 11 commits into from
Jan 31, 2025
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
79 changes: 24 additions & 55 deletions src/Microsoft.OpenApi/Models/OpenApiSecurityRequirement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

using System;
using System.Collections.Generic;
using System.Linq;
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 +19,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<OpenApiSecuritySchemeReference, IList<string>>,
IOpenApiSerializable
{
/// <summary>
Expand All @@ -34,40 +37,36 @@ public OpenApiSecurityRequirement()
/// </summary>
public void SerializeAsV31(IOpenApiWriter writer)
{
SerializeInternal(writer, (writer, element) => element.SerializeAsV31(writer));
SerializeInternal(writer, (w, s) => w.WritePropertyName(s.Reference.ReferenceV3));
}

/// <summary>
/// Serialize <see cref="OpenApiSecurityRequirement"/> to Open Api v3.0
/// </summary>
public void SerializeAsV3(IOpenApiWriter writer)
{
SerializeInternal(writer, (writer, element) => element.SerializeAsV3(writer));
SerializeInternal(writer, (w, s) => w.WritePropertyName(s.Reference.ReferenceV3));
}

/// <summary>
/// Serialize <see cref="OpenApiSecurityRequirement"/>
/// </summary>
private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOpenApiSerializable> callback)
private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, OpenApiSecuritySchemeReference> callback)
{
Utils.CheckArgumentNull(writer);;
Utils.CheckArgumentNull(writer);

writer.WriteStartObject();

foreach (var securitySchemeAndScopesValuePair in this)
// 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.

foreach (var securitySchemeAndScopesValuePair in this.Where(static p => p.Key?.Target is not null))
{
var securityScheme = securitySchemeAndScopesValuePair.Key;
var scopes = securitySchemeAndScopesValuePair.Value;

if (securityScheme.Reference == 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);
callback(writer, securityScheme);

writer.WriteStartArray();

Expand All @@ -87,48 +86,19 @@ private void SerializeInternal(IOpenApiWriter writer, Action<IOpenApiWriter, IOp
/// </summary>
public void SerializeAsV2(IOpenApiWriter writer)
{
Utils.CheckArgumentNull(writer);;

writer.WriteStartObject();

foreach (var securitySchemeAndScopesValuePair in this)
{
var securityScheme = securitySchemeAndScopesValuePair.Key;
var scopes = securitySchemeAndScopesValuePair.Value;

if (securityScheme.Reference == 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;
}

securityScheme.SerializeAsV2(writer);

writer.WriteStartArray();

foreach (var scope in scopes)
{
writer.WriteValue(scope);
}

writer.WriteEndArray();
}

writer.WriteEndObject();
SerializeInternal(writer, (w, s) => s.SerializeAsV2(w));
}

/// <summary>
/// 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<OpenApiSecuritySchemeReference>
{
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
public bool Equals(OpenApiSecurityScheme x, OpenApiSecurityScheme y)
public bool Equals(OpenApiSecuritySchemeReference x, OpenApiSecuritySchemeReference y)
{
if (x == null && y == null)
{
Expand All @@ -140,20 +110,19 @@ 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(OpenApiSecuritySchemeReference obj)
{
return obj?.Reference?.Id == null ? 0 : obj.Reference.Id.GetHashCode();
if (obj is null)
{
return 0;
}
return string.IsNullOrEmpty(obj?.Reference?.Id) ? 0 : obj.Reference.Id.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
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public T Target
{
get
{
_target ??= Reference.HostDocument.ResolveReferenceTo<T>(Reference);
_target ??= Reference.HostDocument?.ResolveReferenceTo<T>(Reference);
return _target;
}
}
Expand Down
Loading
Loading