From 26ee7db66533c6d6327685449a70d44483d8c925 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Thu, 2 Jun 2022 20:41:22 +0900 Subject: [PATCH 1/4] fix: can't configure some properties --- pom.xml | 2 +- .../connector/auth0/Auth0Client.java | 12 +++------- .../connector/auth0/Auth0Configuration.java | 24 +++++++++---------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index 7729bb4..af26d40 100644 --- a/pom.xml +++ b/pom.xml @@ -194,7 +194,7 @@ com.evolveum.midpoint.gui admin-gui - 4.4.1 + 4.4.2 jar classes provided diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java index fe75545..06e246f 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java @@ -61,15 +61,9 @@ public void initClient(Auth0Configuration configuration) throws Auth0Exception { HttpOptions httpOptions = new HttpOptions(); - if (configuration.getConnectionTimeoutInSeconds() != null) { - httpOptions.setConnectTimeout(configuration.getConnectionTimeoutInSeconds()); - } - if (configuration.getReadTimeoutInSeconds() != null) { - httpOptions.setReadTimeout(configuration.getReadTimeoutInSeconds()); - } - if (configuration.getMaxRetries() != null) { - httpOptions.setManagementAPIMaxRetries(configuration.getMaxRetries()); - } + httpOptions.setConnectTimeout(configuration.getConnectionTimeoutInSeconds()); + httpOptions.setReadTimeout(configuration.getReadTimeoutInSeconds()); + httpOptions.setManagementAPIMaxRetries(configuration.getMaxRetries()); // HTTP Proxy applyProxyIfNecessary(httpOptions); diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Configuration.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Configuration.java index 8785a0b..5fb2def 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Configuration.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Configuration.java @@ -24,14 +24,14 @@ public class Auth0Configuration extends AbstractConfiguration { private String domain; private String clientId; private GuardedString clientSecret; - private Integer connectionTimeoutInSeconds = 10; - private Integer readTimeoutInSeconds = 10; - private Integer maxRetries = 3; + private int connectionTimeoutInSeconds = 10; + private int readTimeoutInSeconds = 10; + private int maxRetries = 3; private String httpProxyHost; private int httpProxyPort; private String httpProxyUser; private GuardedString httpProxyPassword; - private Integer defaultQueryPageSize = 50; + private int defaultQueryPageSize = 50; private String[] connectionFilter = new String[]{}; @ConfigurationProperty( @@ -96,11 +96,11 @@ public void setConnectionFilter(String[] connectionFilter) { helpMessageKey = "Connection timeout when connecting to Auth0. (Default: 10)", required = false, confidential = false) - public Integer getConnectionTimeoutInSeconds() { + public int getConnectionTimeoutInSeconds() { return connectionTimeoutInSeconds; } - public void setConnectionTimeoutInSeconds(Integer connectionTimeoutInSeconds) { + public void setConnectionTimeoutInSeconds(int connectionTimeoutInSeconds) { this.connectionTimeoutInSeconds = connectionTimeoutInSeconds; } @@ -110,11 +110,11 @@ public void setConnectionTimeoutInSeconds(Integer connectionTimeoutInSeconds) { helpMessageKey = "Read timeout when fetching data from Auth0. (Default: 10)", required = false, confidential = false) - public Integer getReadTimeoutInSeconds() { + public int getReadTimeoutInSeconds() { return readTimeoutInSeconds; } - public void setReadTimeoutInSeconds(Integer readTimeoutInSeconds) { + public void setReadTimeoutInSeconds(int readTimeoutInSeconds) { this.readTimeoutInSeconds = readTimeoutInSeconds; } @@ -124,11 +124,11 @@ public void setReadTimeoutInSeconds(Integer readTimeoutInSeconds) { helpMessageKey = "Sets the maximum number of consecutive retries for Auth0 Management API requests that fail due to rate-limits being reached. (Default: 3)", required = false, confidential = false) - public Integer getMaxRetries() { + public int getMaxRetries() { return maxRetries; } - public void setMaxRetries(Integer maxRetries) { + public void setMaxRetries(int maxRetries) { this.maxRetries = maxRetries; } @@ -194,11 +194,11 @@ public void setHttpProxyPassword(GuardedString httpProxyPassword) { helpMessageKey = "Set default query page size. Default: 50", required = false, confidential = false) - public Integer getDefaultQueryPageSize() { + public int getDefaultQueryPageSize() { return defaultQueryPageSize; } - public void setDefaultQueryPageSize(Integer defaultQueryPageSize) { + public void setDefaultQueryPageSize(int defaultQueryPageSize) { this.defaultQueryPageSize = defaultQueryPageSize; } From 48fb1ba7e003c4cb9b24390edd1f341940862a85 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Fri, 3 Jun 2022 12:16:14 +0900 Subject: [PATCH 2/4] fix: paged search on resouce --- .../connector/auth0/Auth0Client.java | 195 ++++++--- .../connector/auth0/Auth0Connector.java | 31 +- .../auth0/Auth0OrganizationHandler.java | 24 +- .../connector/auth0/Auth0RoleHandler.java | 24 +- .../connector/auth0/Auth0UserHandler.java | 33 +- .../connector/auth0/Auth0Utils.java | 1 + .../connector/auth0/Auth0ClientTest.java | 397 ++++++++++++++++++ .../connector/auth0/testutil/MockClient.java | 3 +- 8 files changed, 608 insertions(+), 100 deletions(-) create mode 100644 src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java index 06e246f..9f7fc77 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java @@ -143,7 +143,7 @@ protected boolean isExpired(TokenHolder holder) { public List getConnection(ConnectionFilter connectionFilter) throws Auth0Exception { List conns = new ArrayList<>(); - withAuthPaging(connectionFilter, 0, 50, (filter) -> { + withAuthPaging(connectionFilter, 0, 50, (filter, ignore) -> { Request request = internalClient.connections().listAll(filter); ConnectionsPage response = request.execute(); @@ -224,15 +224,21 @@ public List getUserByEmail(String email, FieldsFilter filter) throws Auth0 }); } - public void getUsers(UserFilter userFilter, OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { + public int getUsers(UserFilter userFilter, OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { int pageInitialOffset = resolvePageOffset(options); int pageSize = resolvePageSize(configuration, options); - withAuthPaging(userFilter, pageInitialOffset, pageSize, (filter) -> { + return withAuthPaging(userFilter, pageInitialOffset, pageSize, (filter, skipCount) -> { Request request = internalClient.users().list(filter); UsersPage response = request.execute(); + int count = 0; for (User u : response.getItems()) { + if (count < skipCount) { + count++; + continue; + } + Boolean next = resultsHandler.apply(u); if (!next) { break; @@ -260,7 +266,7 @@ public void removeRolesToUser(Uid uid, List roleIds) throws Auth0Excepti public List getRolesForUser(String userId) throws Auth0Exception { List roles = new ArrayList<>(); - withAuthPaging(new PageFilter(), 0, 50, (filter) -> { + withAuthPaging(new PageFilter(), 0, 50, (filter, ignore) -> { Request request = internalClient.users().listRoles(userId, filter); RolesPage response = request.execute(); @@ -297,7 +303,7 @@ public void removeOrganizationsToUser(Uid uid, List orgIds) throws Auth0 public List getOrganizationsForUser(String userId) throws Auth0Exception { List orgs = new ArrayList<>(); - withAuthPaging(new PageFilter(), 0, 50, (filter) -> { + withAuthPaging(new PageFilter(), 0, 50, (filter, ignore) -> { Request request = internalClient.users().getOrganizations(userId, filter); OrganizationsPage response = request.execute(); @@ -334,7 +340,7 @@ public Map> getOrganizationRolesForUser(String userId) thro List orgs = getOrganizationsForUser(userId); for (Organization org : orgs) { - withAuthPaging(new PageFilter(), 0, 50, (filter) -> { + withAuthPaging(new PageFilter(), 0, 50, (filter, ignore) -> { Request request = internalClient.organizations().getRoles(org.getId(), userId, filter); RolesPage response = request.execute(); @@ -365,7 +371,7 @@ public void removePermissionsToUser(Uid uid, List permissions) throw public List getPermissionsForUser(String userId) throws Auth0Exception { List permissions = new ArrayList<>(); - withAuthPaging(new PageFilter(), 0, 50, (filter) -> { + withAuthPaging(new PageFilter(), 0, 50, (filter, ignore) -> { Request request = internalClient.users().listPermissions(userId, filter); PermissionsPage response = request.execute(); @@ -420,17 +426,23 @@ public List getRoleByName(String roleName) throws Auth0Exception { }); } - public void getRoles(OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { + public int getRoles(OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { int pageInitialOffset = resolvePageOffset(options); int pageSize = resolvePageSize(configuration, options); RolesFilter rolesFilter = new RolesFilter(); - withAuthPaging(rolesFilter, pageInitialOffset, pageSize, (filter) -> { + return withAuthPaging(rolesFilter, pageInitialOffset, pageSize, (filter, skipCount) -> { Request request = internalClient.roles().list(filter); RolesPage response = request.execute(); + int count = 0; for (Role u : response.getItems()) { + if (count < skipCount) { + count++; + continue; + } + Boolean next = resultsHandler.apply(u); if (!next) { break; @@ -458,7 +470,7 @@ public void removePermissionsToRole(Uid uid, List permissions) throw public List getPermissionsForRole(String roleId) throws Auth0Exception { List permissions = new ArrayList<>(); - withAuthPaging(new PageFilter(), 0, 50, (filter) -> { + withAuthPaging(new PageFilter(), 0, 50, (filter, ignore) -> { Request request = internalClient.roles().listPermissions(roleId, filter); PermissionsPage response = request.execute(); @@ -510,17 +522,23 @@ public Organization getOrganizationByName(String orgName) throws Auth0Exception }); } - public void getOrganizations(OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { + public int getOrganizations(OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { int pageInitialOffset = resolvePageOffset(options); int pageSize = resolvePageSize(configuration, options); - PageFilter rolesFilter = new PageFilter(); + PageFilter orgsFilter = new PageFilter(); - withAuthPaging(rolesFilter, pageInitialOffset, pageSize, (filter) -> { + return withAuthPaging(orgsFilter, pageInitialOffset, pageSize, (filter, skipCount) -> { Request request = internalClient.organizations().list(filter); OrganizationsPage response = request.execute(); + int count = 0; for (Organization u : response.getItems()) { + if (count < skipCount) { + count++; + continue; + } + Boolean next = resultsHandler.apply(u); if (!next) { break; @@ -532,72 +550,129 @@ public void getOrganizations(OperationOptions options, ResultHandlerFunction int withAuthPaging(T filter, int pageOffset, int pageSize, PageFunction> callback) throws Auth0Exception { + withTotals(filter, true); - private void withAuthPaging(T filter, int initialOffset, int pageSize, PageFunction> callback) throws Auth0Exception { - int offset = initialOffset; - boolean retried = false; + PageInfo pageInfo = newPageInfo(pageOffset, pageSize); - filter.withTotals(true); + if (pageInfo.isRequestedFullPage()) { + // Start from page 0 in Auth0 + int pageNumber = 0; + int total = 0; - while (true) { - filter.withPage(offset, pageSize); + while (true) { + withPage(filter, pageNumber, pageSize); - Page result = withAuth(() -> { - Page response = callback.apply(filter); - return response; - }); + Page result = withAuth(() -> { + Page response = callback.apply(filter, 0); + return response; + }); + + total = result.getTotal(); + if (total == 0) { + // Not found + break; + } - if (hasNextPage(result)) { - offset++; - continue; + if (hasNextPage(result)) { + pageNumber++; + continue; + } + break; } - break; + return total; + + } else { + // Start from page 0 in Auth0, so need -1 + int pageNumber = pageInfo.initPage - 1; + int total = 0; + + for (int i = 0; i < pageInfo.times; i++) { + withPage(filter, pageNumber, pageSize); + + final int skipCount = i == 0 ? pageInfo.skipCount : 0; + + Page result = withAuth(() -> { + Page response = callback.apply(filter, skipCount); + return response; + }); + + total = result.getTotal(); + if (total == 0) { + // Not found + break; + } + + if (hasNextPage(result)) { + pageNumber++; + continue; + } + break; + } + return total; } } - private void withAuthPaging(T filter, int initialOffset, int pageSize, PageFunction> callback) throws Auth0Exception { - int offset = initialOffset; + protected static class PageInfo { + public final int pageOffset; + public final int initPage; + public final int skipCount; + public final int times; - filter.withTotals(true); + public PageInfo(int pageOffset, int initPage, int skipCount, int times) { + this.pageOffset = pageOffset; + this.initPage = initPage; + this.skipCount = skipCount; + this.times = times; + } - while (true) { - filter.withPage(offset, pageSize); + public boolean isRequestedFullPage() { + return pageOffset == 0; + } + } - Page result = withAuth(() -> { - Page response = callback.apply(filter); - return response; - }); + protected static PageInfo newPageInfo(int pageOffset, int pageSize) { + if (pageOffset == 0) { + // Requested full page + return new PageInfo(pageOffset, 1, 0, -1); - if (hasNextPage(result)) { - offset++; - continue; - } - break; + } else if ((pageOffset + pageSize - 1) % pageSize == 0) { + int initPage = (pageOffset + pageSize - 1) / pageSize; + return new PageInfo(pageOffset, initPage, 0, 1); + + } else { + int initPage = ((pageOffset + pageSize - 1) / pageSize); + int skipCount = pageOffset - ((initPage - 1) * pageSize) -1; + + return new PageInfo(pageOffset, initPage, skipCount, 2); } } - private void withAuthPaging(T filter, int initialOffset, int pageSize, PageFunction> callback) throws Auth0Exception { - int offset = initialOffset; + private void withTotals(BaseFilter filter, boolean includesTotal) { + if (filter instanceof PageFilter) { + ((PageFilter) filter).withTotals(includesTotal); - filter.withTotals(true); + } else if (filter instanceof QueryFilter) { + ((QueryFilter) filter).withTotals(includesTotal); - while (true) { - filter.withPage(offset, pageSize); + } else if (filter instanceof ConnectionFilter) { + ((ConnectionFilter) filter).withTotals(includesTotal); + } + } - Page result = withAuth(() -> { - Page response = callback.apply(filter); - return response; - }); + private void withPage(BaseFilter filter, int pageNumber, int pageSize) { + if (filter instanceof PageFilter) { + ((PageFilter) filter).withPage(pageNumber, pageSize); - if (hasNextPage(result)) { - offset++; - continue; - } - break; + } else if (filter instanceof QueryFilter) { + ((QueryFilter) filter).withPage(pageNumber, pageSize); + + } else if (filter instanceof ConnectionFilter) { + ((ConnectionFilter) filter).withPage(pageNumber, pageSize); } } - private T withAuth(APIFunction callback) throws Auth0Exception { + protected T withAuth(APIFunction callback) throws Auth0Exception { boolean retried = false; // Refresh token if expired in advance @@ -626,7 +701,7 @@ interface APIFunction { @FunctionalInterface interface PageFunction { - public Result apply(One one) throws Auth0Exception; + public Result apply(One one, int skipCount) throws Auth0Exception; } @FunctionalInterface @@ -635,7 +710,11 @@ public interface ResultHandlerFunction { } private static boolean hasNextPage(Page page) { - int remains = (page.getTotal() - page.getStart() + page.getLimit()); + Integer length = page.getLength(); + if (length == null) { + return false; + } + int remains = (page.getTotal() - (page.getStart() + length)); return remains > 0; } } diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Connector.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Connector.java index d084988..d478704 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Connector.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Connector.java @@ -23,10 +23,7 @@ import org.identityconnectors.framework.common.exceptions.*; import org.identityconnectors.framework.common.objects.*; import org.identityconnectors.framework.common.objects.filter.FilterTranslator; -import org.identityconnectors.framework.spi.Configuration; -import org.identityconnectors.framework.spi.ConnectorClass; -import org.identityconnectors.framework.spi.InstanceNameAware; -import org.identityconnectors.framework.spi.PoolableConnector; +import org.identityconnectors.framework.spi.*; import org.identityconnectors.framework.spi.operations.*; import java.util.*; @@ -35,6 +32,8 @@ import static jp.openstandia.connector.auth0.Auth0OrganizationHandler.ORGANIZATION_OBJECT_CLASS; import static jp.openstandia.connector.auth0.Auth0RoleHandler.ROLE_OBJECT_CLASS; import static jp.openstandia.connector.auth0.Auth0UserHandler.USER_OBJECT_CLASS_PREFIX; +import static jp.openstandia.connector.auth0.Auth0Utils.resolvePageOffset; +import static jp.openstandia.connector.auth0.Auth0Utils.resolvePageSize; @ConnectorClass(configurationClass = Auth0Configuration.class, displayNameKey = "Auth0 Connector") public class Auth0Connector implements PoolableConnector, CreateOp, UpdateDeltaOp, DeleteOp, SchemaOp, TestOp, SearchOp, InstanceNameAware { @@ -105,7 +104,8 @@ public Schema schema() { schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildAttributesToGet(), SearchOp.class); schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildReturnDefaultAttributes(), SearchOp.class); - + schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildPageSize(), SearchOp.class); + schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildPagedResultsOffset(), SearchOp.class); Map roleSchemaMap = new HashMap<>(); for (AttributeInfo a : roleSchemaInfo.getAttributeInfo()) { @@ -231,21 +231,36 @@ public FilterTranslator createFilterTranslator(ObjectClass objectCl @Override public void executeQuery(ObjectClass objectClass, Auth0Filter filter, ResultsHandler resultsHandler, OperationOptions options) { try { + int pageSize = resolvePageSize(configuration, options); + int pageOffset = resolvePageOffset(options); + + int total = 0; + if (objectClass.getObjectClassValue().startsWith(USER_OBJECT_CLASS_PREFIX)) { Auth0UserHandler userHandler = new Auth0UserHandler(configuration, client, getSchemaMap(objectClass), resolveDatabaseConnection(objectClass)); - userHandler.getUsers(filter, resultsHandler, options); + total = userHandler.getUsers(filter, resultsHandler, options); } else if (objectClass.equals(ROLE_OBJECT_CLASS)) { Auth0RoleHandler roleHandler = new Auth0RoleHandler(configuration, client, getSchemaMap(objectClass)); - roleHandler.getRoles(filter, resultsHandler, options); + total = roleHandler.getRoles(filter, resultsHandler, options); } else if (objectClass.equals(ORGANIZATION_OBJECT_CLASS)) { Auth0OrganizationHandler organizationHandler = new Auth0OrganizationHandler(configuration, client, getSchemaMap(objectClass)); - organizationHandler.query(filter, resultsHandler, options); + total = organizationHandler.query(filter, resultsHandler, options); } else { throw new InvalidAttributeValueException("Unsupported object class " + objectClass); } + + if (resultsHandler instanceof SearchResultsHandler && + pageOffset > 0) { + + int remaining = total - (pageSize * pageOffset); + + SearchResultsHandler searchResultsHandler = (SearchResultsHandler) resultsHandler; + SearchResult searchResult = new SearchResult(null, remaining); + searchResultsHandler.handleResult(searchResult); + } } catch (Exception e) { throw processException(e); } diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0OrganizationHandler.java b/src/main/java/jp/openstandia/connector/auth0/Auth0OrganizationHandler.java index c681ab6..a74653d 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0OrganizationHandler.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0OrganizationHandler.java @@ -239,10 +239,11 @@ public void delete(Uid uid, OperationOptions options) throws Auth0Exception { * @param filter * @param resultsHandler * @param options + * @return * @throws Auth0Exception */ - public void query(Auth0Filter filter, - ResultsHandler resultsHandler, OperationOptions options) throws Auth0Exception { + public int query(Auth0Filter filter, + ResultsHandler resultsHandler, OperationOptions options) throws Auth0Exception { // Create full attributesToGet by RETURN_DEFAULT_ATTRIBUTES + ATTRIBUTES_TO_GET Set attributesToGet = createFullAttributesToGet(schema, options); boolean allowPartialAttributeValues = shouldAllowPartialAttributeValues(options); @@ -250,29 +251,32 @@ public void query(Auth0Filter filter, if (filter != null) { if (filter.isByName()) { // Filter by __NANE__ - getOrganizationByName(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getOrganizationByName(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } else { // Filter by __UID__ - getOrganizationByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getOrganizationByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } - return; } - client.getOrganizations(options, (org) -> resultsHandler.handle(toConnectorObject(org, attributesToGet, allowPartialAttributeValues))); + return client.getOrganizations(options, (org) -> resultsHandler.handle(toConnectorObject(org, attributesToGet, allowPartialAttributeValues))); } - private void getOrganizationByName(String orgName, - ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { + private int getOrganizationByName(String orgName, + ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { Organization org = client.getOrganizationByName(orgName); resultsHandler.handle(toConnectorObject(org, attributesToGet, allowPartialAttributeValues)); + + return 1; } - private void getOrganizationByUid(String orgId, - ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { + private int getOrganizationByUid(String orgId, + ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { Organization org = client.getOrganizationByUid(orgId); resultsHandler.handle(toConnectorObject(org, attributesToGet, allowPartialAttributeValues)); + + return 1; } private ConnectorObject toConnectorObject(Organization org, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0RoleHandler.java b/src/main/java/jp/openstandia/connector/auth0/Auth0RoleHandler.java index cfe3f36..0e48d76 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0RoleHandler.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0RoleHandler.java @@ -212,9 +212,10 @@ public void deleteRole(Uid uid, OperationOptions options) throws Auth0Exception * @param resultsHandler * @param options * @throws Auth0Exception + * @return */ - public void getRoles(Auth0Filter filter, - ResultsHandler resultsHandler, OperationOptions options) throws Auth0Exception { + public int getRoles(Auth0Filter filter, + ResultsHandler resultsHandler, OperationOptions options) throws Auth0Exception { // Create full attributesToGet by RETURN_DEFAULT_ATTRIBUTES + ATTRIBUTES_TO_GET Set attributesToGet = createFullAttributesToGet(schema, options); boolean allowPartialAttributeValues = shouldAllowPartialAttributeValues(options); @@ -222,31 +223,34 @@ public void getRoles(Auth0Filter filter, if (filter != null) { if (filter.isByName()) { // Filter by __NANE__ - getRoleByName(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getRoleByName(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } else { // Filter by __UID__ - getRoleByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getRoleByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } - return; } - client.getRoles(options, (role) -> resultsHandler.handle(toConnectorObject(role, attributesToGet, allowPartialAttributeValues))); + return client.getRoles(options, (role) -> resultsHandler.handle(toConnectorObject(role, attributesToGet, allowPartialAttributeValues))); } - private void getRoleByName(String roleName, - ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { + private int getRoleByName(String roleName, + ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { List response = client.getRoleByName(roleName); for (Role role : response) { resultsHandler.handle(toConnectorObject(role, attributesToGet, allowPartialAttributeValues)); } + + return response.size(); } - private void getRoleByUid(String roleId, - ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { + private int getRoleByUid(String roleId, + ResultsHandler resultsHandler, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { Role role = client.getRoleByUid(roleId); resultsHandler.handle(toConnectorObject(role, attributesToGet, allowPartialAttributeValues)); + + return 1; } private ConnectorObject toConnectorObject(Role role, Set attributesToGet, boolean allowPartialAttributeValues) throws Auth0Exception { diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0UserHandler.java b/src/main/java/jp/openstandia/connector/auth0/Auth0UserHandler.java index ce078bb..56f201a 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0UserHandler.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0UserHandler.java @@ -564,7 +564,7 @@ public void deleteUser(Uid uid, OperationOptions options) throws Auth0Exception client.deleteUser(uid); } - public void getUsers(Auth0Filter filter, ResultsHandler resultsHandler, OperationOptions options) throws + public int getUsers(Auth0Filter filter, ResultsHandler resultsHandler, OperationOptions options) throws Auth0Exception { // Create full attributesToGet by RETURN_DEFAULT_ATTRIBUTES + ATTRIBUTES_TO_GET Set attributesToGet = createFullAttributesToGet(schema, options); @@ -574,33 +574,34 @@ public void getUsers(Auth0Filter filter, ResultsHandler resultsHandler, Operatio if (filter.isByName()) { // Filter by __NANE__ if (isSMS()) { - getUserByPhoneNumber(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getUserByPhoneNumber(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } else { - getUserByEmail(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getUserByEmail(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } } else { // Filter by __UID__ - getUserByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); + return getUserByUid(filter.attributeValue, resultsHandler, attributesToGet, allowPartialAttributeValues); } - return; } UserFilter userFilter = applyFieldsFilter(attributesToGet, new UserFilter(), ADDITIONAL_ALLOWED_FIELDS_SET) .withQuery("identities.connection:\"" + connection + "\""); - client.getUsers(userFilter, options, (user) -> resultsHandler.handle(toConnectorObject(user, attributesToGet, allowPartialAttributeValues))); + return client.getUsers(userFilter, options, (user) -> resultsHandler.handle(toConnectorObject(user, attributesToGet, allowPartialAttributeValues))); } - private void getUserByUid(String userId, ResultsHandler resultsHandler, Set attributesToGet, - boolean allowPartialAttributeValues) throws Auth0Exception { + private int getUserByUid(String userId, ResultsHandler resultsHandler, Set attributesToGet, + boolean allowPartialAttributeValues) throws Auth0Exception { UserFilter filter = applyFieldsFilter(attributesToGet, new UserFilter(), Collections.emptySet()); User user = client.getUserByUid(userId, filter); resultsHandler.handle(toConnectorObject(user, attributesToGet, allowPartialAttributeValues)); + + return 1; } - private void getUserByPhoneNumber(String attrValue, ResultsHandler resultsHandler, Set attributesToGet, - boolean allowPartialAttributeValues) throws Auth0Exception { + private int getUserByPhoneNumber(String attrValue, ResultsHandler resultsHandler, Set attributesToGet, + boolean allowPartialAttributeValues) throws Auth0Exception { attrValue = attrValue.replace("\"", "\\\""); UserFilter filter = new UserFilter() .withPage(0, 1) @@ -612,10 +613,12 @@ private void getUserByPhoneNumber(String attrValue, ResultsHandler resultsHandle for (User user : response) { resultsHandler.handle(toConnectorObject(user, attributesToGet, allowPartialAttributeValues)); } + + return response.size(); } - private void getUserByEmail(String email, ResultsHandler resultsHandler, Set attributesToGet, - boolean allowPartialAttributeValues) throws Auth0Exception { + private int getUserByEmail(String email, ResultsHandler resultsHandler, Set attributesToGet, + boolean allowPartialAttributeValues) throws Auth0Exception { email = email.replace("\"", "\\\""); UserFilter filter = new UserFilter() .withPage(0, 1) @@ -628,6 +631,8 @@ private void getUserByEmail(String email, ResultsHandler resultsHandler, Set attributesToGet, @@ -662,7 +667,9 @@ private ConnectorObject toConnectorObject(User user, Set attributesToGet builderWrapper.apply(ATTR_NICKNAME, user.getNickname()); builderWrapper.apply(ATTR_GIVEN_NAME, user.getGivenName()); builderWrapper.apply(ATTR_FAMILY_NAME, user.getFamilyName()); - builderWrapper.apply(ATTR_CONNECTION, user.getIdentities().stream().map(i -> i.getConnection()).collect(Collectors.toList())); + builderWrapper.apply(ATTR_CONNECTION, user.getIdentities() != null ? + user.getIdentities().stream().map(i -> i.getConnection()).collect(Collectors.toList()) : + null); if (allowPartialAttributeValues) { // Suppress fetching association diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Utils.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Utils.java index 91a17ea..a6d77da 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Utils.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Utils.java @@ -202,6 +202,7 @@ public static Set createFullAttributesToGet(Map s for (String a : options.getAttributesToGet()) { attributesToGet.add(a); } + attributesToGet.add(Uid.NAME); } return attributesToGet; } diff --git a/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java b/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java new file mode 100644 index 0000000..e47ba00 --- /dev/null +++ b/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java @@ -0,0 +1,397 @@ +package jp.openstandia.connector.auth0; + +import com.auth0.client.mgmt.filter.PageFilter; +import com.auth0.exception.Auth0Exception; +import com.auth0.json.mgmt.Page; +import com.auth0.json.mgmt.users.User; +import com.auth0.json.mgmt.users.UsersPage; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +class Auth0ClientTest { + + @Test + void zeroWithFullPage() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 50; + + // When + int i = client.withAuthPaging(filter, 0, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + Page response = new UsersPage(0, users.size(), users.size(), limit, null, users); + return response; + }); + + // Then + assertEquals(0, i); + } + + @Test + void oneWithFullPage() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 50; + + // When + int i = client.withAuthPaging(filter, 0, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("1")); + Page response = new UsersPage(0, users.size(), users.size(), limit, null, users); + return response; + }); + + // Then + assertEquals(1, i); + } + + @Test + void twoWithFullPage() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 50; + + // When + int i = client.withAuthPaging(filter, 0, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("1")); + users.add(newUser("2")); + Page response = new UsersPage(0, users.size(), users.size(), limit, null, users); + return response; + }); + + // Then + assertEquals(2, i); + } + + @Test + void nextPageWithFullPage() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + AtomicInteger count = new AtomicInteger(0); + int i = client.withAuthPaging(filter, 0, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + + switch (count.incrementAndGet()) { + case 1: { + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("1")); + users.add(newUser("2")); + Page response = new UsersPage(0, users.size(), 3, limit, null, users); + return response; + } + case 2: { + assertEquals(1, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("3")); + Page response = new UsersPage(2, users.size(), 3, limit, null, users); + return response; + } + } + fail("Unexpected called"); + return null; + }); + + // Then + assertEquals(3, i); + assertEquals(2, count.get()); + } + + @Test + void countZero() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + + // When + // For count requested, pageOffset and pageSize are 1 + int i = client.withAuthPaging(filter, 1, 1, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(1, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + Page response = new UsersPage(0, users.size(), users.size(), 1, null, users); + return response; + }); + + // Then + assertEquals(0, i); + } + + @Test + void countOne() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + + // When + // For count requested, pageOffset and pageSize are 1 + int i = client.withAuthPaging(filter, 1, 1, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(1, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("1")); + Page response = new UsersPage(0, users.size(), users.size(), 1, null, users); + return response; + }); + + // Then + assertEquals(1, i); + } + + @Test + void zeroWithOffset() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + int i = client.withAuthPaging(filter, 1, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + Page response = new UsersPage(0, users.size(), 0, limit, null, users); + return response; + }); + + // Then + assertEquals(0, i); + } + + @Test + void oneWithOffset() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + int i = client.withAuthPaging(filter, 1, limit, (f, skipCount) -> { + assertEquals(0, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("1")); + Page response = new UsersPage(0, users.size(), 1, limit, null, users); + return response; + }); + + // Then + assertEquals(1, i); + } + + @Test + void oneWithOffset2() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + AtomicInteger count = new AtomicInteger(0); + int i = client.withAuthPaging(filter, 2, limit, (f, skipCount) -> { + switch (count.incrementAndGet()) { + case 1: { + assertEquals(1, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + // skipped by skipCount + // users.add(newUser("1")); + Page response = new UsersPage(1, users.size(), 1, limit, null, users); + return response; + } + } + fail("Unexpected called"); + return null; + }); + + // Then + assertEquals(1, i); + assertEquals(1, count.get()); + } + + @Test + void threeWithOffset2() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + AtomicInteger count = new AtomicInteger(0); + int i = client.withAuthPaging(filter, 2, limit, (f, skipCount) -> { + switch (count.incrementAndGet()) { + case 1: { + assertEquals(1, skipCount); + assertEquals(0, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + // skipped by skipCount + // users.add(newUser("1")); + users.add(newUser("2")); + Page response = new UsersPage(1, users.size(), 3, limit, null, users); + return response; + } + case 2: { + assertEquals(0, skipCount); + assertEquals(1, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("3")); + Page response = new UsersPage(1, users.size(), 3, limit, null, users); + return response; + } + } + fail("Unexpected called"); + return null; + }); + + // Then + assertEquals(3, i); + assertEquals(2, count.get()); + } + + @Test + void threeWithOffset3() throws Auth0Exception { + Auth0Client client = new Auth0Client() { + @Override + protected T withAuth(APIFunction callback) throws Auth0Exception { + return callback.apply(); + } + }; + + // Given + PageFilter filter = new PageFilter(); + int limit = 2; + + // When + AtomicInteger count = new AtomicInteger(0); + int i = client.withAuthPaging(filter, 3, limit, (f, skipCount) -> { + switch (count.incrementAndGet()) { + case 1: { + assertEquals(0, skipCount); + assertEquals(1, filter.getAsMap().get("page")); + assertEquals(limit, filter.getAsMap().get("per_page")); + + List users = new ArrayList<>(); + users.add(newUser("3")); + Page response = new UsersPage(2, users.size(), 3, limit, null, users); + return response; + } + } + fail("Unexpected called"); + return null; + }); + + // Then + assertEquals(3, i); + assertEquals(1, count.get()); + } + + private User newUser(String id) { + User user = new User("test"); + user.setId(id); + return user; + } +} \ No newline at end of file diff --git a/src/test/java/jp/openstandia/connector/auth0/testutil/MockClient.java b/src/test/java/jp/openstandia/connector/auth0/testutil/MockClient.java index d3ae62f..c8552a9 100644 --- a/src/test/java/jp/openstandia/connector/auth0/testutil/MockClient.java +++ b/src/test/java/jp/openstandia/connector/auth0/testutil/MockClient.java @@ -108,8 +108,9 @@ public List getUsersByFilter(UserFilter filter) throws Auth0Exception { } @Override - public void getUsers(UserFilter userFilter, OperationOptions options, Auth0Client.ResultHandlerFunction resultsHandler) throws Auth0Exception { + public int getUsers(UserFilter userFilter, OperationOptions options, ResultHandlerFunction resultsHandler) throws Auth0Exception { getUsers.accept(userFilter, options, resultsHandler); + return 0; } @Override From b9a1f112c515f5f5adf97f99fa0e946fe9cba461 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Fri, 3 Jun 2022 12:24:12 +0900 Subject: [PATCH 3/4] fix: expired check for Auth0 access token --- .../connector/auth0/Auth0Client.java | 16 +++++++----- .../connector/auth0/Auth0ClientTest.java | 26 +++++++++++++++++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java index 9f7fc77..31b4d0c 100644 --- a/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java +++ b/src/main/java/jp/openstandia/connector/auth0/Auth0Client.java @@ -101,8 +101,12 @@ private void checkClient() throws Auth0Exception { } protected void refreshToken() throws Auth0Exception { + refreshToken(false); + } + + protected void refreshToken(boolean force) throws Auth0Exception { if (configuration.getClientId() != null && configuration.getClientSecret() != null) { - if (isExpired(tokenHolder)) { + if (force || isExpired(tokenHolder, new Date())) { final AuthAPI[] authAPI = new AuthAPI[1]; configuration.getClientSecret().access(c -> { HttpOptions httpOptions = new HttpOptions(); @@ -124,14 +128,14 @@ protected void refreshToken() throws Auth0Exception { } } - protected boolean isExpired(TokenHolder holder) { + protected boolean isExpired(TokenHolder holder, Date now) { if (holder == null) { return true; } long expiresAt = holder.getExpiresAt().getTime(); - long now = new Date().getTime(); + long nowTime = now.getTime(); - if (expiresAt + (60 * 1000) > now) { + if (nowTime + (60 * 1000) > expiresAt) { LOG.ok("Detected the token is expired"); return true; } @@ -684,8 +688,8 @@ protected T withAuth(APIFunction callback) throws Auth0Exception { return response; } catch (APIException e) { // If the api token is expired during paging process, refresh the token then retry - if (!retried && e.getStatusCode() == 401 && e.getError().equals("Invalid tokens.")) { - refreshToken(); + if (!retried && e.getStatusCode() == 401) { + refreshToken(true); retried = true; continue; } diff --git a/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java b/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java index e47ba00..dec30ed 100644 --- a/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java +++ b/src/test/java/jp/openstandia/connector/auth0/Auth0ClientTest.java @@ -2,20 +2,42 @@ import com.auth0.client.mgmt.filter.PageFilter; import com.auth0.exception.Auth0Exception; +import com.auth0.json.auth.TokenHolder; import com.auth0.json.mgmt.Page; import com.auth0.json.mgmt.users.User; import com.auth0.json.mgmt.users.UsersPage; import org.junit.jupiter.api.Test; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; class Auth0ClientTest { + @Test + void expiredAuth0Token() { + Auth0Client client = new Auth0Client(); + + // Given + // 1655111797586 = 2022/06/13 18:16:37.586 GMT+09:00 + // 1655111737586 = 2022/06/13 18:15:37.586 GMT+09:00 + // 1655111737587 = 2022/06/13 18:15:37.587 GMT+09:00 + TokenHolder holder = new TokenHolder(null, null, null, "Bearer", 864000L, "openid", new Date(1655111797586L)); + Date now1 = new Date(1655111737586L); + Date now2 = new Date(1655111737587L); + + // When + boolean expired1 = client.isExpired(holder, now1); + boolean expired2 = client.isExpired(holder, now2); + + // Then + assertFalse(expired1); + assertTrue(expired2); + } + @Test void zeroWithFullPage() throws Auth0Exception { Auth0Client client = new Auth0Client() { From 254d2b174420ed72e67936badc5e539707f5ef7e Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Thu, 30 Jun 2022 11:04:46 +0900 Subject: [PATCH 4/4] build: auto release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index af26d40..04c607c 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ ossrh https://oss.sonatype.org/ - false + true