Skip to content

Commit d3337e2

Browse files
authored
TRUNK-6466: Enhance Proxy Privilege methods (#5473)
1 parent 8cd859a commit d3337e2

File tree

4 files changed

+219
-34
lines changed

4 files changed

+219
-34
lines changed

api/src/main/java/org/openmrs/api/context/Context.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
import org.aopalliance.aop.Advice;
1313
import org.apache.commons.lang3.StringUtils;
14-
import org.hibernate.SessionFactory;
1514
import org.openmrs.Allergen;
1615
import org.openmrs.GlobalProperty;
1716
import org.openmrs.OpenmrsObject;
@@ -726,9 +725,7 @@ public static Set<Role> getAllRoles(User user) throws Exception {
726725
}
727726

728727
/**
729-
* Convenience method. Passes through to userContext.hasPrivilege(String)
730-
*
731-
* <strong>Should</strong> give daemon user full privileges
728+
* Tests whether the currently authenticated user has a particular privilege
732729
*/
733730
public static boolean hasPrivilege(String privilege) {
734731
// the daemon threads have access to all things
@@ -760,19 +757,49 @@ public static void requirePrivilege(String privilege) throws ContextAuthenticati
760757
throw new ContextAuthenticationException(errorMessage);
761758
}
762759
}
763-
760+
764761
/**
765-
* Convenience method. Passes through to {@link UserContext#addProxyPrivilege(String)}
762+
* Adds one or more privileges to the list of privileges that that {@link #hasPrivilege(String)} will
763+
* regard as available regardless of whether the user would otherwise have the privilege.
764+
* <p/>
765+
* This is useful for situations where a system process may need access to some piece of data that the
766+
* user would not otherwise have access to, like a GlobalProperty. <strong>This facility should not be
767+
* used to return data to the user that they otherwise would be unable to see.</strong>
768+
* <p/>
769+
* The expected usage is:
770+
* <p/>
771+
* <pre>{@code
772+
* try {
773+
* Context.addProxyPrivilege(&quot;AAA&quot;);
774+
* Context.get*Service().methodRequiringAAAPrivilege();
775+
* }
776+
* finally {
777+
* Context.removeProxyPrivilege(&quot;AAA&quot;);
778+
* }}
779+
* </pre>
780+
* <p/>
781+
*
782+
* @param privileges privileges to add in string form
783+
* @see #hasPrivilege(String)
784+
* @see #removeProxyPrivilege(String...)
766785
*/
767-
public static void addProxyPrivilege(String privilege) {
768-
getUserContext().addProxyPrivilege(privilege);
786+
public static void addProxyPrivilege(String... privileges) {
787+
getUserContext().addProxyPrivilege(privileges);
769788
}
770-
789+
771790
/**
772-
* Convenience method. Passes through to {@link UserContext#removeProxyPrivilege(String)}
791+
* Removes one or more privileges to the list of privileges that that {@link #hasPrivilege(String)} will
792+
* regard as available regardless of whether the user would otherwise have the privilege.
793+
* <p/>
794+
* This is the compliment for {@link #addProxyPrivilege(String...)} to clean-up the context.
795+
* <p/>
796+
*
797+
* @param privileges privileges to remove in string form
798+
* @see #hasPrivilege(String)
799+
* @see #addProxyPrivilege(String...)
773800
*/
774-
public static void removeProxyPrivilege(String privilege) {
775-
getUserContext().removeProxyPrivilege(privilege);
801+
public static void removeProxyPrivilege(String... privileges) {
802+
getUserContext().removeProxyPrivilege(privileges);
776803
}
777804

778805
/**

api/src/main/java/org/openmrs/api/context/UserContext.java

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
import java.io.Serializable;
1313
import java.util.ArrayList;
14+
import java.util.Arrays;
1415
import java.util.Collections;
1516
import java.util.HashSet;
16-
import java.util.List;
17+
import java.util.LinkedHashSet;
1718
import java.util.Locale;
19+
import java.util.Objects;
1820
import java.util.Set;
1921

2022
import org.apache.commons.lang3.StringUtils;
@@ -59,7 +61,7 @@ public class UserContext implements Serializable {
5961
/**
6062
* User's permission proxies
6163
*/
62-
private List<String> proxies = Collections.synchronizedList(new ArrayList<>());
64+
private final Set<String> proxies = Collections.synchronizedSet(new LinkedHashSet<>());
6365

6466
/**
6567
* User's locale
@@ -223,40 +225,63 @@ public void logout() {
223225
}
224226

225227
/**
226-
* Gives the given privilege to all calls to hasPrivilege. This method was visualized as being
227-
* used as follows (try/finally is important):
228-
*
229-
* <pre>
228+
* Adds one or more privileges to the list of privileges that that {@link #hasPrivilege(String)} will
229+
* regard as available regardless of whether the user would otherwise have the privilege.
230+
* <p/>
231+
* This is useful for situations where a system process may need access to some piece of data that the
232+
* user would not otherwise have access to, like a GlobalProperty. <strong>This facility should not be
233+
* used to return data to the user that they otherwise would be unable to see.</strong>
234+
* <p/>
235+
* The expected usage is:
236+
* <p/>
237+
* <pre>{@code
230238
* try {
231239
* Context.addProxyPrivilege(&quot;AAA&quot;);
232240
* Context.get*Service().methodRequiringAAAPrivilege();
233241
* }
234242
* finally {
235243
* Context.removeProxyPrivilege(&quot;AAA&quot;);
236-
* }
244+
* }}
237245
* </pre>
246+
* <p/>
238247
*
239-
* @param privilege to give to users
248+
* @param privileges privileges to add in string form
249+
* @see #hasPrivilege(String)
250+
* @see #removeProxyPrivilege(String...)
240251
*/
241-
public void addProxyPrivilege(String privilege) {
242-
243-
if (privilege == null) {
252+
public void addProxyPrivilege(String... privileges) {
253+
if (privileges == null || Arrays.stream(privileges).anyMatch(Objects::isNull)) {
244254
throw new IllegalArgumentException("UserContext.addProxyPrivilege does not accept null privileges");
245255
}
246256

247-
log.debug("Adding proxy privilege: {}", privilege);
248-
249-
proxies.add(privilege);
257+
for (String privilege : privileges) {
258+
log.debug("Adding proxy privilege: {}", privilege);
259+
proxies.add(privilege);
260+
}
250261
}
251262

252263
/**
253-
* Will remove one instance of privilege from the privileges that are currently proxied
264+
* Removes one or more privileges to the list of privileges that that {@link #hasPrivilege(String)} will
265+
* regard as available regardless of whether the user would otherwise have the privilege.
266+
* <p/>
267+
* This is the compliment for {@link #addProxyPrivilege(String...)} to clean-up the context.
268+
* <p/>
254269
*
255-
* @param privilege Privilege to remove in string form
270+
* @param privileges privileges to remove in string form
271+
* @see #hasPrivilege(String)
272+
* @see #addProxyPrivilege(String...)
256273
*/
257-
public void removeProxyPrivilege(String privilege) {
258-
log.debug("Removing proxy privilege: {}", privilege);
259-
proxies.remove(privilege);
274+
public void removeProxyPrivilege(String... privileges) {
275+
if (privileges == null || Arrays.stream(privileges).allMatch(Objects::isNull)) {
276+
return;
277+
}
278+
279+
for (String privilege : privileges) {
280+
if (privilege != null) {
281+
log.debug("Removing privilege: {}", privilege);
282+
proxies.remove(privilege);
283+
}
284+
}
260285
}
261286

262287
/**
@@ -315,9 +340,9 @@ public Set<Role> getAllRoles(User user) throws Exception {
315340
}
316341

317342
/**
318-
* Tests whether currently authenticated user has a particular privilege
343+
* Tests whether the currently authenticated user has a particular privilege
319344
*
320-
* @param privilege
345+
* @param privilege The privilege to check if it's available
321346
* @return true if authenticated user has given privilege
322347
* <strong>Should</strong> authorize if authenticated user has specified privilege
323348
* <strong>Should</strong> authorize if authenticated role has specified privilege

api/src/test/java/org/openmrs/api/context/ContextTest.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,4 +368,80 @@ public void clearEntireCache_shouldClearEntireCache() {
368368
Context.getPatientService().getPatient(PERSON_NAME_ID_2);
369369
assertThat(sf.getStatistics().getSecondLevelCacheHitCount(), is(hitCount));
370370
}
371+
372+
/**
373+
* @see Context#addProxyPrivilege(String...)
374+
*/
375+
@Test
376+
public void addProxyPrivilege_shouldAddMultiplePrivileges() {
377+
Context.logout(); // logout to ensure user doesn't have privileges through roles
378+
try {
379+
// arrange - verify user doesn't have these privileges
380+
assertFalse(Context.hasPrivilege("Some Privilege"));
381+
assertFalse(Context.hasPrivilege("Another Privilege"));
382+
383+
// act
384+
Context.addProxyPrivilege("Some Privilege", "Another Privilege");
385+
386+
// assert
387+
assertTrue(Context.hasPrivilege("Some Privilege"));
388+
assertTrue(Context.hasPrivilege("Another Privilege"));
389+
} finally {
390+
Context.removeProxyPrivilege("Some Privilege", "Another Privilege");
391+
authenticate(); // restore authenticated state
392+
}
393+
}
394+
395+
/**
396+
* @see Context#addProxyPrivilege(String...)
397+
*/
398+
@Test
399+
public void addProxyPrivilege_shouldThrowExceptionForNullArray() {
400+
// act & assert
401+
assertThrows(IllegalArgumentException.class, () -> Context.addProxyPrivilege((String[]) null));
402+
}
403+
404+
/**
405+
* @see Context#removeProxyPrivilege(String...)
406+
*/
407+
@Test
408+
public void removeProxyPrivilege_shouldRemoveMultiplePrivileges() {
409+
Context.logout();
410+
try {
411+
// arrange
412+
Context.addProxyPrivilege("Privilege A", "Privilege B", "Privilege C");
413+
414+
// act
415+
Context.removeProxyPrivilege("Privilege A", "Privilege C");
416+
417+
// assert
418+
assertFalse(Context.hasPrivilege("Privilege A"));
419+
assertTrue(Context.hasPrivilege("Privilege B"));
420+
assertFalse(Context.hasPrivilege("Privilege C"));
421+
} finally {
422+
Context.removeProxyPrivilege("Privilege B");
423+
authenticate();
424+
}
425+
}
426+
427+
/**
428+
* @see Context#removeProxyPrivilege(String...)
429+
*/
430+
@Test
431+
public void removeProxyPrivilege_shouldHandleNullArrayGracefully() {
432+
Context.logout();
433+
try {
434+
// arrange
435+
Context.addProxyPrivilege("Some Test Privilege");
436+
437+
// act
438+
Context.removeProxyPrivilege((String[]) null);
439+
440+
// assert - should still have the privilege since null was passed
441+
assertTrue(Context.hasPrivilege("Some Test Privilege"));
442+
} finally {
443+
Context.removeProxyPrivilege("Some Test Privilege");
444+
authenticate();
445+
}
446+
}
371447
}

api/src/test/java/org/openmrs/api/context/UserContextTest.java

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@
2222
import org.springframework.beans.factory.annotation.Autowired;
2323

2424
import static org.hamcrest.MatcherAssert.assertThat;
25+
import static org.hamcrest.Matchers.contains;
26+
import static org.hamcrest.Matchers.empty;
2527
import static org.hamcrest.Matchers.equalTo;
28+
import static org.hamcrest.Matchers.is;
2629
import static org.hamcrest.Matchers.nullValue;
30+
import static org.junit.jupiter.api.Assertions.assertThrows;
2731

2832
public class UserContextTest extends BaseContextSensitiveTest {
2933

@@ -104,11 +108,64 @@ void getDefaultLocationId_shouldReturnNullForInvalidUuid() {
104108
Context.getUserContext().setLocationId(null);
105109
testUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_DEFAULT_LOCATION, "0e32f474-eca5-4cc2-a64d-53b086f27e52");
106110
userService.saveUser(testUser);
107-
111+
108112
// act
109113
Integer locationId = Context.getUserContext().getDefaultLocationId(testUser);
110114

111115
// assert
112116
assertThat(locationId, nullValue());
113117
}
118+
119+
@Test
120+
void addProxyPrivilege_shouldAddMultiplePrivileges() {
121+
// arrange
122+
UserContext userContext = new UserContext(new TestUsernameAuthenticationScheme());
123+
124+
// act
125+
userContext.addProxyPrivilege("Privilege1", "Privilege2", "Privilege3");
126+
127+
// assert
128+
assertThat(userContext.hasPrivilege("Privilege1"), is(true));
129+
assertThat(userContext.hasPrivilege("Privilege2"), is(true));
130+
assertThat(userContext.hasPrivilege("Privilege3"), is(true));
131+
}
132+
133+
@Test
134+
void addProxyPrivilege_shouldThrowExceptionForNullArray() {
135+
// arrange
136+
UserContext userContext = new UserContext(new TestUsernameAuthenticationScheme());
137+
138+
// act & assert
139+
assertThrows(IllegalArgumentException.class, () -> userContext.addProxyPrivilege((String[]) null));
140+
}
141+
142+
@Test
143+
void removeProxyPrivilege_shouldRemoveMultiplePrivileges() {
144+
// arrange
145+
UserContext userContext = new UserContext(new TestUsernameAuthenticationScheme());
146+
userContext.addProxyPrivilege("Privilege1");
147+
userContext.addProxyPrivilege("Privilege2");
148+
userContext.addProxyPrivilege("Privilege3");
149+
150+
// act
151+
userContext.removeProxyPrivilege("Privilege1", "Privilege3");
152+
153+
// assert
154+
assertThat(userContext.hasPrivilege("Privilege1"), is(false));
155+
assertThat(userContext.hasPrivilege("Privilege2"), is(true));
156+
assertThat(userContext.hasPrivilege("Privilege3"), is(false));
157+
}
158+
159+
@Test
160+
void removeProxyPrivilege_shouldHandleNullArrayGracefully() {
161+
// arrange
162+
UserContext userContext = new UserContext(new TestUsernameAuthenticationScheme());
163+
userContext.addProxyPrivilege("Privilege1");
164+
165+
// act
166+
userContext.removeProxyPrivilege((String[]) null);
167+
168+
// assert - should still have the privilege since null was passed
169+
assertThat(userContext.hasPrivilege("Privilege1"), is(true));
170+
}
114171
}

0 commit comments

Comments
 (0)