1
1
package io .unityfoundation .auth ;
2
2
3
- import io .micronaut .core .annotation .Nullable ;
4
3
import io .micronaut .http .HttpResponse ;
4
+ import io .micronaut .http .HttpStatus ;
5
5
import io .micronaut .http .annotation .*;
6
6
import io .micronaut .security .annotation .Secured ;
7
7
import io .micronaut .security .authentication .Authentication ;
8
8
import io .micronaut .security .rules .SecurityRule ;
9
9
import io .micronaut .serde .annotation .Serdeable ;
10
10
import io .unityfoundation .auth .entities .*;
11
+ import jakarta .transaction .Transactional ;
11
12
import jakarta .validation .constraints .NotBlank ;
12
13
import jakarta .validation .constraints .NotEmpty ;
13
14
import jakarta .validation .constraints .NotNull ;
14
15
15
16
import java .util .List ;
17
+ import java .util .Objects ;
18
+ import java .util .Optional ;
16
19
17
20
@ Secured (SecurityRule .IS_AUTHENTICATED )
18
21
@ Controller ("/api/users" )
19
22
public class UserController {
20
23
21
24
private final UserRepo userRepo ;
22
25
private final TenantRepo tenantRepo ;
26
+ private final RoleRepo roleRepo ;
23
27
24
- public UserController (UserRepo userRepo , TenantRepo tenantRepo ) {
28
+ public UserController (UserRepo userRepo , TenantRepo tenantRepo , RoleRepo roleRepo ) {
25
29
this .userRepo = userRepo ;
26
30
this .tenantRepo = tenantRepo ;
31
+ this .roleRepo = roleRepo ;
27
32
}
28
33
29
34
@ Post
@@ -34,55 +39,149 @@ public HttpResponse<?> createUser(@Body AddUserRequest requestDTO,
34
39
35
40
// reject if the declared tenant does not exist
36
41
if (tenantRepo .existsById (requestTenantId )) {
37
- return HttpResponse .badRequest ("Tenant does not exist" );
42
+ return HttpResponse .notFound ("Tenant does not exist" );
38
43
}
39
44
45
+ Role unityAdministrator = roleRepo .findByName ("Unity Administrator" );
46
+
47
+ // ignore roles not defined by application
48
+ List <Long > rolesIntersection = getRolesIntersection (requestDTO .roles ());
49
+
40
50
// 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
+ }
43
61
}
44
62
45
63
// reject if new user already exists under a tenant
46
64
if (userRepo .existsByEmailAndTenantId (requestDTO .email (), requestTenantId )) {
47
65
return HttpResponse .badRequest ("User already exists under declared tenant." );
48
66
}
49
67
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
-
53
68
// if the new user exists, create a new user-role entry
54
69
// 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
+ }
55
83
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 ));
56
91
}
57
92
58
93
@ Patch ("{id}/roles" )
59
- public HttpResponse <UserResponse > updateUserRoles (@ PathVariable Long id , @ Body UpdateUserRolesRequest requestDTO ,
94
+ public HttpResponse <? > updateUserRoles (@ PathVariable Long id , @ Body UpdateUserRolesRequest requestDTO ,
60
95
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
+
62
114
// 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
+ }
64
126
127
+ applyRolesPatch (rolesIntersection , requestTenantId , user .getId ());
65
128
66
129
// 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 ));
67
150
}
68
151
69
152
@ Patch ("{id}" )
70
- public HttpResponse <UserResponse > selfPatch (@ PathVariable Long id , @ Body UpdateSelfRequest requestDTO ,
153
+ public HttpResponse <? > selfPatch (@ PathVariable Long id , @ Body UpdateSelfRequest requestDTO ,
71
154
Authentication authentication ) {
72
- // get user (and verify id?)
73
- // perform patch
74
155
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
+ }
77
160
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 ())));
80
179
}
81
180
82
181
@ Serdeable
83
182
public record UpdateUserRolesRequest (
84
183
@ NotNull Long tenantId ,
85
- List <String > roles ) {
184
+ List <Long > roles ) {
86
185
}
87
186
88
187
@ Serdeable
@@ -92,7 +191,7 @@ public record AddUserRequest(
92
191
@ NotBlank String lastName ,
93
192
@ NotNull Long tenantId ,
94
193
@ NotBlank String password ,
95
- @ NotEmpty List <String > roles ) {
194
+ @ NotEmpty List <Long > roles ) {
96
195
}
97
196
98
197
@ Serdeable
@@ -101,6 +200,4 @@ public record UpdateSelfRequest(
101
200
@ NotBlank String lastName ,
102
201
@ NotBlank String password ) {
103
202
}
104
-
105
-
106
203
}
0 commit comments