Skip to content

Commit c4aa911

Browse files
authored
test: pin /api/me contract and AuthorityInterceptor authority-source behavior (#24497)
1 parent ff51962 commit c4aa911

3 files changed

Lines changed: 391 additions & 0 deletions

File tree

dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/MeControllerTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import static org.hisp.dhis.security.apikey.ApiKeyTokenGenerator.generatePersonalAccessToken;
3838
import static org.junit.jupiter.api.Assertions.assertEquals;
3939
import static org.junit.jupiter.api.Assertions.assertFalse;
40+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
4041
import static org.junit.jupiter.api.Assertions.assertNotNull;
4142
import static org.junit.jupiter.api.Assertions.assertNull;
4243
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -136,6 +137,54 @@ void testHasAuthority() {
136137
assertFalse(GET("/me/authorities/missing").content(HttpStatus.OK).booleanValue());
137138
}
138139

140+
/**
141+
* Contract pins for the {@code /api/me} response shape. These assert the CURRENT contract that
142+
* clients rely on; changing any of them must be a deliberate, reviewed decision.
143+
*/
144+
@Test
145+
void testGetCurrentUser_ContractShape() {
146+
JsonObject me = GET("/me").content(HttpStatus.OK);
147+
148+
assertEquals("userA", me.getString("username").string());
149+
// PIN: "name" is the display name (first name + surname), NOT the username
150+
assertEquals(userA.getFirstName() + " " + userA.getSurname(), me.getString("name").string());
151+
assertNotEquals(me.getString("username").string(), me.getString("name").string());
152+
assertTrue(me.getArray("authorities").stringValues().contains("ALL"));
153+
assertTrue(me.getObject("settings").exists());
154+
// impersonation fields only appear while impersonating / when the feature is enabled
155+
assertFalse(me.get("impersonation").exists());
156+
assertFalse(me.get("canImpersonate").exists());
157+
}
158+
159+
@Test
160+
void testGetAuthorities_ExactSetForLimitedUser() {
161+
switchToNewUser("contract", "F_PERFORM_MAINTENANCE");
162+
assertEquals(
163+
singletonList("F_PERFORM_MAINTENANCE"),
164+
GET("/me/authorities").content(HttpStatus.OK).stringValues());
165+
assertEquals(
166+
singletonList("F_PERFORM_MAINTENANCE"),
167+
GET("/me?fields=authorities")
168+
.content(HttpStatus.OK)
169+
.getArray("authorities")
170+
.stringValues());
171+
}
172+
173+
@Test
174+
void testHasAuthority_NonSuperuserExactMatch() {
175+
switchToNewUser("contract", "F_PERFORM_MAINTENANCE");
176+
assertTrue(
177+
GET("/me/authorization/F_PERFORM_MAINTENANCE").content(HttpStatus.OK).booleanValue());
178+
assertFalse(GET("/me/authorization/F_DOES_NOT_EXIST").content(HttpStatus.OK).booleanValue());
179+
}
180+
181+
@Test
182+
void testHasAuthority_SuperuserImplicitlyHasAnyAuthority() {
183+
// PIN: userA holds ALL; isAuthorized() implicitly grants ANY authority for superusers
184+
assertTrue(
185+
GET("/me/authorization/F_PERFORM_MAINTENANCE").content(HttpStatus.OK).booleanValue());
186+
}
187+
139188
@Test
140189
void testGetEmailVerifiedProperty() {
141190
assertFalse(GET("/me").content().as(JsonMeDto.class).getEmailVerified());

dhis-2/dhis-test-web-api/src/test/java/org/hisp/dhis/webapi/controller/security/ImpersonateUserControllerTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,15 @@
3030
package org.hisp.dhis.webapi.controller.security;
3131

3232
import static org.junit.jupiter.api.Assertions.assertEquals;
33+
import static org.junit.jupiter.api.Assertions.assertFalse;
34+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
35+
import static org.junit.jupiter.api.Assertions.assertTrue;
3336

3437
import java.util.Properties;
3538
import org.hisp.dhis.external.conf.ConfigurationKey;
3639
import org.hisp.dhis.external.conf.DhisConfigurationProvider;
3740
import org.hisp.dhis.http.HttpStatus;
41+
import org.hisp.dhis.jsontree.JsonObject;
3842
import org.hisp.dhis.test.config.H2DhisConfigurationProvider;
3943
import org.hisp.dhis.test.webapi.H2ControllerIntegrationTestBase;
4044
import org.hisp.dhis.test.webapi.json.domain.JsonImpersonateUserResponse;
@@ -209,4 +213,51 @@ void testImpersonateWithAuthorityNotAllowedToBecomeSuperuser() {
209213
assertEquals("Forbidden", response.getHttpStatus());
210214
assertEquals("ERROR", response.getStatus());
211215
}
216+
217+
/**
218+
* PIN: the {@code /api/me} {@code impersonation} field carries the impersonator USERNAME (from
219+
* {@code Authentication#getName()}), never the display name. Frontends match it against the
220+
* username; PR #24124 accidentally changed it to first+surname which this test would catch.
221+
*/
222+
@Test
223+
void testMeImpersonationFieldsDuringAndAfterImpersonation() {
224+
User impersonator = createUserWithAuth("impuser", "F_IMPERSONATE_USER");
225+
createUserWithAuth("target", "NONE");
226+
injectSecurityContextUser(impersonator);
227+
228+
POST("/auth/impersonate?username=target").content(HttpStatus.OK);
229+
230+
JsonObject during =
231+
GET("/me?fields=username,impersonation,canImpersonate").content(HttpStatus.OK);
232+
assertEquals("target", during.getString("username").string());
233+
assertEquals("impuser", during.getString("impersonation").string());
234+
assertNotEquals(impersonator.getName(), during.getString("impersonation").string());
235+
// the impersonated user itself holds no impersonation authority
236+
assertFalse(during.get("canImpersonate").exists());
237+
238+
POST("/auth/impersonateExit").content(HttpStatus.OK);
239+
240+
JsonObject after =
241+
GET("/me?fields=username,impersonation,canImpersonate").content(HttpStatus.OK);
242+
assertEquals("impuser", after.getString("username").string());
243+
assertFalse(after.get("impersonation").exists());
244+
assertTrue(after.getBoolean("canImpersonate").booleanValue());
245+
}
246+
247+
@Test
248+
void testMeCanImpersonateForAdminWhenFeatureEnabled() {
249+
JsonObject me = GET("/me?fields=canImpersonate,impersonation").content(HttpStatus.OK);
250+
assertTrue(me.getBoolean("canImpersonate").booleanValue());
251+
assertFalse(me.get("impersonation").exists());
252+
}
253+
254+
@Test
255+
void testMeCanImpersonateAbsentWithoutAuthority() {
256+
User guest = createUserWithAuth("guestimp", "NONE");
257+
injectSecurityContextUser(guest);
258+
259+
JsonObject me = GET("/me?fields=canImpersonate,impersonation").content(HttpStatus.OK);
260+
assertFalse(me.get("canImpersonate").exists());
261+
assertFalse(me.get("impersonation").exists());
262+
}
212263
}

0 commit comments

Comments
 (0)