Skip to content

Commit 70fcb06

Browse files
authored
feat: add field to me endpoint for showing if user can impersonate (#22412)
1 parent 8088a2e commit 70fcb06

File tree

4 files changed

+43
-30
lines changed

4 files changed

+43
-30
lines changed

dhis-2/dhis-support/dhis-support-external/src/main/java/org/hisp/dhis/external/conf/ConfigurationKey.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,9 @@ public enum ConfigurationKey {
737737

738738
/** The list of IP address from which you will be calling the user impersonation feature. */
739739
SWITCH_USER_ALLOW_LISTED_IPS(
740-
"switch_user_allow_listed_ips", "localhost,127.0.0.1,[0:0:0:0:0:0:0:1]", false),
740+
"switch_user_allow_listed_ips",
741+
"localhost,127.0.0.1,[0:0:0:0:0:0:0:1],0:0:0:0:0:0:0:1",
742+
false),
741743

742744
/** Maximun size for files uploaded as fileResources. */
743745
MAX_FILE_UPLOAD_SIZE_BYTES("max.file_upload_size", Integer.toString(10_000_000), false),

dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/security/ImpersonateUserController.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Collection;
3939
import java.util.List;
4040
import lombok.RequiredArgsConstructor;
41+
import lombok.extern.slf4j.Slf4j;
4142
import org.hisp.dhis.common.OpenApi;
4243
import org.hisp.dhis.external.conf.ConfigurationKey;
4344
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
@@ -83,6 +84,7 @@
8384
@OpenApi.Document(
8485
entity = User.class,
8586
classifiers = {"team:platform", "purpose:support"})
87+
@Slf4j
8688
@RestController
8789
@RequestMapping("/api/auth")
8890
@RequiredArgsConstructor
@@ -133,12 +135,23 @@ public ImpersonateUserResponse impersonateUser(
133135
}
134136

135137
private void validateReq(HttpServletRequest request) throws ForbiddenException {
138+
String remoteAddr = request.getRemoteAddr();
136139
boolean enabled = config.isEnabled(ConfigurationKey.SWITCH_USER_FEATURE_ENABLED);
137140
if (!enabled) {
138-
throw new ForbiddenException("Forbidden, user not allowed to impersonate user");
141+
log.error(
142+
"Impersonation attempt when feature is disabled, from username: {}, IP address: {}",
143+
getCurrentAuthentication().getName(),
144+
remoteAddr);
145+
throw new ForbiddenException(
146+
"Forbidden, user not allowed to impersonate user, feature disabled");
139147
}
140-
if (!hasAllowListedIp(request.getRemoteAddr())) {
141-
throw new ForbiddenException("Forbidden, user not allowed to impersonate user");
148+
if (!hasAllowListedIp(remoteAddr, config)) {
149+
log.error(
150+
"Impersonation attempt from non allow-listed IP address: {}, username: {}",
151+
remoteAddr,
152+
getCurrentAuthentication().getName());
153+
throw new ForbiddenException(
154+
"Forbidden, user not allowed to impersonate user from IP: %s".formatted(remoteAddr));
142155
}
143156
}
144157

@@ -271,7 +284,7 @@ private Authentication getSourceAuthentication(Authentication current) {
271284
return original;
272285
}
273286

274-
private boolean hasAllowListedIp(String remoteAddr) {
287+
public static boolean hasAllowListedIp(String remoteAddr, DhisConfigurationProvider config) {
275288
String property = config.getProperty(ConfigurationKey.SWITCH_USER_ALLOW_LISTED_IPS);
276289
for (String ip : property.split(",")) {
277290
if (ip.trim().equalsIgnoreCase(remoteAddr)) {

dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/user/MeController.java

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
package org.hisp.dhis.webapi.controller.user;
3131

3232
import static org.hisp.dhis.fieldfiltering.FieldFilterParams.*;
33+
import static org.hisp.dhis.webapi.controller.security.ImpersonateUserController.hasAllowListedIp;
3334
import static org.hisp.dhis.webapi.utils.ContextUtils.setNoStore;
3435
import static org.springframework.http.CacheControl.noStore;
3536
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@@ -54,6 +55,8 @@
5455
import org.hisp.dhis.dataapproval.DataApprovalLevel;
5556
import org.hisp.dhis.dataapproval.DataApprovalLevelService;
5657
import org.hisp.dhis.dataset.DataSetService;
58+
import org.hisp.dhis.external.conf.ConfigurationKey;
59+
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
5760
import org.hisp.dhis.feedback.ConflictException;
5861
import org.hisp.dhis.fieldfiltering.FieldFilterService;
5962
import org.hisp.dhis.fieldfiltering.FieldPreset;
@@ -72,6 +75,7 @@
7275
import org.hisp.dhis.program.ProgramService;
7376
import org.hisp.dhis.query.GetObjectParams;
7477
import org.hisp.dhis.render.RenderService;
78+
import org.hisp.dhis.security.Authorities;
7579
import org.hisp.dhis.security.PasswordManager;
7680
import org.hisp.dhis.security.acl.Access;
7781
import org.hisp.dhis.security.acl.AclService;
@@ -118,47 +122,31 @@
118122
@RequestMapping("/api/me")
119123
@RequiredArgsConstructor
120124
public class MeController {
125+
@Nonnull private final ContextService contextService;
126+
@Nonnull private final DhisConfigurationProvider config;
121127
@Nonnull private final UserService userService;
122-
123128
@Nonnull private final UserControllerUtils userControllerUtils;
124-
125-
@Nonnull protected ContextService contextService;
126-
127129
@Nonnull private final RenderService renderService;
128-
129130
@Nonnull private final FieldFilterService fieldFilterService;
130-
131131
@Nonnull private final org.hisp.dhis.fieldfilter.FieldFilterService oldFieldFilterService;
132-
133132
@Nonnull private final IdentifiableObjectManager manager;
134-
135133
@Nonnull private final PasswordManager passwordManager;
136-
137134
@Nonnull private final MessageService messageService;
138-
139135
@Nonnull private final InterpretationService interpretationService;
140-
141136
@Nonnull private final NodeService nodeService;
142-
143137
@Nonnull private final PasswordValidationService passwordValidationService;
144-
145138
@Nonnull private final ProgramService programService;
146-
147139
@Nonnull private final DataSetService dataSetService;
148-
149140
@Nonnull private final AclService aclService;
150-
151141
@Nonnull private final DataApprovalLevelService approvalLevelService;
152-
153142
@Nonnull private final FileResourceService fileResourceService;
154-
155143
@Nonnull private ApiTokenService apiTokenService;
156144

157145
@GetMapping
158146
@OpenApi.Response(MeDto.class)
159147
@OpenApi.EntityType(MeDto.class)
160148
public @ResponseBody ResponseEntity<JsonNode> getCurrentUser(
161-
@CurrentUser(required = true) User user, GetObjectParams params) {
149+
@CurrentUser(required = true) User user, GetObjectParams params, HttpServletRequest request) {
162150

163151
List<String> fields = params.getFields();
164152
if (fields == null || fields.isEmpty()) fields = List.of("*");
@@ -188,26 +176,34 @@ public class MeController {
188176
JsonMap<JsonMixed> s =
189177
settingKeys.isEmpty() ? settings.toJson(false) : settings.toJson(true, settingKeys);
190178
MeDto meDto = new MeDto(user, s, programs, dataSets, patTokens);
191-
determineUserImpersonation(meDto);
179+
determineUserImpersonation(meDto, user.getAllAuthorities(), request);
192180

193181
ObjectNode jsonNodes = fieldFilterService.toObjectNodes(of(meDto, fields)).get(0);
194182

195183
return ResponseEntity.ok(jsonNodes);
196184
}
197185

198-
private void determineUserImpersonation(MeDto meDto) {
186+
private void determineUserImpersonation(
187+
MeDto meDto, Set<String> allAuthorities, HttpServletRequest request) {
199188
Authentication current = SecurityContextHolder.getContext().getAuthentication();
200189

201-
Authentication original = null;
202190
// iterate over granted authorities and find the 'switch user' authority
203191
Collection<? extends GrantedAuthority> authorities = current.getAuthorities();
204192
for (GrantedAuthority auth : authorities) {
205193
// check for switch user type of authority
206-
if (auth instanceof SwitchUserGrantedAuthority) {
207-
original = ((SwitchUserGrantedAuthority) auth).getSource();
208-
meDto.setImpersonation(original.getName());
194+
if (auth instanceof SwitchUserGrantedAuthority userGrantedAuthority) {
195+
meDto.setImpersonation(userGrantedAuthority.getSource().getName());
209196
}
210197
}
198+
199+
String remoteAddr = request.getRemoteAddr();
200+
boolean enabled = config.isEnabled(ConfigurationKey.SWITCH_USER_FEATURE_ENABLED);
201+
if (enabled
202+
&& (allAuthorities.contains(Authorities.ALL.name())
203+
|| allAuthorities.contains(Authorities.F_IMPERSONATE_USER.name()))
204+
&& hasAllowListedIp(remoteAddr, config)) {
205+
meDto.setCanImpersonate(true);
206+
}
211207
}
212208

213209
private boolean fieldsContains(String key, List<String> fields) {

dhis-2/dhis-web-api/src/main/java/org/hisp/dhis/webapi/controller/user/MeDto.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ public MeDto(
210210

211211
@JsonProperty private String impersonation;
212212

213+
@JsonProperty private Boolean canImpersonate;
214+
213215
@JsonProperty private List<ApiToken> patTokens;
214216

215217
@JsonProperty private TwoFactorType twoFactorType;

0 commit comments

Comments
 (0)