Skip to content

Commit 7161527

Browse files
committed
Expand create and update user endpionts
Signed-off-by: montesm <[email protected]>
1 parent b371e7d commit 7161527

File tree

4 files changed

+157
-24
lines changed

4 files changed

+157
-24
lines changed
Lines changed: 119 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,34 @@
11
package io.unityfoundation.auth;
22

3-
import io.micronaut.core.annotation.Nullable;
43
import io.micronaut.http.HttpResponse;
4+
import io.micronaut.http.HttpStatus;
55
import io.micronaut.http.annotation.*;
66
import io.micronaut.security.annotation.Secured;
77
import io.micronaut.security.authentication.Authentication;
88
import io.micronaut.security.rules.SecurityRule;
99
import io.micronaut.serde.annotation.Serdeable;
1010
import io.unityfoundation.auth.entities.*;
11+
import jakarta.transaction.Transactional;
1112
import jakarta.validation.constraints.NotBlank;
1213
import jakarta.validation.constraints.NotEmpty;
1314
import jakarta.validation.constraints.NotNull;
1415

1516
import java.util.List;
17+
import java.util.Objects;
18+
import java.util.Optional;
1619

1720
@Secured(SecurityRule.IS_AUTHENTICATED)
1821
@Controller("/api/users")
1922
public class UserController {
2023

2124
private final UserRepo userRepo;
2225
private final TenantRepo tenantRepo;
26+
private final RoleRepo roleRepo;
2327

24-
public UserController(UserRepo userRepo, TenantRepo tenantRepo) {
28+
public UserController(UserRepo userRepo, TenantRepo tenantRepo, RoleRepo roleRepo) {
2529
this.userRepo = userRepo;
2630
this.tenantRepo = tenantRepo;
31+
this.roleRepo = roleRepo;
2732
}
2833

2934
@Post
@@ -34,55 +39,149 @@ public HttpResponse<?> createUser(@Body AddUserRequest requestDTO,
3439

3540
// reject if the declared tenant does not exist
3641
if (tenantRepo.existsById(requestTenantId)) {
37-
return HttpResponse.badRequest("Tenant does not exist");
42+
return HttpResponse.notFound("Tenant does not exist");
3843
}
3944

45+
Role unityAdministrator = roleRepo.findByName("Unity Administrator");
46+
47+
// ignore roles not defined by application
48+
List<Long> rolesIntersection = getRolesIntersection(requestDTO.roles());
49+
4050
// reject if caller is not a unity nor tenant admin of the declared tenant
41-
if (!isUserUnityOrTenantAdmin(authentication.getName(), requestTenantId)) {
42-
return HttpResponse.badRequest("Authenticated user is not authorized to make changes under declared tenant.");
51+
String authUserEmail = authentication.getName();
52+
if (!userRepo.existsByEmailAndRoleEqualsUnityAdmin(authUserEmail)) {
53+
if (!userRepo.existsByEmailAndTenantEqualsAndIsTenantAdmin(authUserEmail, requestTenantId)) {
54+
return HttpResponse.status(HttpStatus.FORBIDDEN,
55+
"Authenticated user is not authorized to make changes under declared tenant.");
56+
} else if (rolesIntersection.stream().anyMatch(roleId -> roleId.equals(unityAdministrator.getId()))){
57+
// authenticated tenant admin user cannot grant unity admin role
58+
return HttpResponse.status(HttpStatus.FORBIDDEN,
59+
"Authenticated user is not authorized to grant Unity Admin");
60+
}
4361
}
4462

4563
// reject if new user already exists under a tenant
4664
if (userRepo.existsByEmailAndTenantId(requestDTO.email(), requestTenantId)) {
4765
return HttpResponse.badRequest("User already exists under declared tenant.");
4866
}
4967

50-
// reject if the declared roles supersede that of the authenticated user if authenticated user is not a unity admin
51-
// ie. first condition is applied assumes that authenticated user is a tenant admin of the declared tenant
52-
5368
// if the new user exists, create a new user-role entry
5469
// otherwise, create the user along with user-role entry
70+
Optional<User> userOptional = userRepo.findByEmail(requestDTO.email());
71+
User user;
72+
if (userOptional.isEmpty()) {
73+
User newUser = new User();
74+
newUser.setEmail(requestDTO.email());
75+
newUser.setPassword(requestDTO.password());
76+
newUser.setFirstName(requestDTO.firstName);
77+
newUser.setLastName(requestDTO.lastName);
78+
newUser.setStatus(User.UserStatus.ENABLED);
79+
user = userRepo.save(newUser);
80+
} else {
81+
user = userOptional.get();
82+
}
5583

84+
rolesIntersection.forEach(roleId -> userRepo.insertUserRole(user.getId(), requestTenantId, roleId));
85+
86+
return HttpResponse.created(new UserResponse(user.getId(),
87+
user.getEmail(),
88+
user.getFirstName(),
89+
user.getLastName(),
90+
rolesIntersection));
5691
}
5792

5893
@Patch("{id}/roles")
59-
public HttpResponse<UserResponse> updateUserRoles(@PathVariable Long id, @Body UpdateUserRolesRequest requestDTO,
94+
public HttpResponse<?> updateUserRoles(@PathVariable Long id, @Body UpdateUserRolesRequest requestDTO,
6095
Authentication authentication) {
61-
// get user under tenant
96+
Long requestTenantId = requestDTO.tenantId();
97+
98+
// reject if the declared tenant does not exist
99+
if (tenantRepo.existsById(requestTenantId)) {
100+
return HttpResponse.notFound("Tenant does not exist");
101+
}
102+
103+
Optional<User> userOptional = userRepo.findById(id);
104+
if (userOptional.isEmpty()) {
105+
return HttpResponse.notFound("User not found.");
106+
}
107+
108+
User user = userOptional.get();
109+
Role unityAdministrator = roleRepo.findByName("Unity Administrator");
110+
111+
// ignore roles not defined by application
112+
List<Long> rolesIntersection = getRolesIntersection(requestDTO.roles());
113+
62114
// if unity admin, proceed; otherwise, reject if roles exceed authenticated user's under same tenant.
63-
// apply patch
115+
String authUserEmail = authentication.getName();
116+
if (!userRepo.existsByEmailAndRoleEqualsUnityAdmin(authUserEmail)) {
117+
if (!userRepo.existsByEmailAndTenantEqualsAndIsTenantAdmin(authUserEmail, requestTenantId)) {
118+
return HttpResponse.status(HttpStatus.FORBIDDEN,
119+
"Authenticated user is not authorized to make changes under declared tenant.");
120+
} else if (rolesIntersection.stream().anyMatch(roleId -> roleId.equals(unityAdministrator.getId()))){
121+
// authenticated tenant admin user cannot grant unity admin role
122+
return HttpResponse.status(HttpStatus.FORBIDDEN,
123+
"Authenticated user is not authorized to grant Unity Admin");
124+
}
125+
}
64126

127+
applyRolesPatch(rolesIntersection, requestTenantId, user.getId());
65128

66129
// return updated user
130+
return HttpResponse.created(new UserResponse(user.getId(),
131+
user.getEmail(),
132+
user.getFirstName(),
133+
user.getLastName(),
134+
rolesIntersection));
135+
}
136+
137+
private List<Long> getRolesIntersection(List<Long> requestRoles) {
138+
List<Long> roles = roleRepo.findAllRoleIds();
139+
return requestRoles.stream()
140+
.distinct()
141+
.filter(roles::contains)
142+
.toList();
143+
}
144+
145+
146+
@Transactional
147+
public void applyRolesPatch(List<Long> requestRoles, Long requestTenantId, Long userId) {
148+
userRepo.deleteRoleByTenantIdAndUserId(requestTenantId, userId);
149+
requestRoles.forEach(roleId -> userRepo.insertUserRole(userId, requestTenantId, roleId));
67150
}
68151

69152
@Patch("{id}")
70-
public HttpResponse<UserResponse> selfPatch(@PathVariable Long id, @Body UpdateSelfRequest requestDTO,
153+
public HttpResponse<?> selfPatch(@PathVariable Long id, @Body UpdateSelfRequest requestDTO,
71154
Authentication authentication) {
72-
// get user (and verify id?)
73-
// perform patch
74155

75-
// return updated user
76-
}
156+
Optional<User> userOptional = userRepo.findByEmail(authentication.getName());
157+
if (userOptional.isEmpty()) {
158+
return HttpResponse.notFound("User not found.");
159+
}
77160

78-
private boolean isUserUnityOrTenantAdmin(String email, Long requestTenantId) {
79-
return false;
161+
User user = userOptional.get();
162+
if (!Objects.equals(user.getId(), id)) {
163+
return HttpResponse.badRequest("User id mismatch.");
164+
}
165+
166+
if (requestDTO.firstName != null) {
167+
user.setFirstName(requestDTO.firstName);
168+
}
169+
if (requestDTO.lastName != null) {
170+
user.setLastName(requestDTO.lastName);
171+
}
172+
if (requestDTO.password != null) {
173+
user.setPassword(requestDTO.password);
174+
}
175+
176+
User saved = userRepo.save(user);
177+
return HttpResponse.ok(new UserResponse(saved.getId(), saved.getEmail(), saved.getFirstName(), saved.getLastName(),
178+
userRepo.getUserRolesByUserId(saved.getId())));
80179
}
81180

82181
@Serdeable
83182
public record UpdateUserRolesRequest(
84183
@NotNull Long tenantId,
85-
List<String> roles) {
184+
List<Long> roles) {
86185
}
87186

88187
@Serdeable
@@ -92,7 +191,7 @@ public record AddUserRequest(
92191
@NotBlank String lastName,
93192
@NotNull Long tenantId,
94193
@NotBlank String password,
95-
@NotEmpty List<String> roles) {
194+
@NotEmpty List<Long> roles) {
96195
}
97196

98197
@Serdeable
@@ -101,6 +200,4 @@ public record UpdateSelfRequest(
101200
@NotBlank String lastName,
102201
@NotBlank String password) {
103202
}
104-
105-
106203
}

UnityAuth/src/main/java/io/unityfoundation/auth/entities/Role.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,28 @@ public class Role {
1212

1313
private String name;
1414
private String description;
15+
16+
public String getDescription() {
17+
return description;
18+
}
19+
20+
public void setDescription(String description) {
21+
this.description = description;
22+
}
23+
24+
public Long getId() {
25+
return id;
26+
}
27+
28+
public void setId(Long id) {
29+
this.id = id;
30+
}
31+
32+
public String getName() {
33+
return name;
34+
}
35+
36+
public void setName(String name) {
37+
this.name = name;
38+
}
1539
}

UnityAuth/src/main/java/io/unityfoundation/auth/entities/RoleRepo.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
import io.micronaut.data.model.query.builder.sql.Dialect;
77
import io.micronaut.data.repository.CrudRepository;
88

9+
import java.util.List;
10+
911
@JdbcRepository(dialect = Dialect.MYSQL)
1012
public interface RoleRepo extends CrudRepository<Role, Long> {
13+
Role findByName(String name);
14+
15+
@Query("SELECT id FROM role")
16+
List<Long> findAllRoleIds();
1117
}

UnityAuth/src/main/java/io/unityfoundation/auth/entities/UserRepo.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ select count(*) > 0
6969
from user_role ur
7070
inner join user u on u.id = ur.user_id
7171
inner join role r on r.id = ur.role_id
72-
where u.email = :email and (r.name = 'Unity Administrator' or r.name = 'Tenant Administrator')
72+
where u.email = :email and ur.tenant_id = :tenantId and r.name = 'Tenant Administrator'
7373
""")
74-
boolean existsByEmailAndRoleEqualsUnityAdminOrTenantAdmin(String email);
74+
boolean existsByEmailAndTenantEqualsAndIsTenantAdmin(String email, Long tenantId);
7575

7676
@Query("""
7777
select count(*) > 0
@@ -84,4 +84,10 @@ select count(*) > 0
8484

8585
@Query("select role_id from user_role where user_id = :userId")
8686
List<Long> getUserRolesByUserId(Long userId);
87+
88+
@Query("INSERT INTO user_role(user_id, tenant_id, role_id) VALUES (:userId, :tenantId, :roleId)")
89+
void insertUserRole(Long userId, Long tenantId, Long roleId);
90+
91+
@Query("DELETE FROM user_role WHERE tenant_id = :tenantId and user_id = :userId")
92+
void deleteRoleByTenantIdAndUserId(Long tenantId, Long userId);
8793
}

0 commit comments

Comments
 (0)