Skip to content

Commit b32afaf

Browse files
committed
feat: add custom user profile endpoint
The default endpoint only returns the "admin" view of a user profile, which is not what we want in the client, the custom endpoint allows us to decide which "mode" we want to request the profile in.
1 parent 48359eb commit b32afaf

File tree

4 files changed

+75
-8
lines changed

4 files changed

+75
-8
lines changed

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,19 @@ The body of the request should be a [UPConfig](https://www.keycloak.org/docs-api
157157

158158
Required roles: `hawk-manage-profile-structure`
159159

160-
#### POST User Profile Data
160+
#### GET User Profile Data
161+
`/realms/{realm}/hawk/profile/{userId}`
162+
163+
Returns the profile data of a user. This is a custom endpoint that allows the client to choose if the
164+
profile should be returned as the user itself or as an admin. The userProfileMetadata is ALWAYS returned,
165+
otherwise the endpoint behaves exactly like: `/admin/realms/{realm}/users/{userId}`
166+
167+
Supported query parameters:
168+
* **mode** - user|admin - If set to "admin" the update will be done as an admin, otherwise as the user itself
169+
170+
Required roles: `view-users`
171+
172+
#### PUT User Profile Data
161173
`/realms/{realm}/hawk/profile/{userId}`
162174

163175
Allows your client to update the profile data of a user. This is a custom endpoint that
@@ -166,6 +178,9 @@ using impersonation to update the user.
166178

167179
The body of the request should be a [UserRepresentation](https://www.keycloak.org/docs-api/latest/rest-api/index.html#UserRepresentation)
168180

181+
Supported query parameters:
182+
* **mode** - user|admin - If set to "admin" the update will be done as an admin, otherwise as the user itself
183+
169184
Required roles: `hawk-manage-profile-data`
170185

171186
#### GET Cache Buster

src/main/java/com/hawk/keycloak/ApiRoot.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.hawk.keycloak;
22

33
import com.hawk.keycloak.auth.HawkPermissionEvaluator;
4+
import com.hawk.keycloak.profiles.ProfileMode;
45
import com.hawk.keycloak.resources.model.UserResourcePermission;
56
import com.hawk.keycloak.resources.model.UserResourcePermissionsRequest;
67
import com.hawk.keycloak.util.model.ConnectionInfo;
@@ -15,14 +16,14 @@
1516
import org.jboss.resteasy.annotations.cache.NoCache;
1617
import org.keycloak.models.KeycloakSession;
1718
import org.keycloak.protocol.oidc.TokenManager;
18-
import org.keycloak.representations.account.UserRepresentation;
19+
import org.keycloak.representations.idm.AbstractUserRepresentation;
1920
import org.keycloak.representations.idm.RoleRepresentation;
21+
import org.keycloak.representations.idm.UserRepresentation;
2022
import org.keycloak.representations.userprofile.config.UPConfig;
2123
import org.keycloak.services.resources.admin.AdminAuth;
2224

2325
import java.util.Collection;
2426
import java.util.List;
25-
import java.util.Map;
2627
import java.util.stream.Stream;
2728

2829
public class ApiRoot extends org.keycloak.services.resources.admin.AdminRoot {
@@ -191,17 +192,33 @@ public Response putProfileStructure(UPConfig config) {
191192
return requestHandlerFactory.profileStructureRequestHandler(authenticate()).handleUpdateStructure(config);
192193
}
193194

195+
@GET
196+
@Path("profile/{user}")
197+
@Produces(MediaType.APPLICATION_JSON)
198+
public AbstractUserRepresentation getUserProfile(
199+
@Parameter(description = "The id of the user to get the profile for") @PathParam("user") String userId,
200+
@Parameter(description = "Defines the requesting role. Can be 'user' (default) to get the profile in the user view or 'admin' to get the profile in the admin view") @QueryParam("mode") String mode
201+
) {
202+
return requestHandlerFactory.profileDataRequestHandler(authenticate())
203+
.handleProfileRequest(
204+
session.users().getUserById(session.getContext().getRealm(), userId),
205+
ProfileMode.fromString(mode)
206+
);
207+
}
208+
194209
@PUT
195210
@Path("profile/{user}")
196211
@Consumes(MediaType.APPLICATION_JSON)
197212
@Produces(MediaType.APPLICATION_JSON)
198213
public Response updateUserProfile(
199214
@Parameter(description = "The id of the user to update the profile for") @PathParam("user") String userId,
215+
@Parameter(description = "Defines the requesting role. Can be 'user' (default) to update the profile as the user, or 'admin' to update the profile as admin") @QueryParam("mode") String mode,
200216
UserRepresentation rep
201217
) {
202218
return requestHandlerFactory.profileDataRequestHandler(authenticate())
203219
.handleProfileUpdateRequest(
204220
session.users().getUserById(session.getContext().getRealm(), userId),
221+
ProfileMode.fromString(mode),
205222
rep
206223
);
207224
}

src/main/java/com/hawk/keycloak/profiles/ProfileDataRequestHandler.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import org.keycloak.events.EventType;
1010
import org.keycloak.models.KeycloakSession;
1111
import org.keycloak.models.UserModel;
12-
import org.keycloak.representations.account.UserRepresentation;
12+
import org.keycloak.representations.idm.AbstractUserRepresentation;
1313
import org.keycloak.representations.idm.ErrorRepresentation;
14+
import org.keycloak.representations.idm.UserRepresentation;
1415
import org.keycloak.services.ErrorResponse;
1516
import org.keycloak.services.messages.Messages;
1617
import org.keycloak.storage.ReadOnlyException;
@@ -25,7 +26,17 @@ public class ProfileDataRequestHandler {
2526
private final HawkPermissionEvaluator auth;
2627
private final EventBuilder event;
2728

28-
public Response handleProfileUpdateRequest(UserModel user, UserRepresentation rep) {
29+
public AbstractUserRepresentation handleProfileRequest(UserModel user, ProfileMode mode) {
30+
auth.admin().users().requireView();
31+
32+
if(user == null){
33+
throw new NotFoundException("User not found");
34+
}
35+
36+
return getProfile(user, mode, null).toRepresentation();
37+
}
38+
39+
public Response handleProfileUpdateRequest(UserModel user, ProfileMode mode, UserRepresentation rep) {
2940
auth.requireManageProfileData();
3041

3142
if(user == null){
@@ -34,11 +45,9 @@ public Response handleProfileUpdateRequest(UserModel user, UserRepresentation re
3445

3546
event.event(EventType.UPDATE_PROFILE).detail(Details.CONTEXT, UserProfileContext.ACCOUNT.name());
3647

37-
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
38-
UserProfile profile = profileProvider.create(UserProfileContext.ACCOUNT, rep.getRawAttributes(), user);
48+
UserProfile profile = getProfile(user, mode, rep);
3949

4050
try {
41-
4251
profile.update(new EventAuditingAttributeChangeListener(profile, event));
4352

4453
event.success();
@@ -55,6 +64,17 @@ public Response handleProfileUpdateRequest(UserModel user, UserRepresentation re
5564
}
5665
}
5766

67+
private UserProfile getProfile(UserModel user, ProfileMode mode, UserRepresentation rep) {
68+
UserProfileContext context = mode == ProfileMode.USER ? UserProfileContext.ACCOUNT : UserProfileContext.USER_API;
69+
UserProfileProvider profileProvider = session.getProvider(UserProfileProvider.class);
70+
71+
if(rep == null){
72+
return profileProvider.create(context, user);
73+
}
74+
75+
return profileProvider.create(context, rep.getRawAttributes(), user);
76+
}
77+
5878
private String[] validationErrorParamsToString(Object[] messageParameters, Attributes userProfileAttributes) {
5979
if(messageParameters == null)
6080
return null;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.hawk.keycloak.profiles;
2+
3+
import java.util.Objects;
4+
5+
public enum ProfileMode {
6+
USER,
7+
ADMIN;
8+
9+
public static ProfileMode fromString(String mode) {
10+
if (!Objects.equals(mode, "admin")) {
11+
return USER;
12+
}
13+
return ADMIN;
14+
}
15+
}

0 commit comments

Comments
 (0)