Skip to content

Commit

Permalink
Tenant level roles (#97)
Browse files Browse the repository at this point in the history
+ tests
+ readme
related to descope/etc#2563
  • Loading branch information
aviadl committed Feb 12, 2024
1 parent c040b43 commit 50eaaf8
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 58 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,8 @@ String name = "My Role";
String description = "Optional description to briefly explain what this role allows.";
List<String> permissionNames = Arrays.asList("My Updated Permission");

// In case roles are on tenant scope, use the overloaded functions that has the tenantId parameter

try {
rs.create(name, description, permissionNames);
} catch (DescopeException de) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/descope/model/roles/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@AllArgsConstructor
public class Role {
private String name;
private String tenantId;
private String description;
private List<String> permissionNames;
private Long createdTime;
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/com/descope/sdk/mgmt/RolesService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@

public interface RolesService {

void create(String name, String description, List<String> permissionNames)
throws DescopeException;
void create(String name, String description, List<String> permissionNames) throws DescopeException;

void create(String name, String tenantId, String description, List<String> permissionNames) throws DescopeException;

void update(String name, String newName, String description, List<String> permissionNames)
void update(String name, String newName, String description, List<String> permissionNames) throws DescopeException;

void update(String name, String tenantId, String newName, String description, List<String> permissionNames)
throws DescopeException;

void delete(String name) throws DescopeException;

void delete(String name, String tenantId) throws DescopeException;

RoleResponse loadAll() throws DescopeException;
}
34 changes: 21 additions & 13 deletions src/main/java/com/descope/sdk/mgmt/impl/RolesServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@ class RolesServiceImpl extends ManagementsBase implements RolesService {
}

@Override
public void create(String name, String description, List<String> permissionNames)
public void create(String name, String description, List<String> permissionNames) throws DescopeException {
this.create(name, "", description, permissionNames);
}

@Override
public void create(String name, String tenantId, String description, List<String> permissionNames)
throws DescopeException {
if (StringUtils.isBlank(name)) {
throw ServerCommonException.invalidArgument("Name");
}
Map<String, Object> request = mapOf("name", name, "description", description);
Map<String, Object> request = mapOf("name", name, "description", description, "tenantId", tenantId);
if (permissionNames != null) {
request.put("permissionNames", permissionNames);
}
Expand All @@ -39,32 +44,35 @@ public void create(String name, String description, List<String> permissionNames
@Override
public void update(String name, String newName, String description, List<String> permissionNames)
throws DescopeException {
this.update(name, "", newName, description, permissionNames);
}

@Override
public void update(String name, String tenantId, String newName, String description, List<String> permissionNames)
throws DescopeException {
if (StringUtils.isBlank(name)) {
throw ServerCommonException.invalidArgument("Name");
}
if (StringUtils.isBlank(newName)) {
throw ServerCommonException.invalidArgument("NewName");
}
Map<String, Object> request =
mapOf(
"name",
name,
"newName",
newName,
"description",
description,
"permissionNames",
permissionNames);
Map<String, Object> request = mapOf("name", name, "newName", newName, "description", description, "permissionNames",
permissionNames, "tenantId", tenantId);
ApiProxy apiProxy = getApiProxy();
apiProxy.post(getUri(MANAGEMENT_ROLES_UPDATE_LINK), request, Void.class);
}

@Override
public void delete(String name) throws DescopeException {
this.delete(name, "");
}

@Override
public void delete(String name, String tenantId) throws DescopeException {
if (StringUtils.isBlank(name)) {
throw ServerCommonException.invalidArgument("Name");
}
Map<String, String> request = mapOf("name", name);
Map<String, String> request = mapOf("name", name, "tenantId", tenantId);
ApiProxy apiProxy = getApiProxy();
apiProxy.post(getUri(MANAGEMENT_ROLES_DELETE_LINK), request, Void.class);
}
Expand Down
11 changes: 3 additions & 8 deletions src/main/java/com/descope/utils/UriUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,8 @@ public static Map<String, List<String>> splitQuery(URL url) {
return Collections.emptyMap();
}

return Arrays.stream(url.getQuery().split("&"))
.map(UriUtils::splitQueryParameter)
.collect(Collectors.groupingBy(
SimpleImmutableEntry::getKey,
LinkedHashMap::new,
return Arrays.stream(url.getQuery().split("&")).map(UriUtils::splitQueryParameter)
.collect(Collectors.groupingBy(SimpleImmutableEntry::getKey, LinkedHashMap::new,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())));
}

Expand All @@ -59,8 +56,6 @@ public static SimpleImmutableEntry<String, String> splitQueryParameter(String it
final int idx = it.indexOf("=");
final String key = idx > 0 ? it.substring(0, idx) : it;
final String value = idx > 0 && it.length() > idx + 1 ? it.substring(idx + 1) : null;
return new SimpleImmutableEntry<>(
URLDecoder.decode(key, "UTF-8"),
URLDecoder.decode(value, "UTF-8"));
return new SimpleImmutableEntry<>(URLDecoder.decode(key, "UTF-8"), URLDecoder.decode(value, "UTF-8"));
}
}
101 changes: 67 additions & 34 deletions src/test/java/com/descope/sdk/mgmt/impl/RolesServiceImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand All @@ -23,6 +24,7 @@
import com.descope.sdk.TestUtils;
import com.descope.sdk.mgmt.PermissionService;
import com.descope.sdk.mgmt.RolesService;
import com.descope.sdk.mgmt.TenantService;
import java.util.Arrays;
import java.util.List;
import org.assertj.core.api.Assertions;
Expand All @@ -34,32 +36,26 @@
class RolesServiceImplTest {

private final List<String> mockPermissionNames = Arrays.asList("permission1", "permission2");
private final List<Role> mockRole =
Arrays.asList(
Role.builder()
.name("someName")
.permissionNames(mockPermissionNames)
.description("someDesc")
.createdTime(1245667L)
.build());
private final List<Role> mockRole = Arrays.asList(Role.builder().name("someName").permissionNames(mockPermissionNames)
.description("someDesc").createdTime(1245667L).build());
private final RoleResponse mockRoleResponse = new RoleResponse(mockRole);
private RolesService rolesService;
private PermissionService permissionService;
private TenantService tenantService;

@BeforeEach
void setUp() {
Client client = TestUtils.getClient();
ManagementServices mgmtServices = ManagementServiceBuilder.buildServices(client);
this.rolesService = mgmtServices.getRolesService();
this.permissionService = mgmtServices.getPermissionService();
this.tenantService = mgmtServices.getTenantService();
}

@Test
void testRolesForEmptyName() {
ServerCommonException thrown =
assertThrows(
ServerCommonException.class,
() -> rolesService.create("", "someDesc", mockPermissionNames));
ServerCommonException thrown = assertThrows(ServerCommonException.class,
() -> rolesService.create("", "someDesc", mockPermissionNames));
assertNotNull(thrown);
assertEquals("The Name argument is invalid", thrown.getMessage());
}
Expand All @@ -69,19 +65,16 @@ void testRolesCreateSuccess() {
ApiProxy apiProxy = mock(ApiProxy.class);
doReturn(Void.class).when(apiProxy).post(any(), any(), any());
try (MockedStatic<ApiProxyBuilder> mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
mockedApiProxyBuilder.when(
() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
rolesService.create("krishna", "", mockPermissionNames);
verify(apiProxy, times(1)).post(any(), any(), any());
}
}

@Test
void testUpdateForEmptyName() {
ServerCommonException thrown =
assertThrows(
ServerCommonException.class,
() -> rolesService.update("", "", "", mockPermissionNames));
ServerCommonException thrown = assertThrows(ServerCommonException.class,
() -> rolesService.update("", "", "", mockPermissionNames));
assertNotNull(thrown);
assertEquals("The Name argument is invalid", thrown.getMessage());
}
Expand All @@ -91,27 +84,23 @@ void testUpdateForSuccess() {
ApiProxy apiProxy = mock(ApiProxy.class);
doReturn(void.class).when(apiProxy).post(any(), any(), any());
try (MockedStatic<ApiProxyBuilder> mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
mockedApiProxyBuilder.when(
() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
rolesService.update("Test", "name", "10", mockPermissionNames);
verify(apiProxy, times(1)).post(any(), any(), any());
}
}

@Test
void testUpdateForEmptyNewName() {
ServerCommonException thrown =
assertThrows(
ServerCommonException.class,
() -> rolesService.update("krishna", "", "", mockPermissionNames));
ServerCommonException thrown = assertThrows(ServerCommonException.class,
() -> rolesService.update("krishna", "", "", mockPermissionNames));
assertNotNull(thrown);
assertEquals("The NewName argument is invalid", thrown.getMessage());
}

@Test
void testDeleteForEmptyName() {
ServerCommonException thrown =
assertThrows(ServerCommonException.class, () -> rolesService.delete(""));
ServerCommonException thrown = assertThrows(ServerCommonException.class, () -> rolesService.delete(""));
assertNotNull(thrown);
assertEquals("The Name argument is invalid", thrown.getMessage());
}
Expand All @@ -121,8 +110,7 @@ void testDeleteForSuccess() {
ApiProxy apiProxy = mock(ApiProxy.class);
doReturn(Void.class).when(apiProxy).post(any(), any(), any());
try (MockedStatic<ApiProxyBuilder> mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
mockedApiProxyBuilder.when(
() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
rolesService.delete("someName");
verify(apiProxy, times(1)).post(any(), any(), any());
}
Expand All @@ -133,17 +121,14 @@ void testLoadAllForSuccess() {
ApiProxy apiProxy = mock(ApiProxy.class);
doReturn(mockRoleResponse).when(apiProxy).get(any(), any());
try (MockedStatic<ApiProxyBuilder> mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) {
mockedApiProxyBuilder.when(
() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
mockedApiProxyBuilder.when(() -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy);
RoleResponse response = rolesService.loadAll();
Assertions.assertThat(response.getRoles().size()).isEqualTo(1);
Assertions.assertThat(response.getRoles().get(0).getName()).isEqualTo("someName");
Assertions.assertThat(response.getRoles().get(0).getDescription()).isEqualTo("someDesc");
Assertions.assertThat(response.getRoles().get(0).getPermissionNames().size()).isEqualTo(2);
Assertions.assertThat(response.getRoles().get(0).getPermissionNames().get(0))
.isEqualTo("permission1");
Assertions.assertThat(response.getRoles().get(0).getPermissionNames().get(1))
.isEqualTo("permission2");
Assertions.assertThat(response.getRoles().get(0).getPermissionNames().get(0)).isEqualTo("permission1");
Assertions.assertThat(response.getRoles().get(0).getPermissionNames().get(1)).isEqualTo("permission2");
}
}

Expand Down Expand Up @@ -182,4 +167,52 @@ void testFunctionalFullCycle() {
permissionService.delete(p2);
rolesService.delete(r1 + "1");
}

@RetryingTest(value = 3, suspendForMs = 30000, onExceptions = RateLimitExceededException.class)
void testFunctionalFullCycleWithTenantId() {
String p1 = TestUtils.getRandomName("pt-").substring(0, 20);
String p2 = TestUtils.getRandomName("pt-").substring(0, 20);
String r1 = TestUtils.getRandomName("rt-").substring(0, 20);
permissionService.create(p1, "p1");
permissionService.create(p2, "p2");
String tid = tenantService.create(TestUtils.getRandomName("tnfr-").substring(0, 20), null);
rolesService.create(r1, tid, "ttt", Arrays.asList(p1, p2));
RoleResponse roles = rolesService.loadAll();
assertThat(roles.getRoles()).isNotEmpty();
boolean found = false;
for (Role r : roles.getRoles()) {
if (r.getName().equals(r1)) {
found = true;
assertEquals("ttt", r.getDescription());
assertEquals(tid, r.getTenantId());
assertThat(r.getPermissionNames()).contains(p1, p2);
}
}
assertTrue(found);
rolesService.update(r1, tid, r1 + "1", "zzz", Arrays.asList(p1));
roles = rolesService.loadAll();
assertThat(roles.getRoles()).isNotEmpty();
found = false;
for (Role r : roles.getRoles()) {
if (r.getName().equals(r1 + "1")) {
found = true;
assertEquals("zzz", r.getDescription());
assertEquals(tid, r.getTenantId());
assertThat(r.getPermissionNames()).containsExactly(p1);
}
}
assertTrue(found);
permissionService.delete(p1);
permissionService.delete(p2);
rolesService.delete(r1 + "1", tid);
found = false;
roles = rolesService.loadAll();
for (Role r : roles.getRoles()) {
if (r.getName().equals(r1 + "1") && r.getTenantId().equals(tid)) {
found = true;
}
}
assertFalse(found);
tenantService.delete(tid);
}
}

0 comments on commit 50eaaf8

Please sign in to comment.