-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
MembersController.cs
263 lines (246 loc) · 11.6 KB
/
MembersController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
using System.Net;
using Bit.Api.AdminConsole.Public.Models.Request;
using Bit.Api.AdminConsole.Public.Models.Response;
using Bit.Api.Models.Public.Response;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Context;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.AdminConsole.Public.Controllers;
[Route("public/members")]
[Authorize("Organization")]
public class MembersController : Controller
{
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IGroupRepository _groupRepository;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly ICurrentContext _currentContext;
private readonly IUpdateOrganizationUserCommand _updateOrganizationUserCommand;
private readonly IUpdateOrganizationUserGroupsCommand _updateOrganizationUserGroupsCommand;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IPaymentService _paymentService;
private readonly IOrganizationRepository _organizationRepository;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
public MembersController(
IOrganizationUserRepository organizationUserRepository,
IGroupRepository groupRepository,
IOrganizationService organizationService,
IUserService userService,
ICurrentContext currentContext,
IUpdateOrganizationUserCommand updateOrganizationUserCommand,
IUpdateOrganizationUserGroupsCommand updateOrganizationUserGroupsCommand,
IApplicationCacheService applicationCacheService,
IPaymentService paymentService,
IOrganizationRepository organizationRepository,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IRemoveOrganizationUserCommand removeOrganizationUserCommand)
{
_organizationUserRepository = organizationUserRepository;
_groupRepository = groupRepository;
_organizationService = organizationService;
_userService = userService;
_currentContext = currentContext;
_updateOrganizationUserCommand = updateOrganizationUserCommand;
_updateOrganizationUserGroupsCommand = updateOrganizationUserGroupsCommand;
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
_organizationRepository = organizationRepository;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_removeOrganizationUserCommand = removeOrganizationUserCommand;
}
/// <summary>
/// Retrieve a member.
/// </summary>
/// <remarks>
/// Retrieves the details of an existing member of the organization. You need only supply the
/// unique member identifier that was returned upon member creation.
/// </remarks>
/// <param name="id">The identifier of the member to be retrieved.</param>
[HttpGet("{id}")]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Get(Guid id)
{
var (orgUser, collections) = await _organizationUserRepository.GetDetailsByIdWithCollectionsAsync(id);
if (orgUser == null || orgUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var response = new MemberResponseModel(orgUser, await _userService.TwoFactorIsEnabledAsync(orgUser),
collections);
return new JsonResult(response);
}
/// <summary>
/// Retrieve a member's group ids
/// </summary>
/// <remarks>
/// Retrieves the unique identifiers for all groups that are associated with this member. You need only
/// supply the unique member identifier that was returned upon member creation.
/// </remarks>
/// <param name="id">The identifier of the member to be retrieved.</param>
[HttpGet("{id}/group-ids")]
[ProducesResponseType(typeof(HashSet<Guid>), (int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> GetGroupIds(Guid id)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var groupIds = await _groupRepository.GetManyIdsByUserIdAsync(id);
return new JsonResult(groupIds);
}
/// <summary>
/// List all members.
/// </summary>
/// <remarks>
/// Returns a list of your organization's members.
/// Member objects listed in this call do not include information about their associated collections.
/// </remarks>
[HttpGet]
[ProducesResponseType(typeof(ListResponseModel<MemberResponseModel>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> List()
{
var organizationUserUserDetails = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(_currentContext.OrganizationId.Value);
// TODO: Get all CollectionUser associations for the organization and marry them up here for the response.
var orgUsersTwoFactorIsEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(organizationUserUserDetails);
var memberResponses = organizationUserUserDetails.Select(u =>
{
return new MemberResponseModel(u, orgUsersTwoFactorIsEnabled.FirstOrDefault(tuple => tuple.user == u).twoFactorIsEnabled, null);
});
var response = new ListResponseModel<MemberResponseModel>(memberResponses);
return new JsonResult(response);
}
/// <summary>
/// Create a member.
/// </summary>
/// <remarks>
/// Creates a new member object by inviting a user to the organization.
/// </remarks>
/// <param name="model">The request model.</param>
[HttpPost]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody] MemberCreateRequestModel model)
{
var hasStandaloneSecretsManager = false;
var organization = await _organizationRepository.GetByIdAsync(_currentContext.OrganizationId!.Value);
if (organization != null)
{
hasStandaloneSecretsManager = await _paymentService.HasSecretsManagerStandalone(organization);
}
var invite = model.ToOrganizationUserInvite();
invite.AccessSecretsManager = hasStandaloneSecretsManager;
var user = await _organizationService.InviteUserAsync(_currentContext.OrganizationId.Value, null,
systemUser: null, invite, model.ExternalId);
var response = new MemberResponseModel(user, invite.Collections);
return new JsonResult(response);
}
/// <summary>
/// Update a member.
/// </summary>
/// <remarks>
/// Updates the specified member object. If a property is not provided,
/// the value of the existing property will be reset.
/// </remarks>
/// <param name="id">The identifier of the member to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}")]
[ProducesResponseType(typeof(MemberResponseModel), (int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Put(Guid id, [FromBody] MemberUpdateRequestModel model)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
var updatedUser = model.ToOrganizationUser(existingUser);
var associations = model.Collections?.Select(c => c.ToCollectionAccessSelection()).ToList();
await _updateOrganizationUserCommand.UpdateUserAsync(updatedUser, null, associations, model.Groups);
MemberResponseModel response = null;
if (existingUser.UserId.HasValue)
{
var existingUserDetails = await _organizationUserRepository.GetDetailsByIdAsync(id);
response = new MemberResponseModel(existingUserDetails,
await _userService.TwoFactorIsEnabledAsync(existingUserDetails), associations);
}
else
{
response = new MemberResponseModel(updatedUser, associations);
}
return new JsonResult(response);
}
/// <summary>
/// Update a member's groups.
/// </summary>
/// <remarks>
/// Updates the specified member's group associations.
/// </remarks>
/// <param name="id">The identifier of the member to be updated.</param>
/// <param name="model">The request model.</param>
[HttpPut("{id}/group-ids")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> PutGroupIds(Guid id, [FromBody] UpdateGroupIdsRequestModel model)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _updateOrganizationUserGroupsCommand.UpdateUserGroupsAsync(existingUser, model.GroupIds);
return new OkResult();
}
/// <summary>
/// Remove a member.
/// </summary>
/// <remarks>
/// Permanently removes a member from the organization. This cannot be undone.
/// The user account will still remain. The user is only removed from the organization.
/// </remarks>
/// <param name="id">The identifier of the member to be removed.</param>
[HttpDelete("{id}")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> Remove(Guid id)
{
var user = await _organizationUserRepository.GetByIdAsync(id);
if (user == null || user.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _removeOrganizationUserCommand.RemoveUserAsync(_currentContext.OrganizationId.Value, id, null);
return new OkResult();
}
/// <summary>
/// Re-invite a member.
/// </summary>
/// <remarks>
/// Re-sends the invitation email to an organization member.
/// </remarks>
/// <param name="id">The identifier of the member to re-invite.</param>
[HttpPost("{id}/reinvite")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorResponseModel), (int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> PostReinvite(Guid id)
{
var existingUser = await _organizationUserRepository.GetByIdAsync(id);
if (existingUser == null || existingUser.OrganizationId != _currentContext.OrganizationId)
{
return new NotFoundResult();
}
await _organizationService.ResendInviteAsync(_currentContext.OrganizationId.Value, null, id);
return new OkResult();
}
}