From 2a506e9c86681cf45294a7140dda1a60c8fa352b Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:15:22 -0400 Subject: [PATCH 01/10] Parsing StaticWebApps ClientPrincipal --- DataGateway.Config/Authentication.cs | 18 +++-- .../EasyAuthAuthenticationUnitTests.cs | 78 ++++++++++++++----- .../AuthenticationConfigValidatorUnitTests.cs | 2 +- ...ication.cs => AppServiceAuthentication.cs} | 16 ++-- ....cs => AppServiceAuthenticationHandler.cs} | 8 +- ...EasyAuthAuthenticationBuilderExtensions.cs | 23 ++++-- .../StaticWebAppsAuthentication.cs | 63 +++++++++++++++ .../StaticWebAppsAuthenticationHandler.cs | 74 ++++++++++++++++++ .../CosmosSqlMetadataProvider.cs | 70 ++++++++++++++++- DataGateway.Service/Startup.cs | 2 +- DataGateway.Service/hawaii-config.Cosmos.json | 29 +++++-- ...awaii-config.Cosmos.overrides.example.json | 6 +- DataGateway.Service/hawaii-config.MsSql.json | 2 +- ...hawaii-config.MsSql.overrides.example.json | 2 +- DataGateway.Service/hawaii-config.MySql.json | 2 +- ...hawaii-config.MySql.overrides.example.json | 2 +- .../hawaii-config.PostgreSql.json | 2 +- ...i-config.PostgreSql.overrides.example.json | 4 +- DataGateway.Service/hawaii-config.json | 2 +- DataGateway.Service/schema.gql | 10 ++- 20 files changed, 352 insertions(+), 63 deletions(-) rename DataGateway.Service/AuthenticationHelpers/{EasyAuthAuthentication.cs => AppServiceAuthentication.cs} (83%) rename DataGateway.Service/AuthenticationHelpers/{EasyAuthAuthenticationHandler.cs => AppServiceAuthenticationHandler.cs} (91%) create mode 100644 DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs create mode 100644 DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs diff --git a/DataGateway.Config/Authentication.cs b/DataGateway.Config/Authentication.cs index 9a8c835e2a..d886cb3f69 100644 --- a/DataGateway.Config/Authentication.cs +++ b/DataGateway.Config/Authentication.cs @@ -3,20 +3,19 @@ namespace Azure.DataGateway.Config /// /// Authentication configuration. /// - /// Identity Provider. Default is EasyAuth. + /// Identity Provider. /// With EasyAuth, no Audience or Issuer are expected. /// /// Settings enabling validation of the received JWT token. /// Required only when Provider is other than EasyAuth. public record AuthenticationConfig( - string Provider = AuthenticationConfig.EASYAUTH_PROVIDER_NAME, + string Provider, Jwt? Jwt = null) { - public const string EASYAUTH_PROVIDER_NAME = "EasyAuth"; - public bool IsEasyAuthAuthenticationProvider() { - return Provider.Equals(EASYAUTH_PROVIDER_NAME); + return Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(Provider, StringComparison.OrdinalIgnoreCase) + || Enum.GetName(EasyAuthType.AppService)!.Equals(Provider, StringComparison.OrdinalIgnoreCase); } } @@ -26,4 +25,13 @@ public bool IsEasyAuthAuthenticationProvider() /// /// public record Jwt(string Audience, string Issuer); + + /// + /// Different modes in which the runtime can run. + /// + public enum EasyAuthType + { + StaticWebApps, + AppService + } } diff --git a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 07b4b88682..4af7d899ae 100644 --- a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -7,6 +7,7 @@ using System.Text; using System.Text.Json; using System.Threading.Tasks; +using Azure.DataGateway.Config; using Azure.DataGateway.Service.AuthenticationHelpers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -17,7 +18,8 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.VisualStudio.TestTools.UnitTesting; -using static Azure.DataGateway.Service.AuthenticationHelpers.EasyAuthAuthentication; +using static Azure.DataGateway.Service.AuthenticationHelpers.AppServiceAuthentication; +using static Azure.DataGateway.Service.AuthenticationHelpers.StaticWebAppsAuthentication; namespace Azure.DataGateway.Service.Tests.Authentication { @@ -30,19 +32,38 @@ public class EasyAuthAuthenticationUnitTests { #region Positive Tests /// - /// Ensures a valid EasyAuth header/value does NOT result in HTTP 401 Unauthorized response. + /// Ensures a valid AppService EasyAuth header/value does NOT result in HTTP 401 Unauthorized response. /// 403 is okay, as it indicates authorization level failure, not authentication. /// When an authorization header is sent, it contains an invalid value, if the runtime returns an error /// then there is improper JWT validation occurring. /// [DataTestMethod] - [DataRow(false, DisplayName = "Valid EasyAuth header only")] - [DataRow(true, DisplayName = "Valid EasyAuth header and authorization header")] + [DataRow(false, DisplayName = "Valid AppService EasyAuth header only")] + [DataRow(true, DisplayName = "Valid AppService EasyAuth header and authorization header")] [TestMethod] - public async Task TestValidEasyAuthToken(bool sendAuthorizationHeader) + public async Task TestValidAppServiceEasyAuthToken(bool sendAuthorizationHeader) { - string generatedToken = CreateEasyAuthToken(); - HttpContext postMiddlewareContext = await SendRequestAndGetHttpContextState(generatedToken); + string generatedToken = CreateAppServiceEasyAuthToken(); + HttpContext postMiddlewareContext = await SendRequestAndGetHttpContextState(generatedToken, EasyAuthType.AppService); + Assert.IsNotNull(postMiddlewareContext.User.Identity); + Assert.IsTrue(postMiddlewareContext.User.Identity.IsAuthenticated); + Assert.AreEqual(expected: (int)HttpStatusCode.OK, actual: postMiddlewareContext.Response.StatusCode); + } + + /// + /// Ensures a valid StaticWebApps EasyAuth header/value does NOT result in HTTP 401 Unauthorized response. + /// 403 is okay, as it indicates authorization level failure, not authentication. + /// When an authorization header is sent, it contains an invalid value, if the runtime returns an error + /// then there is improper JWT validation occurring. + /// + [DataTestMethod] + [DataRow(false, DisplayName = "Valid StaticWebApps EasyAuth header only")] + [DataRow(true, DisplayName = "Valid StaticWebApps EasyAuth header and authorization header")] + [TestMethod] + public async Task TestValidStaticWebAppsEasyAuthToken(bool sendAuthorizationHeader) + { + string generatedToken = CreateStaticWebAppsEasyAuthToken(); + HttpContext postMiddlewareContext = await SendRequestAndGetHttpContextState(generatedToken, EasyAuthType.StaticWebApps); Assert.IsNotNull(postMiddlewareContext.User.Identity); Assert.IsTrue(postMiddlewareContext.User.Identity.IsAuthenticated); Assert.AreEqual(expected: (int)HttpStatusCode.OK, actual: postMiddlewareContext.Response.StatusCode); @@ -68,7 +89,7 @@ public async Task TestValidEasyAuthToken(bool sendAuthorizationHeader) [TestMethod] public async Task TestInvalidEasyAuthToken(string token, bool sendAuthorizationHeader = false) { - HttpContext postMiddlewareContext = await SendRequestAndGetHttpContextState(token, sendAuthorizationHeader); + HttpContext postMiddlewareContext = await SendRequestAndGetHttpContextState(token, EasyAuthType.StaticWebApps, sendAuthorizationHeader); Assert.IsNotNull(postMiddlewareContext.User.Identity); Assert.IsFalse(postMiddlewareContext.User.Identity.IsAuthenticated); Assert.AreEqual(expected: (int)HttpStatusCode.Unauthorized, actual: postMiddlewareContext.Response.StatusCode); @@ -80,7 +101,7 @@ public async Task TestInvalidEasyAuthToken(string token, bool sendAuthorizationH /// Configures test server with bare minimum middleware /// /// IHost - private static async Task CreateWebHostEasyAuth() + private static async Task CreateWebHostEasyAuth(EasyAuthType easyAuthType) { return await new HostBuilder() .ConfigureWebHost(webBuilder => @@ -90,7 +111,8 @@ private static async Task CreateWebHostEasyAuth() .ConfigureServices(services => { services.AddAuthentication(defaultScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME) - .AddEasyAuthAuthentication(); + .AddEasyAuthAuthentication(easyAuthType.ToString()); + services.AddAuthorization(); }) .ConfigureLogging(o => @@ -125,9 +147,9 @@ private static async Task CreateWebHostEasyAuth() /// The EasyAuth header value(base64 encoded token) to test against the TestServer /// Whether to add authorization header to header dictionary /// - private static async Task SendRequestAndGetHttpContextState(string? token, bool sendAuthorizationHeader = false) + private static async Task SendRequestAndGetHttpContextState(string? token, EasyAuthType easyAuthType, bool sendAuthorizationHeader = false) { - using IHost host = await CreateWebHostEasyAuth(); + using IHost host = await CreateWebHostEasyAuth(easyAuthType); TestServer server = host.GetTestServer(); return await server.SendAsync(context => @@ -135,7 +157,7 @@ private static async Task SendRequestAndGetHttpContextState(string? if (token is not null) { StringValues headerValue = new(new string[] { $"{token}" }); - KeyValuePair easyAuthHeader = new(EasyAuthAuthentication.EASYAUTHHEADER, headerValue); + KeyValuePair easyAuthHeader = new(AppServiceAuthentication.EASYAUTHHEADER, headerValue); context.Request.Headers.Add(easyAuthHeader); } @@ -153,25 +175,25 @@ private static async Task SendRequestAndGetHttpContextState(string? /// Creates a mocked EasyAuth token, namely, the value of the header injected by EasyAuth. /// /// A Base64 encoded string of a serialized EasyAuthClientPrincipal object - private static string CreateEasyAuthToken() + private static string CreateAppServiceEasyAuthToken() { - EasyAuthClaim emailClaim = new() + AppServiceClaim emailClaim = new() { Val = "apple@contoso.com", Typ = ClaimTypes.Upn }; - EasyAuthClaim roleClaim = new() + AppServiceClaim roleClaim = new() { Val = "Anonymous", Typ = ClaimTypes.Role }; - List claims = new(); + List claims = new(); claims.Add(emailClaim); claims.Add(roleClaim); - EasyAuthClientPrincipal token = new() + AppServiceClientPrincipal token = new() { Auth_typ = "aad", Name_typ = "Apple Banana", @@ -182,6 +204,26 @@ private static string CreateEasyAuthToken() string serializedToken = JsonSerializer.Serialize(value: token); return Convert.ToBase64String(Encoding.UTF8.GetBytes(serializedToken)); } + + /// + /// Creates a mocked EasyAuth token, namely, the value of the header injected by EasyAuth. + /// + /// A Base64 encoded string of a serialized EasyAuthClientPrincipal object + private static string CreateStaticWebAppsEasyAuthToken() + { + List roles = new(); + roles.Add("anonymous"); + roles.Add("authenticated"); + + StaticWebAppsClientPrincipal token = new() + { + IdentityProvider = "github", + UserRoles = roles + }; + + string serializedToken = JsonSerializer.Serialize(value: token); + return Convert.ToBase64String(Encoding.UTF8.GetBytes(serializedToken)); + } #endregion } } diff --git a/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs b/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs index 4b82806b47..42e4bcdad2 100644 --- a/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs +++ b/DataGateway.Service.Tests/Configuration/AuthenticationConfigValidatorUnitTests.cs @@ -21,7 +21,7 @@ public class AuthenticationConfigValidatorUnitTests public void ValidateEasyAuthConfig() { RuntimeConfig config = - CreateRuntimeConfigWithAuthN(new AuthenticationConfig()); + CreateRuntimeConfigWithAuthN(new AuthenticationConfig(EasyAuthType.StaticWebApps.ToString())); RuntimeConfigValidator configValidator = GetMockConfigValidator(ref config); diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs similarity index 83% rename from DataGateway.Service/AuthenticationHelpers/EasyAuthAuthentication.cs rename to DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs index 8855f727fa..34dae53408 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs @@ -12,26 +12,26 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// Helper class which parses EasyAuth's injected headers into a ClaimsIdentity object. /// This class provides helper methods for StaticWebApp's Authentication feature: EasyAuth. /// - public static class EasyAuthAuthentication + public static class AppServiceAuthentication { public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; /// /// Representation of authenticated user principal Http header /// injected by EasyAuth /// - public struct EasyAuthClientPrincipal + public struct AppServiceClientPrincipal { public string Auth_typ { get; set; } public string Name_typ { get; set; } public string Role_typ { get; set; } - public IEnumerable Claims { get; set; } + public IEnumerable Claims { get; set; } } /// /// Representation of authenticated user principal claims /// injected by EasyAuth /// - public struct EasyAuthClaim + public struct AppServiceClaim { public string Typ { get; set; } public string Val { get; set; } @@ -53,20 +53,20 @@ public struct EasyAuthClaim { ClaimsIdentity? identity = null; - if (context.Request.Headers.TryGetValue(EasyAuthAuthentication.EASYAUTHHEADER, out StringValues header)) + if (context.Request.Headers.TryGetValue(AppServiceAuthentication.EASYAUTHHEADER, out StringValues header)) { try { string encodedPrincipalData = header[0]; byte[] decodedPrincpalData = Convert.FromBase64String(encodedPrincipalData); string json = Encoding.UTF8.GetString(decodedPrincpalData); - EasyAuthClientPrincipal principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); + AppServiceClientPrincipal principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); identity = new(principal.Auth_typ, principal.Name_typ, principal.Role_typ); if (principal.Claims != null) { - foreach (EasyAuthClaim claim in principal.Claims) + foreach (AppServiceClaim claim in principal.Claims) { identity.AddClaim(new Claim(type: claim.Typ, value: claim.Val)); } @@ -77,7 +77,7 @@ public struct EasyAuthClaim // Logging the parsing failure exception to the console, but not rethrowing // nor creating a DataGateway exception because the authentication handler // will create and send a 401 unauthorized response to the client. - Console.Error.WriteLine("Failure processing the EasyAuth header."); + Console.Error.WriteLine("Failure processing the AppServie EasyAuth header."); Console.Error.WriteLine(error.Message); Console.Error.WriteLine(error.StackTrace); } diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs similarity index 91% rename from DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs rename to DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs index 942f1b90bb..60c4d58d63 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs @@ -17,7 +17,7 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// Usage modelled from Microsoft.Identity.Web. /// Ref: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs /// - public class EasyAuthAuthenticationHandler : AuthenticationHandler + public class AppServiceAuthenticationHandler : AuthenticationHandler { private const string EASY_AUTH_HEADER = "X-MS-CLIENT-PRINCIPAL"; @@ -29,7 +29,7 @@ public class EasyAuthAuthenticationHandler : AuthenticationHandlerLogger factory. /// URL encoder. /// System clock. - public EasyAuthAuthenticationHandler( + public AppServiceAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, @@ -49,11 +49,11 @@ protected override Task HandleAuthenticateAsync() { if (Context.Request.Headers[EASY_AUTH_HEADER].Count > 0) { - ClaimsIdentity? identity = EasyAuthAuthentication.Parse(Context); + ClaimsIdentity? identity = AppServiceAuthentication.Parse(Context); if (identity is null) { - return Task.FromResult(AuthenticateResult.Fail(failureMessage: "Invalid EasyAuth token.")); + return Task.FromResult(AuthenticateResult.Fail(failureMessage: "Invalid AppService EasyAuth token.")); } ClaimsPrincipal? claimsPrincipal = new(identity); diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs index 173ff3c880..1017c7af39 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs @@ -1,3 +1,5 @@ +using System; +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Authentication; namespace Azure.DataGateway.Service.AuthenticationHelpers @@ -15,17 +17,28 @@ public static class EasyAuthAuthenticationBuilderExtensions /// Authentication builder. /// The builder, to chain commands. public static AuthenticationBuilder AddEasyAuthAuthentication( - this AuthenticationBuilder builder) + this AuthenticationBuilder builder, string easyAuthAuthenticationProvider) { if (builder is null) { throw new System.ArgumentNullException(nameof(builder)); } - builder.AddScheme( - authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - options => { }); + if (Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) + { + builder.AddScheme( + authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + options => { }); + } + + if (Enum.GetName(EasyAuthType.AppService)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) + { + builder.AddScheme( + authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + options => { }); + } return builder; } diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs new file mode 100644 index 0000000000..3876ec8cab --- /dev/null +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Azure.DataGateway.Service.AuthenticationHelpers +{ + public class StaticWebAppsAuthentication + { + public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; + + public class StaticWebAppsClientPrincipal + { + public string? IdentityProvider { get; set; } + public string? UserId { get; set; } + public string? UserDetails { get; set; } + public IEnumerable? UserRoles { get; set; } + } + + public static ClaimsIdentity? Parse(HttpRequest req) + { + ClaimsIdentity? identity = null; + StaticWebAppsClientPrincipal principal = new(); + try + { + if (req.Headers.TryGetValue(EASYAUTHHEADER, out StringValues header)) + { + string data = header[0]; + byte[] decoded = Convert.FromBase64String(data); + string json = Encoding.UTF8.GetString(decoded); + principal = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new(); + } + + if (!principal?.UserRoles?.Any() ?? true) + { + return identity; + } + + identity = new(principal!.IdentityProvider); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, principal.UserId ?? string.Empty)); + identity.AddClaim(new Claim(ClaimTypes.Name, principal.UserDetails ?? string.Empty)); + identity.AddClaims(principal.UserRoles!.Select(r => new Claim(ClaimTypes.Role, r))); + + return identity; + } + catch (Exception error) + { + // Logging the parsing failure exception to the console, but not rethrowing + // nor creating a DataGateway exception because the authentication handler + // will create and send a 401 unauthorized response to the client. + Console.Error.WriteLine("Failure processing the StaticWebApps EasyAuth header."); + Console.Error.WriteLine(error.Message); + Console.Error.WriteLine(error.StackTrace); + } + + return identity; + } + } +} diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs new file mode 100644 index 0000000000..c3da9a1198 --- /dev/null +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs @@ -0,0 +1,74 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Azure.DataGateway.Service.AuthenticationHelpers +{ + /// + /// This class is used to best integrate with ASP.NET Core AuthenticationHandler base class. + /// Ref: https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationHandler.cs + /// When "EasyAuth" is configured, this handler authenticates the user once per request, + /// and utilizes the base class default handler for + /// - AuthenticateAsync: Authenticates the current request. + /// - Forbid Async: Creates 403 HTTP Response. + /// Usage modelled from Microsoft.Identity.Web. + /// Ref: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs + /// + public class StaticWebAppsAuthenticationHandler : AuthenticationHandler + { + private const string EASY_AUTH_HEADER = "X-MS-CLIENT-PRINCIPAL"; + + /// + /// Constructor for the EasyAuthAuthenticationHandler. + /// Note the parameters are required by the base class. + /// + /// App service authentication options. + /// Logger factory. + /// URL encoder. + /// System clock. + public StaticWebAppsAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock + ) : base(options, logger, encoder, clock) + { + } + + /// + /// Gets any authentication data for a request. When an EasyAuth header is present, + /// parses the header and authenticates the user within a ClaimsPrincipal object. + /// The ClaimsPrincipal is a security principal usable by middleware to identify the + /// authenticated user. + /// + /// An authentication result to ASP.NET Core library authentication mechanisms + protected override Task HandleAuthenticateAsync() + { + if (Context.Request.Headers[EASY_AUTH_HEADER].Count > 0) + { + ClaimsIdentity? identity = StaticWebAppsAuthentication.Parse(Context.Request); + + if (identity is null) + { + return Task.FromResult(AuthenticateResult.Fail(failureMessage: "Invalid StaticWebApps EasyAuth token.")); + } + + ClaimsPrincipal? claimsPrincipal = new(identity); + + if (claimsPrincipal is not null) + { + // AuthenticationTicket is Asp.Net Core Abstraction of Authentication information + // Ref: aspnetcore/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs + AuthenticationTicket ticket = new(claimsPrincipal, EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME); + AuthenticateResult success = AuthenticateResult.Success(ticket); + return Task.FromResult(success); + } + } + // Try another handler + return Task.FromResult(AuthenticateResult.NoResult()); + } + } +} diff --git a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs index 956df5a5b1..f403db05fd 100644 --- a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs +++ b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs @@ -97,14 +97,82 @@ string s when string.IsNullOrEmpty(s) && !string.IsNullOrEmpty(_cosmosDb.Contain public TableDefinition GetTableDefinition(string entityName) { - throw new NotSupportedException("Cosmos backends don't support direct table definitions. Definitions are provided via the GraphQL schema"); + if (!EntityToDatabaseObject.TryGetValue(entityName, out DatabaseObject? databaseObject)) + { + throw new InvalidCastException($"Table Definition for {entityName} has not been inferred."); + } + + return databaseObject!.TableDefinition; } public Task InitializeAsync() { + GenerateDatabaseObjectForEntities(); return Task.CompletedTask; } + /// + /// Create a DatabaseObject for all the exposed entities. + /// + private void GenerateDatabaseObjectForEntities() + { + string schemaName, dbObjectName; + Dictionary sourceObjects = new(); + foreach ((string entityName, Entity entity) + in _entities) + { + if (!EntityToDatabaseObject.ContainsKey(entityName)) + { + // Reuse the same Database object for multiple entities if they share the same source. + if (!sourceObjects.TryGetValue(entity.GetSourceName(), out DatabaseObject? sourceObject)) + { + // parse source name into a tuple of (schemaName, databaseObjectName) + (schemaName, dbObjectName) = ParseSchemaAndDbObjectName(entity.GetSourceName())!; + sourceObject = new() + { + SchemaName = schemaName, + Name = dbObjectName, + TableDefinition = new() + }; + + sourceObjects.Add(entity.GetSourceName(), sourceObject); + } + + EntityToDatabaseObject.Add(entityName, sourceObject); + } + } + } + + /// + /// Helper function will parse the schema and database object name + /// from the provided and string and sort out if a default schema + /// should be used. It then returns the appropriate schema and + /// db object name as a tuple of strings. + /// + /// source string to parse + /// + /// + public (string, string) ParseSchemaAndDbObjectName(string source) + { + (string? schemaName, string dbObjectName) = EntitySourceNamesParser.ParseSchemaAndTable(source)!; + + if (string.IsNullOrEmpty(schemaName)) + { + throw new DataGatewayException(message: $"Missing database name for entity name: {source} in Config file for Cosmos", + statusCode: System.Net.HttpStatusCode.ServiceUnavailable, + subStatusCode: DataGatewayException.SubStatusCodes.ErrorInInitialization); + } + + if (string.IsNullOrEmpty(dbObjectName)) + { + throw new DataGatewayException(message: $"Missing container name for entity name: {source} in Config file for Cosmos", + statusCode: System.Net.HttpStatusCode.ServiceUnavailable, + subStatusCode: DataGatewayException.SubStatusCodes.ErrorInInitialization); + } + + return (schemaName, dbObjectName); + } + public string GraphQLSchema() { if (_cosmosDb.GraphQLSchema is null && _fileSystem.File.Exists(_cosmosDb.GraphQLSchemaPath)) diff --git a/DataGateway.Service/Startup.cs b/DataGateway.Service/Startup.cs index 73ec7241f1..036cc7721c 100644 --- a/DataGateway.Service/Startup.cs +++ b/DataGateway.Service/Startup.cs @@ -322,7 +322,7 @@ private void ConfigureAuthentication(IServiceCollection services) runtimeConfig.IsEasyAuthAuthenticationProvider()) { services.AddAuthentication(EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME) - .AddEasyAuthAuthentication(); + .AddEasyAuthAuthentication(runtimeConfig.AuthNConfig.Provider); } } diff --git a/DataGateway.Service/hawaii-config.Cosmos.json b/DataGateway.Service/hawaii-config.Cosmos.json index 38eae70365..40be5b95f3 100644 --- a/DataGateway.Service/hawaii-config.Cosmos.json +++ b/DataGateway.Service/hawaii-config.Cosmos.json @@ -25,34 +25,49 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth" + "provider": "StaticWebApps" } } }, "entities": { "Planet": { - "source": "planet", + "source": "graphqldb.planet", + "rest": false, + "graphql": true, "permissions": [ { "role": "anonymous", - "actions": [ "*" ] + "actions": [ "create", "read", "update", "delete" ] }, { "role": "authenticated", - "actions": [ "*" ] + "actions": [ "create", "read", "update", "delete" ] } ] }, "Character": { - "source": "planet", + "source": "graphqldb.character", + "rest": false, + "graphql": true, + "permissions": [ + { + "role": "authenticated", + "actions": [ "create", "read", "update", "delete" ] + } + ] + }, + "Star": { + "source": "graphqldb.star", + "rest": false, + "graphql": true, "permissions": [ { "role": "anonymous", - "actions": [ "*" ] + "actions": [ "create", "read", "update", "delete" ] }, { "role": "authenticated", - "actions": [ "*" ] + "actions": [ "create", "read", "update", "delete" ] } ] } diff --git a/DataGateway.Service/hawaii-config.Cosmos.overrides.example.json b/DataGateway.Service/hawaii-config.Cosmos.overrides.example.json index 8e5c3df511..6da127b02a 100644 --- a/DataGateway.Service/hawaii-config.Cosmos.overrides.example.json +++ b/DataGateway.Service/hawaii-config.Cosmos.overrides.example.json @@ -2,7 +2,7 @@ "$schema": "../schemas/hawaii.draft-01.schema.json", "data-source": { "database-type": "cosmos", - "connection-string": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", + "connection-string": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" }, "cosmos": { "database": "graphqldb", @@ -25,7 +25,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth" + "provider": "StaticWebApps" } } }, @@ -35,7 +35,7 @@ "permissions": [ { "role": "anonymous", - "actions": ["*"] + "actions": [ "*" ] } ] }, diff --git a/DataGateway.Service/hawaii-config.MsSql.json b/DataGateway.Service/hawaii-config.MsSql.json index 1afc2a17e0..e24135468f 100644 --- a/DataGateway.Service/hawaii-config.MsSql.json +++ b/DataGateway.Service/hawaii-config.MsSql.json @@ -24,7 +24,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/hawaii-config.MsSql.overrides.example.json b/DataGateway.Service/hawaii-config.MsSql.overrides.example.json index 3f8b7f7b90..3cbe7eb7c2 100644 --- a/DataGateway.Service/hawaii-config.MsSql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.MsSql.overrides.example.json @@ -24,7 +24,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/hawaii-config.MySql.json b/DataGateway.Service/hawaii-config.MySql.json index eaf6533447..37378d8ecc 100644 --- a/DataGateway.Service/hawaii-config.MySql.json +++ b/DataGateway.Service/hawaii-config.MySql.json @@ -21,7 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/hawaii-config.MySql.overrides.example.json b/DataGateway.Service/hawaii-config.MySql.overrides.example.json index 1e0eb89e93..33c340b2c9 100644 --- a/DataGateway.Service/hawaii-config.MySql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.MySql.overrides.example.json @@ -21,7 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/hawaii-config.PostgreSql.json b/DataGateway.Service/hawaii-config.PostgreSql.json index d08608c2eb..04f68d8b5f 100644 --- a/DataGateway.Service/hawaii-config.PostgreSql.json +++ b/DataGateway.Service/hawaii-config.PostgreSql.json @@ -21,7 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json b/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json index 03efb7b17d..b1287f34a8 100644 --- a/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json @@ -21,13 +21,13 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" - } } } + } } }, "entities": { diff --git a/DataGateway.Service/hawaii-config.json b/DataGateway.Service/hawaii-config.json index 139a3da016..dbfc0054c0 100644 --- a/DataGateway.Service/hawaii-config.json +++ b/DataGateway.Service/hawaii-config.json @@ -21,7 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "EasyAuth", + "provider": "StaticWebApps", "jwt": { "audience": "", "issuer": "" diff --git a/DataGateway.Service/schema.gql b/DataGateway.Service/schema.gql index 6a28900a23..cd1f8a7607 100644 --- a/DataGateway.Service/schema.gql +++ b/DataGateway.Service/schema.gql @@ -11,5 +11,11 @@ type Planet @model { name : String, character: Character, age : Int, - dimension : String -} + dimension : String, + stars: [Star] +} + +type Star @model { + id : ID, + name : String +} From 46f69bd5a739c90675ec416881665d0f0362a4f2 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Tue, 5 Jul 2022 17:37:21 -0400 Subject: [PATCH 02/10] Address pr comments with more comments. --- DataGateway.Config/Authentication.cs | 1 + .../EasyAuthAuthenticationUnitTests.cs | 2 +- .../AppServiceAuthentication.cs | 6 +++--- .../EasyAuthAuthenticationBuilderExtensions.cs | 1 + .../StaticWebAppsAuthentication.cs | 13 ++++++++++--- .../MetadataProviders/CosmosSqlMetadataProvider.cs | 3 ++- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/DataGateway.Config/Authentication.cs b/DataGateway.Config/Authentication.cs index d886cb3f69..ee6d035e5b 100644 --- a/DataGateway.Config/Authentication.cs +++ b/DataGateway.Config/Authentication.cs @@ -12,6 +12,7 @@ public record AuthenticationConfig( string Provider, Jwt? Jwt = null) { + public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; public bool IsEasyAuthAuthenticationProvider() { return Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(Provider, StringComparison.OrdinalIgnoreCase) diff --git a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 4af7d899ae..02d9d4af11 100644 --- a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -157,7 +157,7 @@ private static async Task SendRequestAndGetHttpContextState(string? if (token is not null) { StringValues headerValue = new(new string[] { $"{token}" }); - KeyValuePair easyAuthHeader = new(AppServiceAuthentication.EASYAUTHHEADER, headerValue); + KeyValuePair easyAuthHeader = new(AuthenticationConfig.EASYAUTHHEADER, headerValue); context.Request.Headers.Add(easyAuthHeader); } diff --git a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs index 34dae53408..ac0c4bf840 100644 --- a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; @@ -10,11 +11,10 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers { /// /// Helper class which parses EasyAuth's injected headers into a ClaimsIdentity object. - /// This class provides helper methods for StaticWebApp's Authentication feature: EasyAuth. + /// This class provides helper methods for AppService's Authentication feature: EasyAuth. /// public static class AppServiceAuthentication { - public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; /// /// Representation of authenticated user principal Http header /// injected by EasyAuth @@ -53,7 +53,7 @@ public struct AppServiceClaim { ClaimsIdentity? identity = null; - if (context.Request.Headers.TryGetValue(AppServiceAuthentication.EASYAUTHHEADER, out StringValues header)) + if (context.Request.Headers.TryGetValue(AuthenticationConfig.EASYAUTHHEADER, out StringValues header)) { try { diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs index 1017c7af39..276dbffd51 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs @@ -15,6 +15,7 @@ public static class EasyAuthAuthenticationBuilderExtensions /// Add authentication with Static Web Apps. /// /// Authentication builder. + /// EasyAuth provider type. StaticWebApps or AppService /// The builder, to chain commands. public static AuthenticationBuilder AddEasyAuthAuthentication( this AuthenticationBuilder builder, string easyAuthAuthenticationProvider) diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs index 3876ec8cab..ec78047d65 100644 --- a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs @@ -4,15 +4,22 @@ using System.Security.Claims; using System.Text; using System.Text.Json; +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Primitives; namespace Azure.DataGateway.Service.AuthenticationHelpers { + /// + /// Helper class which parses EasyAuth's injected headers into a ClaimsIdentity object. + /// This class provides helper methods for StaticWebApps' Authentication feature: EasyAuth. + /// public class StaticWebAppsAuthentication { - public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; - + /// + /// Link for reference of how StaticWebAppsClientPrincipal is defined + /// https://docs.microsoft.com/azure/static-web-apps/user-information?tabs=csharp#client-principal-data + /// public class StaticWebAppsClientPrincipal { public string? IdentityProvider { get; set; } @@ -27,7 +34,7 @@ public class StaticWebAppsClientPrincipal StaticWebAppsClientPrincipal principal = new(); try { - if (req.Headers.TryGetValue(EASYAUTHHEADER, out StringValues header)) + if (req.Headers.TryGetValue(AuthenticationConfig.EASYAUTHHEADER, out StringValues header)) { string data = header[0]; byte[] decoded = Convert.FromBase64String(data); diff --git a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs index f403db05fd..f3b4424cbc 100644 --- a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs +++ b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs @@ -145,9 +145,10 @@ private void GenerateDatabaseObjectForEntities() /// /// Helper function will parse the schema and database object name - /// from the provided and string and sort out if a default schema + /// from the provided source string and sort out if a default schema /// should be used. It then returns the appropriate schema and /// db object name as a tuple of strings. + /// i.e. source = 'graphqldb.planet' -> databaseName ='graphqldb'; containerName ='planet' /// /// source string to parse /// From 0a952fce5fdfd4f3bdb77f63bf96d9b2dc8499d8 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Wed, 6 Jul 2022 11:25:11 -0400 Subject: [PATCH 03/10] Clean up config files. --- DataGateway.Config/Authentication.cs | 2 +- DataGateway.Service/hawaii-config.MsSql.json | 6 +----- .../hawaii-config.MsSql.overrides.example.json | 6 +----- DataGateway.Service/hawaii-config.MySql.json | 6 +----- .../hawaii-config.MySql.overrides.example.json | 6 +----- DataGateway.Service/hawaii-config.PostgreSql.json | 6 +----- .../hawaii-config.PostgreSql.overrides.example.json | 7 +------ DataGateway.Service/hawaii-config.json | 6 +----- 8 files changed, 8 insertions(+), 37 deletions(-) diff --git a/DataGateway.Config/Authentication.cs b/DataGateway.Config/Authentication.cs index ee6d035e5b..00c4590659 100644 --- a/DataGateway.Config/Authentication.cs +++ b/DataGateway.Config/Authentication.cs @@ -3,7 +3,7 @@ namespace Azure.DataGateway.Config /// /// Authentication configuration. /// - /// Identity Provider. + /// Identity Provider. Default is StaticWebApps. /// With EasyAuth, no Audience or Issuer are expected. /// /// Settings enabling validation of the received JWT token. diff --git a/DataGateway.Service/hawaii-config.MsSql.json b/DataGateway.Service/hawaii-config.MsSql.json index e24135468f..5dbfad2c4b 100644 --- a/DataGateway.Service/hawaii-config.MsSql.json +++ b/DataGateway.Service/hawaii-config.MsSql.json @@ -24,11 +24,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.MsSql.overrides.example.json b/DataGateway.Service/hawaii-config.MsSql.overrides.example.json index 3cbe7eb7c2..9f3c44f6d4 100644 --- a/DataGateway.Service/hawaii-config.MsSql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.MsSql.overrides.example.json @@ -24,11 +24,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.MySql.json b/DataGateway.Service/hawaii-config.MySql.json index 37378d8ecc..a41f9c28bd 100644 --- a/DataGateway.Service/hawaii-config.MySql.json +++ b/DataGateway.Service/hawaii-config.MySql.json @@ -21,11 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.MySql.overrides.example.json b/DataGateway.Service/hawaii-config.MySql.overrides.example.json index 33c340b2c9..be4d25dd4a 100644 --- a/DataGateway.Service/hawaii-config.MySql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.MySql.overrides.example.json @@ -21,11 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.PostgreSql.json b/DataGateway.Service/hawaii-config.PostgreSql.json index 04f68d8b5f..417c28d46e 100644 --- a/DataGateway.Service/hawaii-config.PostgreSql.json +++ b/DataGateway.Service/hawaii-config.PostgreSql.json @@ -21,11 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json b/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json index b1287f34a8..03226d9433 100644 --- a/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json +++ b/DataGateway.Service/hawaii-config.PostgreSql.overrides.example.json @@ -21,12 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } - } + "provider": "StaticWebApps" } } }, diff --git a/DataGateway.Service/hawaii-config.json b/DataGateway.Service/hawaii-config.json index dbfc0054c0..460ed37178 100644 --- a/DataGateway.Service/hawaii-config.json +++ b/DataGateway.Service/hawaii-config.json @@ -21,11 +21,7 @@ "allow-credentials": false }, "authentication": { - "provider": "StaticWebApps", - "jwt": { - "audience": "", - "issuer": "" - } + "provider": "StaticWebApps" } } }, From ab347a48253b18bba41adc7dc12d3082bbddec8c Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Mon, 11 Jul 2022 10:35:28 -0400 Subject: [PATCH 04/10] Address PR comments --- DataGateway.Config/Authentication.cs | 2 +- .../Authentication/EasyAuthAuthenticationUnitTests.cs | 2 +- .../AuthenticationHelpers/AppServiceAuthentication.cs | 4 ++-- .../AppServiceAuthenticationHandler.cs | 5 ++--- .../AuthenticationHelpers/StaticWebAppsAuthentication.cs | 2 +- .../StaticWebAppsAuthenticationHandler.cs | 9 ++++----- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/DataGateway.Config/Authentication.cs b/DataGateway.Config/Authentication.cs index 00c4590659..c4cf1d3b32 100644 --- a/DataGateway.Config/Authentication.cs +++ b/DataGateway.Config/Authentication.cs @@ -12,7 +12,7 @@ public record AuthenticationConfig( string Provider, Jwt? Jwt = null) { - public const string EASYAUTHHEADER = "X-MS-CLIENT-PRINCIPAL"; + public const string CLIENT_PRINCIPAL_HEADER = "X-MS-CLIENT-PRINCIPAL"; public bool IsEasyAuthAuthenticationProvider() { return Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(Provider, StringComparison.OrdinalIgnoreCase) diff --git a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index 02d9d4af11..eb28bfbd54 100644 --- a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -157,7 +157,7 @@ private static async Task SendRequestAndGetHttpContextState(string? if (token is not null) { StringValues headerValue = new(new string[] { $"{token}" }); - KeyValuePair easyAuthHeader = new(AuthenticationConfig.EASYAUTHHEADER, headerValue); + KeyValuePair easyAuthHeader = new(AuthenticationConfig.CLIENT_PRINCIPAL_HEADER, headerValue); context.Request.Headers.Add(easyAuthHeader); } diff --git a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs index ac0c4bf840..05b8b63cab 100644 --- a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthentication.cs @@ -53,7 +53,7 @@ public struct AppServiceClaim { ClaimsIdentity? identity = null; - if (context.Request.Headers.TryGetValue(AuthenticationConfig.EASYAUTHHEADER, out StringValues header)) + if (context.Request.Headers.TryGetValue(AuthenticationConfig.CLIENT_PRINCIPAL_HEADER, out StringValues header)) { try { @@ -77,7 +77,7 @@ public struct AppServiceClaim // Logging the parsing failure exception to the console, but not rethrowing // nor creating a DataGateway exception because the authentication handler // will create and send a 401 unauthorized response to the client. - Console.Error.WriteLine("Failure processing the AppServie EasyAuth header."); + Console.Error.WriteLine("Failure processing the AppService EasyAuth header."); Console.Error.WriteLine(error.Message); Console.Error.WriteLine(error.StackTrace); } diff --git a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs index 60c4d58d63..318f5e2ec1 100644 --- a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -19,8 +20,6 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// public class AppServiceAuthenticationHandler : AuthenticationHandler { - private const string EASY_AUTH_HEADER = "X-MS-CLIENT-PRINCIPAL"; - /// /// Constructor for the EasyAuthAuthenticationHandler. /// Note the parameters are required by the base class. @@ -47,7 +46,7 @@ ISystemClock clock /// An authentication result to ASP.NET Core library authentication mechanisms protected override Task HandleAuthenticateAsync() { - if (Context.Request.Headers[EASY_AUTH_HEADER].Count > 0) + if (Context.Request.Headers[AuthenticationConfig.CLIENT_PRINCIPAL_HEADER].Count > 0) { ClaimsIdentity? identity = AppServiceAuthentication.Parse(Context); diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs index ec78047d65..9c35599e88 100644 --- a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs @@ -34,7 +34,7 @@ public class StaticWebAppsClientPrincipal StaticWebAppsClientPrincipal principal = new(); try { - if (req.Headers.TryGetValue(AuthenticationConfig.EASYAUTHHEADER, out StringValues header)) + if (req.Headers.TryGetValue(AuthenticationConfig.CLIENT_PRINCIPAL_HEADER, out StringValues header)) { string data = header[0]; byte[] decoded = Convert.FromBase64String(data); diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs index c3da9a1198..0f22cb2edc 100644 --- a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -15,17 +16,15 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// - AuthenticateAsync: Authenticates the current request. /// - Forbid Async: Creates 403 HTTP Response. /// Usage modelled from Microsoft.Identity.Web. - /// Ref: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs + /// Ref: https://docs.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript /// public class StaticWebAppsAuthenticationHandler : AuthenticationHandler { - private const string EASY_AUTH_HEADER = "X-MS-CLIENT-PRINCIPAL"; - /// /// Constructor for the EasyAuthAuthenticationHandler. /// Note the parameters are required by the base class. /// - /// App service authentication options. + /// Static Web Apps authentication options. /// Logger factory. /// URL encoder. /// System clock. @@ -47,7 +46,7 @@ ISystemClock clock /// An authentication result to ASP.NET Core library authentication mechanisms protected override Task HandleAuthenticateAsync() { - if (Context.Request.Headers[EASY_AUTH_HEADER].Count > 0) + if (Context.Request.Headers[AuthenticationConfig.CLIENT_PRINCIPAL_HEADER].Count > 0) { ClaimsIdentity? identity = StaticWebAppsAuthentication.Parse(Context.Request); From fa979e524c6303c8ec81fd103047f68ec9a1e3c4 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Mon, 11 Jul 2022 15:49:12 -0400 Subject: [PATCH 05/10] Refactor authorization handler --- .../AppServiceAuthenticationHandler.cs | 72 ------------------- ...EasyAuthAuthenticationBuilderExtensions.cs | 29 ++++---- ...er.cs => EasyAuthAuthenticationHandler.cs} | 15 ++-- .../EasyAuthAuthenticationOptions.cs | 2 + .../StaticWebAppsAuthentication.cs | 4 +- .../CosmosSqlMetadataProvider.cs | 4 +- 6 files changed, 28 insertions(+), 98 deletions(-) delete mode 100644 DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs rename DataGateway.Service/AuthenticationHelpers/{StaticWebAppsAuthenticationHandler.cs => EasyAuthAuthenticationHandler.cs} (84%) diff --git a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs deleted file mode 100644 index 318f5e2ec1..0000000000 --- a/DataGateway.Service/AuthenticationHelpers/AppServiceAuthenticationHandler.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System.Security.Claims; -using System.Text.Encodings.Web; -using System.Threading.Tasks; -using Azure.DataGateway.Config; -using Microsoft.AspNetCore.Authentication; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Azure.DataGateway.Service.AuthenticationHelpers -{ - /// - /// This class is used to best integrate with ASP.NET Core AuthenticationHandler base class. - /// Ref: https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationHandler.cs - /// When "EasyAuth" is configured, this handler authenticates the user once per request, - /// and utilizes the base class default handler for - /// - AuthenticateAsync: Authenticates the current request. - /// - Forbid Async: Creates 403 HTTP Response. - /// Usage modelled from Microsoft.Identity.Web. - /// Ref: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/AppServicesAuth/AppServicesAuthenticationHandler.cs - /// - public class AppServiceAuthenticationHandler : AuthenticationHandler - { - /// - /// Constructor for the EasyAuthAuthenticationHandler. - /// Note the parameters are required by the base class. - /// - /// App service authentication options. - /// Logger factory. - /// URL encoder. - /// System clock. - public AppServiceAuthenticationHandler( - IOptionsMonitor options, - ILoggerFactory logger, - UrlEncoder encoder, - ISystemClock clock - ) : base(options, logger, encoder, clock) - { - } - - /// - /// Gets any authentication data for a request. When an EasyAuth header is present, - /// parses the header and authenticates the user within a ClaimsPrincipal object. - /// The ClaimsPrincipal is a security principal usable by middleware to identify the - /// authenticated user. - /// - /// An authentication result to ASP.NET Core library authentication mechanisms - protected override Task HandleAuthenticateAsync() - { - if (Context.Request.Headers[AuthenticationConfig.CLIENT_PRINCIPAL_HEADER].Count > 0) - { - ClaimsIdentity? identity = AppServiceAuthentication.Parse(Context); - - if (identity is null) - { - return Task.FromResult(AuthenticateResult.Fail(failureMessage: "Invalid AppService EasyAuth token.")); - } - - ClaimsPrincipal? claimsPrincipal = new(identity); - if (claimsPrincipal is not null) - { - // AuthenticationTicket is Asp.Net Core Abstraction of Authentication information - // Ref: aspnetcore/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs - AuthenticationTicket ticket = new(claimsPrincipal, EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME); - AuthenticateResult success = AuthenticateResult.Success(ticket); - return Task.FromResult(success); - } - } - // Try another handler - return Task.FromResult(AuthenticateResult.NoResult()); - } - } -} diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs index 276dbffd51..6061b49a74 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs @@ -25,22 +25,19 @@ public static AuthenticationBuilder AddEasyAuthAuthentication( throw new System.ArgumentNullException(nameof(builder)); } - if (Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) - { - builder.AddScheme( - authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - options => { }); - } - - if (Enum.GetName(EasyAuthType.AppService)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) - { - builder.AddScheme( - authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - options => { }); - } - + builder.AddScheme( + authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, + options => { + if (Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) + { + options.EasyAuthProvider = EasyAuthType.StaticWebApps; + } + else if (Enum.GetName(EasyAuthType.AppService)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) + { + options.EasyAuthProvider = EasyAuthType.AppService; + } + }); return builder; } } diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs similarity index 84% rename from DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs rename to DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs index 0f22cb2edc..7330b7b3d9 100644 --- a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs @@ -15,10 +15,8 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// and utilizes the base class default handler for /// - AuthenticateAsync: Authenticates the current request. /// - Forbid Async: Creates 403 HTTP Response. - /// Usage modelled from Microsoft.Identity.Web. - /// Ref: https://docs.microsoft.com/en-us/azure/static-web-apps/user-information?tabs=javascript /// - public class StaticWebAppsAuthenticationHandler : AuthenticationHandler + public class EasyAuthAuthenticationHandler : AuthenticationHandler { /// /// Constructor for the EasyAuthAuthenticationHandler. @@ -28,7 +26,7 @@ public class StaticWebAppsAuthenticationHandler : AuthenticationHandlerLogger factory. /// URL encoder. /// System clock. - public StaticWebAppsAuthenticationHandler( + public EasyAuthAuthenticationHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, @@ -48,11 +46,16 @@ protected override Task HandleAuthenticateAsync() { if (Context.Request.Headers[AuthenticationConfig.CLIENT_PRINCIPAL_HEADER].Count > 0) { - ClaimsIdentity? identity = StaticWebAppsAuthentication.Parse(Context.Request); + ClaimsIdentity? identity = Options.EasyAuthProvider switch + { + EasyAuthType.StaticWebApps => StaticWebAppsAuthentication.Parse(Context), + EasyAuthType.AppService => AppServiceAuthentication.Parse(Context), + _ => null + }; if (identity is null) { - return Task.FromResult(AuthenticateResult.Fail(failureMessage: "Invalid StaticWebApps EasyAuth token.")); + return Task.FromResult(AuthenticateResult.Fail(failureMessage: $"Invalid {Options.EasyAuthProvider.ToString()} EasyAuth token.")); } ClaimsPrincipal? claimsPrincipal = new(identity); diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationOptions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationOptions.cs index 59a06e0381..62b21a26f9 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationOptions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationOptions.cs @@ -1,3 +1,4 @@ +using Azure.DataGateway.Config; using Microsoft.AspNetCore.Authentication; namespace Azure.DataGateway.Service.AuthenticationHelpers @@ -12,5 +13,6 @@ namespace Azure.DataGateway.Service.AuthenticationHelpers /// public class EasyAuthAuthenticationOptions : AuthenticationSchemeOptions { + public EasyAuthType EasyAuthProvider { get; set; } } } diff --git a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs index 9c35599e88..595ddac5cb 100644 --- a/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs +++ b/DataGateway.Service/AuthenticationHelpers/StaticWebAppsAuthentication.cs @@ -28,13 +28,13 @@ public class StaticWebAppsClientPrincipal public IEnumerable? UserRoles { get; set; } } - public static ClaimsIdentity? Parse(HttpRequest req) + public static ClaimsIdentity? Parse(HttpContext context) { ClaimsIdentity? identity = null; StaticWebAppsClientPrincipal principal = new(); try { - if (req.Headers.TryGetValue(AuthenticationConfig.CLIENT_PRINCIPAL_HEADER, out StringValues header)) + if (context.Request.Headers.TryGetValue(AuthenticationConfig.CLIENT_PRINCIPAL_HEADER, out StringValues header)) { string data = header[0]; byte[] decoded = Convert.FromBase64String(data); diff --git a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs index f3b4424cbc..9d853354e9 100644 --- a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs +++ b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs @@ -127,7 +127,7 @@ private void GenerateDatabaseObjectForEntities() if (!sourceObjects.TryGetValue(entity.GetSourceName(), out DatabaseObject? sourceObject)) { // parse source name into a tuple of (schemaName, databaseObjectName) - (schemaName, dbObjectName) = ParseSchemaAndDbObjectName(entity.GetSourceName())!; + (schemaName, dbObjectName) = ParseDatabaseAndContainerName(entity.GetSourceName())!; sourceObject = new() { SchemaName = schemaName, @@ -153,7 +153,7 @@ private void GenerateDatabaseObjectForEntities() /// source string to parse /// /// - public (string, string) ParseSchemaAndDbObjectName(string source) + public (string, string) ParseDatabaseAndContainerName(string source) { (string? schemaName, string dbObjectName) = EntitySourceNamesParser.ParseSchemaAndTable(source)!; From c0f353eb237fae75938b129668ca1989757a0241 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Mon, 11 Jul 2022 16:00:54 -0400 Subject: [PATCH 06/10] Refactor code --- DataGateway.Config/Authentication.cs | 3 +-- .../EasyAuthAuthenticationBuilderExtensions.cs | 8 ++++---- DataGateway.Service/Startup.cs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/DataGateway.Config/Authentication.cs b/DataGateway.Config/Authentication.cs index c4cf1d3b32..46b7cd8af0 100644 --- a/DataGateway.Config/Authentication.cs +++ b/DataGateway.Config/Authentication.cs @@ -15,8 +15,7 @@ public record AuthenticationConfig( public const string CLIENT_PRINCIPAL_HEADER = "X-MS-CLIENT-PRINCIPAL"; public bool IsEasyAuthAuthenticationProvider() { - return Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(Provider, StringComparison.OrdinalIgnoreCase) - || Enum.GetName(EasyAuthType.AppService)!.Equals(Provider, StringComparison.OrdinalIgnoreCase); + return Enum.GetNames(typeof(EasyAuthType)).Any(x => x.Equals(Provider, StringComparison.OrdinalIgnoreCase)); } } diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs index 6061b49a74..1c66e0e720 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs @@ -18,7 +18,7 @@ public static class EasyAuthAuthenticationBuilderExtensions /// EasyAuth provider type. StaticWebApps or AppService /// The builder, to chain commands. public static AuthenticationBuilder AddEasyAuthAuthentication( - this AuthenticationBuilder builder, string easyAuthAuthenticationProvider) + this AuthenticationBuilder builder, EasyAuthType easyAuthAuthenticationProvider) { if (builder is null) { @@ -29,11 +29,11 @@ public static AuthenticationBuilder AddEasyAuthAuthentication( authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, options => { - if (Enum.GetName(EasyAuthType.StaticWebApps)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) - { + if (easyAuthAuthenticationProvider is EasyAuthType.StaticWebApps) + { options.EasyAuthProvider = EasyAuthType.StaticWebApps; } - else if (Enum.GetName(EasyAuthType.AppService)!.Equals(easyAuthAuthenticationProvider, StringComparison.OrdinalIgnoreCase)) + else if (easyAuthAuthenticationProvider is EasyAuthType.AppService) { options.EasyAuthProvider = EasyAuthType.AppService; } diff --git a/DataGateway.Service/Startup.cs b/DataGateway.Service/Startup.cs index 1f82e11958..21712b0819 100644 --- a/DataGateway.Service/Startup.cs +++ b/DataGateway.Service/Startup.cs @@ -321,7 +321,7 @@ private void ConfigureAuthentication(IServiceCollection services) runtimeConfig.IsEasyAuthAuthenticationProvider()) { services.AddAuthentication(EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME) - .AddEasyAuthAuthentication(runtimeConfig.AuthNConfig.Provider); + .AddEasyAuthAuthentication((EasyAuthType)Enum.Parse(typeof(EasyAuthType), runtimeConfig.AuthNConfig.Provider, true)); } } From 26cea5b5cf2bd621a4686100b4609538d30d1a71 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:05:59 -0400 Subject: [PATCH 07/10] EntityToDatabaseObject is not needed for cosmos, clean it up --- .../EasyAuthAuthenticationUnitTests.cs | 2 +- .../Authorization/AuthorizationHelpers.cs | 1 + .../Authorization/AuthorizationResolver.cs | 5 ++ .../CosmosSqlMetadataProvider.cs | 71 +------------------ 4 files changed, 8 insertions(+), 71 deletions(-) diff --git a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs index eb28bfbd54..857432dc2a 100644 --- a/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs +++ b/DataGateway.Service.Tests/Authentication/EasyAuthAuthenticationUnitTests.cs @@ -111,7 +111,7 @@ private static async Task CreateWebHostEasyAuth(EasyAuthType easyAuthType .ConfigureServices(services => { services.AddAuthentication(defaultScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME) - .AddEasyAuthAuthentication(easyAuthType.ToString()); + .AddEasyAuthAuthentication(easyAuthType); services.AddAuthorization(); }) diff --git a/DataGateway.Service.Tests/Authorization/AuthorizationHelpers.cs b/DataGateway.Service.Tests/Authorization/AuthorizationHelpers.cs index 457b725df6..252a3421b0 100644 --- a/DataGateway.Service.Tests/Authorization/AuthorizationHelpers.cs +++ b/DataGateway.Service.Tests/Authorization/AuthorizationHelpers.cs @@ -33,6 +33,7 @@ public static AuthorizationResolver InitAuthorizationResolver(RuntimeConfig runt Mock metadataProvider = new(); TableDefinition sampleTable = CreateSampleTable(); metadataProvider.Setup(x => x.GetTableDefinition(TEST_ENTITY)).Returns(sampleTable); + metadataProvider.Setup(x => x.GetDatabaseType()).Returns(DatabaseType.mssql); string outParam; Dictionary> _exposedNameToBackingColumnMapping = CreateColumnMappingTable(); diff --git a/DataGateway.Service/Authorization/AuthorizationResolver.cs b/DataGateway.Service/Authorization/AuthorizationResolver.cs index 416ff9e988..66aa55eea2 100644 --- a/DataGateway.Service/Authorization/AuthorizationResolver.cs +++ b/DataGateway.Service/Authorization/AuthorizationResolver.cs @@ -521,6 +521,11 @@ public IEnumerable GetRolesForField(string entityName, string actionName /// Collection of columns in table definition. private IEnumerable ResolveTableDefinitionColumns(string entityName) { + if (_metadataProvider.GetDatabaseType() is DatabaseType.cosmos) + { + return new List(); + } + return _metadataProvider.GetTableDefinition(entityName).Columns.Keys; } #endregion diff --git a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs index 9d853354e9..956df5a5b1 100644 --- a/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs +++ b/DataGateway.Service/Services/MetadataProviders/CosmosSqlMetadataProvider.cs @@ -97,83 +97,14 @@ string s when string.IsNullOrEmpty(s) && !string.IsNullOrEmpty(_cosmosDb.Contain public TableDefinition GetTableDefinition(string entityName) { - if (!EntityToDatabaseObject.TryGetValue(entityName, out DatabaseObject? databaseObject)) - { - throw new InvalidCastException($"Table Definition for {entityName} has not been inferred."); - } - - return databaseObject!.TableDefinition; + throw new NotSupportedException("Cosmos backends don't support direct table definitions. Definitions are provided via the GraphQL schema"); } public Task InitializeAsync() { - GenerateDatabaseObjectForEntities(); return Task.CompletedTask; } - /// - /// Create a DatabaseObject for all the exposed entities. - /// - private void GenerateDatabaseObjectForEntities() - { - string schemaName, dbObjectName; - Dictionary sourceObjects = new(); - foreach ((string entityName, Entity entity) - in _entities) - { - if (!EntityToDatabaseObject.ContainsKey(entityName)) - { - // Reuse the same Database object for multiple entities if they share the same source. - if (!sourceObjects.TryGetValue(entity.GetSourceName(), out DatabaseObject? sourceObject)) - { - // parse source name into a tuple of (schemaName, databaseObjectName) - (schemaName, dbObjectName) = ParseDatabaseAndContainerName(entity.GetSourceName())!; - sourceObject = new() - { - SchemaName = schemaName, - Name = dbObjectName, - TableDefinition = new() - }; - - sourceObjects.Add(entity.GetSourceName(), sourceObject); - } - - EntityToDatabaseObject.Add(entityName, sourceObject); - } - } - } - - /// - /// Helper function will parse the schema and database object name - /// from the provided source string and sort out if a default schema - /// should be used. It then returns the appropriate schema and - /// db object name as a tuple of strings. - /// i.e. source = 'graphqldb.planet' -> databaseName ='graphqldb'; containerName ='planet' - /// - /// source string to parse - /// - /// - public (string, string) ParseDatabaseAndContainerName(string source) - { - (string? schemaName, string dbObjectName) = EntitySourceNamesParser.ParseSchemaAndTable(source)!; - - if (string.IsNullOrEmpty(schemaName)) - { - throw new DataGatewayException(message: $"Missing database name for entity name: {source} in Config file for Cosmos", - statusCode: System.Net.HttpStatusCode.ServiceUnavailable, - subStatusCode: DataGatewayException.SubStatusCodes.ErrorInInitialization); - } - - if (string.IsNullOrEmpty(dbObjectName)) - { - throw new DataGatewayException(message: $"Missing container name for entity name: {source} in Config file for Cosmos", - statusCode: System.Net.HttpStatusCode.ServiceUnavailable, - subStatusCode: DataGatewayException.SubStatusCodes.ErrorInInitialization); - } - - return (schemaName, dbObjectName); - } - public string GraphQLSchema() { if (_cosmosDb.GraphQLSchema is null && _fileSystem.File.Exists(_cosmosDb.GraphQLSchemaPath)) From 28aec601323c235f5d41b13bffe6eafa90bae1a3 Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Mon, 11 Jul 2022 22:09:40 -0400 Subject: [PATCH 08/10] Fix format --- .../EasyAuthAuthenticationBuilderExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs index 1c66e0e720..2b9cec9230 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationBuilderExtensions.cs @@ -1,4 +1,3 @@ -using System; using Azure.DataGateway.Config; using Microsoft.AspNetCore.Authentication; @@ -28,9 +27,10 @@ public static AuthenticationBuilder AddEasyAuthAuthentication( builder.AddScheme( authenticationScheme: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, displayName: EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME, - options => { + options => + { if (easyAuthAuthenticationProvider is EasyAuthType.StaticWebApps) - { + { options.EasyAuthProvider = EasyAuthType.StaticWebApps; } else if (easyAuthAuthenticationProvider is EasyAuthType.AppService) From f386733754f6b4eab99166913ea5f719c7924aaf Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Tue, 12 Jul 2022 09:25:13 -0400 Subject: [PATCH 09/10] address pr comment --- .../AuthenticationHelpers/EasyAuthAuthenticationHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs index 7330b7b3d9..5a51cef584 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs @@ -55,7 +55,7 @@ protected override Task HandleAuthenticateAsync() if (identity is null) { - return Task.FromResult(AuthenticateResult.Fail(failureMessage: $"Invalid {Options.EasyAuthProvider.ToString()} EasyAuth token.")); + return Task.FromResult(AuthenticateResult.Fail(failureMessage: $"Invalid {Options.EasyAuthProvider} EasyAuth token.")); } ClaimsPrincipal? claimsPrincipal = new(identity); From 25fb6493ded6dced24319279aa7f07578387e24a Mon Sep 17 00:00:00 2001 From: tarazou9 <40870773+tarazou9@users.noreply.github.com> Date: Tue, 12 Jul 2022 10:20:34 -0400 Subject: [PATCH 10/10] pr comments --- .../AuthenticationHelpers/EasyAuthAuthenticationHandler.cs | 2 +- DataGateway.Service/Startup.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs index 5a51cef584..8ff5ee2eab 100644 --- a/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs +++ b/DataGateway.Service/AuthenticationHelpers/EasyAuthAuthenticationHandler.cs @@ -22,7 +22,7 @@ public class EasyAuthAuthenticationHandler : AuthenticationHandler - /// Static Web Apps authentication options. + /// Easy Auth authentication options. /// Logger factory. /// URL encoder. /// System clock. diff --git a/DataGateway.Service/Startup.cs b/DataGateway.Service/Startup.cs index c1d00fc2af..67a90fbdff 100644 --- a/DataGateway.Service/Startup.cs +++ b/DataGateway.Service/Startup.cs @@ -333,7 +333,7 @@ private void ConfigureAuthentication(IServiceCollection services) runtimeConfig.IsEasyAuthAuthenticationProvider()) { services.AddAuthentication(EasyAuthAuthenticationDefaults.AUTHENTICATIONSCHEME) - .AddEasyAuthAuthentication((EasyAuthType)Enum.Parse(typeof(EasyAuthType), runtimeConfig.AuthNConfig.Provider, true)); + .AddEasyAuthAuthentication((EasyAuthType)Enum.Parse(typeof(EasyAuthType), runtimeConfig.AuthNConfig.Provider, ignoreCase: true)); } }