Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,9 @@ public enum ConfigurationKey {

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

/** Maximun size for files uploaded as fileResources. */
MAX_FILE_UPLOAD_SIZE_BYTES("max.file_upload_size", Integer.toString(10_000_000), false),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.util.Collection;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hisp.dhis.common.OpenApi;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
Expand Down Expand Up @@ -83,6 +84,7 @@
@OpenApi.Document(
entity = User.class,
classifiers = {"team:platform", "purpose:support"})
@Slf4j
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
Expand Down Expand Up @@ -133,12 +135,23 @@ public ImpersonateUserResponse impersonateUser(
}

private void validateReq(HttpServletRequest request) throws ForbiddenException {
String remoteAddr = request.getRemoteAddr();
boolean enabled = config.isEnabled(ConfigurationKey.SWITCH_USER_FEATURE_ENABLED);
if (!enabled) {
throw new ForbiddenException("Forbidden, user not allowed to impersonate user");
log.error(
"Impersonation attempt when feature is disabled, from username: {}, IP address: {}",
getCurrentAuthentication().getName(),
remoteAddr);
throw new ForbiddenException(
"Forbidden, user not allowed to impersonate user, feature disabled");
}
if (!hasAllowListedIp(request.getRemoteAddr())) {
throw new ForbiddenException("Forbidden, user not allowed to impersonate user");
if (!hasAllowListedIp(remoteAddr, config)) {
log.error(
"Impersonation attempt from non allow-listed IP address: {}, username: {}",
remoteAddr,
getCurrentAuthentication().getName());
throw new ForbiddenException(
"Forbidden, user not allowed to impersonate user from IP: %s".formatted(remoteAddr));
}
}

Expand Down Expand Up @@ -271,7 +284,7 @@ private Authentication getSourceAuthentication(Authentication current) {
return original;
}

private boolean hasAllowListedIp(String remoteAddr) {
public static boolean hasAllowListedIp(String remoteAddr, DhisConfigurationProvider config) {
String property = config.getProperty(ConfigurationKey.SWITCH_USER_ALLOW_LISTED_IPS);
for (String ip : property.split(",")) {
if (ip.trim().equalsIgnoreCase(remoteAddr)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
package org.hisp.dhis.webapi.controller.user;

import static org.hisp.dhis.fieldfiltering.FieldFilterParams.*;
import static org.hisp.dhis.webapi.controller.security.ImpersonateUserController.hasAllowListedIp;
import static org.hisp.dhis.webapi.utils.ContextUtils.setNoStore;
import static org.springframework.http.CacheControl.noStore;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
Expand All @@ -54,6 +55,8 @@
import org.hisp.dhis.dataapproval.DataApprovalLevel;
import org.hisp.dhis.dataapproval.DataApprovalLevelService;
import org.hisp.dhis.dataset.DataSetService;
import org.hisp.dhis.external.conf.ConfigurationKey;
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
import org.hisp.dhis.feedback.ConflictException;
import org.hisp.dhis.fieldfiltering.FieldFilterService;
import org.hisp.dhis.fieldfiltering.FieldPreset;
Expand All @@ -72,6 +75,7 @@
import org.hisp.dhis.program.ProgramService;
import org.hisp.dhis.query.GetObjectParams;
import org.hisp.dhis.render.RenderService;
import org.hisp.dhis.security.Authorities;
import org.hisp.dhis.security.PasswordManager;
import org.hisp.dhis.security.acl.Access;
import org.hisp.dhis.security.acl.AclService;
Expand Down Expand Up @@ -118,47 +122,31 @@
@RequestMapping("/api/me")
@RequiredArgsConstructor
public class MeController {
@Nonnull private final ContextService contextService;
@Nonnull private final DhisConfigurationProvider config;
@Nonnull private final UserService userService;

@Nonnull private final UserControllerUtils userControllerUtils;

@Nonnull protected ContextService contextService;

@Nonnull private final RenderService renderService;

@Nonnull private final FieldFilterService fieldFilterService;

@Nonnull private final org.hisp.dhis.fieldfilter.FieldFilterService oldFieldFilterService;

@Nonnull private final IdentifiableObjectManager manager;

@Nonnull private final PasswordManager passwordManager;

@Nonnull private final MessageService messageService;

@Nonnull private final InterpretationService interpretationService;

@Nonnull private final NodeService nodeService;

@Nonnull private final PasswordValidationService passwordValidationService;

@Nonnull private final ProgramService programService;

@Nonnull private final DataSetService dataSetService;

@Nonnull private final AclService aclService;

@Nonnull private final DataApprovalLevelService approvalLevelService;

@Nonnull private final FileResourceService fileResourceService;

@Nonnull private ApiTokenService apiTokenService;

@GetMapping
@OpenApi.Response(MeDto.class)
@OpenApi.EntityType(MeDto.class)
public @ResponseBody ResponseEntity<JsonNode> getCurrentUser(
@CurrentUser(required = true) User user, GetObjectParams params) {
@CurrentUser(required = true) User user, GetObjectParams params, HttpServletRequest request) {

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

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

return ResponseEntity.ok(jsonNodes);
}

private void determineUserImpersonation(MeDto meDto) {
private void determineUserImpersonation(
MeDto meDto, Set<String> allAuthorities, HttpServletRequest request) {
Authentication current = SecurityContextHolder.getContext().getAuthentication();

Authentication original = null;
// iterate over granted authorities and find the 'switch user' authority
Collection<? extends GrantedAuthority> authorities = current.getAuthorities();
for (GrantedAuthority auth : authorities) {
// check for switch user type of authority
if (auth instanceof SwitchUserGrantedAuthority) {
original = ((SwitchUserGrantedAuthority) auth).getSource();
meDto.setImpersonation(original.getName());
if (auth instanceof SwitchUserGrantedAuthority userGrantedAuthority) {
meDto.setImpersonation(userGrantedAuthority.getSource().getName());
}
}

String remoteAddr = request.getRemoteAddr();
boolean enabled = config.isEnabled(ConfigurationKey.SWITCH_USER_FEATURE_ENABLED);
if (enabled
&& (allAuthorities.contains(Authorities.ALL.name())
|| allAuthorities.contains(Authorities.F_IMPERSONATE_USER.name()))
&& hasAllowListedIp(remoteAddr, config)) {
meDto.setCanImpersonate(true);
}
}

private boolean fieldsContains(String key, List<String> fields) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ public MeDto(

@JsonProperty private String impersonation;

@JsonProperty private Boolean canImpersonate;

@JsonProperty private List<ApiToken> patTokens;

@JsonProperty private TwoFactorType twoFactorType;
Expand Down
Loading