diff --git a/src/Clients/DevDad.SaaSAdmin.API/DevDad.SaaSAdmin.API.csproj b/src/Clients/DevDad.SaaSAdmin.API/DevDad.SaaSAdmin.API.csproj
index 278a9d9..f190eb0 100644
--- a/src/Clients/DevDad.SaaSAdmin.API/DevDad.SaaSAdmin.API.csproj
+++ b/src/Clients/DevDad.SaaSAdmin.API/DevDad.SaaSAdmin.API.csproj
@@ -7,6 +7,7 @@
+
@@ -14,6 +15,7 @@
+
diff --git a/src/Clients/DevDad.SaaSAdmin.API/EndpointExtensions.cs b/src/Clients/DevDad.SaaSAdmin.API/EndpointExtensions.cs
index be4b597..05c37bc 100644
--- a/src/Clients/DevDad.SaaSAdmin.API/EndpointExtensions.cs
+++ b/src/Clients/DevDad.SaaSAdmin.API/EndpointExtensions.cs
@@ -1,18 +1,20 @@
using System;
-using System.Text.Json;
using System.Threading.Tasks;
-using DevDad.SaaSAdmin.AccountManager.Contracts;
-using DevDad.SaaSAdmin.API.ApiServices;
-using DevDad.SaaSAdmin.API.PublicModels;
-using DevDad.SaaSAdmin.iFX;
-using DevDad.SaaSAdmin.StoreManager.Contracts;
+
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+
+using DevDad.SaaSAdmin.AccountManager.Contracts;
+using DevDad.SaaSAdmin.API.ApiServices;
+using DevDad.SaaSAdmin.API.PublicModels;
+using DevDad.SaaSAdmin.StoreManager.Contracts;
using ThatDeveloperDad.iFX.Serialization;
+using Microsoft.AspNetCore.Authorization;
+using DevDad.SaaSAdmin.iFX;
namespace DevDad.SaaSAdmin.API;
@@ -109,34 +111,44 @@ public static class EndpointExtensions
await context.Response.WriteAsync("Hello There from AppEndpoints!");
});
- app.MapPost("/loadProfile", async Task (LoadProfileRequest requestData, HttpContext httpContext) =>
+ app.MapPost("/loadProfile",
+ async Task (LoadProfileRequest requestData, HttpContext httpContext) =>
{
IResult result = Results.NoContent();
try
{
IAccountManager? acctManager = componentRegistry.GetService();
- LoadAccountProfileRequest mgrRequest = new("LoadUserProfile")
+ LoadAccountProfileRequest mgrRequest = new("LoadOrCreateUserProfile")
{
UserId = requestData.UserEntraId
};
- CustomerProfileResponse mgrResponse = await acctManager!.LoadCustomerProfileAsync(mgrRequest);
+ CustomerProfileResponse mgrResponse = await acctManager!.LoadOrCreateCustomerProfileAsync(mgrRequest);
- if(mgrResponse.HasErrors)
+ if(mgrResponse.Payload == null)
+ {
+ result = Results.NotFound("No profile found for the provided Id.");
+ logger.LogInformation($"No profile found for UserEntraId {requestData.UserEntraId}");
+ }
+ else if(mgrResponse.HasErrors)
{
result = Results.BadRequest(mgrResponse.ErrorReport);
string errorReport = string.Join(Environment.NewLine, mgrResponse.ErrorReport);
logger.LogError(errorReport);
}
- else if(mgrResponse.Payload == null)
- {
- result = Results.NotFound("No profile found for the provided Id.");
- logger.LogInformation($"No profile found for UserEntraId {requestData.UserEntraId}");
- }
else
{
- result = Results.Ok(mgrResponse.Payload);
+ LoadProfileResponse apiResponse = new()
+ {
+ UserId = mgrResponse.Payload.UserId,
+ DisplayName = mgrResponse.Payload.DisplayName,
+ SubscriptionSku = mgrResponse.Payload
+ .Subscription?.SKU
+ ?? SubscriptionIdentifiers.SKUS_TDMF_FREE
+ };
+
+ result = Results.Ok(apiResponse);
logger.LogInformation($"Profile loaded successfully for UserEntraId {requestData.UserEntraId}");
}
}
@@ -148,7 +160,8 @@ public static class EndpointExtensions
return result;
})
- .Accepts("application/json");
+ .Accepts("application/json")
+ .RequireAuthorization(ApiConstants.AuthorizationPolicies.AllowApiConsumersOnly);
// This endpoint will be called from the Application when a user clicks on an
// Upgrade to Paid Plan button. It will send the basic information to the
diff --git a/src/Clients/DevDad.SaaSAdmin.API/Program.cs b/src/Clients/DevDad.SaaSAdmin.API/Program.cs
index 9daeec8..b70dc66 100644
--- a/src/Clients/DevDad.SaaSAdmin.API/Program.cs
+++ b/src/Clients/DevDad.SaaSAdmin.API/Program.cs
@@ -1,13 +1,22 @@
using System;
-using DevDad.SaaSAdmin.API.ApiServices;
-using DotNetEnv;
+
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using Microsoft.Identity.Web;
+
+using DotNetEnv;
+
+using DevDad.SaaSAdmin.API.ApiServices;
using ThatDeveloperDad.iFX;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Authorization;
+using System.Linq;
namespace DevDad.SaaSAdmin.API;
@@ -20,33 +29,14 @@ public static void Main(string[] args)
builder.Services.AddOpenApi();
var bootLogger = CreateBootLogger();
IConfiguration systemConfig = LoadSystemConfiguration(bootLogger);
- builder = AddUtilityServices(systemConfig, bootLogger, builder);
-
- // Add services to the container.
- builder.Services.AddAuthorization(options =>
- {
- // Need to check the app Environment to determine if we're in Dev or Prod here.
- var environment = builder.Environment;
-
- if (environment.IsDevelopment())
- {
- options.AddPolicy(ApiConstants.AuthorizationPolicies.AllowApiConsumersOnly,
- policy => policy.RequireAssertion(_ => true));
- }
- else
- {
- options.AddPolicy(ApiConstants.AuthorizationPolicies.AllowApiConsumersOnly,
- policy => policy.RequireAuthenticatedUser());
- }
- });
-
- // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
-
+
+ builder = AddSecurityServices(systemConfig, bootLogger, builder);
+ builder = AddUtilityServices(systemConfig, bootLogger, builder);
var app = builder.Build();
app.UseHttpsRedirection();
-
+ app.UseAuthentication();
app.UseAuthorization();
// All the services we'd registered in the Application's DI container are considered
@@ -84,6 +74,68 @@ public static void Main(string[] args)
app.Run();
}
+ static WebApplicationBuilder AddSecurityServices(IConfiguration configuration,
+ ILogger bootLog,
+ WebApplicationBuilder appBuilder)
+ {
+ bootLog.LogTrace("Configuring AuthN and AuthZ ");
+
+ appBuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddMicrosoftIdentityWebApi(options => {
+ appBuilder.Configuration.Bind("AzureAd", options);
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidAudience = configuration["AzureAd:Audience"],
+ ValidIssuer = $"{configuration["AzureAd:Instance"]}{configuration["AzureAd:TenantId"]}/v2.0"
+ };
+ },
+ options => {
+ appBuilder.Configuration.Bind("AzureAd", options);
+ });
+ bootLog.LogTrace("Configured MS Identity & JWT Bearer options.");
+
+ // Add services to the container.
+ appBuilder.Services.AddAuthorization(options =>
+ {
+
+ options.AddPolicy(ApiConstants.AuthorizationPolicies.AllowApiConsumersOnly,
+ policy =>
+ policy.RequireAuthenticatedUser()
+ .RequireAssertion((AuthorizationHandlerContext context)=>
+ {
+ bootLog.LogTrace("Evaluationg AuthZ Policy.");
+ // Validate that the appId on the ConfidentialClientApp
+ // is in the list of approved apps.
+ string[] allowedClients = configuration
+ .GetSection("AzureAd:AllowedClients")
+ .Get()
+ ?? Array.Empty();
+
+ string appId = context.User.FindFirst("appid")?.Value??string.Empty;
+
+ if(appId == string.Empty)
+ { bootLog.LogWarning("No AppId found in the JWT Token. Denying access.");
+ return false;
+ }
+
+ bool appIdIsAllowed = allowedClients.Contains(appId);
+ if(!appIdIsAllowed)
+ {
+ bootLog.LogWarning($"AppId {appId} is not in the list of allowed clients. Denying access.");
+ }
+ else
+ {
+ bootLog.LogInformation($"AppId {appId} is in the list of allowed clients. Allowing access.");
+ }
+ return appIdIsAllowed;
+ }));
+ bootLog.LogTrace("Set policy to Restricted for prod env.");
+ });
+
+ return appBuilder;
+ }
static WebApplicationBuilder AddUtilityServices(IConfiguration systemConfig,
ILogger bootLog,
diff --git a/src/Clients/DevDad.SaaSAdmin.API/PublicModels/LoadProfileResponse.cs b/src/Clients/DevDad.SaaSAdmin.API/PublicModels/LoadProfileResponse.cs
new file mode 100644
index 0000000..ab6d1d2
--- /dev/null
+++ b/src/Clients/DevDad.SaaSAdmin.API/PublicModels/LoadProfileResponse.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace DevDad.SaaSAdmin.API.PublicModels;
+
+public class LoadProfileResponse
+{
+ public string UserId { get; set; } = string.Empty;
+ public string DisplayName {get;set;} = string.Empty;
+ public string SubscriptionSku { get; set; } = string.Empty;
+}
diff --git a/src/Clients/DevDad.SaaSAdmin.API/appsettings.json b/src/Clients/DevDad.SaaSAdmin.API/appsettings.json
index edac7ac..2379193 100644
--- a/src/Clients/DevDad.SaaSAdmin.API/appsettings.json
+++ b/src/Clients/DevDad.SaaSAdmin.API/appsettings.json
@@ -6,6 +6,15 @@
}
},
"AllowedHosts": "*",
+ "AzureAd": {
+ "Instance": "https://login.microsoftonline.com/",
+ "TenantId": "6f9b52cc-54e3-4c18-a92d-35f0448d1e0d",
+ "ClientId": "58f44182-7cbf-4293-a78d-2e4528ca298f",
+ "Audience": "api://58f44182-7cbf-4293-a78d-2e4528ca298f",
+ "AllowedClients": [
+ "63ff669d-b2fb-4367-8471-8605957304c6"
+ ]
+ },
"Architecture":
{
"GlobalBehaviors":[
diff --git a/src/Managers/DevDad.SaaSAdmin.AccountManager/CustomerAccountManager.cs b/src/Managers/DevDad.SaaSAdmin.AccountManager/CustomerAccountManager.cs
index 37cbac4..f631b6e 100644
--- a/src/Managers/DevDad.SaaSAdmin.AccountManager/CustomerAccountManager.cs
+++ b/src/Managers/DevDad.SaaSAdmin.AccountManager/CustomerAccountManager.cs
@@ -112,6 +112,17 @@ public async Task LoadOrCreateCustomerProfileAsync(Load
response.Payload = builderResponse.Payload;
+ // Get the user's current subscirption Sku.
+ // If there isn't one, default to the Free Sub SKU.
+ var userSubSku = builderResponse.Payload?.Subscription?.SKU
+ ?? SubscriptionIdentifiers.SKUS_TDMF_FREE;
+
+ // Get the subscirption Tempalte for that SKU, and use it
+ // to ensure that the User is a member of the correct EntraID Groups
+ // for The DM's Familiar.
+ var skuTemplate = await _catalogAccess.GetCatalogItemAsync(userSubSku);
+ var reconciled = await TryReconcileUserMembership(requestData, response, requestData.UserId!, skuTemplate!);
+
if(response.Payload == null)
{
response.AddError(new ServiceError{
@@ -127,203 +138,217 @@ public async Task LoadOrCreateCustomerProfileAsync(Load
public async Task ManageCustomerSubscriptionAsync(ManageSubscriptionRequest actionRequest)
- {
- ManageSubscriptionResponse thisResponse = new(actionRequest, null);
-
- // When a SubsriptionActionRequest arrives, we'll generally follow the same basic steps:
- if(actionRequest == null)
- {
- ServiceError nullRequestPayload = new()
- {
- Message = "The SubscriptionActionRequest was null.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "NullRequestPayload"
- };
- thisResponse.AddError(nullRequestPayload);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- var requestErrors = TryGetChangeDetail(actionRequest, out SubscriptionActionDetail? actionDetail);
-
- // now, we can interrogate the requestErrors and run a null-check on the details.
- if(requestErrors.Any())
- {
- thisResponse.AddErrors(requestErrors);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- if(actionDetail == null)
- {
- ServiceError nullRequestPayload = new()
- {
- Message = "The SubscriptionActionDetail is null.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "NullRequestPayload"
- };
- thisResponse.AddError(nullRequestPayload);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- // 1 & 2 are the same for every request. Do Those Here.
- // 1: Load the Profile of the identified Customer.
- BuildProfileRequest buildProfRequest = new(actionRequest, actionRequest.CustomerProfileId);
- CustomerProfileResponse builderResponse = await AccountBuilder().LoadOrBuildCustomer(buildProfRequest);
-
- if(builderResponse.HasErrors)
- {
- thisResponse.AddErrors(builderResponse);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- CustomerProfile? customerProfile = builderResponse.Payload;
-
- // If we can neither Load not Create a profile for the identified Customer, we need to bail now.
- if(customerProfile == null)
- {
- ServiceError profileNotFound = new()
- {
- Message = $"The Customer Profile {actionRequest.CustomerProfileId} was not found.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "ProfileNotFound"
- };
- thisResponse.AddError(profileNotFound);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- string? userIdentityId = customerProfile.GetUserIdForVendor(_userIdentityAccess.IdentityVendor);
- if(userIdentityId == null)
- {
- // Something Really, REALLY F***ed up happened to get to this edge case.
- ServiceError noUserIdentityId = new()
- {
- Message = $"The Customer Profile {actionRequest.CustomerProfileId} has no Id in the Identity Service.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "NoUserIdentityId"
- };
- thisResponse.AddError(noUserIdentityId);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- // 2: Load the Catalog Item that gives us the details for the Subscription SKU
- SubscriptionTemplateResource? skuTemplate =
- await _catalogAccess.GetCatalogItemAsync(actionDetail.SubscriptionSku);
+ {
+ ManageSubscriptionResponse thisResponse = new(actionRequest, null);
+
+ // When a SubsriptionActionRequest arrives, we'll generally follow the same basic steps:
+ if (actionRequest == null)
+ {
+ ServiceError nullRequestPayload = new()
+ {
+ Message = "The SubscriptionActionRequest was null.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "NullRequestPayload"
+ };
+ thisResponse.AddError(nullRequestPayload);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ var requestErrors = TryGetChangeDetail(actionRequest, out SubscriptionActionDetail? actionDetail);
+
+ // now, we can interrogate the requestErrors and run a null-check on the details.
+ if (requestErrors.Any())
+ {
+ thisResponse.AddErrors(requestErrors);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ if (actionDetail == null)
+ {
+ ServiceError nullRequestPayload = new()
+ {
+ Message = "The SubscriptionActionDetail is null.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "NullRequestPayload"
+ };
+ thisResponse.AddError(nullRequestPayload);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ // 1 & 2 are the same for every request. Do Those Here.
+ // 1: Load the Profile of the identified Customer.
+ BuildProfileRequest buildProfRequest = new(actionRequest, actionRequest.CustomerProfileId);
+ CustomerProfileResponse builderResponse = await AccountBuilder().LoadOrBuildCustomer(buildProfRequest);
+
+ if (builderResponse.HasErrors)
+ {
+ thisResponse.AddErrors(builderResponse);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ CustomerProfile? customerProfile = builderResponse.Payload;
+
+ // If we can neither Load not Create a profile for the identified Customer, we need to bail now.
+ if (customerProfile == null)
+ {
+ ServiceError profileNotFound = new()
+ {
+ Message = $"The Customer Profile {actionRequest.CustomerProfileId} was not found.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "ProfileNotFound"
+ };
+ thisResponse.AddError(profileNotFound);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ string? userIdentityId = customerProfile.GetUserIdForVendor(_userIdentityAccess.IdentityVendor);
+ if (userIdentityId == null)
+ {
+ // Something Really, REALLY F***ed up happened to get to this edge case.
+ ServiceError noUserIdentityId = new()
+ {
+ Message = $"The Customer Profile {actionRequest.CustomerProfileId} has no Id in the Identity Service.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "NoUserIdentityId"
+ };
+ thisResponse.AddError(noUserIdentityId);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ // 2: Load the Catalog Item that gives us the details for the Subscription SKU
+ SubscriptionTemplateResource? skuTemplate =
+ await _catalogAccess.GetCatalogItemAsync(actionDetail.SubscriptionSku);
+
+ // If we can't load the Template for the provided subscription SKU, we cannot continue.
+ if (skuTemplate == null)
+ {
+ ServiceError skuNotFound = new()
+ {
+ Message = $"The Subscription SKU {actionDetail.SubscriptionSku} was not found in the Catalog.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "SkuNotFound"
+ };
+ thisResponse.AddError(skuNotFound);
+ return thisResponse;
+ }
+
+ // 3 & 4 are going to be different for each Activity. We'll use a Strategy Pattern for those.
+ // 3: Make sure the Activity is applicable to the current Subscription Status
+ // 4: Perform the Activity
+ ModifySubscriptionData changeSubData = new(customerProfile, actionDetail);
+ ModifySubscriptionRequest changeSubscriptionRequest = new(actionRequest, changeSubData);
+ var changeSubscriptionResponse = await AccountBuilder().PerformSubscriptionAction(changeSubscriptionRequest);
+
+ if (changeSubscriptionResponse.HasErrors)
+ {
+ thisResponse.AddErrors(changeSubscriptionResponse);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ if (changeSubscriptionResponse.HasWarnings)
+ {
+ thisResponse.AddErrors(changeSubscriptionResponse);
+ }
+
+ if (changeSubscriptionResponse.Payload == null)
+ {
+ ServiceError noChangeResult = new()
+ {
+ Message = "The Change Subscription Activity did not return a result.",
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "NoChangeResult"
+ };
+ thisResponse.AddError(noChangeResult);
+ thisResponse.Payload = false;
+ return thisResponse;
+ }
+
+ customerProfile = changeSubscriptionResponse.Payload;
+
+ // If the inbound Request has a VendorCustomerId, we need to ensure that that
+ // ID is stored with the Customer's local account profile.
+ customerProfile = UpdateVendorIds(customerProfile, actionDetail);
+
+ // 5, 6, and 7 are the same for every request. Do Those Here.
+ // 5: Save the modified account back to local storage.
+ UserAccountResource accountResource = DomainObjectMapper
+ .MapEntities(customerProfile);
+ var saveResult = await _userAccountAccess.SaveUserAccountAsync(accountResource);
+ if (saveResult.Item2 != null)
+ {
+ thisResponse.AddError(new ServiceError
+ {
+ Message = saveResult.Item2.Message,
+ Severity = ErrorSeverity.Error,
+ Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
+ ErrorKind = "UserAccountSaveError"
+ });
+ return thisResponse;
+ }
+
+ // 6: Reconcile the Authorization Groups that the customer SHOULD have membership in at the Identity Service.
+ //This facility doesn't exist yet. We'll need to add it to the UserIdentityAccess service.
+ var reconciled = await TryReconcileUserMembership(actionRequest, thisResponse, userIdentityId, skuTemplate);
+
+ // 7: Handle the result of all this stuff.
+ if (thisResponse.HasErrors)
+ {
+ thisResponse.Payload = false;
+ // Need a way to send an alert to a Human here.
+ }
+ else
+ {
+ thisResponse.Payload = true;
+ }
+
+ return thisResponse;
+ }
+
+ private async Task TryReconcileUserMembership(
+ OperationRequest actionRequest,
+ OperationResponse thisResponse,
+ string userIdentityId,
+ SubscriptionTemplateResource skuTemplate)
+ {
+ bool result = false;
+
+ var reconcileData = new ReconcileMembershipsData
+ {
+ UserId = userIdentityId!,
+ ExpectedGroups = skuTemplate.ConfersMembershipIn
+ };
+
+ var reconcileGroupsRequest = new ReconcileMembershipsRequest
+ (actionRequest,
+ reconcileData);
+
+ var reconcileResponse = await _userIdentityAccess.ReconcileUserMembershipsAsync(reconcileGroupsRequest);
+ if (reconcileResponse.HasErrors)
+ {
+ thisResponse.AddErrors(reconcileResponse);
+
+ }
- // If we can't load the Template for the provided subscription SKU, we cannot continue.
- if(skuTemplate == null)
- {
- ServiceError skuNotFound = new()
- {
- Message = $"The Subscription SKU {actionDetail.SubscriptionSku} was not found in the Catalog.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "SkuNotFound"
- };
- thisResponse.AddError(skuNotFound);
- return thisResponse;
- }
-
- // 3 & 4 are going to be different for each Activity. We'll use a Strategy Pattern for those.
- // 3: Make sure the Activity is applicable to the current Subscription Status
- // 4: Perform the Activity
- ModifySubscriptionData changeSubData = new(customerProfile, actionDetail);
- ModifySubscriptionRequest changeSubscriptionRequest = new(actionRequest, changeSubData);
- var changeSubscriptionResponse = await AccountBuilder().PerformSubscriptionAction(changeSubscriptionRequest);
-
- if(changeSubscriptionResponse.HasErrors)
- {
- thisResponse.AddErrors(changeSubscriptionResponse);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- if(changeSubscriptionResponse.HasWarnings)
- {
- thisResponse.AddErrors(changeSubscriptionResponse);
- }
-
- if(changeSubscriptionResponse.Payload == null)
- {
- ServiceError noChangeResult = new()
- {
- Message = "The Change Subscription Activity did not return a result.",
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "NoChangeResult"
- };
- thisResponse.AddError(noChangeResult);
- thisResponse.Payload = false;
- return thisResponse;
- }
-
- customerProfile = changeSubscriptionResponse.Payload;
-
- // If the inbound Request has a VendorCustomerId, we need to ensure that that
- // ID is stored with the Customer's local account profile.
- customerProfile = UpdateVendorIds(customerProfile, actionDetail);
-
- // 5, 6, and 7 are the same for every request. Do Those Here.
- // 5: Save the modified account back to local storage.
- UserAccountResource accountResource = DomainObjectMapper
- .MapEntities(customerProfile);
- var saveResult = await _userAccountAccess.SaveUserAccountAsync(accountResource);
- if(saveResult.Item2 != null)
- {
- thisResponse.AddError(new ServiceError{
- Message = saveResult.Item2.Message,
- Severity = ErrorSeverity.Error,
- Site = $"{nameof(CustomerAccountManager)}.{nameof(ManageCustomerSubscriptionAsync)}",
- ErrorKind = "UserAccountSaveError"
- });
- return thisResponse;
- }
-
- // 6: Reconcile the Authorization Groups that the customer SHOULD have membership in at the Identity Service.
- //This facility doesn't exist yet. We'll need to add it to the UserIdentityAccess service.
- var reconcileData = new ReconcileMembershipsData
- {
- UserId = userIdentityId!,
- ExpectedGroups = skuTemplate.ConfersMembershipIn
- };
-
- var reconcileGroupsRequest = new ReconcileMembershipsRequest
- (actionRequest,
- reconcileData);
-
- var reconcileResponse = await _userIdentityAccess.ReconcileUserMembershipsAsync(reconcileGroupsRequest);
- if(reconcileResponse.HasErrors)
- {
- thisResponse.AddErrors(reconcileResponse);
- thisResponse.Payload = false;
- }
-
- string reconcileLog = $"Reconciled User Memberships for {userIdentityId} in {actionRequest.WorkloadName}: Added {reconcileResponse.MembershipsAdded}, Removed {reconcileResponse.MembershipsRemoved}";
- _logger?.LogInformation(reconcileLog);
-
- // 7: Handle the result of all this stuff.
- if(thisResponse.HasErrors)
- {
- thisResponse.Payload = false;
- // Need a way to send an alert to a Human here.
- }
- else
- {
- thisResponse.Payload = true;
- }
-
- return thisResponse;
- }
+ result = reconcileResponse.Successful;
+ string reconcileLog = $"Reconciled User Memberships for {userIdentityId} in {actionRequest.WorkloadName}: Added {reconcileResponse.MembershipsAdded}, Removed {reconcileResponse.MembershipsRemoved}";
+ _logger?.LogInformation(reconcileLog);
+ return result;
+ }
- public async Task StoreCustomerProfileAsync(SaveAccountProfileRequest request)
+ public async Task StoreCustomerProfileAsync(SaveAccountProfileRequest request)
{
CustomerProfileResponse response = new(request);
diff --git a/src/ResourceAccess/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider.csproj b/src/ResourceAccess/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider.csproj
index 7a89bac..34a79de 100644
--- a/src/ResourceAccess/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider.csproj
+++ b/src/ResourceAccess/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider/DevDad.SaaSAdmin.UserAccountAccess.AzureTableProvider.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/Tests/TestConsole/Program.cs b/src/Tests/TestConsole/Program.cs
index 187f9e8..37f7df3 100644
--- a/src/Tests/TestConsole/Program.cs
+++ b/src/Tests/TestConsole/Program.cs
@@ -1,16 +1,13 @@
using System.Net.Http.Json;
-using DevDad.SaaSAdmin.AccountManager.Contracts;
-using DevDad.SaaSAdmin.iFX;
-using DevDad.SaaSAdmin.StoreAccess.Abstractions;
-using DevDad.SaaSAdmin.StoreAccess.LsApi;
+
using DotNetEnv;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestConsole.OtherTests;
-using ThatDeveloperDad.iFX;
-using ThatDeveloperDad.iFX.CollectionUtilities;
-using ThatDeveloperDad.iFX.DomainUtilities;
+using DevDad.SaaSAdmin.iFX;
+using DevDad.SaaSAdmin.StoreAccess.Abstractions;
+using DevDad.SaaSAdmin.StoreAccess.LsApi;
using ThatDeveloperDad.iFX.Serialization;
namespace TestConsole
@@ -24,7 +21,7 @@ static void Main(string[] args)
//TestJsonDrill();
//TestRequestConstruction(bootLogger);
- TestHostedApi(bootLogger);
+
/* IConfiguration systemConfig = LoadSystemConfiguration(bootLogger);
IServiceProvider globalUtilities = BuildUtilityProvider(systemConfig, bootLogger);
@@ -111,24 +108,7 @@ static void Main(string[] args)
bootLogger.LogInformation("Nothing more to do. Imma take a nap right here.");
}
- static void TestHostedApi(ILogger logger)
- {
- string url = "https://tdmf-admin-api-f4a5caaydzgncqf4.eastus2-01.azurewebsites.net/loadProfile";
- string userId = "a996d415-159c-47fa-ae5a-4b6db581ebb1";
- var request = new
- {
- UserEntraId = userId
- };
-
- var apiCLient = new HttpClient();
-
- var response = apiCLient.PostAsJsonAsync