Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add delete user identity server endpoint and delete profile and forms api endpoint #442

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
38 changes: 38 additions & 0 deletions backend/src/StamAcasa.Api/Controllers/ProfileAdminController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using StamAcasa.Common.Services;

namespace StamAcasa.Api.Controllers
{
[Route("api/profile")]
[Authorize(AuthenticationSchemes = "adminApi")]
[ApiExplorerSettings(IgnoreApi = true)]
[ApiController]
public class ProfileAdminController : ControllerBase
{
private readonly IUserService _userService;

public ProfileAdminController(IUserService userService)
{
_userService = userService;
}

/// <summary>
/// Delete user profile and related data.
/// </summary>
/// <returns></returns>
[HttpDelete]
public async Task<IActionResult> DeleteProfile()
{
var sub = User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
if (sub == null)
return new UnauthorizedResult();

await _userService.DeleteUserAndDependentData(sub);

return Ok();
}
}
}
3 changes: 2 additions & 1 deletion backend/src/StamAcasa.Api/Controllers/ProfileController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
Expand Down
8 changes: 6 additions & 2 deletions backend/src/StamAcasa.Api/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
"ApiSecret": "svpqYnJSR8xzn8Rl"
},
{
"ApiName": "usersApi",
"ApiSecret": "st4k!b7s$af201cv"
"ApiName": "usersApi",
"ApiSecret": "st4k!b7s$af201cv"
},
{
"ApiName": "adminApi",
"ApiSecret": "3rXGcXpcPKxAIshJ"
}
],
"TargetFolder": "answers",
Expand Down
1 change: 1 addition & 0 deletions backend/src/StamAcasa.Common/Services/IUserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public interface IUserService
Task<IEnumerable<UserInfo>> GetDependentInfo(string sub);
Task<IEnumerable<UserInfo>> GetAll();
Task<IEnumerable<UserInfo>> GetAllParents();
Task DeleteUserAndDependentData(string sub);
}
}
34 changes: 34 additions & 0 deletions backend/src/StamAcasa.Common/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,39 @@ await _context.Users.ForEachAsync(u =>
var result = parents.Select(_mapper.Map<UserInfo>);
return result;
}

public async Task DeleteUserAndDependentData(string sub)
{
var user = await _context.Users
.Include(u => u.DependentUsers)
.FirstOrDefaultAsync(u => u.Sub == sub);
if (user == null)
{
return;
}

var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _context.Database.BeginTransaction())
{
await DeleteUserAndForms(user);

foreach (var familyMember in user.DependentUsers)
{
await DeleteUserAndForms(familyMember);
}

await _context.SaveChangesAsync();
transaction.Commit();
}
});
}

