Skip to content

Commit b371e7d

Browse files
committed
wip - add endpoint to accomodate crud actions for users
1 parent efb965f commit b371e7d

File tree

7 files changed

+234
-12
lines changed

7 files changed

+234
-12
lines changed

UnityAuth/src/main/java/io/unityfoundation/auth/AuthController.java

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,15 @@
44
import io.micronaut.core.annotation.Nullable;
55
import io.micronaut.http.HttpResponse;
66
import io.micronaut.http.HttpStatus;
7-
import io.micronaut.http.annotation.Body;
8-
import io.micronaut.http.annotation.Controller;
9-
import io.micronaut.http.annotation.Post;
7+
import io.micronaut.http.annotation.*;
108
import io.micronaut.http.exceptions.HttpStatusException;
119
import io.micronaut.security.annotation.Secured;
1210
import io.micronaut.security.authentication.Authentication;
1311
import io.micronaut.security.rules.SecurityRule;
1412
import io.micronaut.serde.annotation.Serdeable;
13+
import io.unityfoundation.auth.entities.*;
1514
import io.unityfoundation.auth.entities.Permission.PermissionScope;
16-
import io.unityfoundation.auth.entities.Service;
1715
import io.unityfoundation.auth.entities.Service.ServiceStatus;
18-
import io.unityfoundation.auth.entities.ServiceRepo;
19-
import io.unityfoundation.auth.entities.Tenant;
20-
import io.unityfoundation.auth.entities.Tenant.TenantStatus;
21-
import io.unityfoundation.auth.entities.TenantRepo;
22-
import io.unityfoundation.auth.entities.User;
23-
import io.unityfoundation.auth.entities.UserRepo;
2416
import jakarta.validation.constraints.NotNull;
2517
import java.util.List;
2618
import java.util.Optional;
@@ -33,11 +25,13 @@ public class AuthController {
3325
private final UserRepo userRepo;
3426
private final ServiceRepo serviceRepo;
3527
private final TenantRepo tenantRepo;
28+
private final RoleRepo roleRepo;
3629

37-
public AuthController(UserRepo userRepo, ServiceRepo serviceRepo, TenantRepo tenantRepo) {
30+
public AuthController(UserRepo userRepo, ServiceRepo serviceRepo, TenantRepo tenantRepo, RoleRepo roleRepo) {
3831
this.userRepo = userRepo;
3932
this.serviceRepo = serviceRepo;
4033
this.tenantRepo = tenantRepo;
34+
this.roleRepo = roleRepo;
4135
}
4236

4337
@Post("/principal/permissions")
@@ -49,7 +43,7 @@ public UserPermissionsResponse permissions(@Body UserPermissionsRequest requestD
4943
}
5044
Tenant tenant = maybeTenant.get();
5145

52-
if (!tenant.getStatus().equals(TenantStatus.ENABLED)){
46+
if (!tenant.getStatus().equals(Tenant.TenantStatus.ENABLED)){
5347
return new UserPermissionsResponse.Failure("The tenant is not enabled.");
5448
}
5549

@@ -110,6 +104,40 @@ public HttpResponse<HasPermissionResponse> hasPermission(@Body HasPermissionRequ
110104
return createHasPermissionResponse(true, user.getEmail(), null, commonPermissions);
111105
}
112106

107+
@Get("/roles")
108+
public HttpResponse<?> getRoles() {
109+
return HttpResponse.ok(roleRepo.findAll());
110+
}
111+
112+
@Get("/tenants")
113+
public HttpResponse<?> getTenants(Authentication authentication) {
114+
115+
String authenticatedUserEmail = authentication.getName();
116+
117+
if(userRepo.existsByEmailAndRoleEqualsUnityAdmin(authenticatedUserEmail)) {
118+
return HttpResponse.ok(tenantRepo.findAll());
119+
}
120+
121+
return HttpResponse.ok(tenantRepo.findAllByUserEmail(authenticatedUserEmail));
122+
}
123+
124+
@Get("/tenants/{id}/users")
125+
public HttpResponse<List<UserResponse>> getTenantUsers(@PathVariable Long id, Authentication authentication) {
126+
127+
// reject if the declared tenant does not exist
128+
if (tenantRepo.existsById(id)) {
129+
return HttpResponse.badRequest();
130+
}
131+
132+
// todo: it would be nice to capture the roles and have them automatically mapped to UserResponse.roles
133+
List<UserResponse> tenantUsers = userRepo.findAllByTenantId(id).stream().map(user ->
134+
new UserResponse(user.getId(), user.getEmail(), user.getFirstName(), user.getLastName(),
135+
userRepo.getUserRolesByUserId(user.getId())
136+
)).toList();
137+
138+
return HttpResponse.ok(tenantUsers);
139+
}
140+
113141
private boolean checkUserStatus(User user) {
114142
return user == null || user.getStatus() != User.UserStatus.ENABLED;
115143
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package io.unityfoundation.auth;
2+
3+
import io.micronaut.core.annotation.Nullable;
4+
import io.micronaut.http.HttpResponse;
5+
import io.micronaut.http.annotation.*;
6+
import io.micronaut.security.annotation.Secured;
7+
import io.micronaut.security.authentication.Authentication;
8+
import io.micronaut.security.rules.SecurityRule;
9+
import io.micronaut.serde.annotation.Serdeable;
10+
import io.unityfoundation.auth.entities.*;
11+
import jakarta.validation.constraints.NotBlank;
12+
import jakarta.validation.constraints.NotEmpty;
13+
import jakarta.validation.constraints.NotNull;
14+
15+
import java.util.List;
16+
17+
@Secured(SecurityRule.IS_AUTHENTICATED)
18+
@Controller("/api/users")
19+
public class UserController {
20+
21+
private final UserRepo userRepo;
22+
private final TenantRepo tenantRepo;
23+
24+
public UserController(UserRepo userRepo, TenantRepo tenantRepo) {
25+
this.userRepo = userRepo;
26+
this.tenantRepo = tenantRepo;
27+
}
28+
29+
@Post
30+
public HttpResponse<?> createUser(@Body AddUserRequest requestDTO,
31+
Authentication authentication) {
32+
33+
Long requestTenantId = requestDTO.tenantId();
34+
35+
// reject if the declared tenant does not exist
36+
if (tenantRepo.existsById(requestTenantId)) {
37+
return HttpResponse.badRequest("Tenant does not exist");
38+
}
39+
40+
// 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.");
43+
}
44+
45+
// reject if new user already exists under a tenant
46+
if (userRepo.existsByEmailAndTenantId(requestDTO.email(), requestTenantId)) {
47+
return HttpResponse.badRequest("User already exists under declared tenant.");
48+
}
49+
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+
// if the new user exists, create a new user-role entry
54+
// otherwise, create the user along with user-role entry
55+
56+
}
57+
58+
@Patch("{id}/roles")
59+
public HttpResponse<UserResponse> updateUserRoles(@PathVariable Long id, @Body UpdateUserRolesRequest requestDTO,
60+
Authentication authentication) {
61+
// get user under tenant
62+
// if unity admin, proceed; otherwise, reject if roles exceed authenticated user's under same tenant.
63+
// apply patch
64+
65+
66+
// return updated user
67+
}
68+
69+
@Patch("{id}")
70+
public HttpResponse<UserResponse> selfPatch(@PathVariable Long id, @Body UpdateSelfRequest requestDTO,
71+
Authentication authentication) {
72+
// get user (and verify id?)
73+
// perform patch
74+
75+
// return updated user
76+
}
77+
78+
private boolean isUserUnityOrTenantAdmin(String email, Long requestTenantId) {
79+
return false;
80+
}
81+
82+
@Serdeable
83+
public record UpdateUserRolesRequest(
84+
@NotNull Long tenantId,
85+
List<String> roles) {
86+
}
87+
88+
@Serdeable
89+
public record AddUserRequest(
90+
@NotBlank String email,
91+
@NotBlank String firstName,
92+
@NotBlank String lastName,
93+
@NotNull Long tenantId,
94+
@NotBlank String password,
95+
@NotEmpty List<String> roles) {
96+
}
97+
98+
@Serdeable
99+
public record UpdateSelfRequest(
100+
@NotBlank String firstName,
101+
@NotBlank String lastName,
102+
@NotBlank String password) {
103+
}
104+
105+
106+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.unityfoundation.auth;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
import java.util.List;
7+
8+
@Serdeable
9+
public record UserResponse(
10+
Long id,
11+
String email,
12+
String firstName,
13+
String lastName,
14+
List<Long> roles) {
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.unityfoundation.auth.entities;
2+
3+
import io.micronaut.data.annotation.GeneratedValue;
4+
import io.micronaut.data.annotation.Id;
5+
import io.micronaut.data.annotation.MappedEntity;
6+
7+
@MappedEntity
8+
public class Role {
9+
@Id
10+
@GeneratedValue
11+
private Long id;
12+
13+
private String name;
14+
private String description;
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.unityfoundation.auth.entities;
2+
3+
4+
import io.micronaut.data.annotation.Query;
5+
import io.micronaut.data.jdbc.annotation.JdbcRepository;
6+
import io.micronaut.data.model.query.builder.sql.Dialect;
7+
import io.micronaut.data.repository.CrudRepository;
8+
9+
@JdbcRepository(dialect = Dialect.MYSQL)
10+
public interface RoleRepo extends CrudRepository<Role, Long> {
11+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
package io.unityfoundation.auth.entities;
22

33

4+
import io.micronaut.data.annotation.Query;
45
import io.micronaut.data.jdbc.annotation.JdbcRepository;
56
import io.micronaut.data.model.query.builder.sql.Dialect;
67
import io.micronaut.data.repository.CrudRepository;
78

9+
import java.util.List;
10+
811
@JdbcRepository(dialect = Dialect.MYSQL)
912
public interface TenantRepo extends CrudRepository<Tenant, Long> {
13+
14+
@Query("""
15+
SELECT t.*
16+
FROM user_role ur
17+
INNER JOIN tenant t ON t.id = ur.tenant_id
18+
INNER JOIN user u ON u.id = ur.user_id
19+
WHERE u.email = :email
20+
21+
""")
22+
List<Tenant> findAllByUserEmail(String email);
1023
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ public interface UserRepo extends CrudRepository<User, Long> {
1414

1515
Optional<User> findByEmail(String email);
1616

17+
@Query("""
18+
SELECT count(*) > 0
19+
FROM user_role ur
20+
inner join user u on u.id = ur.user_id
21+
WHERE u.email = :email
22+
and ur.tenant_id = :tenantId;
23+
""")
24+
boolean existsByEmailAndTenantId(String email, Long tenantId);
25+
1726
@Query("""
1827
SELECT count(*) > 0
1928
FROM user_role ur
@@ -49,5 +58,30 @@ SELECT count(*) > 0
4958
""")
5059
List<TenantPermission> getTenantPermissionsFor(Long userId);
5160

61+
@Query("""
62+
select u.*
63+
from user_role ur inner join user u on u.id = ur.user_id
64+
where ur.tenant_id = :tenantId""")
65+
List<User> findAllByTenantId(Long tenantId);
66+
67+
@Query("""
68+
select count(*) > 0
69+
from user_role ur
70+
inner join user u on u.id = ur.user_id
71+
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')
73+
""")
74+
boolean existsByEmailAndRoleEqualsUnityAdminOrTenantAdmin(String email);
75+
76+
@Query("""
77+
select count(*) > 0
78+
from user_role ur
79+
inner join user u on u.id = ur.user_id
80+
inner join role r on r.id = ur.role_id
81+
where u.email = :email and r.name = 'Unity Administrator'
82+
""")
83+
boolean existsByEmailAndRoleEqualsUnityAdmin(String email);
5284

85+
@Query("select role_id from user_role where user_id = :userId")
86+
List<Long> getUserRolesByUserId(Long userId);
5387
}

0 commit comments

Comments
 (0)