diff --git a/examples/management-cli/src/main/java/com/descope/TenantUpdate.java b/examples/management-cli/src/main/java/com/descope/TenantUpdate.java index 67c8be28..5676a047 100644 --- a/examples/management-cli/src/main/java/com/descope/TenantUpdate.java +++ b/examples/management-cli/src/main/java/com/descope/TenantUpdate.java @@ -10,9 +10,9 @@ @Command(name = "tenant-update", description = "Update a Descope tenant") public class TenantUpdate extends TenantBase implements Callable { - @Option(names = { "-n", "--name"}, description = "Tenant name") + @Option(names = { "-n", "--name" }, description = "Tenant name") String name; - @Option(names = { "-d", "--domain"}, description = "Self provisioned domains. Multiple are supported.") + @Option(names = { "-d", "--domain" }, description = "Self provisioned domains. Multiple are supported.") List selfProvisionedDomains; @Override @@ -21,7 +21,7 @@ public Integer call() { try { var client = new DescopeClient(); var tenantService = client.getManagementServices().getTenantService(); - tenantService.update(tenantId, name, selfProvisionedDomains); + tenantService.update(tenantId, name, selfProvisionedDomains, nil); System.out.printf("Tenant %s [%s] updated\n", name, tenantId); } catch (DescopeException de) { exitCode = 1; diff --git a/src/main/java/com/descope/literals/Routes.java b/src/main/java/com/descope/literals/Routes.java index a1ef4898..3a82d879 100644 --- a/src/main/java/com/descope/literals/Routes.java +++ b/src/main/java/com/descope/literals/Routes.java @@ -93,6 +93,7 @@ public static class ManagementEndPoints { public static final String UPDATE_TENANT_LINK = "/v1/mgmt/tenant/update"; public static final String DELETE_TENANT_LINK = "/v1/mgmt/tenant/delete"; public static final String LOAD_ALL_TENANTS_LINK = "/v1/mgmt/tenant/all"; + public static final String TENANT_SEARCH_ALL_LINK = "/v1/mgmt/tenant/search"; // SSO public static final String SSO_GET_SETTINGS_LINK = "/mgmt/sso/settings"; diff --git a/src/main/java/com/descope/model/tenant/Tenant.java b/src/main/java/com/descope/model/tenant/Tenant.java index c61cd154..5541beac 100644 --- a/src/main/java/com/descope/model/tenant/Tenant.java +++ b/src/main/java/com/descope/model/tenant/Tenant.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -16,4 +17,5 @@ public class Tenant { String id; String name; List selfProvisioningDomains; + Map customAttributes; } diff --git a/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java b/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java new file mode 100644 index 00000000..9dcd7a61 --- /dev/null +++ b/src/main/java/com/descope/model/tenant/request/TenantSearchRequest.java @@ -0,0 +1,23 @@ +package com.descope.model.tenant.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TenantSearchRequest { + @JsonProperty("tenantIds") + List ids; + @JsonProperty("tenantNames") + List names; + Map customAttributes; + @JsonProperty("tenantSelfProvisioningDomains") + List selfProvisioningDomains; +} diff --git a/src/main/java/com/descope/sdk/mgmt/TenantService.java b/src/main/java/com/descope/sdk/mgmt/TenantService.java index c5e0e7bd..1fa3bc3a 100644 --- a/src/main/java/com/descope/sdk/mgmt/TenantService.java +++ b/src/main/java/com/descope/sdk/mgmt/TenantService.java @@ -2,50 +2,95 @@ import com.descope.exception.DescopeException; import com.descope.model.tenant.Tenant; +import com.descope.model.tenant.request.TenantSearchRequest; import java.util.List; +import java.util.Map; /** Provides functions for managing tenants in a project. */ public interface TenantService { /** - * Create a new tenant with the given name. selfProvisioningDomains is an optional list of domains + * Create a new tenant with the given name. selfProvisioningDomains is an + * optional list of domains * that are associated with this tenant. * - * @param name - The tenant name must be unique per project. - * @param selfProvisioningDomains - Users authenticating from these domains will be associated - * with this tenant. + * @param name - The tenant name must be unique per project. + * @param selfProvisioningDomains - Users authenticating from these domains will + * be associated + * with this tenant. * @return The tenant ID generated automatically for the tenant. * @throws DescopeException in case of errors */ String create(String name, List selfProvisioningDomains) throws DescopeException; /** - * Create a new tenant with the given name and ID. selfProvisioningDomains is an optional list of + * Create a new tenant with the given name. selfProvisioningDomains is an + * optional list of domains that are associated with this tenant. + * + * + * @param name - The tenant name must be unique per project. + * @param selfProvisioningDomains - Users authenticating from these domains will + * be associated with this tenant. + * @param customAttributes - Custom attributes to apply to tenant (needs + * to be pre-configured) + * @return The tenant ID generated automatically for the tenant. + * @throws DescopeException in case of errors + */ + String create(String name, List selfProvisioningDomains, Map customAttributes) + throws DescopeException; + + /** + * Create a new tenant with the given name and ID. selfProvisioningDomains is an + * optional list of * domains that are associated with this tenant. * - * @param id - The tenant ID must be unique per project. - * @param name - The tenant name must be unique per project. - * @param selfProvisioningDomains - Users authenticating from these domains will be associated - * with this tenant. + * @param id - The tenant ID must be unique per project. + * @param name - The tenant name must be unique per project. + * @param selfProvisioningDomains - Users authenticating from these domains will + * be associated + * with this tenant. * @throws DescopeException in case of errors */ void createWithId(String id, String name, List selfProvisioningDomains) throws DescopeException; /** - * Update an existing tenant's name and domains. IMPORTANT: All parameters are required and will - * override whatever value is currently set in the existing tenant. Use carefully. + * Create a new tenant with the given name and ID. selfProvisioningDomains is an + * optional list of + * domains that are associated with this tenant. * - * @param id - Tenant ID - * @param name - The tenant name must be unique per project. - * @param selfProvisioningDomains - Users authenticating from these domains will be associated - * with this tenant. + * @param id - The tenant ID must be unique per project. + * @param name - The tenant name must be unique per project. + * @param selfProvisioningDomains - Users authenticating from these domains will + * be associated with this tenant. + * @param customAttributes - Custom attributes to apply to tenant (needs + * to be pre-configured) * @throws DescopeException in case of errors */ - void update(String id, String name, List selfProvisioningDomains) throws DescopeException; + void createWithId(String id, String name, List selfProvisioningDomains, + Map customAttributes) + throws DescopeException; /** - * Delete an existing tenant. IMPORTANT: This action is irreversible. Use carefully. + * Update an existing tenant's name and domains. IMPORTANT: All parameters are + * required and will + * override whatever value is currently set in the existing tenant. Use + * carefully. + * + * @param id - Tenant ID + * @param name - The tenant name must be unique per project. + * @param selfProvisioningDomains - Users authenticating from these domains will + * be associated with this tenant. + * @param customAttributes - Custom attributes to apply to tenant (needs + * to be pre-configured) + * @throws DescopeException in case of errors + */ + void update(String id, String name, List selfProvisioningDomains, Map customAttributes) + throws DescopeException; + + /** + * Delete an existing tenant. IMPORTANT: This action is irreversible. Use + * carefully. * * @param id - Tenant ID * @throws DescopeException in case of errors @@ -55,8 +100,23 @@ void createWithId(String id, String name, List selfProvisioningDomains) /** * Load all project tenants.s * - * @return {@link Tenant Tenant} + * @return {{@link List} of {@link Tenant} * @throws DescopeException in case of errors */ List loadAll() throws DescopeException; + + /** + * Search all tenants according to given filters. + * + * @param request The options optional parameter allows to fine-tune the search + * filters and + * results. Using nil will result in a filter-less query with a + * set amount of results. + * @return {{@link List} of {@link Tenant} + * @throws DescopeException If there occurs any exception, a subtype of this + * exception will be + * thrown. + */ + List searchAll(TenantSearchRequest request) throws DescopeException; + } diff --git a/src/main/java/com/descope/sdk/mgmt/impl/TenantServiceImpl.java b/src/main/java/com/descope/sdk/mgmt/impl/TenantServiceImpl.java index 0191c505..20d224ae 100644 --- a/src/main/java/com/descope/sdk/mgmt/impl/TenantServiceImpl.java +++ b/src/main/java/com/descope/sdk/mgmt/impl/TenantServiceImpl.java @@ -3,6 +3,7 @@ import static com.descope.literals.Routes.ManagementEndPoints.CREATE_TENANT_LINK; import static com.descope.literals.Routes.ManagementEndPoints.DELETE_TENANT_LINK; import static com.descope.literals.Routes.ManagementEndPoints.LOAD_ALL_TENANTS_LINK; +import static com.descope.literals.Routes.ManagementEndPoints.TENANT_SEARCH_ALL_LINK; import static com.descope.literals.Routes.ManagementEndPoints.UPDATE_TENANT_LINK; import com.descope.exception.DescopeException; @@ -10,6 +11,7 @@ import com.descope.model.client.Client; import com.descope.model.mgmt.ManagementParams; import com.descope.model.tenant.Tenant; +import com.descope.model.tenant.request.TenantSearchRequest; import com.descope.model.tenant.response.GetAllTenantsResponse; import com.descope.sdk.mgmt.TenantService; import java.net.URI; @@ -29,7 +31,17 @@ public String create(String name, List selfProvisioningDomains) throws D if (StringUtils.isBlank(name)) { throw ServerCommonException.invalidArgument("name"); } - var tenant = new Tenant("", name, selfProvisioningDomains); + var tenant = new Tenant("", name, selfProvisioningDomains, null); + return create(tenant); + } + + @Override + public String create(String name, List selfProvisioningDomains, Map customAttributes) + throws DescopeException { + if (StringUtils.isBlank(name)) { + throw ServerCommonException.invalidArgument("name"); + } + var tenant = new Tenant("", name, selfProvisioningDomains, customAttributes); return create(tenant); } @@ -40,7 +52,19 @@ public void createWithId(String id, String name, List selfProvisioningDo throw ServerCommonException.invalidArgument("id or name"); } - var tenant = new Tenant(id, name, selfProvisioningDomains); + var tenant = new Tenant(id, name, selfProvisioningDomains, null); + create(tenant); + } + + @Override + public void createWithId(String id, String name, List selfProvisioningDomains, + Map customAttributes) + throws DescopeException { + if (StringUtils.isAnyBlank(id, name)) { + throw ServerCommonException.invalidArgument("id or name"); + } + + var tenant = new Tenant(id, name, selfProvisioningDomains, customAttributes); create(tenant); } @@ -52,13 +76,13 @@ private String create(Tenant tenant) { } @Override - public void update(String id, String name, List selfProvisioningDomains) + public void update(String id, String name, List selfProvisioningDomains, Map customAttributes) throws DescopeException { if (StringUtils.isAnyBlank(id, name)) { throw ServerCommonException.invalidArgument("id or name"); } - var tenant = new Tenant(id, name, selfProvisioningDomains); + var tenant = new Tenant(id, name, selfProvisioningDomains, customAttributes); update(tenant); } @@ -87,6 +111,19 @@ public List loadAll() throws DescopeException { return response.getTenants(); } + @Override + public List searchAll(TenantSearchRequest request) + throws DescopeException { + if (request == null) { + request = TenantSearchRequest.builder().build(); + } + + URI composeSearchAllUri = composeSearchAllUri(); + var apiProxy = getApiProxy(); + GetAllTenantsResponse response = apiProxy.post(composeSearchAllUri, request, GetAllTenantsResponse.class); + return response.getTenants(); + } + private URI composeCreateTenantUri() { return getUri(CREATE_TENANT_LINK); } @@ -102,4 +139,8 @@ private URI composeDeleteTenantUri() { private URI loadAllTenantsUri() { return getUri(LOAD_ALL_TENANTS_LINK); } + + private URI composeSearchAllUri() { + return getUri(TENANT_SEARCH_ALL_LINK); + } } diff --git a/src/test/java/com/descope/sdk/TestUtils.java b/src/test/java/com/descope/sdk/TestUtils.java index 18511237..3d2ad085 100644 --- a/src/test/java/com/descope/sdk/TestUtils.java +++ b/src/test/java/com/descope/sdk/TestUtils.java @@ -118,4 +118,4 @@ public static ManagementParams getManagementParams() { .build(); } -} +} \ No newline at end of file diff --git a/src/test/java/com/descope/sdk/mgmt/impl/TenantServiceImplTest.java b/src/test/java/com/descope/sdk/mgmt/impl/TenantServiceImplTest.java index 58bb83bc..fd6d96b8 100644 --- a/src/test/java/com/descope/sdk/mgmt/impl/TenantServiceImplTest.java +++ b/src/test/java/com/descope/sdk/mgmt/impl/TenantServiceImplTest.java @@ -15,6 +15,7 @@ import com.descope.exception.RateLimitExceededException; import com.descope.exception.ServerCommonException; import com.descope.model.tenant.Tenant; +import com.descope.model.tenant.request.TenantSearchRequest; import com.descope.model.tenant.response.GetAllTenantsResponse; import com.descope.proxy.ApiProxy; import com.descope.proxy.impl.ApiProxyBuilder; @@ -29,12 +30,11 @@ public class TenantServiceImplTest { private final List selfProvisioningDomains = List.of("domain1", "domain2"); - Tenant mockTenant = - Tenant.builder() - .id("id") - .name("name") - .selfProvisioningDomains(selfProvisioningDomains) - .build(); + Tenant mockTenant = Tenant.builder() + .id("id") + .name("name") + .selfProvisioningDomains(selfProvisioningDomains) + .build(); private TenantService tenantService; @@ -42,15 +42,13 @@ public class TenantServiceImplTest { void setUp() { var authParams = TestUtils.getManagementParams(); var client = TestUtils.getClient(); - this.tenantService = - ManagementServiceBuilder.buildServices(client, authParams).getTenantService(); + this.tenantService = ManagementServiceBuilder.buildServices(client, authParams).getTenantService(); } @Test void testCreateForEmptyName() { - ServerCommonException thrown = - assertThrows( - ServerCommonException.class, () -> tenantService.create("", selfProvisioningDomains)); + ServerCommonException thrown = assertThrows( + ServerCommonException.class, () -> tenantService.create("", selfProvisioningDomains)); assertNotNull(thrown); assertEquals("The name argument is invalid", thrown.getMessage()); } @@ -61,7 +59,7 @@ void testCreateSuccess() { doReturn(mockTenant).when(apiProxy).post(any(), any(), any()); try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); var response = tenantService.create("someName", selfProvisioningDomains); assertThat(response).isEqualTo("id"); } @@ -69,10 +67,9 @@ void testCreateSuccess() { @Test void testCreateWithIdForEmptyId() { - ServerCommonException thrown = - assertThrows( - ServerCommonException.class, - () -> tenantService.createWithId("", "", selfProvisioningDomains)); + ServerCommonException thrown = assertThrows( + ServerCommonException.class, + () -> tenantService.createWithId("", "", selfProvisioningDomains)); assertNotNull(thrown); assertEquals("The id or name argument is invalid", thrown.getMessage()); } @@ -83,7 +80,7 @@ void testCreateWithIdForSuccess() { doReturn(mockTenant).when(apiProxy).post(any(), any(), any()); try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); tenantService.createWithId("someLoginId", "someName", selfProvisioningDomains); verify(apiProxy, times(1)).post(any(), any(), any()); } @@ -91,10 +88,9 @@ void testCreateWithIdForSuccess() { @Test void testUpdateForEmptyId() { - ServerCommonException thrown = - assertThrows( - ServerCommonException.class, - () -> tenantService.update("", "", selfProvisioningDomains)); + ServerCommonException thrown = assertThrows( + ServerCommonException.class, + () -> tenantService.update("", "", selfProvisioningDomains, null)); assertNotNull(thrown); assertEquals("The id or name argument is invalid", thrown.getMessage()); } @@ -105,16 +101,15 @@ void testUpdateForSuccess() { doReturn(mockTenant).when(apiProxy).post(any(), any(), any()); try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); - tenantService.update("someLoginId", "someName", selfProvisioningDomains); + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + tenantService.update("someLoginId", "someName", selfProvisioningDomains, null); verify(apiProxy, times(1)).post(any(), any(), any()); } } @Test void testDeleteForEmptyId() { - ServerCommonException thrown = - assertThrows(ServerCommonException.class, () -> tenantService.delete("")); + ServerCommonException thrown = assertThrows(ServerCommonException.class, () -> tenantService.delete("")); assertNotNull(thrown); assertEquals("The id argument is invalid", thrown.getMessage()); } @@ -125,7 +120,7 @@ void testDeleteForSuccess() { doReturn(mockTenant).when(apiProxy).post(any(), any(), any()); try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); tenantService.delete("someId"); verify(apiProxy, times(1)).post(any(), any(), any()); } @@ -138,7 +133,7 @@ void testLoadAllForSuccess() { doReturn(mockTenantsResponse).when(apiProxy).get(any(), any()); try (MockedStatic mockedApiProxyBuilder = mockStatic(ApiProxyBuilder.class)) { mockedApiProxyBuilder.when( - () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); + () -> ApiProxyBuilder.buildProxy(any(), any())).thenReturn(apiProxy); var response = tenantService.loadAll(); assertThat(response.size()).isEqualTo(1); } @@ -160,7 +155,7 @@ void testFunctionalFullCycle() { } } assertTrue(found); - tenantService.update(tenantId, name + "1", List.of(name + ".com")); + tenantService.update(tenantId, name + "1", List.of(name + ".com"), null); tenants = tenantService.loadAll(); assertThat(tenants).isNotEmpty(); found = false; @@ -171,6 +166,21 @@ void testFunctionalFullCycle() { assertThat(t.getSelfProvisioningDomains()).containsOnly(name + ".com"); } } + + var tenantSearchRequest = TenantSearchRequest.builder().names(List.of(name + "1")).build(); + tenants = tenantService.searchAll(tenantSearchRequest); + assertThat(tenants).isNotEmpty(); + found = false; + for (var t : tenants) { + if (t.getId().equals(tenantId)) { + found = true; + assertEquals(name + "1", t.getName()); + } + } + + tenantSearchRequest = TenantSearchRequest.builder().names(List.of("doesnotexists")).build(); + tenants = tenantService.searchAll(tenantSearchRequest); + assertThat(tenants).isEmpty(); tenantService.delete(tenantId); } }