diff --git a/Application/EdFi.Ods.Api/EdFi.Ods.Api.csproj b/Application/EdFi.Ods.Api/EdFi.Ods.Api.csproj index 79b8cc4563..55b6bfda3e 100644 --- a/Application/EdFi.Ods.Api/EdFi.Ods.Api.csproj +++ b/Application/EdFi.Ods.Api/EdFi.Ods.Api.csproj @@ -27,7 +27,7 @@ - + diff --git a/Application/EdFi.Ods.Tests/EdFi.Ods.Api/Caching/StubSecurityContext.cs b/Application/EdFi.Ods.Tests/EdFi.Ods.Api/Caching/StubSecurityContext.cs deleted file mode 100644 index 5a03eaebd1..0000000000 --- a/Application/EdFi.Ods.Tests/EdFi.Ods.Api/Caching/StubSecurityContext.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Licensed to the Ed-Fi Alliance under one or more agreements. -// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. -// See the LICENSE and NOTICES files in the project root for more information. - -using System.Collections.Generic; -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Linq; -using EdFi.Security.DataAccess.Contexts; -using EdFi.Security.DataAccess.Models; -using FakeItEasy; - -namespace EdFi.Ods.Tests.EdFi.Ods.Api.Caching -{ - public class SecurityContextMock - { - /// - /// Sets up a queryable fake security context with minimal data - /// - /// - public static ISecurityContext GetMockedSecurityContext() - { - var securityContext = A.Fake(); - // The underlying SecurityRepository implementation expects this application, so force it to be there in the fake - securityContext.Actions = GetFakeDbSet().SetupData(); - securityContext.ClaimSets = GetFakeDbSet().SetupData(); - securityContext.ResourceClaims = GetFakeDbSet().SetupData(); - securityContext.AuthorizationStrategies = GetFakeDbSet().SetupData(); - securityContext.ClaimSetResourceClaimActions = GetFakeDbSet().SetupData(); - securityContext.ResourceClaimActionAuthorizationStrategies = GetFakeDbSet().SetupData(); - securityContext.ResourceClaimActions = GetFakeDbSet().SetupData(); - - return securityContext; - } - - private static DbSet GetFakeDbSet() where T : class - { - return A.Fake>(o => o.Implements(typeof(IQueryable)).Implements(typeof(IDbAsyncEnumerable))); - } - } -} diff --git a/Application/EdFi.Security.DataAccess/Contexts/ISecurityContext.cs b/Application/EdFi.Security.DataAccess/Contexts/ISecurityContext.cs index 4f16b814dc..2a869cab4b 100644 --- a/Application/EdFi.Security.DataAccess/Contexts/ISecurityContext.cs +++ b/Application/EdFi.Security.DataAccess/Contexts/ISecurityContext.cs @@ -4,7 +4,8 @@ // See the LICENSE and NOTICES files in the project root for more information. using System; -using System.Data.Entity; +using System.Threading; +using Microsoft.EntityFrameworkCore; using System.Threading.Tasks; using EdFi.Security.DataAccess.Models; using Action = EdFi.Security.DataAccess.Models.Action; @@ -32,6 +33,6 @@ public interface ISecurityContext : IDisposable int SaveChanges(); - Task SaveChangesAsync(); + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/Application/EdFi.Security.DataAccess/Contexts/PostgresSecurityContext.cs b/Application/EdFi.Security.DataAccess/Contexts/PostgresSecurityContext.cs index 8ce7189311..d0aa4dc6a2 100644 --- a/Application/EdFi.Security.DataAccess/Contexts/PostgresSecurityContext.cs +++ b/Application/EdFi.Security.DataAccess/Contexts/PostgresSecurityContext.cs @@ -3,58 +3,22 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. -using System.Data.Entity; -using System.Data.Entity.Core.Metadata.Edm; -using System.Data.Entity.Infrastructure; -using System.Data.Entity.ModelConfiguration.Conventions; -using EdFi.Common; using EdFi.Common.Utils.Extensions; +using Microsoft.EntityFrameworkCore; namespace EdFi.Security.DataAccess.Contexts { public class PostgresSecurityContext : SecurityContext { - public PostgresSecurityContext(string connectionString) - : base(connectionString) { } + public PostgresSecurityContext(DbContextOptions options) + : base(options) { } - protected override void OnModelCreating(DbModelBuilder modelBuilder) + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.Conventions.Add(); - modelBuilder.Conventions.Add(); - - modelBuilder.Properties().Configure(c => c.HasColumnName(c.ClrPropertyInfo.Name.ToLowerInvariant())); - } - - private class TableLowerCaseNamingConvention : IStoreModelConvention - { - public void Apply(EntitySet entitySet, DbModel model) - { - Preconditions.ThrowIfNull(entitySet, nameof(entitySet)); - Preconditions.ThrowIfNull(model, nameof(model)); - - entitySet.Table = entitySet.Table.ToLowerInvariant(); - } - } - - private class ForeignKeyLowerCaseNamingConvention : IStoreModelConvention - { - public void Apply(AssociationType association, DbModel model) - { - Preconditions.ThrowIfNull(association, nameof(association)); - Preconditions.ThrowIfNull(model, nameof(model)); - - if (!association.IsForeignKey) - { - return; - } - - association.Constraint.FromProperties.ForEach(PropertyNamesToLowerInvariant); - association.Constraint.ToProperties.ForEach(PropertyNamesToLowerInvariant); - - void PropertyNamesToLowerInvariant(EdmProperty property) => property.Name = property.Name.ToLowerInvariant(); - } + modelBuilder.Model.GetEntityTypes().ForEach(entityType => + entityType.SetSchema("dbo")); } } } diff --git a/Application/EdFi.Security.DataAccess/Contexts/SecurityContext.cs b/Application/EdFi.Security.DataAccess/Contexts/SecurityContext.cs index a2c2a38898..c962cb9b26 100644 --- a/Application/EdFi.Security.DataAccess/Contexts/SecurityContext.cs +++ b/Application/EdFi.Security.DataAccess/Contexts/SecurityContext.cs @@ -3,20 +3,15 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. -using System.Data.Entity; +using Microsoft.EntityFrameworkCore; using EdFi.Security.DataAccess.Models; -using EdFi.Security.DataAccess.Utils; namespace EdFi.Security.DataAccess.Contexts { public abstract class SecurityContext : DbContext, ISecurityContext { - protected SecurityContext(string connectionString) - : base(connectionString) - { - Database.SetInitializer(new ValidateDatabase()); - Database.SetInitializer(new ValidateDatabase()); - } + protected SecurityContext(DbContextOptions options) + : base(options) { } public DbSet Actions { get; set; } @@ -33,13 +28,5 @@ protected SecurityContext(string connectionString) public DbSet ClaimSetResourceClaimActionAuthorizationStrategyOverrides { get; set; } public DbSet ResourceClaimActionAuthorizationStrategies { get; set; } - - protected override void OnModelCreating(DbModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasOptional(rc => rc.ParentResourceClaim) - .WithMany() - .HasForeignKey(fk => fk.ParentResourceClaimId); - } } } diff --git a/Application/EdFi.Security.DataAccess/Contexts/SecurityContextFactory.cs b/Application/EdFi.Security.DataAccess/Contexts/SecurityContextFactory.cs index c8dcc874f7..ee58e63120 100644 --- a/Application/EdFi.Security.DataAccess/Contexts/SecurityContextFactory.cs +++ b/Application/EdFi.Security.DataAccess/Contexts/SecurityContextFactory.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using EdFi.Common.Configuration; using EdFi.Security.DataAccess.Providers; +using Microsoft.EntityFrameworkCore; namespace EdFi.Security.DataAccess.Contexts { @@ -17,21 +18,49 @@ public class SecurityContextFactory : ISecurityContextFactory private readonly IDictionary _securityContextTypeByDatabaseEngine = new Dictionary { - {DatabaseEngine.SqlServer, typeof(SqlServerSecurityContext)}, - {DatabaseEngine.Postgres, typeof(PostgresSecurityContext)} + { DatabaseEngine.SqlServer, typeof(SqlServerSecurityContext) }, + { DatabaseEngine.Postgres, typeof(PostgresSecurityContext) } }; - public SecurityContextFactory(ISecurityDatabaseConnectionStringProvider connectionStringProvider, DatabaseEngine databaseEngine) + public SecurityContextFactory(ISecurityDatabaseConnectionStringProvider connectionStringProvider, + DatabaseEngine databaseEngine) { _connectionStringProvider = connectionStringProvider; _databaseEngine = databaseEngine; } - public ISecurityContext CreateContext() + public Type GetSecurityContextType() { if (_securityContextTypeByDatabaseEngine.TryGetValue(_databaseEngine, out Type contextType)) { - return Activator.CreateInstance(contextType, _connectionStringProvider.GetConnectionString()) as ISecurityContext; + return contextType; + } + + throw new InvalidOperationException( + $"No SecurityContext defined for database type {_databaseEngine.DisplayName}"); + } + + public ISecurityContext CreateContext() + { + if (_databaseEngine == DatabaseEngine.SqlServer) + { + return Activator.CreateInstance( + GetSecurityContextType(), + new DbContextOptionsBuilder() + .UseSqlServer(_connectionStringProvider.GetConnectionString()) + .Options) as + ISecurityContext; + } + + if (_databaseEngine == DatabaseEngine.Postgres) + { + return Activator.CreateInstance( + GetSecurityContextType(), + new DbContextOptionsBuilder() + .UseNpgsql(_connectionStringProvider.GetConnectionString()) + .UseLowerCaseNamingConvention() + .Options) as + ISecurityContext; } throw new InvalidOperationException( diff --git a/Application/EdFi.Security.DataAccess/Contexts/SqlServerSecurityContext.cs b/Application/EdFi.Security.DataAccess/Contexts/SqlServerSecurityContext.cs index 09d520a57a..a3318c48e2 100644 --- a/Application/EdFi.Security.DataAccess/Contexts/SqlServerSecurityContext.cs +++ b/Application/EdFi.Security.DataAccess/Contexts/SqlServerSecurityContext.cs @@ -3,11 +3,13 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. +using Microsoft.EntityFrameworkCore; + namespace EdFi.Security.DataAccess.Contexts { public class SqlServerSecurityContext : SecurityContext { // The default behavior is appropriate for this sub-class. - public SqlServerSecurityContext(string connectionString) : base(connectionString) { } + public SqlServerSecurityContext(DbContextOptions options) : base(options) { } } } diff --git a/Application/EdFi.Security.DataAccess/EdFi.Security.DataAccess.csproj b/Application/EdFi.Security.DataAccess/EdFi.Security.DataAccess.csproj index 5dd8879dff..3c294f9cb9 100644 --- a/Application/EdFi.Security.DataAccess/EdFi.Security.DataAccess.csproj +++ b/Application/EdFi.Security.DataAccess/EdFi.Security.DataAccess.csproj @@ -27,8 +27,10 @@ - + + + diff --git a/Application/EdFi.Security.DataAccess/Models/ClaimSetResourceClaimActionAuthorizationStrategyOverrides.cs b/Application/EdFi.Security.DataAccess/Models/ClaimSetResourceClaimActionAuthorizationStrategyOverrides.cs index b59a0c138f..a63bc0aa78 100644 --- a/Application/EdFi.Security.DataAccess/Models/ClaimSetResourceClaimActionAuthorizationStrategyOverrides.cs +++ b/Application/EdFi.Security.DataAccess/Models/ClaimSetResourceClaimActionAuthorizationStrategyOverrides.cs @@ -1,11 +1,15 @@ -using System; -using System.Collections.Generic; +// SPDX-License-Identifier: Apache-2.0 +// Licensed to the Ed-Fi Alliance under one or more agreements. +// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. +// See the LICENSE and NOTICES files in the project root for more information. + using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Text; +using Microsoft.EntityFrameworkCore; namespace EdFi.Security.DataAccess.Models { + [Index(nameof(ClaimSetResourceClaimActionId), nameof(AuthorizationStrategyId), IsUnique = true)] public class ClaimSetResourceClaimActionAuthorizationStrategyOverrides { [Key] @@ -15,14 +19,12 @@ public class ClaimSetResourceClaimActionAuthorizationStrategyOverrides public int ClaimSetResourceClaimActionId { get; set; } [Required] - [Index(IsUnique = true, Order = 1)] [ForeignKey("ClaimSetResourceClaimActionId")] public ClaimSetResourceClaimAction ClaimSetResourceClaimAction { get; set; } public int AuthorizationStrategyId { get; set; } [Required] - [Index(IsUnique = true, Order = 2)] [ForeignKey("AuthorizationStrategyId")] public AuthorizationStrategy AuthorizationStrategy { get; set; } } diff --git a/Application/EdFi.Security.DataAccess/Models/ResourceClaim.cs b/Application/EdFi.Security.DataAccess/Models/ResourceClaim.cs index dbf782ae06..6ef39e97d3 100644 --- a/Application/EdFi.Security.DataAccess/Models/ResourceClaim.cs +++ b/Application/EdFi.Security.DataAccess/Models/ResourceClaim.cs @@ -3,7 +3,6 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. -using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; diff --git a/Application/EdFi.Security.DataAccess/Models/ResourceClaimActionAuthorizationStrategies.cs b/Application/EdFi.Security.DataAccess/Models/ResourceClaimActionAuthorizationStrategies.cs index 9aa2f5576c..1e27767849 100644 --- a/Application/EdFi.Security.DataAccess/Models/ResourceClaimActionAuthorizationStrategies.cs +++ b/Application/EdFi.Security.DataAccess/Models/ResourceClaimActionAuthorizationStrategies.cs @@ -1,8 +1,10 @@ -using System; -using System.Collections.Generic; +// SPDX-License-Identifier: Apache-2.0 +// Licensed to the Ed-Fi Alliance under one or more agreements. +// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. +// See the LICENSE and NOTICES files in the project root for more information. + using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; -using System.Text; namespace EdFi.Security.DataAccess.Models { diff --git a/Application/EdFi.Security.DataAccess/Repositories/SecurityTableGateway.cs b/Application/EdFi.Security.DataAccess/Repositories/SecurityTableGateway.cs index f21d29045f..f941720222 100644 --- a/Application/EdFi.Security.DataAccess/Repositories/SecurityTableGateway.cs +++ b/Application/EdFi.Security.DataAccess/Repositories/SecurityTableGateway.cs @@ -5,11 +5,11 @@ using System; using System.Collections.Generic; -using System.Data.Entity; using System.Linq; using EdFi.Common.Utils.Extensions; using EdFi.Security.DataAccess.Contexts; using EdFi.Security.DataAccess.Models; +using Microsoft.EntityFrameworkCore; using Action = EdFi.Security.DataAccess.Models.Action; namespace EdFi.Security.DataAccess.Repositories; @@ -60,8 +60,9 @@ public List GetClaimSetResourceClaimActions() var claimSetResourceClaimActions = context.ClaimSetResourceClaimActions.Include(csrc => csrc.Action) .Include(csrc => csrc.ClaimSet) .Include(csrc => csrc.ResourceClaim) - .Include(csrc => csrc.AuthorizationStrategyOverrides.Select(aso => aso.AuthorizationStrategy)) - .ToList(); + .Include(csrc => csrc.AuthorizationStrategyOverrides) + .ThenInclude(aso => aso.AuthorizationStrategy) + .ToList(); // Replace empty lists with null since some consumers expect it that way claimSetResourceClaimActions.Where(csrc => csrc.AuthorizationStrategyOverrides.Count == 0) diff --git a/Application/EdFi.Security.DataAccess/Utils/ValidateDatabase.cs b/Application/EdFi.Security.DataAccess/Utils/ValidateDatabase.cs deleted file mode 100644 index 6f953cf3cf..0000000000 --- a/Application/EdFi.Security.DataAccess/Utils/ValidateDatabase.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Licensed to the Ed-Fi Alliance under one or more agreements. -// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. -// See the LICENSE and NOTICES files in the project root for more information. - -using System; -using System.Configuration; -using System.Data.Entity; -using System.Transactions; -using EdFi.Common; - -namespace EdFi.Security.DataAccess.Utils -{ - public class ValidateDatabase : IDatabaseInitializer - where TContext : DbContext - { - public void InitializeDatabase(TContext context) - { - Preconditions.ThrowIfNull(context, nameof(context)); - - using (new TransactionScope(TransactionScopeOption.Suppress)) - { - if (!context.Database.Exists()) - { - throw new ConfigurationErrorsException("Unable to open connection to the Security database."); - } - } - } - } -} diff --git a/tests/EdFi.Security.DataAccess.UnitTests/Contexts/SecurityContextFactoryTests.cs b/tests/EdFi.Security.DataAccess.UnitTests/Contexts/SecurityContextFactoryTests.cs index 5463c1d854..7140b368bd 100644 --- a/tests/EdFi.Security.DataAccess.UnitTests/Contexts/SecurityContextFactoryTests.cs +++ b/tests/EdFi.Security.DataAccess.UnitTests/Contexts/SecurityContextFactoryTests.cs @@ -22,12 +22,11 @@ public void Given_configured_for_SqlServer_then_create_SqlServerSecurityContext( var connectionstringProvider = A.Fake(); A.CallTo(() => connectionstringProvider.GetConnectionString()).Returns( - "Server=(local); Database=EdFi_Security; Trusted_Connection=True; Application Name=EdFi.Ods.WebApi;"); + "Server=(local); Database=EdFi_Security; Trusted_Connection=True; Application Name=EdFi.Ods.WebApi;"); new SecurityContextFactory(connectionstringProvider, DatabaseEngine.SqlServer) - .CreateContext() - .ShouldBeOfType() - .ShouldNotBeNull(); + .GetSecurityContextType() + .ShouldBe(typeof(SqlServerSecurityContext)); } [Test] @@ -39,9 +38,8 @@ public void Given_configured_for_Postgres_then_create_PostgresSecurityContext() "Host=localhost; Port=5432; Username=postgres; Database=EdFi_Security; Application Name=EdFi.Ods.WebApi;"); new SecurityContextFactory(connectionstringProvider, DatabaseEngine.Postgres) - .CreateContext() - .ShouldBeOfType() - .ShouldNotBeNull(); + .GetSecurityContextType() + .ShouldBe(typeof(PostgresSecurityContext)); } private class UnsupportedDatabaseEngine : DatabaseEngine diff --git a/tests/EdFi.Security.DataAccess.UnitTests/StubSecurityContext.cs b/tests/EdFi.Security.DataAccess.UnitTests/StubSecurityContext.cs deleted file mode 100644 index 2de2fd0fb5..0000000000 --- a/tests/EdFi.Security.DataAccess.UnitTests/StubSecurityContext.cs +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Licensed to the Ed-Fi Alliance under one or more agreements. -// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. -// See the LICENSE and NOTICES files in the project root for more information. - -using System.Collections.Generic; -using System.Data.Entity; -using System.Data.Entity.Infrastructure; -using System.Linq; -using EdFi.Security.DataAccess.Contexts; -using EdFi.Security.DataAccess.Models; -using FakeItEasy; - -namespace EdFi.Security.DataAccess.UnitTests -{ - public class SecurityContextMock - { - /// - /// Sets up a queryable fake security context with minimal data - /// - /// - public static ISecurityContext GetMockedSecurityContext() - { - var securityContext = A.Fake(); - // The underlying SecurityRepository implementation expects this application, so force it to be there in the fake - securityContext.Actions = GetFakeDbSet().SetupData(); - securityContext.AuthorizationStrategies = GetFakeDbSet().SetupData(); - securityContext.ClaimSets = GetFakeDbSet().SetupData(); - securityContext.ClaimSetResourceClaimActions = GetFakeDbSet().SetupData(); - securityContext.ResourceClaims = GetFakeDbSet().SetupData(); - securityContext.ResourceClaimActions = GetFakeDbSet().SetupData(); - securityContext.ClaimSetResourceClaimActionAuthorizationStrategyOverrides = GetFakeDbSet().SetupData(); - securityContext.ResourceClaimActionAuthorizationStrategies = GetFakeDbSet().SetupData(); - - return securityContext; - } - - private static DbSet GetFakeDbSet() where T : class - { - return A.Fake>(o => o.Implements(typeof(IQueryable)).Implements(typeof(IDbAsyncEnumerable))); - } - } -}