mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 00:22:50 -05:00
[SM-1293] Add endpoint to fetch secret's access policies (#4146)
* Add authz handling for secret access policy reads * Add the ability to fetch secret access polices from the repository * refactor response models * Add new endpoint
This commit is contained in:
@ -24,6 +24,7 @@ public class AccessPoliciesController : Controller
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IProjectRepository _projectRepository;
|
||||
private readonly ISecretRepository _secretRepository;
|
||||
private readonly IServiceAccountGrantedPolicyUpdatesQuery _serviceAccountGrantedPolicyUpdatesQuery;
|
||||
private readonly IServiceAccountRepository _serviceAccountRepository;
|
||||
private readonly IUpdateServiceAccountGrantedPoliciesCommand _updateServiceAccountGrantedPoliciesCommand;
|
||||
@ -41,6 +42,7 @@ public class AccessPoliciesController : Controller
|
||||
IAccessPolicyRepository accessPolicyRepository,
|
||||
IServiceAccountRepository serviceAccountRepository,
|
||||
IProjectRepository projectRepository,
|
||||
ISecretRepository secretRepository,
|
||||
IAccessClientQuery accessClientQuery,
|
||||
IServiceAccountGrantedPolicyUpdatesQuery serviceAccountGrantedPolicyUpdatesQuery,
|
||||
IProjectServiceAccountsAccessPoliciesUpdatesQuery projectServiceAccountsAccessPoliciesUpdatesQuery,
|
||||
@ -52,6 +54,7 @@ public class AccessPoliciesController : Controller
|
||||
_currentContext = currentContext;
|
||||
_serviceAccountRepository = serviceAccountRepository;
|
||||
_projectRepository = projectRepository;
|
||||
_secretRepository = secretRepository;
|
||||
_accessPolicyRepository = accessPolicyRepository;
|
||||
_updateServiceAccountGrantedPoliciesCommand = updateServiceAccountGrantedPoliciesCommand;
|
||||
_accessClientQuery = accessClientQuery;
|
||||
@ -259,6 +262,22 @@ public class AccessPoliciesController : Controller
|
||||
return new ProjectServiceAccountsAccessPoliciesResponseModel(results);
|
||||
}
|
||||
|
||||
[HttpGet("/secrets/{secretId}/access-policies")]
|
||||
public async Task<SecretAccessPoliciesResponseModel> GetSecretAccessPoliciesAsync(Guid secretId)
|
||||
{
|
||||
var secret = await _secretRepository.GetByIdAsync(secretId);
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(User, secret, SecretOperations.ReadAccessPolicies);
|
||||
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User)!.Value;
|
||||
var accessPolicies = await _accessPolicyRepository.GetSecretAccessPoliciesAsync(secretId, userId);
|
||||
return new SecretAccessPoliciesResponseModel(accessPolicies, userId);
|
||||
}
|
||||
|
||||
private async Task<(AccessClientType AccessClientType, Guid UserId)> CheckUserHasWriteAccessToProjectAsync(
|
||||
Project project)
|
||||
{
|
||||
|
@ -9,161 +9,133 @@ public abstract class BaseAccessPolicyResponseModel : ResponseModel
|
||||
{
|
||||
protected BaseAccessPolicyResponseModel(BaseAccessPolicy baseAccessPolicy, string obj) : base(obj)
|
||||
{
|
||||
Id = baseAccessPolicy.Id;
|
||||
Read = baseAccessPolicy.Read;
|
||||
Write = baseAccessPolicy.Write;
|
||||
CreationDate = baseAccessPolicy.CreationDate;
|
||||
RevisionDate = baseAccessPolicy.RevisionDate;
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public bool Read { get; set; }
|
||||
public bool Write { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime RevisionDate { get; set; }
|
||||
|
||||
public string? GetUserDisplayName(User? user)
|
||||
protected static string? GetUserDisplayName(User? user)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(user?.Name) ? user?.Email : user?.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
public class UserAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "userProjectAccessPolicy";
|
||||
private const string _objectName = "userAccessPolicy";
|
||||
|
||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy) : base(accessPolicy, _objectName)
|
||||
{
|
||||
SetProperties(accessPolicy);
|
||||
}
|
||||
|
||||
public UserProjectAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||
public UserAccessPolicyResponseModel(UserProjectAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||
{
|
||||
CurrentUser = currentUserId == accessPolicy.User?.Id;
|
||||
SetProperties(accessPolicy);
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
}
|
||||
|
||||
public UserProjectAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
|
||||
public UserAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||
{
|
||||
CurrentUser = currentUserId == accessPolicy.User?.Id;
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
}
|
||||
|
||||
public UserAccessPolicyResponseModel(UserSecretAccessPolicy accessPolicy, Guid currentUserId) : base(accessPolicy, _objectName)
|
||||
{
|
||||
CurrentUser = currentUserId == accessPolicy.User?.Id;
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
}
|
||||
|
||||
public UserAccessPolicyResponseModel() : base(new UserProjectAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? OrganizationUserId { get; set; }
|
||||
public string? OrganizationUserName { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public bool? CurrentUser { get; set; }
|
||||
|
||||
private void SetProperties(UserProjectAccessPolicy accessPolicy)
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
UserId = accessPolicy.User?.Id;
|
||||
}
|
||||
}
|
||||
|
||||
public class UserServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
public class GroupAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "userServiceAccountAccessPolicy";
|
||||
private const string _objectName = "groupAccessPolicy";
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
SetProperties(accessPolicy);
|
||||
}
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel(UserServiceAccountAccessPolicy accessPolicy, Guid userId)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
SetProperties(accessPolicy);
|
||||
CurrentUser = accessPolicy.User?.Id == userId;
|
||||
}
|
||||
|
||||
public UserServiceAccountAccessPolicyResponseModel() : base(new UserServiceAccountAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? OrganizationUserId { get; set; }
|
||||
public string? OrganizationUserName { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public bool CurrentUser { get; set; }
|
||||
|
||||
private void SetProperties(UserServiceAccountAccessPolicy accessPolicy)
|
||||
{
|
||||
OrganizationUserId = accessPolicy.OrganizationUserId;
|
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
|
||||
OrganizationUserName = GetUserDisplayName(accessPolicy.User);
|
||||
UserId = accessPolicy.User?.Id;
|
||||
}
|
||||
}
|
||||
|
||||
public class GroupProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "groupProjectAccessPolicy";
|
||||
|
||||
public GroupProjectAccessPolicyResponseModel(GroupProjectAccessPolicy accessPolicy)
|
||||
public GroupAccessPolicyResponseModel(GroupProjectAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
GroupId = accessPolicy.GroupId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
GroupName = accessPolicy.Group?.Name;
|
||||
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
|
||||
}
|
||||
|
||||
public GroupProjectAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName)
|
||||
public GroupAccessPolicyResponseModel(GroupServiceAccountAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
GroupId = accessPolicy.GroupId;
|
||||
GroupName = accessPolicy.Group?.Name;
|
||||
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
|
||||
}
|
||||
|
||||
public GroupAccessPolicyResponseModel(GroupSecretAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
GroupId = accessPolicy.GroupId;
|
||||
GroupName = accessPolicy.Group?.Name;
|
||||
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
|
||||
}
|
||||
|
||||
public GroupAccessPolicyResponseModel() : base(new GroupProjectAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? GroupId { get; set; }
|
||||
public string? GroupName { get; set; }
|
||||
public bool? CurrentUserInGroup { get; set; }
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
}
|
||||
|
||||
public class GroupServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "groupServiceAccountAccessPolicy";
|
||||
|
||||
public GroupServiceAccountAccessPolicyResponseModel(GroupServiceAccountAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
GroupId = accessPolicy.GroupId;
|
||||
GroupName = accessPolicy.Group?.Name;
|
||||
GrantedServiceAccountId = accessPolicy.GrantedServiceAccountId;
|
||||
CurrentUserInGroup = accessPolicy.CurrentUserInGroup;
|
||||
}
|
||||
|
||||
public GroupServiceAccountAccessPolicyResponseModel() : base(new GroupServiceAccountAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? GroupId { get; set; }
|
||||
public string? GroupName { get; set; }
|
||||
public Guid? GrantedServiceAccountId { get; set; }
|
||||
public bool? CurrentUserInGroup { get; set; }
|
||||
}
|
||||
|
||||
public class ServiceAccountProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
public class ServiceAccountAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "serviceAccountProjectAccessPolicy";
|
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
|
||||
public ServiceAccountAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
ServiceAccountId = accessPolicy.ServiceAccountId;
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
|
||||
GrantedProjectName = accessPolicy.GrantedProject?.Name;
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel()
|
||||
public ServiceAccountAccessPolicyResponseModel(ServiceAccountSecretAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
ServiceAccountId = accessPolicy.ServiceAccountId;
|
||||
ServiceAccountName = accessPolicy.ServiceAccount?.Name;
|
||||
}
|
||||
|
||||
public ServiceAccountAccessPolicyResponseModel()
|
||||
: base(new ServiceAccountProjectAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? ServiceAccountId { get; set; }
|
||||
public string? ServiceAccountName { get; set; }
|
||||
}
|
||||
|
||||
public class GrantedProjectAccessPolicyResponseModel : BaseAccessPolicyResponseModel
|
||||
{
|
||||
private const string _objectName = "grantedProjectAccessPolicy";
|
||||
|
||||
public GrantedProjectAccessPolicyResponseModel(ServiceAccountProjectAccessPolicy accessPolicy)
|
||||
: base(accessPolicy, _objectName)
|
||||
{
|
||||
GrantedProjectId = accessPolicy.GrantedProjectId;
|
||||
GrantedProjectName = accessPolicy.GrantedProject?.Name;
|
||||
}
|
||||
|
||||
public GrantedProjectAccessPolicyResponseModel()
|
||||
: base(new ServiceAccountProjectAccessPolicy(), _objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public Guid? GrantedProjectId { get; set; }
|
||||
public string? GrantedProjectName { get; set; }
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class GrantedProjectAccessPolicyPermissionDetailsResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "grantedProjectAccessPolicyPermissionDetails";
|
||||
|
||||
public GrantedProjectAccessPolicyPermissionDetailsResponseModel(
|
||||
ServiceAccountProjectAccessPolicyPermissionDetails apPermissionDetails, string obj = _objectName) : base(obj)
|
||||
{
|
||||
AccessPolicy = new GrantedProjectAccessPolicyResponseModel(apPermissionDetails.AccessPolicy);
|
||||
HasPermission = apPermissionDetails.HasPermission;
|
||||
}
|
||||
|
||||
public GrantedProjectAccessPolicyPermissionDetailsResponseModel()
|
||||
: base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public GrantedProjectAccessPolicyResponseModel AccessPolicy { get; set; } = new();
|
||||
public bool HasPermission { get; set; }
|
||||
}
|
@ -15,10 +15,10 @@ public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserProjectAccessPolicy accessPolicy:
|
||||
UserAccessPolicies.Add(new UserProjectAccessPolicyResponseModel(accessPolicy, userId));
|
||||
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
|
||||
break;
|
||||
case GroupProjectAccessPolicy accessPolicy:
|
||||
GroupAccessPolicies.Add(new GroupProjectAccessPolicyResponseModel(accessPolicy));
|
||||
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -28,7 +28,7 @@ public class ProjectPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
}
|
||||
|
||||
public List<UserProjectAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
|
||||
public List<GroupProjectAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ public class ProjectServiceAccountsAccessPoliciesResponseModel : ResponseModel
|
||||
}
|
||||
|
||||
ServiceAccountAccessPolicies = projectServiceAccountsAccessPolicies.ServiceAccountAccessPolicies
|
||||
.Select(x => new ServiceAccountProjectAccessPolicyResponseModel(x)).ToList();
|
||||
.Select(x => new ServiceAccountAccessPolicyResponseModel(x)).ToList();
|
||||
}
|
||||
|
||||
public ProjectServiceAccountsAccessPoliciesResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public List<ServiceAccountProjectAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
|
||||
public List<ServiceAccountAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class SecretAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "secretAccessPolicies";
|
||||
|
||||
public SecretAccessPoliciesResponseModel(SecretAccessPolicies? accessPolicies, Guid userId) :
|
||||
base(_objectName)
|
||||
{
|
||||
if (accessPolicies == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UserAccessPolicies = accessPolicies.UserAccessPolicies.Select(x => new UserAccessPolicyResponseModel(x, userId)).ToList();
|
||||
GroupAccessPolicies = accessPolicies.GroupAccessPolicies.Select(x => new GroupAccessPolicyResponseModel(x)).ToList();
|
||||
ServiceAccountAccessPolicies = accessPolicies.ServiceAccountAccessPolicies.Select(x => new ServiceAccountAccessPolicyResponseModel(x)).ToList();
|
||||
}
|
||||
|
||||
public SecretAccessPoliciesResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = [];
|
||||
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = [];
|
||||
public List<ServiceAccountAccessPolicyResponseModel> ServiceAccountAccessPolicies { get; set; } = [];
|
||||
|
||||
}
|
@ -18,13 +18,13 @@ public class ServiceAccountGrantedPoliciesPermissionDetailsResponseModel : Respo
|
||||
}
|
||||
|
||||
GrantedProjectPolicies = grantedPoliciesPermissionDetails.ProjectGrantedPolicies
|
||||
.Select(x => new ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
|
||||
.Select(x => new GrantedProjectAccessPolicyPermissionDetailsResponseModel(x)).ToList();
|
||||
}
|
||||
|
||||
public ServiceAccountGrantedPoliciesPermissionDetailsResponseModel() : base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public List<ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
|
||||
public List<GrantedProjectAccessPolicyPermissionDetailsResponseModel> GrantedProjectPolicies { get; set; } =
|
||||
[];
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
switch (baseAccessPolicy)
|
||||
{
|
||||
case UserServiceAccountAccessPolicy accessPolicy:
|
||||
UserAccessPolicies.Add(new UserServiceAccountAccessPolicyResponseModel(accessPolicy, userId));
|
||||
UserAccessPolicies.Add(new UserAccessPolicyResponseModel(accessPolicy, userId));
|
||||
break;
|
||||
case GroupServiceAccountAccessPolicy accessPolicy:
|
||||
GroupAccessPolicies.Add(new GroupServiceAccountAccessPolicyResponseModel(accessPolicy));
|
||||
GroupAccessPolicies.Add(new GroupAccessPolicyResponseModel(accessPolicy));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -33,7 +33,7 @@ public class ServiceAccountPeopleAccessPoliciesResponseModel : ResponseModel
|
||||
{
|
||||
}
|
||||
|
||||
public List<UserServiceAccountAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
public List<UserAccessPolicyResponseModel> UserAccessPolicies { get; set; } = new();
|
||||
|
||||
public List<GroupServiceAccountAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
public List<GroupAccessPolicyResponseModel> GroupAccessPolicies { get; set; } = new();
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.SecretsManager.Models.Data;
|
||||
|
||||
namespace Bit.Api.SecretsManager.Models.Response;
|
||||
|
||||
public class ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel : ResponseModel
|
||||
{
|
||||
private const string _objectName = "serviceAccountProjectAccessPolicyPermissionDetails";
|
||||
|
||||
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel(
|
||||
ServiceAccountProjectAccessPolicyPermissionDetails apPermissionDetails, string obj = _objectName) : base(obj)
|
||||
{
|
||||
AccessPolicy = new ServiceAccountProjectAccessPolicyResponseModel(apPermissionDetails.AccessPolicy);
|
||||
HasPermission = apPermissionDetails.HasPermission;
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyPermissionDetailsResponseModel()
|
||||
: base(_objectName)
|
||||
{
|
||||
}
|
||||
|
||||
public ServiceAccountProjectAccessPolicyResponseModel AccessPolicy { get; set; } = new();
|
||||
public bool HasPermission { get; set; }
|
||||
}
|
Reference in New Issue
Block a user