diff --git a/.github/workflows/api-e2e-mssql-multitenant.yml b/.github/workflows/api-e2e-mssql-multitenant.yml
index 1280b7c56..d4d097ae1 100644
--- a/.github/workflows/api-e2e-mssql-multitenant.yml
+++ b/.github/workflows/api-e2e-mssql-multitenant.yml
@@ -40,9 +40,6 @@ jobs:
mkdir ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi ../../Docker/Application
- - name: Copy admin console folder to docker context
- run: cp -r ../EdFi.Ods.AdminApi.AdminConsole ../../Docker/Application
-
- name: Copy admin api common folder to docker context
run: cp -r ../EdFi.Ods.AdminApi.Common ../../Docker/Application
diff --git a/.github/workflows/api-e2e-mssql-singletenant.yml b/.github/workflows/api-e2e-mssql-singletenant.yml
index 0756042dc..71deb318a 100644
--- a/.github/workflows/api-e2e-mssql-singletenant.yml
+++ b/.github/workflows/api-e2e-mssql-singletenant.yml
@@ -40,9 +40,6 @@ jobs:
mkdir ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi ../../Docker/Application
- - name: Copy admin console folder to docker context
- run: cp -r ../EdFi.Ods.AdminApi.AdminConsole ../../Docker/Application
-
- name: Copy admin api common folder to docker context
run: cp -r ../EdFi.Ods.AdminApi.Common ../../Docker/Application
diff --git a/.github/workflows/api-e2e-pgsql-multitenant.yml b/.github/workflows/api-e2e-pgsql-multitenant.yml
index bc6b1dafc..918835e10 100644
--- a/.github/workflows/api-e2e-pgsql-multitenant.yml
+++ b/.github/workflows/api-e2e-pgsql-multitenant.yml
@@ -40,9 +40,6 @@ jobs:
mkdir ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi ../../Docker/Application
- - name: Copy admin console folder to docker context
- run: cp -r ../EdFi.Ods.AdminApi.AdminConsole ../../Docker/Application
-
- name: Copy admin api common folder to docker context
run: cp -r ../EdFi.Ods.AdminApi.Common ../../Docker/Application
diff --git a/.github/workflows/api-e2e-pgsql-singletenant.yml b/.github/workflows/api-e2e-pgsql-singletenant.yml
index 91410a675..d6fd769ad 100644
--- a/.github/workflows/api-e2e-pgsql-singletenant.yml
+++ b/.github/workflows/api-e2e-pgsql-singletenant.yml
@@ -39,7 +39,6 @@ jobs:
run: |
mkdir ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi ../../Docker/Application
- cp -r ../EdFi.Ods.AdminApi.AdminConsole ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi.Common ../../Docker/Application
cp -r ../EdFi.Ods.AdminApi.V1 ../../Docker/Application
diff --git a/Application/Ed-Fi-ODS-AdminApi.sln b/Application/Ed-Fi-ODS-AdminApi.sln
index 0359a68df..8162d5eb7 100644
--- a/Application/Ed-Fi-ODS-AdminApi.sln
+++ b/Application/Ed-Fi-ODS-AdminApi.sln
@@ -20,8 +20,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IntegrationTests", "Integra
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdFi.Ods.AdminApi.DBTests", "EdFi.Ods.AdminApi.DBTests\EdFi.Ods.AdminApi.DBTests.csproj", "{73259EC2-4AA0-40C2-9C60-8AB1BF369CF5}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdFi.Ods.AdminApi.AdminConsole", "EdFi.Ods.AdminApi.AdminConsole\EdFi.Ods.AdminApi.AdminConsole.csproj", "{0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdFi.Ods.AdminApi.Common", "EdFi.Ods.AdminApi.Common\EdFi.Ods.AdminApi.Common.csproj", "{C9C86866-562B-4EA3-9AAC-F3297F0754D6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdFi.Ods.AdminApi.Common.UnitTests", "EdFi.Ods.AdminApi.Common.UnitTests\EdFi.Ods.AdminApi.Common.UnitTests.csproj", "{D0B566A2-000E-48FC-9CD0-702F7A00CA76}"
@@ -60,14 +58,6 @@ Global
{73259EC2-4AA0-40C2-9C60-8AB1BF369CF5}.Release|Any CPU.Build.0 = Release|Any CPU
{73259EC2-4AA0-40C2-9C60-8AB1BF369CF5}.Release|x64.ActiveCfg = Release|Any CPU
{73259EC2-4AA0-40C2-9C60-8AB1BF369CF5}.Release|x64.Build.0 = Release|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Debug|x64.ActiveCfg = Debug|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Debug|x64.Build.0 = Debug|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Release|Any CPU.Build.0 = Release|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Release|x64.ActiveCfg = Release|Any CPU
- {0F34C4F6-F7A2-442A-9E54-FCBD9A00F914}.Release|x64.Build.0 = Release|Any CPU
{C9C86866-562B-4EA3-9AAC-F3297F0754D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9C86866-562B-4EA3-9AAC-F3297F0754D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9C86866-562B-4EA3-9AAC-F3297F0754D6}.Debug|x64.ActiveCfg = Debug|Any CPU
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole.UnitTests/EdFi.Ods.AdminApi.AdminConsole.UnitTests.csproj b/Application/EdFi.Ods.AdminApi.AdminConsole.UnitTests/EdFi.Ods.AdminApi.AdminConsole.UnitTests.csproj
deleted file mode 100644
index 44e89119d..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole.UnitTests/EdFi.Ods.AdminApi.AdminConsole.UnitTests.csproj
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
- net8.0
- enable
- enable
-
- false
- true
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/EdFi.Ods.AdminApi.AdminConsole.csproj b/Application/EdFi.Ods.AdminApi.AdminConsole/EdFi.Ods.AdminApi.AdminConsole.csproj
deleted file mode 100644
index 624421407..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/EdFi.Ods.AdminApi.AdminConsole.csproj
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
- net8.0
- enable
- enable
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/ReadTenants.cs b/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/ReadTenants.cs
deleted file mode 100644
index 46a382f68..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/ReadTenants.cs
+++ /dev/null
@@ -1,41 +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 EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Services.Tenants;
-using EdFi.Ods.AdminApi.Common.Features;
-using EdFi.Ods.AdminApi.Common.Infrastructure;
-using EdFi.Ods.AdminApi.Common.Infrastructure.Security;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
-using Microsoft.Extensions.Caching.Memory;
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Features.Tenants;
-
-public class ReadTenants : IFeature
-{
- public void MapEndpoints(IEndpointRouteBuilder endpoints)
- {
- AdminApiEndpointBuilder.MapGet(endpoints, "/tenants", GetTenantsAsync)
- .BuildForVersions(AdminApiVersions.AdminConsole);
-
- AdminApiEndpointBuilder.MapGet(endpoints, "/tenants/{tenantId}", GetTenantsByTenantIdAsync)
- .BuildForVersions(AdminApiVersions.AdminConsole);
- }
-
- public static async Task GetTenantsAsync(IAdminConsoleTenantsService adminConsoleTenantsService, IMemoryCache memoryCache)
- {
- var tenants = await adminConsoleTenantsService.GetTenantsAsync(true);
- return Results.Ok(tenants);
- }
-
- public static async Task GetTenantsByTenantIdAsync(IAdminConsoleTenantsService adminConsoleTenantsService,
- IMemoryCache memoryCache, int tenantId)
- {
- var tenant = await adminConsoleTenantsService.GetTenantByTenantIdAsync(tenantId);
- if (tenant != null)
- return Results.Ok(tenant);
- return Results.NotFound();
- }
-}
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/TenantModel.cs b/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/TenantModel.cs
deleted file mode 100644
index c71568403..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Features/Tenants/TenantModel.cs
+++ /dev/null
@@ -1,19 +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.Dynamic;
-using EdFi.Ods.AdminApi.Common.Constants;
-using Swashbuckle.AspNetCore.Annotations;
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Features.Tenants;
-
-[SwaggerSchema]
-public class TenantModel
-{
- [SwaggerSchema(Description = AdminConsoleConstants.TenantIdDescription, Nullable = false)]
- public int TenantId { get; set; }
- [SwaggerSchema(Description = AdminConsoleConstants.TenantIdDescription, Nullable = false, Format = "{\r\n \"name\": \"Tenant1\",\r\n \"edfiApiDiscoveryUrl\": \"https://api.ed-fi.org/v7.2/api\"\r\n }")]
- public ExpandoObject? Document { get; set; }
-}
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Helper/AdminConsoleFeatureHelper.cs b/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Helper/AdminConsoleFeatureHelper.cs
deleted file mode 100644
index 0bdd78898..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Helper/AdminConsoleFeatureHelper.cs
+++ /dev/null
@@ -1,15 +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.Reflection;
-using EdFi.Ods.AdminApi.Common.Features;
-using EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Helper;
-
-public static class AdminConsoleFeatureHelper
-{
- public static List GetFeatures() => FeatureHelper.GetFeatures(Assembly.GetExecutingAssembly());
-}
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/IMarkerForEdFiAdminConsoleManagement.cs b/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/IMarkerForEdFiAdminConsoleManagement.cs
deleted file mode 100644
index 8b4dfe60d..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/IMarkerForEdFiAdminConsoleManagement.cs
+++ /dev/null
@@ -1,11 +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.
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Infrastructure;
-
-public interface IMarkerForEdFiAdminConsoleManagement
-{
-}
-
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/TenantBackgroundService.cs b/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/TenantBackgroundService.cs
deleted file mode 100644
index 79c7ef230..000000000
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/TenantBackgroundService.cs
+++ /dev/null
@@ -1,56 +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 EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Services.Tenants;
-using EdFi.Ods.AdminApi.Common.Settings;
-using log4net;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Options;
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Services;
-
-public class TenantBackgroundService : BackgroundService
-{
- private readonly IDisposable _optionsChangedListener;
- private static readonly ILog _log = LogManager.GetLogger(typeof(TenantService));
- private readonly IServiceScopeFactory _serviceScopeFactory;
- public TenantBackgroundService(IOptionsMonitor optionsMonitor, IServiceScopeFactory serviceScopeFactory)
- {
- _serviceScopeFactory = serviceScopeFactory;
- _optionsChangedListener = optionsMonitor.OnChange(async (opt) => await OnAppSettingsChangedAsync())!;
- }
-
- private async Task OnAppSettingsChangedAsync()
- {
- using IServiceScope scope = _serviceScopeFactory.CreateScope();
- _log.Info("The appsettings file has been modified");
-
- IAdminConsoleTenantsService scopedProcessingService =
- scope.ServiceProvider.GetRequiredService();
-
- await scopedProcessingService.InitializeTenantsAsync();
- }
-
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- while (!stoppingToken.IsCancellationRequested)
- {
- await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
- }
- }
-
- public override async Task StopAsync(CancellationToken cancellationToken)
- {
- _log.Info("Stopping background");
- await base.StopAsync(cancellationToken);
- }
-
- public override void Dispose()
- {
- _optionsChangedListener.Dispose();
- base.Dispose();
- }
-}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleConstants.cs b/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleConstants.cs
deleted file mode 100644
index ba0eee5eb..000000000
--- a/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleConstants.cs
+++ /dev/null
@@ -1,24 +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.
-
-namespace EdFi.Ods.AdminApi.Common.Constants;
-
-public static class AdminConsoleConstants
-{
- public const string AdminConsoleSettingsKey = "AdminConsoleSettings";
-
-
- public const string EnableCorsKey = "CorsSettings:EnableCors";
-
- public const string AllowedOriginsCorsKey = "CorsSettings:AllowedOrigins";
-
- public const string CorsPolicyName = "allowAllCorsPolicyName";
-
- public const string TenantsCacheKey = "adminconsole.tenants";
-
- public const string TenantIdDescription = "Admin API Tenant Id";
-
- public const string TenantDocumentDescription = "Tenant Document as JSON object";
-}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleValidationConstants.cs b/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleValidationConstants.cs
deleted file mode 100644
index f61c16aaf..000000000
--- a/Application/EdFi.Ods.AdminApi.Common/Constants/AdminConsoleValidationConstants.cs
+++ /dev/null
@@ -1,20 +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.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EdFi.Ods.AdminApi.Common.Constants;
-
-public static class AdminConsoleValidationConstants
-{
- public const string OdsIntanceIdIsNotValid = "The instance id is not valid.";
- public const string OdsIntanceIdStatusIsNotCompleted = "The instance cannot be deleted because it is not in a COMPLETED status.";
- public const string OdsInstanceIdStatusIsNotPendingDelete = "The instance status is invalid; it is not marked as 'Pending Delete'.";
- public const string OdsIntanceIdStatusIsPendingRename = "The instance cannot be set rename failed because it is not in a PENDING_RENAME status.";
-}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Constants/Constants.cs b/Application/EdFi.Ods.AdminApi.Common/Constants/Constants.cs
index c1c34507b..36c03fb1a 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Constants/Constants.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Constants/Constants.cs
@@ -5,6 +5,14 @@
namespace EdFi.Ods.AdminApi.Common.Constants;
+public class Constants
+{
+ public const string TenantsCacheKey = "tenants";
+ public const string TenantNameDescription = "Admin API Tenant Name";
+ public const string TenantConnectionStringDescription = "Tenant connection strings";
+ public const string DefaultTenantName = "default";
+}
+
public enum AdminApiMode
{
V2,
diff --git a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/AdminApiVersions.cs b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/AdminApiVersions.cs
index 5f0d5d7ac..c00e4bf76 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/AdminApiVersions.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/AdminApiVersions.cs
@@ -17,7 +17,6 @@ public class AdminApiVersions
public static readonly AdminApiVersion V1 = new(1.1, "v1");
public static readonly AdminApiVersion V2 = new(2.0, "v2");
- public static readonly AdminApiVersion AdminConsole = new(1.0, "adminconsole");
private static ApiVersionSet? _versionSet;
public static void Initialize(WebApplication app)
@@ -25,21 +24,11 @@ public static void Initialize(WebApplication app)
if (_isInitialized)
throw new InvalidOperationException("Versions are already initialized");
- if (app.Configuration.GetValue("AppSettings:EnableAdminConsoleAPI"))
- {
- _versionSet = app.NewApiVersionSet()
- .HasApiVersion(V1.Version)
- .HasApiVersion(V2.Version)
- .HasApiVersion(AdminConsole.Version)
- .Build();
- }
- else
- {
- _versionSet = app.NewApiVersionSet()
- .HasApiVersion(V1.Version)
- .HasApiVersion(V2.Version)
- .Build();
- }
+ _versionSet = app.NewApiVersionSet()
+ .HasApiVersion(V1.Version)
+ .HasApiVersion(V2.Version)
+ .Build();
+
_isInitialized = true;
}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/Helpers/ConnectionStringHelper.cs b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/Helpers/ConnectionStringHelper.cs
index 5fc0b06e0..b9ba02d5c 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/Helpers/ConnectionStringHelper.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/Helpers/ConnectionStringHelper.cs
@@ -46,4 +46,39 @@ ex is FormatException ||
}
return result;
}
+
+ public static (string? Host, string? Database) GetHostAndDatabase(string databaseEngine, string? connectionString)
+ {
+ if (databaseEngine.Equals(DatabaseEngineEnum.SqlServer, StringComparison.InvariantCultureIgnoreCase))
+ {
+ try
+ {
+ var builder = new SqlConnectionStringBuilder(connectionString);
+ return (builder.DataSource, builder.InitialCatalog);
+ }
+ catch (Exception ex) when (
+ ex is ArgumentException or
+ FormatException or
+ KeyNotFoundException)
+ {
+ _log.Error(ex);
+ return (null, null);
+ }
+ }
+ else if (databaseEngine.Equals(DatabaseEngineEnum.PostgreSql, StringComparison.InvariantCultureIgnoreCase))
+ {
+ try
+ {
+ var builder = new NpgsqlConnectionStringBuilder(connectionString);
+ return (builder.Host, builder.Database);
+ }
+ catch (ArgumentException ex)
+ {
+ _log.Error(ex);
+ return (null, null);
+ }
+ }
+
+ return (null, null);
+ }
}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/MultiTenancy/TenantIdentificationMiddleware.cs b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/MultiTenancy/TenantIdentificationMiddleware.cs
index fb9d28a7e..83ddfd1f3 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Infrastructure/MultiTenancy/TenantIdentificationMiddleware.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Infrastructure/MultiTenancy/TenantIdentificationMiddleware.cs
@@ -80,18 +80,11 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
}
else
{
- if (_options.Value.EnableAdminConsoleAPI)
+ if (!context.Request.Path.Value!.Contains("adminconsole/tenants") &&
+ context.Request.Method != "GET" &&
+ !context.Request.Path.Value.Contains("health", StringComparison.InvariantCultureIgnoreCase))
{
- if (!context.Request.Path.Value!.Contains("adminconsole/tenants") &&
- context.Request.Method != "GET" &&
- !context.Request.Path.Value.Contains("health", StringComparison.InvariantCultureIgnoreCase))
- {
- ThrowTenantValidationError("Tenant header is missing (adminconsole)");
- }
- }
- else if (!NonFeatureEndpoints())
- {
- ThrowTenantValidationError("Tenant header is missing");
+ ThrowTenantValidationError("Tenant header is missing (adminconsole)");
}
}
}
@@ -101,13 +94,6 @@ bool RequestFromSwagger() => (context.Request.Path.Value != null &&
context.Request.Path.Value.Contains("swagger", StringComparison.InvariantCultureIgnoreCase)) ||
context.Request.Headers.Referer.FirstOrDefault(x => x != null && x.ToLower().Contains("swagger", StringComparison.InvariantCultureIgnoreCase)) != null;
- bool NonFeatureEndpoints() => context.Request.Path.Value != null &&
- (context.Request.Path.Value.Contains("health", StringComparison.InvariantCultureIgnoreCase)
- || context.Request.Path.Value.Equals("/")
- || context.Request.Path.Value.Contains("connect", StringComparison.InvariantCultureIgnoreCase)
- || (context.Request.PathBase.HasValue && !context.Request.Path.HasValue)
- || (context.Request.Path.StartsWithSegments(new PathString("/.well-known"))));
-
void ThrowTenantValidationError(string errorMessage)
{
throw new ValidationException([new ValidationFailure("Tenant", errorMessage)]);
diff --git a/Application/EdFi.Ods.AdminApi.Common/Settings/AdminConsoleSettings.cs b/Application/EdFi.Ods.AdminApi.Common/Settings/AdminConsoleSettings.cs
deleted file mode 100644
index 3c2487ad8..000000000
--- a/Application/EdFi.Ods.AdminApi.Common/Settings/AdminConsoleSettings.cs
+++ /dev/null
@@ -1,14 +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.
-
-namespace EdFi.Ods.AdminApi.Common.Settings;
-
-public class AdminConsoleSettings : IEncryptionKeySettings
-{
- public CorsSettings CorsSettings { get; set; } = new CorsSettings();
- public string EncryptionKey { get; set; } = string.Empty;
- public string VendorCompany { get; set; } = string.Empty;
- public string ApplicationName { get; set; } = string.Empty;
-}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Settings/AppSettings.cs b/Application/EdFi.Ods.AdminApi.Common/Settings/AppSettings.cs
index c94148a75..635140d3c 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Settings/AppSettings.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Settings/AppSettings.cs
@@ -1,38 +1,35 @@
-// 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.
-
+// 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.
+
namespace EdFi.Ods.AdminApi.Common.Settings;
public class AppSettingsFile
{
public required AppSettings AppSettings { get; set; }
public required SwaggerSettings SwaggerSettings { get; set; }
- public required AdminConsoleSettings AdminConsoleSettings { get; set; }
- public string? EdFiApiDiscoveryUrl { get; set; }
- public string[] ConnectionStrings { get; set; } = [];
+ public Dictionary ConnectionStrings { get; set; } = [];
public Dictionary Tenants { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public required TestingSettings Testing { get; set; }
}
-
-public class AppSettings
-{
- public int DefaultPageSizeOffset { get; set; }
- public int DefaultPageSizeLimit { get; set; }
+
+public class AppSettings
+{
+ public int DefaultPageSizeOffset { get; set; }
+ public int DefaultPageSizeLimit { get; set; }
public string? DatabaseEngine { get; set; }
- public string? EncryptionKey { get; set; }
- public bool MultiTenancy { get; set; }
- public bool PreventDuplicateApplications { get; set; }
- public bool EnableAdminConsoleAPI { get; set; }
- public bool EnableApplicationResetEndpoint { get; set; }
+ public string? EncryptionKey { get; set; }
+ public bool MultiTenancy { get; set; }
+ public bool PreventDuplicateApplications { get; set; }
+ public bool EnableApplicationResetEndpoint { get; set; }
public string? AdminApiMode { get; set; }
-}
-
-public class SwaggerSettings
-{
- public bool EnableSwagger { get; set; }
- public string? DefaultTenant { get; set; }
+}
+
+public class SwaggerSettings
+{
+ public bool EnableSwagger { get; set; }
+ public string? DefaultTenant { get; set; }
}
@@ -53,4 +50,4 @@ public class GeneralRule
public string Period { get; set; } = string.Empty;
public int Limit { get; set; }
}
-}
+}
diff --git a/Application/EdFi.Ods.AdminApi.Common/Settings/TenantSettings.cs b/Application/EdFi.Ods.AdminApi.Common/Settings/TenantSettings.cs
index e0595875f..284a4758a 100644
--- a/Application/EdFi.Ods.AdminApi.Common/Settings/TenantSettings.cs
+++ b/Application/EdFi.Ods.AdminApi.Common/Settings/TenantSettings.cs
@@ -12,7 +12,5 @@ public class TenantsSection
public class TenantSettings
{
- public Dictionary ConnectionStrings { get; set; } = new(StringComparer.OrdinalIgnoreCase);
-
- public string EdFiApiDiscoveryUrl { get; set; } = string.Empty;
-}
+ public Dictionary ConnectionStrings { get; set; } = new(StringComparer.OrdinalIgnoreCase);
+}
diff --git a/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/AddTenantCommandTests.cs b/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/AddTenantCommandTests.cs
new file mode 100644
index 000000000..2653c6ee1
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/AddTenantCommandTests.cs
@@ -0,0 +1,132 @@
+// 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.IO;
+using System.Text.Json;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+using NUnit.Framework;
+using Shouldly;
+
+namespace EdFi.Ods.AdminApi.DBTests.Database.CommandTests;
+
+[TestFixture]
+public class AddTenantCommandTests
+{
+ private string _testFilePath;
+
+ private static string GetTestFilePath(string testName)
+ {
+ var dir = Path.Combine(Path.GetTempPath(), "AdminApiTests");
+ Directory.CreateDirectory(dir);
+ return Path.Combine(dir, $"{testName}_{Guid.NewGuid()}.json");
+ }
+
+ private void CopyTestSettings(string destPath, string sourceFile = "testsappsettings.json")
+ {
+ var sourcePath = Path.Combine(TestContext.CurrentContext.TestDirectory, sourceFile);
+ File.Copy(sourcePath, destPath, true);
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ if (!string.IsNullOrEmpty(_testFilePath) && File.Exists(_testFilePath))
+ {
+ File.Delete(_testFilePath);
+ }
+ }
+
+ [Test]
+ public void ShouldThrowWhenAppSettingsIsEmpty()
+ {
+ _testFilePath = GetTestFilePath(nameof(ShouldThrowWhenAppSettingsIsEmpty));
+ File.WriteAllText(_testFilePath, string.Empty);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new AddTenantCommand(provider);
+
+ var model = new TestAddTenantModel("newtenant", "sec", "adm");
+
+ var ex = Should.Throw(() => command.Execute(model));
+ ex.Message.ShouldBe("appsettings.json contains invalid JSON.");
+ }
+
+ [Test]
+ public void ShouldThrowWhenTenantsSectionMissing()
+ {
+ _testFilePath = GetTestFilePath(nameof(ShouldThrowWhenTenantsSectionMissing));
+ var json = @"{ ""ConnectionStrings"": { ""EdFi_Admin"": ""a"", ""EdFi_Security"": ""b"" } }";
+ File.WriteAllText(_testFilePath, json);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new AddTenantCommand(provider);
+
+ var model = new TestAddTenantModel("newtenant", "sec", "adm");
+
+ var ex = Should.Throw(() => command.Execute(model));
+ ex.Message.ShouldBe("Tenants section missing in appsettings.json.");
+ }
+
+ [Test]
+ public void ShouldThrowWhenTenantAlreadyExists()
+ {
+ _testFilePath = GetTestFilePath(nameof(ShouldThrowWhenTenantAlreadyExists));
+ CopyTestSettings(_testFilePath);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new AddTenantCommand(provider);
+
+ var model = new TestAddTenantModel("tenant1", "sec", "adm");
+
+ var ex = Should.Throw(() => command.Execute(model));
+ ex.Message.ShouldBe("Tenant 'tenant1' already exists.");
+ }
+
+ [Test]
+ public void ShouldThrowWhenAppSettingsContainsInvalidJson()
+ {
+ _testFilePath = GetTestFilePath(nameof(ShouldThrowWhenAppSettingsContainsInvalidJson));
+ File.WriteAllText(_testFilePath, "{ invalid json }");
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new AddTenantCommand(provider);
+
+ var model = new TestAddTenantModel("newtenant", "sec", "adm");
+
+ var ex = Should.Throw(() => command.Execute(model));
+ ex.Message.ShouldBe("appsettings.json contains invalid JSON.");
+ }
+
+ [Test]
+ public void ShouldAddNewTenantWhenValid()
+ {
+ _testFilePath = GetTestFilePath(nameof(ShouldAddNewTenantWhenValid));
+ CopyTestSettings(_testFilePath);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new AddTenantCommand(provider);
+
+ var model = new TestAddTenantModel("newtenant", "sec-conn", "adm-conn");
+
+ command.Execute(model);
+
+ var updatedJson = File.ReadAllText(_testFilePath);
+ using var doc = JsonDocument.Parse(updatedJson);
+ var tenants = doc.RootElement.GetProperty("Tenants");
+ tenants.TryGetProperty("newtenant", out var newTenant).ShouldBeTrue();
+ var connStrings = newTenant.GetProperty("ConnectionStrings");
+ connStrings.GetProperty("EdFi_Security").GetString().ShouldBe("sec-conn");
+ connStrings.GetProperty("EdFi_Admin").GetString().ShouldBe("adm-conn");
+ }
+
+ private class TestAddTenantModel(string tenantName, string sec, string adm) : IAddTenantModel
+ {
+ public string TenantName { get; } = tenantName;
+ public string EdFiSecurityConnectionString { get; } = sec;
+ public string EdFiAdminConnectionString { get; } = adm;
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/DeleteTenantCommandTests.cs b/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/DeleteTenantCommandTests.cs
new file mode 100644
index 000000000..84e42267d
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.DBTests/Database/CommandTests/DeleteTenantCommandTests.cs
@@ -0,0 +1,113 @@
+// 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.IO;
+using System.Text.Json;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+using NUnit.Framework;
+using Shouldly;
+
+namespace EdFi.Ods.AdminApi.DBTests.Database.CommandTests;
+
+[TestFixture]
+public class DeleteTenantCommandTests
+{
+ private string _testFilePath = string.Empty;
+
+ private static string GetTestFilePath(string testName)
+ {
+ var dir = Path.Combine(Path.GetTempPath(), "AdminApiTests");
+ Directory.CreateDirectory(dir);
+ return Path.Combine(dir, $"{testName}_{Guid.NewGuid()}.json");
+ }
+
+ private void CopyTestSettings(string destPath, string sourceFile = "testsappsettings.json")
+ {
+ var sourcePath = Path.Combine(TestContext.CurrentContext.TestDirectory, sourceFile);
+ File.Copy(sourcePath, destPath, true);
+ }
+
+ [TearDown]
+ public void Cleanup()
+ {
+ if (_testFilePath is not null && File.Exists(_testFilePath))
+ {
+ File.Delete(_testFilePath);
+ }
+ }
+
+ [Test]
+ public void Should_throw_when_appsettings_is_empty()
+ {
+ _testFilePath = GetTestFilePath(nameof(Should_throw_when_appsettings_is_empty));
+ File.WriteAllText(_testFilePath, string.Empty);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new DeleteTenantCommand(provider);
+
+ var ex = Should.Throw(() => command.Execute("tenant1"));
+ ex.Message.ShouldBe("appsettings.json contains invalid JSON.");
+ }
+
+ [Test]
+ public void Should_throw_when_tenants_section_missing()
+ {
+ _testFilePath = GetTestFilePath(nameof(Should_throw_when_tenants_section_missing));
+ var json = @"{ ""ConnectionStrings"": { ""EdFi_Admin"": ""a"", ""EdFi_Security"": ""b"" } }";
+ File.WriteAllText(_testFilePath, json);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new DeleteTenantCommand(provider);
+
+ var ex = Should.Throw(() => command.Execute("tenant1"));
+ ex.Message.ShouldBe("Tenants section missing in appsettings.json.");
+ }
+
+ [Test]
+ public void Should_throw_when_tenant_does_not_exist()
+ {
+ _testFilePath = GetTestFilePath(nameof(Should_throw_when_tenant_does_not_exist));
+ CopyTestSettings(_testFilePath);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new DeleteTenantCommand(provider);
+
+ var ex = Should.Throw(() => command.Execute("notarealtenant"));
+ ex.Message.ShouldBe("Tenant 'notarealtenant' does not exist.");
+ }
+
+ [Test]
+ public void Should_throw_when_appsettings_contains_invalid_json()
+ {
+ _testFilePath = GetTestFilePath(nameof(Should_throw_when_appsettings_contains_invalid_json));
+ File.WriteAllText(_testFilePath, "{ invalid json }");
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new DeleteTenantCommand(provider);
+
+ var ex = Should.Throw(() => command.Execute("tenant1"));
+ ex.Message.ShouldBe("appsettings.json contains invalid JSON.");
+ }
+
+ [Test]
+ public void Should_delete_tenant_when_valid()
+ {
+ _testFilePath = GetTestFilePath(nameof(Should_delete_tenant_when_valid));
+ CopyTestSettings(_testFilePath);
+
+ var provider = new FileSystemAppSettingsFileProvider(_testFilePath);
+ var command = new DeleteTenantCommand(provider);
+
+ command.Execute("tenant1");
+
+ var updatedJson = File.ReadAllText(_testFilePath);
+ using var doc = JsonDocument.Parse(updatedJson);
+ var tenants = doc.RootElement.GetProperty("Tenants");
+ tenants.TryGetProperty("tenant1", out _).ShouldBeFalse();
+ tenants.TryGetProperty("tenant2", out _).ShouldBeTrue();
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.DBTests/EdFi.Ods.AdminApi.DBTests.csproj b/Application/EdFi.Ods.AdminApi.DBTests/EdFi.Ods.AdminApi.DBTests.csproj
index 33a6a7f49..2feef5fbe 100644
--- a/Application/EdFi.Ods.AdminApi.DBTests/EdFi.Ods.AdminApi.DBTests.csproj
+++ b/Application/EdFi.Ods.AdminApi.DBTests/EdFi.Ods.AdminApi.DBTests.csproj
@@ -31,6 +31,9 @@
+
+ Always
+
Always
diff --git a/Application/EdFi.Ods.AdminApi.DBTests/appsettings.json b/Application/EdFi.Ods.AdminApi.DBTests/appsettings.json
index 25bce9ed3..e42caf77b 100644
--- a/Application/EdFi.Ods.AdminApi.DBTests/appsettings.json
+++ b/Application/EdFi.Ods.AdminApi.DBTests/appsettings.json
@@ -10,5 +10,19 @@
"ConnectionStrings": {
"EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
"EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ },
+ "Tenants": {
+ "tenant1": {
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ }
+ },
+ "tenant2": {
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ }
+ }
}
}
diff --git a/Application/EdFi.Ods.AdminApi.DBTests/testsappsettings.json b/Application/EdFi.Ods.AdminApi.DBTests/testsappsettings.json
new file mode 100644
index 000000000..a32758504
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.DBTests/testsappsettings.json
@@ -0,0 +1,20 @@
+{
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ },
+ "Tenants": {
+ "tenant1": {
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ }
+ },
+ "tenant2": {
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=localhost;Initial Catalog=EdFi_Admin_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=localhost;Initial Catalog=EdFi_Security_Test;Integrated Security=False;User Id=sa;Password=P@55w0rd;Encrypt=true;Trusted_Connection=false;TrustServerCertificate=True"
+ }
+ }
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.UnitTests/EdFi.Ods.AdminApi.UnitTests.csproj b/Application/EdFi.Ods.AdminApi.UnitTests/EdFi.Ods.AdminApi.UnitTests.csproj
index 6363b7252..35363105e 100644
--- a/Application/EdFi.Ods.AdminApi.UnitTests/EdFi.Ods.AdminApi.UnitTests.csproj
+++ b/Application/EdFi.Ods.AdminApi.UnitTests/EdFi.Ods.AdminApi.UnitTests.csproj
@@ -15,7 +15,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantTests.cs b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantTests.cs
new file mode 100644
index 000000000..db9aeb241
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantTests.cs
@@ -0,0 +1,111 @@
+// 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.Threading.Tasks;
+using AutoMapper;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Features.Tenants;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using FakeItEasy;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using NUnit.Framework;
+using Shouldly;
+
+namespace EdFi.Ods.AdminApi.UnitTests.Features.Tenants;
+
+[TestFixture]
+public class AddTenantTests
+{
+ [Test]
+ public async Task Handle_ReturnsBadRequest_WhenNotMultiTenant()
+ {
+ var options = A.Fake>();
+ A.CallTo(() => options.Value).Returns(new AppSettings { MultiTenancy = false, DatabaseEngine = "Postgres" });
+
+ var addTenantCommand = new TestAddTenantCommand();
+ var mapper = A.Fake();
+ var request = new AddTenant.AddTenantRequest { TenantName = "tenant1" };
+ var serviceScopeFactory = A.Fake();
+ var tenantsService = A.Fake();
+ var scope = A.Fake();
+ var scopedServiceProvider = A.Fake();
+
+ A.CallTo(() => scopedServiceProvider.GetService(typeof(ITenantsService))).Returns(tenantsService);
+
+ A.CallTo(() => serviceScopeFactory.CreateScope()).Returns(scope);
+ A.CallTo(() => scope.ServiceProvider).Returns(scopedServiceProvider);
+
+ var validator = new TestValidator(tenantsService, options);
+
+ var result = await AddTenant.Handle(validator, addTenantCommand, mapper, request, serviceScopeFactory, options);
+
+ // Assert
+ var badRequest = result as IResult;
+ badRequest.ShouldNotBeNull();
+ badRequest.GetType().Name.ShouldStartWith("BadRequest");
+ var valueProperty = badRequest.GetType().GetProperty("Value");
+ valueProperty.ShouldNotBeNull();
+ var value = valueProperty.GetValue(badRequest);
+ value.ShouldNotBeNull();
+ value.ToString().ShouldContain("Not multitenant environment.");
+ }
+
+ [Test]
+ public async Task Handle_CallsValidatorAndCommand_AndReturnsCreated_WhenValid()
+ {
+ var options = A.Fake>();
+ A.CallTo(() => options.Value).Returns(new AppSettings { MultiTenancy = true, DatabaseEngine = "Postgres" });
+
+ var addTenantCommand = new TestAddTenantCommand();
+ var mapper = A.Fake();
+ var model = A.Fake();
+ var request = new AddTenant.AddTenantRequest { TenantName = "tenant2" };
+ var serviceScopeFactory = A.Fake();
+ var tenantsService = A.Fake();
+ var scope = A.Fake();
+ var scopedServiceProvider = A.Fake();
+
+ A.CallTo(() => scopedServiceProvider.GetService(typeof(ITenantsService))).Returns(tenantsService);
+ A.CallTo(() => mapper.Map(request)).Returns(model);
+
+ A.CallTo(() => serviceScopeFactory.CreateScope()).Returns(scope);
+ A.CallTo(() => scope.ServiceProvider).Returns(scopedServiceProvider);
+
+ var validator = new TestValidator(tenantsService, options);
+
+ var result = await AddTenant.Handle(validator, addTenantCommand, mapper, request, serviceScopeFactory, options);
+
+ result.ShouldNotBeNull();
+ result.GetType().Name.ShouldBe("Created");
+ }
+
+ private class TestValidator(ITenantsService service, IOptions options) : AddTenant.Validator(service, options)
+ {
+ public Task GuardAsync(AddTenant.AddTenantRequest request)
+ {
+ // Always succeed
+ return Task.CompletedTask;
+ }
+ }
+
+ private class TestAddTenantCommand : AddTenantCommand
+ {
+ public string ExecutedTenantName { get; private set; }
+
+ public TestAddTenantCommand() : base(A.Fake())
+ {
+ }
+
+ public override void Execute(IAddTenantModel model)
+ {
+ ExecutedTenantName = model.TenantName;
+ }
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantValidatorTests.cs b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantValidatorTests.cs
new file mode 100644
index 000000000..4252d3eb4
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/AddTenantValidatorTests.cs
@@ -0,0 +1,129 @@
+// 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.Linq;
+using System.Threading.Tasks;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Features.Tenants;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using FakeItEasy;
+using Microsoft.Extensions.Options;
+using NUnit.Framework;
+using Shouldly;
+
+namespace EdFi.Ods.AdminApi.UnitTests.Features.Tenants;
+
+[TestFixture]
+public class AddTenantValidatorTests
+{
+ private AddTenant.Validator _validator;
+ private ITenantsService _tenantsService;
+ private IOptions _options;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _tenantsService = A.Fake();
+ _options = Options.Create(new AppSettings { DatabaseEngine = "SqlServer" });
+ _validator = new AddTenant.Validator(_tenantsService, _options);
+ }
+
+ [Test]
+ public void Should_Have_Error_When_TenantName_Is_Empty()
+ {
+ var model = new AddTenant.AddTenantRequest { TenantName = string.Empty };
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.TenantName)).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Should_Have_Error_When_TenantName_Exceeds_Max_Length()
+ {
+ var model = new AddTenant.AddTenantRequest { TenantName = new string('A', 101) };
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.TenantName)).ShouldBeTrue();
+ }
+
+ [Test]
+ public async Task Should_Have_Error_When_TenantName_Is_Not_Unique()
+ {
+ var existingTenants = new List
+ {
+ new() { TenantName = "ExistingTenant" }
+ };
+ A.CallTo(() => _tenantsService.GetTenantsAsync(true)).Returns(Task.FromResult(existingTenants));
+
+ var model = new AddTenant.AddTenantRequest { TenantName = "ExistingTenant" };
+ var result = await _validator.ValidateAsync(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.TenantName)).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Should_Have_Error_When_EdFiAdminConnectionString_Exceeds_Max_Length()
+ {
+ var model = new AddTenant.AddTenantRequest
+ {
+ TenantName = "UniqueTenant",
+ EdFiAdminConnectionString = new string('C', 501)
+ };
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.EdFiAdminConnectionString)).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Should_Have_Error_When_EdFiAdminConnectionString_Is_Invalid()
+ {
+ var model = new AddTenant.AddTenantRequest
+ {
+ TenantName = "UniqueTenant",
+ EdFiAdminConnectionString = "invalid-connection-string"
+ };
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.EdFiAdminConnectionString)).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Should_Have_Error_When_EdFiSecurityConnectionString_Exceeds_Max_Length()
+ {
+ var model = new AddTenant.AddTenantRequest
+ {
+ TenantName = "UniqueTenant",
+ EdFiSecurityConnectionString = new string('C', 501)
+ };
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.EdFiSecurityConnectionString)).ShouldBeTrue();
+ }
+
+ [Test]
+ public void Should_Have_Error_When_EdFiSecurityConnectionString_Is_Invalid()
+ {
+ var model = new AddTenant.AddTenantRequest
+ {
+ TenantName = "UniqueTenant",
+ EdFiSecurityConnectionString = "invalid-connection-string"
+ };
+
+ var result = _validator.Validate(model);
+ result.Errors.Any(x => x.PropertyName == nameof(model.EdFiSecurityConnectionString)).ShouldBeTrue();
+ }
+
+ [Test]
+ public async Task Should_Not_Have_Error_For_Valid_Model()
+ {
+ // Setup empty tenants list to ensure uniqueness check passes
+ A.CallTo(() => _tenantsService.GetTenantsAsync(true)).Returns(Task.FromResult(new List()));
+
+ var model = new AddTenant.AddTenantRequest
+ {
+ TenantName = "UniqueTenant",
+ EdFiAdminConnectionString = "Server=.;Database=Admin;Trusted_Connection=True;",
+ EdFiSecurityConnectionString = "Server=.;Database=Security;Trusted_Connection=True;"
+ };
+
+ var result = await _validator.ValidateAsync(model);
+ result.IsValid.ShouldBeTrue();
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/DeleteTenantTests.cs b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/DeleteTenantTests.cs
new file mode 100644
index 000000000..4173a0bcd
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi.UnitTests/Features/Tenants/DeleteTenantTests.cs
@@ -0,0 +1,153 @@
+// 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.Threading.Tasks;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Features.Tenants;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using FakeItEasy;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using NUnit.Framework;
+using Shouldly;
+
+namespace EdFi.Ods.AdminApi.UnitTests.Features.Tenants;
+
+[TestFixture]
+public class DeleteTenantTests
+{
+ private ITenantsService _tenantsService = null!;
+ private DeleteTenantCommand _deleteTenantCommand = null!;
+ private IServiceScopeFactory _serviceScopeFactory = null!;
+ private IOptions _options = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _tenantsService = A.Fake();
+ _deleteTenantCommand = A.Fake();
+ _serviceScopeFactory = A.Fake();
+ _options = Options.Create(new AppSettings { MultiTenancy = true });
+ }
+
+ [Test]
+ public async Task Handle_ShouldReturnBadRequest_WhenMultiTenancyIsDisabled()
+ {
+ // Arrange
+ var options = Options.Create(new AppSettings { MultiTenancy = false });
+
+ // Act
+ var result = await DeleteTenant.Handle(
+ _tenantsService,
+ _deleteTenantCommand,
+ _serviceScopeFactory,
+ options,
+ "tenant1");
+
+ // Assert
+ var badRequest = result as IResult;
+ badRequest.ShouldNotBeNull();
+
+ badRequest.GetType().Name.ShouldStartWith("BadRequest");
+ var valueProperty = badRequest.GetType().GetProperty("Value");
+ valueProperty.ShouldNotBeNull();
+ var value = valueProperty.GetValue(badRequest);
+ value.ShouldNotBeNull();
+ value.ToString().ShouldContain("Not multitenant environment.");
+ }
+
+ [Test]
+ public async Task Handle_ShouldReturnNotFound_WhenTenantDoesNotExist()
+ {
+ // Arrange
+ A.CallTo(() => _tenantsService.GetTenantByTenantIdAsync(A._)).Returns(Task.FromResult(null));
+
+ // Act
+ var result = await DeleteTenant.Handle(
+ _tenantsService,
+ _deleteTenantCommand,
+ _serviceScopeFactory,
+ _options,
+ "tenant1");
+
+ // Assert
+ var notFound = result as IResult;
+ notFound.ShouldNotBeNull();
+ var httpResult = notFound as Microsoft.AspNetCore.Http.HttpResults.NotFound;
+ httpResult.ShouldNotBeNull();
+ }
+
+ [Test]
+ public async Task Handle_ShouldDeleteTenantAndReturnOk_WhenTenantExists()
+ {
+ // Arrange
+ var tenant = new TenantModel { TenantName = "tenant1" };
+ A.CallTo(() => _tenantsService.GetTenantByTenantIdAsync("tenant1")).Returns(Task.FromResult(tenant));
+
+ var scope = A.Fake();
+ var scopedServiceProvider = A.Fake();
+ var scopedTenantsService = A.Fake();
+
+ A.CallTo(() => _serviceScopeFactory.CreateScope()).Returns(scope);
+ A.CallTo(() => scope.ServiceProvider).Returns(scopedServiceProvider);
+ A.CallTo(() => scopedServiceProvider.GetService(typeof(ITenantsService))).Returns(scopedTenantsService);
+ A.CallTo(() => scopedTenantsService.InitializeTenantsAsync()).Returns(Task.CompletedTask);
+
+ // Use a test double for DeleteTenantCommand
+ var testDeleteTenantCommand = new TestDeleteTenantCommand();
+
+ // Act
+ var result = await DeleteTenant.Handle(
+ _tenantsService,
+ testDeleteTenantCommand,
+ _serviceScopeFactory,
+ _options,
+ "tenant1");
+
+ // Assert
+ testDeleteTenantCommand.ExecutedTenantName.ShouldBe("tenant1");
+ A.CallTo(() => scopedTenantsService.InitializeTenantsAsync()).MustHaveHappened();
+
+ var okResult = result as IResult;
+ okResult.ShouldNotBeNull();
+ var httpResult = okResult as Microsoft.AspNetCore.Http.HttpResults.Ok
-
diff --git a/Application/EdFi.Ods.AdminApi/Features/FeatureConstants.cs b/Application/EdFi.Ods.AdminApi/Features/FeatureConstants.cs
index a37f568a1..4148a618c 100644
--- a/Application/EdFi.Ods.AdminApi/Features/FeatureConstants.cs
+++ b/Application/EdFi.Ods.AdminApi/Features/FeatureConstants.cs
@@ -28,6 +28,7 @@ public static class FeatureConstants
public const string EdOrgIdsValidationMessage = "Please provide at least one education organization id.";
public const string VendorIdValidationMessage = "Please provide valid vendor id.";
public const string ClaimSetAlreadyExistsMessage = "A claim set with this name already exists in the database. Please enter a unique name.";
+ public const string TenantAlreadyExistsMessage = "A tenant with this name already exists. Please enter a unique name.";
public const string ClaimSetNameMaxLengthMessage = "The claim set name must be less than 255 characters.";
public const string ClaimSetNotFound = "No such claim set exists in the database.";
public const string InvalidResourceClaimActions = "Please provide a valid resourceClaimActions object.";
@@ -48,6 +49,7 @@ public static class FeatureConstants
public const string OdsInstanceDerivativeCombinedKeyMustBeUnique = "The combined key ODS instance id and derivative type must be unique.";
public const string OdsInstanceContextCombinedKeyMustBeUnique = "The combined key ODS instance id and context key must be unique.";
public const string OdsInstanceConnectionStringInvalid = "The connection string is not valid.";
+ public const string TenantConnectionStringInvalid = "The connection string is not valid.";
public const string OdsInstanceDerivativeIdDescription = "ODS instance derivative id.";
public const string OdsInstanceDerivativeOdsInstanceIdDescription = "ODS instance derivative ODS instance id.";
public const string OdsInstanceDerivativeDerivativeTypeDescription = "derivative type.";
diff --git a/Application/EdFi.Ods.AdminApi/Features/Tenants/AddTenant.cs b/Application/EdFi.Ods.AdminApi/Features/Tenants/AddTenant.cs
new file mode 100644
index 000000000..bdab671a9
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Features/Tenants/AddTenant.cs
@@ -0,0 +1,116 @@
+// 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 AutoMapper;
+using EdFi.Ods.AdminApi.Common.Features;
+using EdFi.Ods.AdminApi.Common.Infrastructure;
+using EdFi.Ods.AdminApi.Common.Infrastructure.ErrorHandling;
+using EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using FluentValidation;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Swashbuckle.AspNetCore.Annotations;
+
+namespace EdFi.Ods.AdminApi.Features.Tenants;
+
+public class AddTenant : IFeature
+{
+ public void MapEndpoints(IEndpointRouteBuilder endpoints)
+ {
+ AdminApiEndpointBuilder
+ .MapPost(endpoints, "/tenants", Handle)
+ .WithDefaultSummaryAndDescription()
+ .WithRouteOptions(b => b.WithResponseCode(201))
+ .BuildForVersions(AdminApiVersions.V2);
+ }
+
+ public static async Task Handle(
+ Validator validator,
+ AddTenantCommand addTenantCommand,
+ IMapper mapper,
+ AddTenantRequest request,
+ IServiceScopeFactory serviceScopeFactory,
+ IOptions options)
+ {
+ if (!options.Value.MultiTenancy)
+ return Results.BadRequest(new { message = "Not multitenant environment." });
+
+ await validator.GuardAsync(request);
+
+ var model = mapper.Map(request);
+ addTenantCommand.Execute(model);
+
+ await InitializeTenantsAsync(serviceScopeFactory);
+
+ return Results.Created($"/tenants/{request.TenantName}", null);
+ }
+
+ [SwaggerSchema(Title = "AddTenantRequest")]
+ public class AddTenantRequest : IAddTenantModel
+ {
+ [SwaggerSchema(Description = "The unique name of the tenant.", Nullable = false)]
+ public string TenantName { get; set; } = string.Empty;
+
+ [SwaggerSchema(Description = "The connection string for EdFi_Security.", Nullable = true)]
+ public string? EdFiSecurityConnectionString { get; set; }
+
+ [SwaggerSchema(Description = "The connection string for EdFi_Admin.", Nullable = true)]
+ public string? EdFiAdminConnectionString { get; set; }
+ }
+
+ public class Validator : AbstractValidator
+ {
+ private readonly ITenantsService _tenantsService;
+ private readonly string _databaseEngine;
+
+ public Validator([FromServices] ITenantsService tenantsService, IOptions options)
+ {
+ _tenantsService = tenantsService;
+ _databaseEngine = options.Value.DatabaseEngine ?? throw new NotFoundException("AppSettings", "DatabaseEngine");
+
+ RuleFor(x => x.TenantName)
+ .NotEmpty()
+ .MaximumLength(100)
+ .Must(BeAUniqueName)
+ .WithMessage(FeatureConstants.TenantAlreadyExistsMessage);
+
+ RuleFor(m => m.EdFiAdminConnectionString)
+ .MaximumLength(500)
+ .Must(BeAValidConnectionString)
+ .WithMessage(FeatureConstants.TenantConnectionStringInvalid)
+ .When(m => !string.IsNullOrEmpty(m.EdFiAdminConnectionString));
+
+ RuleFor(m => m.EdFiSecurityConnectionString)
+ .MaximumLength(500)
+ .Must(BeAValidConnectionString)
+ .WithMessage(FeatureConstants.TenantConnectionStringInvalid)
+ .When(m => !string.IsNullOrEmpty(m.EdFiSecurityConnectionString));
+ }
+
+ private bool BeAUniqueName(string name)
+ {
+ var tenants = _tenantsService.GetTenantsAsync(true).Result;
+ return tenants.TrueForAll(x => x.TenantName != name);
+ }
+
+ private bool BeAValidConnectionString(string? connectionString)
+ {
+ return ConnectionStringHelper.ValidateConnectionString(_databaseEngine, connectionString);
+ }
+ }
+
+ private static async Task InitializeTenantsAsync(IServiceScopeFactory serviceScopeFactory)
+ {
+ using IServiceScope scope = serviceScopeFactory.CreateScope();
+
+ ITenantsService scopedProcessingService =
+ scope.ServiceProvider.GetRequiredService();
+
+ await scopedProcessingService.InitializeTenantsAsync();
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Features/Tenants/DeleteTenant.cs b/Application/EdFi.Ods.AdminApi/Features/Tenants/DeleteTenant.cs
new file mode 100644
index 000000000..586c2670a
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Features/Tenants/DeleteTenant.cs
@@ -0,0 +1,66 @@
+// 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 EdFi.Ods.AdminApi.Common.Features;
+using EdFi.Ods.AdminApi.Common.Infrastructure;
+using EdFi.Ods.AdminApi.Common.Infrastructure.Extensions;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using Swashbuckle.AspNetCore.Annotations;
+
+namespace EdFi.Ods.AdminApi.Features.Tenants;
+
+public class DeleteTenant : IFeature
+{
+ public void MapEndpoints(IEndpointRouteBuilder endpoints)
+ {
+ AdminApiEndpointBuilder
+ .MapDelete(endpoints, "/tenants/{tenantName}", Handle)
+ .WithDefaultSummaryAndDescription()
+ .WithRouteOptions(b => b.WithResponseCode(200, FeatureCommonConstants.DeletedSuccessResponseDescription))
+ .BuildForVersions(AdminApiVersions.V2);
+ }
+
+ public static async Task Handle(
+ [FromServices] ITenantsService iTenantsService,
+ DeleteTenantCommand deleteTenantCommand,
+ IServiceScopeFactory serviceScopeFactory,
+ IOptions options,
+ string tenantName)
+ {
+ if (!options.Value.MultiTenancy)
+ return Results.BadRequest(new { message = "Not multitenant environment." });
+
+ var tenant = await iTenantsService.GetTenantByTenantIdAsync(tenantName);
+ if (tenant == null)
+ return Results.NotFound();
+
+ deleteTenantCommand.Execute(tenantName);
+
+ await InitializeTenantsAsync(serviceScopeFactory);
+
+ return Results.Ok("Tenant".ToJsonObjectResponseDeleted());
+ }
+
+ [SwaggerSchema(Title = "DeleteTenantRequest")]
+ public class DeleteTenantRequest
+ {
+ [SwaggerSchema(Description = "The unique name of the tenant to delete.", Nullable = false)]
+ public string TenantName { get; set; } = string.Empty;
+ }
+
+ private static async Task InitializeTenantsAsync(IServiceScopeFactory serviceScopeFactory)
+ {
+ using IServiceScope scope = serviceScopeFactory.CreateScope();
+
+ ITenantsService scopedProcessingService =
+ scope.ServiceProvider.GetRequiredService();
+
+ await scopedProcessingService.InitializeTenantsAsync();
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Features/Tenants/ReadTenants.cs b/Application/EdFi.Ods.AdminApi/Features/Tenants/ReadTenants.cs
new file mode 100644
index 000000000..4258b03f5
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Features/Tenants/ReadTenants.cs
@@ -0,0 +1,101 @@
+// 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 EdFi.Ods.AdminApi.Common.Features;
+using EdFi.Ods.AdminApi.Common.Infrastructure;
+using EdFi.Ods.AdminApi.Common.Infrastructure.ErrorHandling;
+using EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+
+namespace EdFi.Ods.AdminApi.Features.Tenants;
+
+public class ReadTenants : IFeature
+{
+ public void MapEndpoints(IEndpointRouteBuilder endpoints)
+ {
+ AdminApiEndpointBuilder.MapGet(endpoints, "/tenants", GetTenantsAsync)
+ .BuildForVersions(AdminApiVersions.V2);
+
+ AdminApiEndpointBuilder.MapGet(endpoints, "/tenants/{tenantName}", GetTenantsByTenantIdAsync)
+ .BuildForVersions(AdminApiVersions.V2);
+ }
+
+ public static async Task GetTenantsAsync([FromServices] ITenantsService tenantsService, IMemoryCache memoryCache, IOptions options)
+ {
+ var _databaseEngine = options.Value.DatabaseEngine ?? throw new NotFoundException("AppSettings", "DatabaseEngine");
+
+ var tenants = await tenantsService.GetTenantsAsync(true);
+
+ var response = tenants
+ .Select(t =>
+ {
+ var adminHostAndDatabase = ConnectionStringHelper.GetHostAndDatabase(_databaseEngine, t.ConnectionStrings.EdFiAdminConnectionString);
+ var securityHostAndDatabase = ConnectionStringHelper.GetHostAndDatabase(_databaseEngine, t.ConnectionStrings.EdFiSecurityConnectionString);
+
+ return new TenantsResponse
+ {
+ TenantName = t.TenantName,
+ AdminConnectionString = new EdfiConnectionString()
+ {
+ host = adminHostAndDatabase.Host,
+ database = adminHostAndDatabase.Database
+ },
+ SecurityConnectionString = new EdfiConnectionString()
+ {
+ host = securityHostAndDatabase.Host,
+ database = securityHostAndDatabase.Database
+ }
+ };
+ })
+ .ToList();
+
+ return Results.Ok(response);
+ }
+
+ public static async Task GetTenantsByTenantIdAsync([FromServices] ITenantsService tenantsService,
+ IMemoryCache memoryCache, string tenantName, IOptions options)
+ {
+ var _databaseEngine = options.Value.DatabaseEngine ?? throw new NotFoundException("AppSettings", "DatabaseEngine");
+
+ var tenant = await tenantsService.GetTenantByTenantIdAsync(tenantName);
+ if (tenant == null)
+ return Results.NotFound();
+
+ var adminHostAndDatabase = ConnectionStringHelper.GetHostAndDatabase(_databaseEngine, tenant.ConnectionStrings.EdFiAdminConnectionString);
+ var securityHostAndDatabase = ConnectionStringHelper.GetHostAndDatabase(_databaseEngine, tenant.ConnectionStrings.EdFiSecurityConnectionString);
+
+ return Results.Ok(new TenantsResponse
+ {
+ TenantName = tenant.TenantName,
+ AdminConnectionString = new EdfiConnectionString()
+ {
+ host = adminHostAndDatabase.Host,
+ database = adminHostAndDatabase.Database
+ },
+ SecurityConnectionString = new EdfiConnectionString()
+ {
+ host = securityHostAndDatabase.Host,
+ database = securityHostAndDatabase.Database
+ }
+ });
+ }
+}
+
+public class TenantsResponse
+{
+ public string? TenantName { get; set; }
+ public EdfiConnectionString? AdminConnectionString { get; set; }
+ public EdfiConnectionString? SecurityConnectionString { get; set; }
+}
+
+public class EdfiConnectionString
+{
+ public string? host { get; set; }
+ public string? database { get; set; }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Features/Tenants/TenantModel.cs b/Application/EdFi.Ods.AdminApi/Features/Tenants/TenantModel.cs
new file mode 100644
index 000000000..0e9ee8dd5
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Features/Tenants/TenantModel.cs
@@ -0,0 +1,38 @@
+// 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 EdFi.Ods.AdminApi.Common.Constants;
+using Swashbuckle.AspNetCore.Annotations;
+
+namespace EdFi.Ods.AdminApi.Features.Tenants;
+
+[SwaggerSchema]
+public class TenantModel
+{
+ [SwaggerSchema(Description = Constants.TenantNameDescription, Nullable = false)]
+ public required string TenantName { get; set; }
+
+ [SwaggerSchema(Description = Constants.TenantConnectionStringDescription, Nullable = false)]
+ public TenantModelConnectionStrings ConnectionStrings { get; set; } = new();
+}
+
+[SwaggerSchema]
+public class TenantModelConnectionStrings
+{
+ public string EdFiSecurityConnectionString { get; set; }
+ public string EdFiAdminConnectionString { get; set; }
+
+ public TenantModelConnectionStrings()
+ {
+ EdFiAdminConnectionString = string.Empty;
+ EdFiSecurityConnectionString = string.Empty;
+ }
+
+ public TenantModelConnectionStrings(string edFiAdminConnectionString, string edFiSecurityConnectionString)
+ {
+ EdFiAdminConnectionString = edFiAdminConnectionString;
+ EdFiSecurityConnectionString = edFiSecurityConnectionString;
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/AddTenantCommand.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/AddTenantCommand.cs
new file mode 100644
index 000000000..deb15ee54
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/AddTenantCommand.cs
@@ -0,0 +1,63 @@
+// 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.Text.Json;
+using System.Text.Json.Nodes;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+
+namespace EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+
+public class AddTenantCommand(IAppSettingsFileProvider fileProvider)
+{
+ private static readonly object _fileLock = new();
+ private readonly IAppSettingsFileProvider _fileProvider = fileProvider;
+
+ public virtual void Execute(IAddTenantModel model)
+ {
+ lock (_fileLock)
+ {
+ var json = _fileProvider.ReadAllText();
+ try
+ {
+ var root = JsonNode.Parse(json) ?? throw new InvalidOperationException("appsettings.json is empty or invalid.");
+
+ var tenantsNode = root["Tenants"] as JsonObject ?? throw new InvalidOperationException("Tenants section missing in appsettings.json.");
+
+ if (tenantsNode.ContainsKey(model.TenantName))
+ {
+ throw new InvalidOperationException($"Tenant '{model.TenantName}' already exists.");
+ }
+
+ var tenantObj = new JsonObject
+ {
+ ["ConnectionStrings"] = new JsonObject
+ {
+ ["EdFi_Security"] = model.EdFiSecurityConnectionString,
+ ["EdFi_Admin"] = model.EdFiAdminConnectionString
+ }
+ };
+
+ tenantsNode[model.TenantName] = tenantObj;
+
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ var updatedJson = root.ToJsonString(options);
+
+ _fileProvider.WriteAllText(updatedJson);
+ }
+ catch (JsonException ex)
+ {
+ throw new InvalidOperationException("appsettings.json contains invalid JSON.", ex);
+ }
+ }
+ }
+
+}
+
+public interface IAddTenantModel
+{
+ public string TenantName { get; }
+ public string? EdFiSecurityConnectionString { get; }
+ public string? EdFiAdminConnectionString { get; }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/DeleteTenantCommand.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/DeleteTenantCommand.cs
new file mode 100644
index 000000000..a20166d76
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/Database/Commands/DeleteTenantCommand.cs
@@ -0,0 +1,52 @@
+// 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.Text.Json;
+using System.Text.Json.Nodes;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
+
+namespace EdFi.Ods.AdminApi.Infrastructure.Database.Commands;
+
+public class DeleteTenantCommand(IAppSettingsFileProvider fileProvider)
+{
+ private static readonly object _fileLock = new();
+
+ private readonly IAppSettingsFileProvider _fileProvider = fileProvider;
+
+ public virtual void Execute(string tenantName)
+ {
+ lock (_fileLock)
+ {
+ var json = _fileProvider.ReadAllText();
+ try
+ {
+ var root = JsonNode.Parse(json) ?? throw new InvalidOperationException("appsettings.json is empty or invalid.");
+
+ var tenantsNode = root["Tenants"] as JsonObject ?? throw new InvalidOperationException("Tenants section missing in appsettings.json.");
+
+ if (!tenantsNode.ContainsKey(tenantName))
+ {
+ throw new InvalidOperationException($"Tenant '{tenantName}' does not exist.");
+ }
+
+ tenantsNode.Remove(tenantName);
+
+ var options = new JsonSerializerOptions { WriteIndented = true };
+ var updatedJson = root.ToJsonString(options);
+
+ _fileProvider.WriteAllText(updatedJson);
+ }
+ catch (JsonException ex)
+ {
+ throw new InvalidOperationException("appsettings.json contains invalid JSON.", ex);
+ }
+ }
+ }
+}
+
+public interface IDeleteTenantModel
+{
+ public string TenantName { get; set; }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/FileSystemAppSettingsFileProvider.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/FileSystemAppSettingsFileProvider.cs
new file mode 100644
index 000000000..199e10cd0
--- /dev/null
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/Helpers/FileSystemAppSettingsFileProvider.cs
@@ -0,0 +1,25 @@
+// 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.
+
+namespace EdFi.Ods.AdminApi.Infrastructure.Helpers;
+
+public interface IAppSettingsFileProvider
+{
+ string ReadAllText();
+ void WriteAllText(string content);
+}
+
+public class FileSystemAppSettingsFileProvider(string filePath) : IAppSettingsFileProvider
+{
+ public string ReadAllText()
+ {
+ return File.ReadAllText(filePath);
+ }
+
+ public void WriteAllText(string content)
+ {
+ File.WriteAllText(filePath, content);
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/Tenants/TenantService.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/Services/Tenants/TenantService.cs
similarity index 54%
rename from Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/Tenants/TenantService.cs
rename to Application/EdFi.Ods.AdminApi/Infrastructure/Services/Tenants/TenantService.cs
index 4b122bfa8..afe214091 100644
--- a/Application/EdFi.Ods.AdminApi.AdminConsole/Infrastructure/Services/Tenants/TenantService.cs
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/Services/Tenants/TenantService.cs
@@ -1,103 +1,109 @@
-// 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.Dynamic;
-using EdFi.Ods.AdminApi.AdminConsole.Features.Tenants;
-using EdFi.Ods.AdminApi.Common.Constants;
-using EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;
-using EdFi.Ods.AdminApi.Common.Settings;
-using log4net;
-using Microsoft.Extensions.Caching.Memory;
-using Microsoft.Extensions.Options;
-
-namespace EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Services.Tenants;
-
-public interface IAdminConsoleTenantsService
-{
- Task InitializeTenantsAsync();
- Task> GetTenantsAsync(bool fromCache = false);
- Task GetTenantByTenantIdAsync(int tenantId);
-}
-
-public class TenantService(IOptionsSnapshot options,
- IMemoryCache memoryCache) : IAdminConsoleTenantsService
-{
- private const string ADMIN_DB_KEY = "EdFi_Admin";
- protected AppSettingsFile _appSettings = options.Value;
- private readonly IMemoryCache _memoryCache = memoryCache;
- private static readonly ILog _log = LogManager.GetLogger(typeof(TenantService));
-
- public async Task InitializeTenantsAsync()
- {
- var tenants = await GetTenantsAsync();
- //store it in memorycache
- await Task.FromResult(_memoryCache.Set(AdminConsoleConstants.TenantsCacheKey, tenants));
- }
-
- public async Task> GetTenantsAsync(bool fromCache = false)
- {
- List results;
-
- if (fromCache)
- {
- results = await GetTenantsFromCacheAsync();
- if (results.Count > 0)
- {
- return results;
- }
- }
-
- results = [];
- //check multitenancy
- if (_appSettings.AppSettings.MultiTenancy)
- {
- var ordinalId = 1;
- foreach (var tenantConfig in _appSettings.Tenants)
- {
- var connectionString = tenantConfig.Value.ConnectionStrings.First(p => p.Key == ADMIN_DB_KEY).Value;
- if (!ConnectionStringHelper.ValidateConnectionString(_appSettings.AppSettings.DatabaseEngine!, connectionString))
- {
- _log.WarnFormat("Tenant {Key} has an invalid connection string for database {ADMIN_DB_KEY}. Database engine is {engine}",
- tenantConfig.Key, ADMIN_DB_KEY, _appSettings.AppSettings.DatabaseEngine);
- }
- dynamic document = new ExpandoObject();
- document.edfiApiDiscoveryUrl = tenantConfig.Value.EdFiApiDiscoveryUrl;
- document.name = tenantConfig.Key;
- results.Add(new TenantModel()
- {
- TenantId = ordinalId,
- Document = document,
- });
- ordinalId++;
- }
- }
- else
- {
- dynamic document = new ExpandoObject();
- document.edfiApiDiscoveryUrl = _appSettings.EdFiApiDiscoveryUrl;
- document.name = "default";
- results.Add(new TenantModel()
- {
- TenantId = 1,
- Document = document,
- });
- }
- return results;
- }
-
- public async Task GetTenantByTenantIdAsync(int tenantId)
- {
- var tenants = await GetTenantsAsync();
- var tenant = tenants.FirstOrDefault(p => p.TenantId == tenantId);
- return tenant;
- }
-
- private async Task> GetTenantsFromCacheAsync()
- {
- var tenants = await Task.FromResult(_memoryCache.Get>(AdminConsoleConstants.TenantsCacheKey));
- return tenants ?? [];
- }
-}
-
+// 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 EdFi.Ods.AdminApi.Common.Constants;
+using EdFi.Ods.AdminApi.Common.Infrastructure.Helpers;
+using EdFi.Ods.AdminApi.Common.Settings;
+using EdFi.Ods.AdminApi.Features.Tenants;
+using log4net;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+
+namespace EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
+
+public interface ITenantsService
+{
+ Task InitializeTenantsAsync();
+ Task> GetTenantsAsync(bool fromCache = false);
+ Task GetTenantByTenantIdAsync(string tenantName);
+}
+
+public class TenantService(IOptionsSnapshot options,
+ IMemoryCache memoryCache) : ITenantsService
+{
+ private const string ADMIN_DB_KEY = "EdFi_Admin";
+ private const string SECURITY_DB_KEY = "EdFi_Security";
+ protected AppSettingsFile _appSettings = options.Value;
+ private readonly IMemoryCache _memoryCache = memoryCache;
+ private static readonly ILog _log = LogManager.GetLogger(typeof(TenantService));
+
+ public async Task InitializeTenantsAsync()
+ {
+ var tenants = await GetTenantsAsync();
+ //store it in memorycache
+ await Task.FromResult(_memoryCache.Set(Constants.TenantsCacheKey, tenants));
+ }
+
+ public async Task> GetTenantsAsync(bool fromCache = false)
+ {
+ List results;
+
+ if (fromCache)
+ {
+ results = await GetTenantsFromCacheAsync();
+ if (results.Count > 0)
+ {
+ return results;
+ }
+ }
+
+ results = [];
+
+ if (_appSettings.AppSettings.MultiTenancy)
+ {
+ foreach (var tenantConfig in _appSettings.Tenants)
+ {
+ /// Admin database
+ var adminConnectionString = tenantConfig.Value.ConnectionStrings.First(p => p.Key == ADMIN_DB_KEY).Value;
+ if (!ConnectionStringHelper.ValidateConnectionString(_appSettings.AppSettings.DatabaseEngine!, adminConnectionString))
+ {
+ _log.WarnFormat("Tenant {Key} has an invalid connection string for database {ADMIN_DB_KEY}. Database engine is {engine}",
+ tenantConfig.Key, ADMIN_DB_KEY, _appSettings.AppSettings.DatabaseEngine);
+ }
+
+ /// Security database
+ var securityConnectionString = tenantConfig.Value.ConnectionStrings.First(p => p.Key == SECURITY_DB_KEY).Value;
+ if (!ConnectionStringHelper.ValidateConnectionString(_appSettings.AppSettings.DatabaseEngine!, securityConnectionString))
+ {
+ _log.WarnFormat("Tenant {Key} has an invalid connection string for database {SECURITY_DB_KEY}. Database engine is {engine}",
+ tenantConfig.Key, SECURITY_DB_KEY, _appSettings.AppSettings.DatabaseEngine);
+ }
+
+ results.Add(new TenantModel()
+ {
+ TenantName = tenantConfig.Key,
+ ConnectionStrings = new(adminConnectionString, securityConnectionString)
+ });
+ }
+ }
+ else
+ {
+ results.Add(new TenantModel()
+ {
+ TenantName = Constants.DefaultTenantName,
+ ConnectionStrings = new TenantModelConnectionStrings
+ (
+ edFiAdminConnectionString: _appSettings.ConnectionStrings.First(p => p.Key == ADMIN_DB_KEY).Value,
+ edFiSecurityConnectionString: _appSettings.ConnectionStrings.First(p => p.Key == SECURITY_DB_KEY).Value
+ )
+ });
+ }
+
+ return results;
+ }
+
+ public async Task GetTenantByTenantIdAsync(string tenantName)
+ {
+ var tenants = await GetTenantsAsync();
+ var tenant = tenants.FirstOrDefault(p => p.TenantName.Equals(tenantName, StringComparison.OrdinalIgnoreCase));
+ return tenant;
+ }
+
+ private async Task> GetTenantsFromCacheAsync()
+ {
+ var tenants = await Task.FromResult(_memoryCache.Get>(Constants.TenantsCacheKey));
+ return tenants ?? [];
+ }
+}
diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationBuilderExtensions.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationBuilderExtensions.cs
index 6d5bcedf1..3c3af9ac0 100644
--- a/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationBuilderExtensions.cs
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationBuilderExtensions.cs
@@ -20,14 +20,15 @@
using EdFi.Ods.AdminApi.Common.Settings;
using EdFi.Ods.AdminApi.Features.Connect;
using EdFi.Ods.AdminApi.Infrastructure.Documentation;
+using EdFi.Ods.AdminApi.Infrastructure.Helpers;
using EdFi.Ods.AdminApi.Infrastructure.Security;
+using EdFi.Ods.AdminApi.Infrastructure.Services.Tenants;
using EdFi.Security.DataAccess.Contexts;
using FluentValidation;
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Http.Json;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using Microsoft.OpenApi.Models;
@@ -40,6 +41,11 @@ public static class WebApplicationBuilderExtensions
public static void AddServices(this WebApplicationBuilder webApplicationBuilder)
{
webApplicationBuilder.Services.AddSingleton();
+
+ var env = webApplicationBuilder.Environment;
+ var appSettingsPath = Path.Combine(env.ContentRootPath, "appsettings.json");
+ webApplicationBuilder.Services.AddSingleton(new FileSystemAppSettingsFileProvider(appSettingsPath));
+
ConfigureRateLimiting(webApplicationBuilder);
ConfigurationManager config = webApplicationBuilder.Configuration;
webApplicationBuilder.Services.Configure(config.GetSection("AppSettings"));
@@ -201,6 +207,10 @@ public static void AddServices(this WebApplicationBuilder webApplicationBuilder)
webApplicationBuilder.Services.AddTransient();
webApplicationBuilder.Services.AddTransient();
+
+ webApplicationBuilder.Services.Configure(webApplicationBuilder.Configuration);
+
+ webApplicationBuilder.Services.AddTransient();
}
private static void EnableMultiTenancySupport(this WebApplicationBuilder webApplicationBuilder)
diff --git a/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationExtensions.cs b/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationExtensions.cs
index e0b1120ec..bef144208 100644
--- a/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationExtensions.cs
+++ b/Application/EdFi.Ods.AdminApi/Infrastructure/WebApplicationExtensions.cs
@@ -2,11 +2,9 @@
// 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 EdFi.Ods.AdminApi.AdminConsole.Infrastructure.Helper;
using EdFi.Ods.AdminApi.Common.Constants;
-using static OpenIddict.Abstractions.OpenIddictConstants.Permissions;
-using AdminApiV2Features = EdFi.Ods.AdminApi.Infrastructure.Helpers;
using AdminApiV1Features = EdFi.Ods.AdminApi.V1.Infrastructure.Helpers;
+using AdminApiV2Features = EdFi.Ods.AdminApi.Infrastructure.Helpers;
namespace EdFi.Ods.AdminApi.Infrastructure;
@@ -38,17 +36,6 @@ public static void MapFeatureEndpoints(this WebApplication application)
}
}
- public static void MapAdminConsoleFeatureEndpoints(this WebApplication application)
- {
- application.UseEndpoints(endpoints =>
- {
- foreach (var routeBuilder in AdminConsoleFeatureHelper.GetFeatures())
- {
- routeBuilder.MapEndpoints(endpoints);
- }
- });
- }
-
public static void DefineSwaggerUIWithApiVersions(this WebApplication application, params string[] versions)
{
application.UseSwaggerUI(definitions =>
diff --git a/Application/EdFi.Ods.AdminApi/Program.cs b/Application/EdFi.Ods.AdminApi/Program.cs
index 360b9675a..365d6f56f 100644
--- a/Application/EdFi.Ods.AdminApi/Program.cs
+++ b/Application/EdFi.Ods.AdminApi/Program.cs
@@ -3,8 +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 EdFi.Ods.AdminApi.AdminConsole;
-using EdFi.Ods.AdminApi.AdminConsole.Configurations;
using EdFi.Ods.AdminApi.Common.Constants;
using EdFi.Ods.AdminApi.Common.Infrastructure;
using EdFi.Ods.AdminApi.Common.Infrastructure.MultiTenancy;
@@ -30,28 +28,16 @@
// logging
var _logger = LogManager.GetLogger("Program");
_logger.Info("Starting Admin API");
-var adminConsoleIsEnabled = builder.Configuration.GetValue("AppSettings:EnableAdminConsoleAPI");
var adminApiMode = builder.Configuration.GetValue("AppSettings:AdminApiMode", AdminApiMode.V2);
var databaseEngine = builder.Configuration.GetValue("AppSettings:DatabaseEngine");
// Log configuration values as requested
_logger.InfoFormat("Configuration - ApiMode: {0}, Engine: {1}", adminApiMode, databaseEngine);
-//Order is important to enable CORS
-if (adminConsoleIsEnabled && adminApiMode == AdminApiMode.V2)
- builder.RegisterAdminConsoleCorsDependencies(_logger);
-
builder.AddServices();
-if (adminConsoleIsEnabled && adminApiMode == AdminApiMode.V2)
- builder.RegisterAdminConsoleDependencies();
-
var app = builder.Build();
-//Order is important to enable CORS
-if (adminConsoleIsEnabled && adminApiMode == AdminApiMode.V2)
- app.UseCorsForAdminConsole();
-
var pathBase = app.Configuration.GetValue("AppSettings:PathBase");
if (!string.IsNullOrEmpty(pathBase))
{
@@ -74,12 +60,6 @@
app.UseAuthorization();
app.MapFeatureEndpoints();
-//Map AdminConsole endpoints if the flag is enable
-if (adminConsoleIsEnabled && adminApiMode == AdminApiMode.V2)
-{
- app.MapAdminConsoleFeatureEndpoints();
-}
-
app.MapControllers();
app.UseHealthChecks("/health");
diff --git a/Application/EdFi.Ods.AdminApi/appsettings.Development.json b/Application/EdFi.Ods.AdminApi/appsettings.Development.json
index f42389bdb..589118bda 100644
--- a/Application/EdFi.Ods.AdminApi/appsettings.Development.json
+++ b/Application/EdFi.Ods.AdminApi/appsettings.Development.json
@@ -1,8 +1,8 @@
{
"AppSettings": {
- "MultiTenancy": false,
- "EnableAdminConsoleAPI": true,
- "DatabaseEngine": "SqlServer"
+ "MultiTenancy": true,
+ "DatabaseEngine": "SqlServer",
+ "IgnoresCertificateErrors": true
},
"AdminConsoleSettings": {
"CorsSettings": {
@@ -21,8 +21,8 @@
"AllowRegistration": true
},
"ConnectionStrings": {
- "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin;Integrated Security=True;Encrypt=false",
- "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security;Integrated Security=True;Encrypt=false"
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True",
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True"
},
"SwaggerSettings": {
"EnableSwagger": true,
@@ -37,17 +37,15 @@
"Tenants": {
"tenant1": {
"ConnectionStrings": {
- "EdFi_Security": "host=localhost;port=5401;username=postgres;password=P@ssw0rd;database=EdFi_Security;application name=AdminApi;",
- "EdFi_Admin": "host=localhost;port=5401;username=postgres;password=P@ssw0rd;database=EdFi_Admin;application name=AdminApi;"
- },
- "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api6/"
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True",
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True"
+ }
},
"tenant2": {
"ConnectionStrings": {
- "EdFi_Security": "host=localhost;port=5402;username=postgres;password=P@ssw0rd;database=EdFi_Security;application name=AdminApi;",
- "EdFi_Admin": "host=localhost;port=5402;username=postgres;password=P@ssw0rd;database=EdFi_Admin;application name=AdminApi;"
- },
- "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api4/"
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True",
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin7;Integrated Security=True;Trusted_Connection=true;Encrypt=True;TrustServerCertificate=True"
+ }
}
},
"Testing": {
diff --git a/Application/EdFi.Ods.AdminApi/appsettings.json b/Application/EdFi.Ods.AdminApi/appsettings.json
index 7f442ad9e..7e3a8c281 100644
--- a/Application/EdFi.Ods.AdminApi/appsettings.json
+++ b/Application/EdFi.Ods.AdminApi/appsettings.json
@@ -1,96 +1,90 @@
{
- "AppSettings": {
- "DatabaseEngine": "SqlServer",
- "EncryptionKey": "{ BASE_64_ENCRYPTION_KEY }",
- "PathBase": "",
- "DefaultPageSizeOffset": 0,
- "DefaultPageSizeLimit": 25,
- "MultiTenancy": false,
- "PreventDuplicateApplications": false,
- "EnableAdminConsoleAPI": true,
- "EnableApplicationResetEndpoint": false,
- "adminApiMode": "v2" // or "v1"
+ "AppSettings": {
+ "DatabaseEngine": "SqlServer",
+ "EncryptionKey": "{ BASE_64_ENCRYPTION_KEY }",
+ "PathBase": "",
+ "DefaultPageSizeOffset": 0,
+ "DefaultPageSizeLimit": 25,
+ "MultiTenancy": false,
+ "PreventDuplicateApplications": false,
+ "EnableApplicationResetEndpoint": false,
+ "adminApiMode": "v2"
+ },
+ "AdminConsoleSettings": {
+ "ApplicationName": "Ed-Fi Health Check",
+ "ClaimsetName": "Ed-Fi Admin App",
+ "VendorCompany": "Ed-Fi Administrative Tools",
+ "VendorContactName": "",
+ "VendorNamespacePrefixes": "uri://ed-fi.org",
+ "CorsSettings": {
+ "EnableCors": false,
+ "AllowedOrigins": [
+ "https://localhost"
+ ]
},
- "AdminConsoleSettings": {
- "ApplicationName": "Ed-Fi Health Check",
- "ClaimsetName": "Ed-Fi Admin App",
- "VendorCompany": "Ed-Fi Administrative Tools",
- "VendorContactName": "",
- "VendorNamespacePrefixes": "uri://ed-fi.org",
- "CorsSettings": {
- "EnableCors": false,
- "AllowedOrigins": [
- "https://localhost"
- ]
- },
- "EncryptionKey": "abcdefghi"
- },
- "Authentication": {
-
- "IssuerUrl": "",
- "SigningKey": "",
- "ValidateIssuerSigningKey": true,
- "RoleClaimAttribute": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
- "AllowRegistration": true,
- // V1:
- "Authority": null
- },
- "SwaggerSettings": {
- "EnableSwagger": false,
- "DefaultTenant": ""
- },
- "EnableDockerEnvironment": false,
- "ConnectionStrings": {
- "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin;Integrated Security=True",
- "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security;Integrated Security=True"
- },
- "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api/",
- "Log4NetCore": {
- "Log4NetConfigFileName": "log4net/log4net.config"
- },
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "OpenIddict.*": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
- },
- "Tenants": {
- "tenant1": {
- "ConnectionStrings": {
- "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security_Tenant1;Integrated Security=True",
- "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin_Tenant1;Integrated Security=True"
- },
- "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api6/"
- },
- "tenant2": {
- "ConnectionStrings": {
- "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security_Tenant2;Integrated Security=True",
- "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin_Tenant2;Integrated Security=True"
- },
- "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api4/"
- }
- },
- "AllowedHosts": "*",
-
- "IpRateLimiting": {
- "EnableEndpointRateLimiting": true,
- "StackBlockedRequests": false,
- "RealIpHeader": "X-Real-IP",
- "ClientIdHeader": "X-ClientId",
- "HttpStatusCode": 429,
- "IpWhitelist": [],
- "EndpointWhitelist": [],
- "GeneralRules": [
- {
- "Endpoint": "POST:/Connect/Register",
- "Period": "1m",
- "Limit": 3
- }
- ]
+ "EncryptionKey": "abcdefghi"
+ },
+ "Authentication": {
+ "IssuerUrl": "",
+ "SigningKey": "",
+ "ValidateIssuerSigningKey": true,
+ "RoleClaimAttribute": "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
+ "AllowRegistration": true,
+ "Authority": null
+ },
+ "SwaggerSettings": {
+ "EnableSwagger": false,
+ "DefaultTenant": ""
+ },
+ "EnableDockerEnvironment": false,
+ "ConnectionStrings": {
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin;Integrated Security=True",
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security;Integrated Security=True"
+ },
+ "EdFiApiDiscoveryUrl": "https://api.ed-fi.org/v7.2/api/",
+ "Log4NetCore": {
+ "Log4NetConfigFileName": "log4net/log4net.config"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "OpenIddict.*": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "Tenants": {
+ "tenant1": {
+ "ConnectionStrings": {
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security_Tenant1;Integrated Security=True",
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin_Tenant1;Integrated Security=True"
+ }
},
- "Testing": {
- "InjectException": false
+ "tenant2": {
+ "ConnectionStrings": {
+ "EdFi_Security": "Data Source=.\\;Initial Catalog=EdFi_Security_Tenant2;Integrated Security=True",
+ "EdFi_Admin": "Data Source=.\\;Initial Catalog=EdFi_Admin_Tenant2;Integrated Security=True"
+ }
}
+ },
+ "AllowedHosts": "*",
+ "IpRateLimiting": {
+ "EnableEndpointRateLimiting": true,
+ "StackBlockedRequests": false,
+ "RealIpHeader": "X-Real-IP",
+ "ClientIdHeader": "X-ClientId",
+ "HttpStatusCode": 429,
+ "IpWhitelist": [],
+ "EndpointWhitelist": [],
+ "GeneralRules": [
+ {
+ "Endpoint": "POST:/Connect/Register",
+ "Period": "1m",
+ "Limit": 3
+ }
+ ]
+ },
+ "Testing": {
+ "InjectException": false
+ }
}
diff --git a/Docker/Settings/dev/mssql/run.sh b/Docker/Settings/dev/mssql/run.sh
index 4201643be..6724b72d4 100644
--- a/Docker/Settings/dev/mssql/run.sh
+++ b/Docker/Settings/dev/mssql/run.sh
@@ -30,4 +30,7 @@ if [[ -f /ssl/server.crt ]]; then
update-ca-certificates
fi
+# Writing permissions for multitenant environment so the user can create tenants
+chmod 664 /app/appsettings.json
+
dotnet EdFi.Ods.AdminApi.dll
diff --git a/Docker/Settings/dev/pgsql/run.sh b/Docker/Settings/dev/pgsql/run.sh
index e2a35993d..4d06e4231 100644
--- a/Docker/Settings/dev/pgsql/run.sh
+++ b/Docker/Settings/dev/pgsql/run.sh
@@ -56,4 +56,7 @@ if [[ -f /ssl/server.crt ]]; then
update-ca-certificates
fi
+# Writing permissions for multitenant environment so the user can create tenants
+chmod 664 /app/appsettings.json
+
dotnet EdFi.Ods.AdminApi.dll
diff --git a/Docker/Settings/mssql/run.sh b/Docker/Settings/mssql/run.sh
index 77cc10825..aab016500 100644
--- a/Docker/Settings/mssql/run.sh
+++ b/Docker/Settings/mssql/run.sh
@@ -31,4 +31,7 @@ if [[ -f /ssl/server.crt ]]; then
update-ca-certificates
fi
+# Writing permissions for multitenant environment so the user can create tenants
+chmod 664 /app/appsettings.json
+
dotnet EdFi.Ods.AdminApi.dll
diff --git a/Docker/Settings/pgsql/run.sh b/Docker/Settings/pgsql/run.sh
index 1d845507e..00c92195e 100755
--- a/Docker/Settings/pgsql/run.sh
+++ b/Docker/Settings/pgsql/run.sh
@@ -34,4 +34,7 @@ if [[ -f /ssl/server.crt ]]; then
update-ca-certificates
fi
+# Writing permissions for multitenant environment so the user can create tenants
+chmod 664 /app/appsettings.json
+
dotnet EdFi.Ods.AdminApi.dll
diff --git a/Docker/V1/Compose/mssql/compose-build-binaries.yml b/Docker/V1/Compose/mssql/compose-build-binaries.yml
index 8a4857d90..7e1514417 100644
--- a/Docker/V1/Compose/mssql/compose-build-binaries.yml
+++ b/Docker/V1/Compose/mssql/compose-build-binaries.yml
@@ -51,7 +51,6 @@ services:
AppSettings__DatabaseEngine: SqlServer
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: false
AppSettings__EnableApplicationResetEndpoint: false
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: false
@@ -63,7 +62,6 @@ services:
AdminConsoleSettings__CorsSettings__EnableCors: "${ENABLE_CORS:-false}"
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: ""
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V1/Compose/mssql/compose-build-dev.yml b/Docker/V1/Compose/mssql/compose-build-dev.yml
index 8945e5c6e..c243bf004 100644
--- a/Docker/V1/Compose/mssql/compose-build-dev.yml
+++ b/Docker/V1/Compose/mssql/compose-build-dev.yml
@@ -107,7 +107,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: false
AppSettings__EnableApplicationResetEndpoint: false
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: false
@@ -118,7 +117,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: ""
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/V1/Compose/pgsql/compose-build-binaries.yml b/Docker/V1/Compose/pgsql/compose-build-binaries.yml
index f9fef083d..85ae8de46 100644
--- a/Docker/V1/Compose/pgsql/compose-build-binaries.yml
+++ b/Docker/V1/Compose/pgsql/compose-build-binaries.yml
@@ -47,7 +47,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: false
AppSettings__EnableApplicationResetEndpoint: false
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: false
@@ -59,7 +58,6 @@ services:
AdminConsoleSettings__CorsSettings__EnableCors: "${ENABLE_CORS:-false}"
ConnectionStrings__EdFi_Admin: "host=admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: ""
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V1/Compose/pgsql/compose-build-dev.yml b/Docker/V1/Compose/pgsql/compose-build-dev.yml
index 2bba77d24..eda3bc541 100644
--- a/Docker/V1/Compose/pgsql/compose-build-dev.yml
+++ b/Docker/V1/Compose/pgsql/compose-build-dev.yml
@@ -82,7 +82,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: false
AppSettings__EnableApplicationResetEndpoint: false
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: false
@@ -92,7 +91,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "host=db-admin;port=5432;username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=db-admin;port=5432;username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: ""
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/V2/Compose/mssql/MultiTenant/compose-build-binaries-multi-tenant.yml b/Docker/V2/Compose/mssql/MultiTenant/compose-build-binaries-multi-tenant.yml
index 28234b908..6494dc0ec 100644
--- a/Docker/V2/Compose/mssql/MultiTenant/compose-build-binaries-multi-tenant.yml
+++ b/Docker/V2/Compose/mssql/MultiTenant/compose-build-binaries-multi-tenant.yml
@@ -35,7 +35,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -64,10 +63,8 @@ services:
SwaggerSettings__DefaultTenant: ${DEFAULT_TENANT:-tenant1}
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/mssql/MultiTenant/compose-build-dev-multi-tenant.yml b/Docker/V2/Compose/mssql/MultiTenant/compose-build-dev-multi-tenant.yml
index 4933c3ea6..b6a2a9cdf 100644
--- a/Docker/V2/Compose/mssql/MultiTenant/compose-build-dev-multi-tenant.yml
+++ b/Docker/V2/Compose/mssql/MultiTenant/compose-build-dev-multi-tenant.yml
@@ -41,7 +41,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: ${MULTITENANCY_ENABLED:-true}
@@ -72,10 +71,8 @@ services:
SwaggerSettings__DefaultTenant: ${DEFAULT_TENANT:-tenant2}
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Admin;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Security;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Admin;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Security;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml b/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
index 24c4fef8b..b3d76f28a 100644
--- a/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
+++ b/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
@@ -39,7 +39,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -66,10 +65,8 @@ services:
SwaggerSettings__DefaultTenant: ${DEFAULT_TENANT:-tenant1}
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-dev-multi-tenant.yml b/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
index edf389d40..c8fec34f2 100644
--- a/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
+++ b/Docker/V2/Compose/mssql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
@@ -43,7 +43,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -72,10 +71,8 @@ services:
SwaggerSettings__DefaultTenant: ${DEFAULT_TENANT:-tenant2}
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant1,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "Data Source=db-admin-tenant2,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/mssql/SingleTenant/compose-build-binaries.yml b/Docker/V2/Compose/mssql/SingleTenant/compose-build-binaries.yml
index fdfd35a68..86fac53f8 100644
--- a/Docker/V2/Compose/mssql/SingleTenant/compose-build-binaries.yml
+++ b/Docker/V2/Compose/mssql/SingleTenant/compose-build-binaries.yml
@@ -38,7 +38,6 @@ services:
AppSettings__DatabaseEngine: SqlServer
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -50,7 +49,6 @@ services:
AdminConsoleSettings__CorsSettings__EnableCors: "${ENABLE_CORS:-false}"
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V2/Compose/mssql/SingleTenant/compose-build-dev.yml b/Docker/V2/Compose/mssql/SingleTenant/compose-build-dev.yml
index 28b8f6dae..4af7944c0 100644
--- a/Docker/V2/Compose/mssql/SingleTenant/compose-build-dev.yml
+++ b/Docker/V2/Compose/mssql/SingleTenant/compose-build-dev.yml
@@ -43,7 +43,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: ${MULTITENANCY_ENABLED:-false}
@@ -54,7 +53,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-binaries.yml b/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-binaries.yml
index 763ab0a90..10c92c755 100644
--- a/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-binaries.yml
+++ b/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-binaries.yml
@@ -40,7 +40,6 @@ services:
AppSettings__DatabaseEngine: SqlServer
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
AppSettings__PathBase: ${ADMIN_API_VIRTUAL_NAME:-adminapi}
@@ -52,7 +51,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=$SQLSERVER_USER;Password=$SQLSERVER_PASSWORD; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-dev.yml b/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-dev.yml
index 260588c4b..fcf974c28 100644
--- a/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-dev.yml
+++ b/Docker/V2/Compose/mssql/SingleTenant/compose-build-idp-dev.yml
@@ -45,7 +45,6 @@ services:
AppSettings__DatabaseEngine: "SqlServer"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -56,7 +55,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "Data Source=db-admin,1433;Initial Catalog=EdFi_Admin;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
ConnectionStrings__EdFi_Security: "Data Source=db-admin,1433;Initial Catalog=EdFi_Security;User Id=${SQLSERVER_USER};Password=${SQLSERVER_PASSWORD}; Integrated Security=False;Encrypt=false;TrustServerCertificate=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-binaries-multi-tenant.yml b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-binaries-multi-tenant.yml
index f0ac3c4a9..4959d1188 100644
--- a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-binaries-multi-tenant.yml
+++ b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-binaries-multi-tenant.yml
@@ -37,7 +37,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -59,10 +58,8 @@ services:
POSTGRES_USER: "${POSTGRES_USER}"
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
depends_on:
- db-admin-tenant1
- db-admin-tenant2
diff --git a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-dev-multi-tenant.yml b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-dev-multi-tenant.yml
index 676cdc590..b92449799 100644
--- a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-dev-multi-tenant.yml
+++ b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-dev-multi-tenant.yml
@@ -40,7 +40,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -66,10 +65,8 @@ services:
POSTGRES_USER: "${POSTGRES_USER}"
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant1;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "host=db-admin-tenant1;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant2;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "host=db-admin-tenant2;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
index c6ace890c..24d54b6c7 100644
--- a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
+++ b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-binaries-multi-tenant.yml
@@ -38,7 +38,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -60,10 +59,8 @@ services:
POSTGRES_USER: "${POSTGRES_USER}"
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
depends_on:
- db-admin-tenant1
- db-admin-tenant2
diff --git a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-dev-multi-tenant.yml b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
index ff7226d51..3e8896a92 100644
--- a/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
+++ b/Docker/V2/Compose/pgsql/MultiTenant/compose-build-idp-dev-multi-tenant.yml
@@ -42,7 +42,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-true}"
@@ -68,10 +67,8 @@ services:
POSTGRES_USER: "${POSTGRES_USER}"
Tenants__tenant1__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant1__ConnectionStrings__EdFi_Security: "host=db-admin-tenant1;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant1__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
Tenants__tenant2__ConnectionStrings__EdFi_Admin: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
Tenants__tenant2__ConnectionStrings__EdFi_Security: "host=db-admin-tenant2;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- Tenants__tenant2__EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
entrypoint: ["/bin/sh"]
command: ["-c","/app/run.sh"]
depends_on:
diff --git a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-binaries.yml b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-binaries.yml
index 84ee6f58f..b61225bfd 100644
--- a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-binaries.yml
+++ b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-binaries.yml
@@ -36,7 +36,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -48,7 +47,6 @@ services:
AdminConsoleSettings__CorsSettings__EnableCors: "${ENABLE_CORS:-false}"
ConnectionStrings__EdFi_Admin: "host=pb-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=pb-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-dev.yml b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-dev.yml
index 2b12c21d2..5dd09d27d 100644
--- a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-dev.yml
+++ b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-dev.yml
@@ -40,7 +40,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -50,7 +49,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "host=db-admin;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=db-admin;port=${POSTGRES_PORT:-5432};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-binaries.yml b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-binaries.yml
index 64f5943c4..d709b23f4 100644
--- a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-binaries.yml
+++ b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-binaries.yml
@@ -40,7 +40,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -50,7 +49,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "host=pb-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=pb-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
IpRateLimiting__RealIpHeader: ${IPRATELIMITING__REALIPHEADER:-X-Real-IP}
diff --git a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-dev.yml b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-dev.yml
index 982ec350b..6da92de72 100644
--- a/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-dev.yml
+++ b/Docker/V2/Compose/pgsql/SingleTenant/compose-build-idp-dev.yml
@@ -42,7 +42,6 @@ services:
AppSettings__DatabaseEngine: "PostgreSql"
AppSettings__DefaultPageSizeLimit: ${PAGING_LIMIT:-25}
AppSettings__DefaultPageSizeOffset: ${PAGING_OFFSET:-0}
- AppSettings__EnableAdminConsoleAPI: ${ENABLE_ADMIN_CONSOLE:-true}
AppSettings__EnableApplicationResetEndpoint: ${ENABLE_APPLICATION_RESET_ENDPOINT:-true}
AppSettings__EncryptionKey: "TDMyNH0lJmo7aDRnNXYoSmAwSXQpV09nbitHSWJTKn0="
AppSettings__MultiTenancy: "${MULTITENANCY_ENABLED:-false}"
@@ -53,7 +52,6 @@ services:
Authentication__SigningKey: ${SIGNING_KEY}
ConnectionStrings__EdFi_Admin: "host=db-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Admin;pooling=true"
ConnectionStrings__EdFi_Security: "host=db-admin;port=${POSTGRES_PORT};username=${POSTGRES_USER};password=${POSTGRES_PASSWORD};database=EdFi_Security;pooling=true"
- EdFiApiDiscoveryUrl: "${EDFI_API_DISCOVERY_URL:-https://host.docker.internal/api/}"
EnableDockerEnvironment: true
IpRateLimiting__EnableEndpointRateLimiting: ${IPRATELIMITING__ENABLEENDPOINTRATELIMITING:-false}
IpRateLimiting__StackBlockedRequests: ${IPRATELIMITING__STACKBLOCKEDREQUESTS:-false}
diff --git a/Docker/dev.mssql.Dockerfile b/Docker/dev.mssql.Dockerfile
index 737e3c8ee..b55f4ff26 100644
--- a/Docker/dev.mssql.Dockerfile
+++ b/Docker/dev.mssql.Dockerfile
@@ -18,9 +18,6 @@ COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi/
COPY --from=assets ./Application/EdFi.Ods.AdminApi EdFi.Ods.AdminApi/
RUN rm -f EdFi.Ods.AdminApi/appsettings.Development.json
-COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi.AdminConsole/
-COPY --from=assets ./Application/EdFi.Ods.AdminApi.AdminConsole EdFi.Ods.AdminApi.AdminConsole/
-
COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi.Common/
COPY --from=assets ./Application/EdFi.Ods.AdminApi.Common EdFi.Ods.AdminApi.Common/
@@ -31,11 +28,6 @@ RUN export ASPNETCORE_ENVIRONMENT=$ASPNETCORE_ENVIRONMENT
RUN dotnet restore && dotnet build -c Release
RUN dotnet publish -c Release /p:EnvironmentName=$ASPNETCORE_ENVIRONMENT --no-build -o /app/EdFi.Ods.AdminApi
-WORKDIR /source/EdFi.Ods.AdminApi.AdminConsole
-RUN export ASPNETCORE_ENVIRONMENT=$ASPNETCORE_ENVIRONMENT
-RUN dotnet restore && dotnet build -c Release
-RUN dotnet publish -c Release /p:EnvironmentName=$ASPNETCORE_ENVIRONMENT --no-build -o /app/EdFi.Ods.AdminApi.AdminConsole
-
FROM mcr.microsoft.com/dotnet/aspnet:8.0.8-alpine3.20-amd64@sha256:98fa594b91cda6cac28d2aae25567730db6f8857367fab7646bdda91bc784b5f AS runtimebase
RUN apk upgrade --no-cache && \
apk add dos2unix=~7 bash=~5 gettext=~0 icu=~74 curl musl=~1.2.5-r1 && \
diff --git a/Docker/dev.pgsql.Dockerfile b/Docker/dev.pgsql.Dockerfile
index 53d6b1938..6e83ffcca 100644
--- a/Docker/dev.pgsql.Dockerfile
+++ b/Docker/dev.pgsql.Dockerfile
@@ -18,9 +18,6 @@ COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi/
COPY --from=assets ./Application/EdFi.Ods.AdminApi EdFi.Ods.AdminApi/
RUN rm -f EdFi.Ods.AdminApi/appsettings.Development.json
-COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi.AdminConsole/
-COPY --from=assets ./Application/EdFi.Ods.AdminApi.AdminConsole EdFi.Ods.AdminApi.AdminConsole/
-
COPY --from=assets ./Application/NuGet.Config EdFi.Ods.AdminApi.Common/
COPY --from=assets ./Application/EdFi.Ods.AdminApi.Common EdFi.Ods.AdminApi.Common/
diff --git a/docs/http/claimsets.http b/docs/http/claimsets.http
new file mode 100644
index 000000000..d0ba9275d
--- /dev/null
+++ b/docs/http/claimsets.http
@@ -0,0 +1,37 @@
+# This file is intended for use with Admin API 2 running in multi-tenant mode,
+# along with ODS/API 7.x in multi-tenant mode. It assumes there are two
+# different tenants named "tenant1" and "tenant2". Each has only one ODS
+# instance.
+
+@adminapi_url=https://localhost:7214
+@adminapi_client=adminapi_client2
+@adminapi_secret=adminapi_SECRET_2025_rftyguhijkotgyhuijok
+
+### Register a new client
+POST {{adminapi_url}}/connect/register
+Content-Type: application/x-www-form-urlencoded
+
+ClientId={{adminapi_client}}&ClientSecret={{adminapi_secret}}&DisplayName=Admin+API+{{adminapi_client}}
+
+### Get a token
+# @name tokenRequest
+POST {{adminapi_url}}/connect/token
+Content-Type: application/x-www-form-urlencoded
+Authorization: basic {{adminapi_client}}:{{adminapi_secret}}
+
+grant_type=client_credentials&scope=edfi_admin_api/full_access
+
+###
+@token={{tokenRequest.response.body.access_token}}
+
+
+### Get claimsets V1
+GET {{adminapi_url}}/v1/claimsets
+Content-Type: application/json
+Authorization: bearer {{token}}
+
+
+### Get claimsets V2
+GET {{adminapi_url}}/v2/claimsets
+Content-Type: application/json
+Authorization: bearer {{token}}
diff --git a/docs/http/tenants.http b/docs/http/tenants.http
new file mode 100644
index 000000000..333215cce
--- /dev/null
+++ b/docs/http/tenants.http
@@ -0,0 +1,62 @@
+# This file is intended for use with Admin API 2 running in multi-tenant mode,
+# along with ODS/API 7.x in multi-tenant mode. It assumes there are two
+# different tenants named "tenant1" and "tenant2". Each has only one ODS
+# instance.
+
+# @adminapi_url=https://localhost/adminapi
+@adminapi_url=https://localhost/adminapi
+
+@adminapi_client=adminapi_client22
+@adminapi_secret=adminapi_SECRET_2025_rftyguhijkotgyhuijok
+
+### Register a new client
+POST {{adminapi_url}}/connect/register
+Content-Type: application/x-www-form-urlencoded
+Tenant: tenant1
+
+ClientId={{adminapi_client}}&ClientSecret={{adminapi_secret}}&DisplayName=Admin+API+{{adminapi_client}}
+
+### Get a token
+# @name tokenRequest
+POST {{adminapi_url}}/connect/token
+Content-Type: application/x-www-form-urlencoded
+Authorization: basic {{adminapi_client}}:{{adminapi_secret}}
+Tenant: tenant1
+
+grant_type=client_credentials&scope=edfi_admin_api/full_access
+
+###
+@token={{tokenRequest.response.body.access_token}}
+
+### Get tenants V2
+GET {{adminapi_url}}/v2/tenants
+Content-Type: application/json
+Authorization: bearer {{token}}
+
+### Get tenant V2 by id
+GET {{adminapi_url}}/v2/tenants/default
+Content-Type: application/json
+Authorization: bearer {{token}}
+Tenant: tenant1
+
+### Create tenant
+POST {{adminapi_url}}/v2/tenants
+Content-Type: application/json
+Authorization: bearer {{token}}
+Tenant: tenant1
+
+{
+ "TenantName": "tenant3",
+ "EdFiSecurityConnectionString": "123",
+ "EdFiAdminConnectionString": "Data Source=db-admin;Initial Catalog=EdFi_Admin;User Id=edfi;Password=P@55w0rd;Encrypt=false;TrustServerCertificate=true"
+}
+
+### Create tenant no connection strings
+POST {{adminapi_url}}/v2/tenants
+Content-Type: application/json
+Authorization: bearer {{token}}
+Tenant: tenant1
+
+{
+ "TenantName": "tenantNoConnStrings2"
+}
\ No newline at end of file