private async Task DeleteUserAndForms(User user)
{
await _context.Database.ExecuteSqlRawAsync("DELETE FROM \"Forms\" WHERE \"UserId\" = {0}", new object[] { user.Id });
_context.Remove(user);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityServer.Data;
using IdentityServer4;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Validation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;

namespace StamAcasa.IdentityServer.Quickstart.Account
{
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class DeleteAccountController : ControllerBase
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly DefaultTokenService _tokenService;
private readonly IStamAcasaIdentityConfiguration _identityConfiguration;
private readonly IHttpClientFactory _clientFactory;
private readonly string _apiUrl;
private const string IdsrvClientId = "idsrvClient";

public DeleteAccountController(UserManager<ApplicationUser> userManager, DefaultTokenService tokenService, IStamAcasaIdentityConfiguration identityConfiguration, IHttpClientFactory clientFactory, IConfiguration configuration)
{
_userManager = userManager;
_tokenService = tokenService;
_identityConfiguration = identityConfiguration;
_clientFactory = clientFactory;
_apiUrl = configuration["StamAcasaApi"];
}

[HttpPost]
public async Task<IActionResult> DeleteAccountAsync([FromBody] DeleteAccountModel model)
{
var user = await _userManager.FindByNameAsync(model.Username);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should not provide info to anyone if a user with specified username exists or not

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree with this one.
Can you please make the change @irinel-nistor ?

if (user == null || !await _userManager.CheckPasswordAsync(user, model.Password))
{
return new UnauthorizedResult();
}

string tokenValue = await CreateAccessToken(user);
var deleteResponse = await DeleteApiUserDataAsync(tokenValue);
if (!deleteResponse.IsSuccessStatusCode)
{
return StatusCode(StatusCodes.Status500InternalServerError, new { message = "Unexpected error occurred deleting user data" });
}

var response = await _userManager.DeleteAsync(user);
if (!response.Succeeded)
{
return StatusCode(StatusCodes.Status500InternalServerError, $"Unexpected error occurred deleting user with ID '{user.Id}'.");
}

return Ok();
}

private async Task<HttpResponseMessage> DeleteApiUserDataAsync(string tokenValue)
{
var client = _clientFactory.CreateClient();

var request = new HttpRequestMessage(HttpMethod.Delete,
$"{_apiUrl}/api/Profile");
request.Headers.Add("Authorization", $"Bearer {tokenValue}");

return await client.SendAsync(request);
}

private async Task<string> CreateAccessToken(ApplicationUser user)
{
var IdentityUser = new IdentityServerUser(user.Id)
{
IdentityProvider = IdentityServerConstants.LocalIdentityProvider,
AuthenticationTime = DateTime.UtcNow,
};

var request = new TokenCreationRequest
{
Subject = IdentityUser.CreatePrincipal(),
IncludeAllIdentityClaims = true,
Resources = new Resources(_identityConfiguration.Ids, _identityConfiguration.Apis())
};

request.ValidatedRequest = new ValidatedRequest
{
Subject = request.Subject
};
request.ValidatedRequest.SetClient(_identityConfiguration.Clients.FirstOrDefault(c => c.ClientId == IdsrvClientId));

var accesToken = await _tokenService.CreateAccessTokenAsync(request);
var token = await _tokenService.CreateSecurityTokenAsync(accesToken);
return token;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace StamAcasa.IdentityServer.Quickstart.Account
{
public class DeleteAccountModel
{
public string Username { get; set; }

public string Password { get; set; }
}
}
3 changes: 3 additions & 0 deletions backend/src/StamAcasa.IdentityServer/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using StamAcasa.IdentityServer;
using StamAcasa.Common.Queue;
using EasyNetQ;
using IdentityServer4.Services;

namespace IdentityServer
{
Expand Down Expand Up @@ -92,6 +93,8 @@ public void ConfigureServices(IServiceCollection services)
Configuration.GetValue<string>("RabbitMQ:User"),
Configuration.GetValue<string>("RabbitMQ:Password"))
));
services.AddScoped<DefaultTokenService>();
services.AddSingleton(_identityConfiguration);
services.AddSingleton<IQueueService, QueueService>();
services.AddSingleton<PasswordValidationMessages>();
}
Expand Down
21 changes: 21 additions & 0 deletions backend/src/StamAcasa.IdentityServer/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"Name": "usersApi",
"Secret": "st4k!b7s$af201cv",
"ClaimList": [ "openid", "email" ]
},
{
"Name": "adminApi",
"Secret": "3rXGcXpcPKxAIshJ",
"ClaimList": [ "openid" ]
}
],
"ClientApplications": [
Expand Down Expand Up @@ -125,6 +130,21 @@
],
"AllowAccessTokensViaBrowser": true,
"AccessTokenType": 1
},
{
"ClientId": "idsrvClient",
"ClientSecrets": [
{
"Value": "HcBFXeMlI/5iUCtyLErjz4rJ66u2Bdk+ytqj0aELxhw="
}
],
"ClientName": "Identiy Server Client",
"AllowedGrantTypes": [ "client_credentials" ],
"AllowedScopes": [
"openid",
"adminApi"
],
"AccessTokenType": 1
}
],
"EnableEmailConfirmation": true,
Expand All @@ -148,6 +168,7 @@
"Password": "password"
},
"IdentityServerPublicOrigin": "",
"StamAcasaApi": "http://stamacasa.api",
"Certificate": {
"Base64Encoded": "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",
"Password": "1234"
Expand Down