From ce6a43b400cc02d163f7802c6f698086948b6c8f Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jul 2026 15:31:20 +0530 Subject: [PATCH 1/2] chore: Add Auth Client tests --- .fernignore | 12 +- src/test/java/com/auth0/AssertsUtil.java | 20 + .../java/com/auth0/client/MockServer.java | 361 +++ .../auth0/client/RecordedRequestMatcher.java | 156 ++ .../java/com/auth0/client/UrlMatcher.java | 154 ++ .../com/auth0/client/auth/AuthAPITest.java | 2407 +++++++++++++++++ .../client/auth/AuthorizeUrlBuilderTest.java | 259 ++ .../client/auth/LogoutUrlBuilderTest.java | 106 + .../auth/RSAClientAssertionSignerTest.java | 165 ++ .../auth/add_oob_authenticator_response.json | 8 + .../auth/add_otp_authenticator_response.json | 6 + .../auth/back_channel_authorize_response.json | 5 + .../back_channel_login_status_response.json | 6 + src/test/resources/auth/error_plaintext.json | 1 + .../auth/error_with_description.json | 6 + ...with_description_and_extra_properties.json | 5 + src/test/resources/auth/error_with_error.json | 3 + .../auth/error_with_error_description.json | 4 + .../auth/list_authenticators_response.json | 26 + .../auth/mfa_challenge_request_response.json | 5 + .../auth/password_strength_error_none.json | 85 + .../auth/password_strength_error_some.json | 85 + .../resources/auth/passwordless_email.json | 5 + src/test/resources/auth/passwordless_sms.json | 6 + .../auth/pushed_authorization_response.json | 4 + src/test/resources/auth/reset_password.json | 1 + src/test/resources/auth/rsa_private_key.pem | 28 + src/test/resources/auth/rsa_public_key.pem | 9 + src/test/resources/auth/sign_up.json | 5 + src/test/resources/auth/sign_up_username.json | 7 + src/test/resources/auth/tokens.json | 7 + src/test/resources/auth/user_info.json | 20 + .../org.mockito.plugins.MockMaker | 1 + 33 files changed, 3971 insertions(+), 7 deletions(-) create mode 100644 src/test/java/com/auth0/AssertsUtil.java create mode 100644 src/test/java/com/auth0/client/MockServer.java create mode 100644 src/test/java/com/auth0/client/RecordedRequestMatcher.java create mode 100644 src/test/java/com/auth0/client/UrlMatcher.java create mode 100644 src/test/java/com/auth0/client/auth/AuthAPITest.java create mode 100644 src/test/java/com/auth0/client/auth/AuthorizeUrlBuilderTest.java create mode 100644 src/test/java/com/auth0/client/auth/LogoutUrlBuilderTest.java create mode 100644 src/test/java/com/auth0/client/auth/RSAClientAssertionSignerTest.java create mode 100644 src/test/resources/auth/add_oob_authenticator_response.json create mode 100644 src/test/resources/auth/add_otp_authenticator_response.json create mode 100644 src/test/resources/auth/back_channel_authorize_response.json create mode 100644 src/test/resources/auth/back_channel_login_status_response.json create mode 100644 src/test/resources/auth/error_plaintext.json create mode 100644 src/test/resources/auth/error_with_description.json create mode 100644 src/test/resources/auth/error_with_description_and_extra_properties.json create mode 100644 src/test/resources/auth/error_with_error.json create mode 100644 src/test/resources/auth/error_with_error_description.json create mode 100644 src/test/resources/auth/list_authenticators_response.json create mode 100644 src/test/resources/auth/mfa_challenge_request_response.json create mode 100644 src/test/resources/auth/password_strength_error_none.json create mode 100644 src/test/resources/auth/password_strength_error_some.json create mode 100644 src/test/resources/auth/passwordless_email.json create mode 100644 src/test/resources/auth/passwordless_sms.json create mode 100644 src/test/resources/auth/pushed_authorization_response.json create mode 100644 src/test/resources/auth/reset_password.json create mode 100644 src/test/resources/auth/rsa_private_key.pem create mode 100644 src/test/resources/auth/rsa_public_key.pem create mode 100644 src/test/resources/auth/sign_up.json create mode 100644 src/test/resources/auth/sign_up_username.json create mode 100644 src/test/resources/auth/tokens.json create mode 100644 src/test/resources/auth/user_info.json create mode 100644 src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker diff --git a/.fernignore b/.fernignore index ef704b0e1..819c62a7e 100644 --- a/.fernignore +++ b/.fernignore @@ -32,14 +32,12 @@ src/main/java/com/auth0/client/LoggingOptions.java # TokenProvider interface (shared between Fern-generated Management API and auth0-provided Authentication API) src/main/java/com/auth0/client/mgmt/TokenProvider.java -# Test infrastructure from auth0-real -src/test/java/com/auth0/net/ -src/test/java/com/auth0/exception/ -src/test/java/com/auth0/utils/ -src/test/java/com/auth0/json/ +# Shared test infrastructure for the Authentication API tests (manually maintained from auth0-real) +src/test/java/com/auth0/client/MockServer.java +src/test/java/com/auth0/client/RecordedRequestMatcher.java +src/test/java/com/auth0/client/UrlMatcher.java +src/test/java/com/auth0/AssertsUtil.java src/test/resources/auth/ -src/test/resources/keys/ -src/test/resources/mgmt/ src/test/resources/mockito-extensions/ src/test/java/com/auth0/client/legacy/ diff --git a/src/test/java/com/auth0/AssertsUtil.java b/src/test/java/com/auth0/AssertsUtil.java new file mode 100644 index 000000000..0a965f8e2 --- /dev/null +++ b/src/test/java/com/auth0/AssertsUtil.java @@ -0,0 +1,20 @@ +package com.auth0; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.function.Executable; + +public class AssertsUtil { + + public static T verifyThrows(Class expectedType, Executable executable) { + return assertThrows(expectedType, executable); + } + + public static T verifyThrows(Class expectedType, Executable executable, String message) { + T result = assertThrows(expectedType, executable); + assertThat(result.getMessage(), is(message)); + return result; + } +} diff --git a/src/test/java/com/auth0/client/MockServer.java b/src/test/java/com/auth0/client/MockServer.java new file mode 100644 index 000000000..eba538670 --- /dev/null +++ b/src/test/java/com/auth0/client/MockServer.java @@ -0,0 +1,361 @@ +package com.auth0.client; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import okio.Buffer; + +public class MockServer { + + public static final String AUTH_USER_INFO = "src/test/resources/auth/user_info.json"; + public static final String AUTH_RESET_PASSWORD = "src/test/resources/auth/reset_password.json"; + public static final String AUTH_SIGN_UP = "src/test/resources/auth/sign_up.json"; + public static final String AUTH_SIGN_UP_USERNAME = "src/test/resources/auth/sign_up_username.json"; + public static final String AUTH_TOKENS = "src/test/resources/auth/tokens.json"; + public static final String AUTH_ERROR_WITH_ERROR_DESCRIPTION = + "src/test/resources/auth/error_with_error_description.json"; + public static final String AUTH_ERROR_WITH_ERROR = "src/test/resources/auth/error_with_error.json"; + public static final String AUTH_ERROR_WITH_DESCRIPTION = "src/test/resources/auth/error_with_description.json"; + public static final String AUTH_ERROR_WITH_DESCRIPTION_AND_EXTRA_PROPERTIES = + "src/test/resources/auth/error_with_description_and_extra_properties.json"; + public static final String AUTH_ERROR_PLAINTEXT = "src/test/resources/auth/error_plaintext.json"; + public static final String AUTH_OOB_AUTHENTICATOR_RESPONSE = + "src/test/resources/auth/add_oob_authenticator_response.json"; + public static final String AUTH_OTP_AUTHENTICATOR_RESPONSE = + "src/test/resources/auth/add_otp_authenticator_response.json"; + public static final String AUTH_LIST_AUTHENTICATORS_RESPONSE = + "src/test/resources/auth/list_authenticators_response.json"; + public static final String AUTH_CHALLENGE_RESPONSE = "src/test/resources/auth/mfa_challenge_request_response.json"; + public static final String MGMT_ERROR_WITH_MESSAGE = "src/test/resources/mgmt/error_with_message.json"; + public static final String MGMT_CLIENT_GRANTS_LIST = "src/test/resources/mgmt/client_grants_list.json"; + public static final String MGMT_CLIENT_GRANTS_PAGED_LIST = "src/test/resources/mgmt/client_grants_paged_list.json"; + public static final String MGMT_CLIENT_GRANT = "src/test/resources/mgmt/client_grant.json"; + public static final String MGMT_CLIENTS_LIST = "src/test/resources/mgmt/clients_list.json"; + public static final String MGMT_CLIENTS_PAGED_LIST = "src/test/resources/mgmt/clients_paged_list.json"; + public static final String MGMT_CLIENT = "src/test/resources/mgmt/client.json"; + public static final String MGMT_CLIENT_CREDENTIAL = "src/test/resources/mgmt/client_credential.json"; + public static final String MGMT_CLIENT_CREDENTIAL_LIST = "src/test/resources/mgmt/client_credential_list.json"; + public static final String MGMT_CONNECTIONS_LIST = "src/test/resources/mgmt/connections_list.json"; + public static final String MGMT_CONNECTIONS_PAGED_LIST = "src/test/resources/mgmt/connections_paged_list.json"; + public static final String MGMT_CONNECTION = "src/test/resources/mgmt/connection.json"; + public static final String MGMT_CONNECTION_SCIM_CONFIGURATION = + "src/test/resources/mgmt/connection_scim_configuration.json"; + public static final String MGMT_CONNECTION_DEFAULT_SCIM_CONFIGURATION = + "src/test/resources/mgmt/default_connection_scim_configuration.json"; + public static final String MGMT_CONNECTION_SCIM_TOKENS = "src/test/resources/mgmt/connection_scim_tokens.json"; + public static final String MGMT_CONNECTION_SCIM_TOKEN = "src/test/resources/mgmt/connection_scim_token.json"; + public static final String MGMT_ENABLED_CLIENTS_FOR_CONNECTION = + "src/test/resources/mgmt/enabled_clients_for_connection.json"; + public static final String MGMT_CONNECTION_KEY = "src/test/resources/mgmt/connection_key.json"; + public static final String MGMT_ROTATE_KEY = "src/test/resources/mgmt/rotate_key.json"; + public static final String MGMT_DEVICE_CREDENTIALS_LIST = "src/test/resources/mgmt/device_credentials_list.json"; + public static final String MGMT_DEVICE_CREDENTIALS = "src/test/resources/mgmt/device_credentials.json"; + public static final String MGMT_GRANTS_LIST = "src/test/resources/mgmt/grants_list.json"; + public static final String MGMT_GRANTS_PAGED_LIST = "src/test/resources/mgmt/grants_paged_list.json"; + public static final String MGMT_LOG_EVENTS_LIST = "src/test/resources/mgmt/event_logs_list.json"; + public static final String MGMT_LOG_EVENTS_PAGED_LIST = "src/test/resources/mgmt/event_logs_paged_list.json"; + public static final String MGMT_LOG_EVENT = "src/test/resources/mgmt/event_log.json"; + public static final String MGMT_LOG_STREAM = "src/test/resources/mgmt/log_stream.json"; + public static final String MGMT_LOG_STREAMS_LIST = "src/test/resources/mgmt/log_streams_list.json"; + public static final String MGMT_RESOURCE_SERVERS_LIST = "src/test/resources/mgmt/resource_servers_list.json"; + public static final String MGMT_RESOURCE_SERVERS_PAGED_LIST = + "src/test/resources/mgmt/resource_servers_paged_list.json"; + public static final String MGMT_RESOURCE_SERVER = "src/test/resources/mgmt/resource_server.json"; + public static final String MGMT_ROLE = "src/test/resources/mgmt/role.json"; + public static final String MGMT_ROLES_LIST = "src/test/resources/mgmt/roles_list.json"; + public static final String MGMT_ROLES_PAGED_LIST = "src/test/resources/mgmt/roles_paged_list.json"; + public static final String MGMT_ROLE_PERMISSIONS_LIST = "src/test/resources/mgmt/role_permissions_list.json"; + public static final String MGMT_ROLE_PERMISSIONS_PAGED_LIST = + "src/test/resources/mgmt/role_permissions_paged_list.json"; + public static final String MGMT_ROLE_USERS_LIST = "src/test/resources/mgmt/role_users_list.json"; + public static final String MGMT_ROLE_USERS_PAGED_LIST = "src/test/resources/mgmt/role_users_paged_list.json"; + public static final String MGMT_ROLE_USERS_CHECKPOINT_PAGED_LIST = + "src/test/resources/mgmt/role_users_checkpoint_paged_list.json"; + public static final String MGMT_RULES_LIST = "src/test/resources/mgmt/rules_list.json"; + public static final String MGMT_RULES_CONFIGS_LIST = "src/test/resources/mgmt/rules_configs_list.json"; + public static final String MGMT_RULES_PAGED_LIST = "src/test/resources/mgmt/rules_paged_list.json"; + public static final String MGMT_RULE = "src/test/resources/mgmt/rule.json"; + public static final String MGMT_USER_BLOCKS = "src/test/resources/mgmt/user_blocks.json"; + public static final String MGMT_BRANDING_SETTINGS = "src/test/resources/mgmt/branding_settings.json"; + public static final String MGMT_BRANDING_LOGIN_TEMPLATE = "src/test/resources/mgmt/branding_login_template.json"; + public static final String MGMT_BLACKLISTED_TOKENS_LIST = "src/test/resources/mgmt/blacklisted_tokens_list.json"; + public static final String MGMT_EMAIL_PROVIDER = "src/test/resources/mgmt/email_provider.json"; + public static final String MGMT_EMAIL_TEMPLATE = "src/test/resources/mgmt/email_template.json"; + public static final String MGMT_USERS_LIST = "src/test/resources/mgmt/users_list.json"; + public static final String MGMT_USERS_PAGED_LIST = "src/test/resources/mgmt/users_paged_list.json"; + public static final String MGMT_USER_PERMISSIONS_PAGED_LIST = + "src/test/resources/mgmt/user_permissions_paged_list.json"; + public static final String MGMT_USER_ROLES_PAGED_LIST = "src/test/resources/mgmt/user_roles_paged_list.json"; + public static final String MGMT_REFRESH_TOKEN = "src/test/resources/mgmt/refresh_token.json"; + public static final String MGMT_SESSION = "src/test/resources/mgmt/session.json"; + public static final String MGMT_USER_REFRESH_TOKENS = "src/test/resources/mgmt/user_refresh_tokens.json"; + public static final String MGMT_USER_SESSIONS = "src/test/resources/mgmt/user_sessions.json"; + public static final String MGMT_USER = "src/test/resources/mgmt/user.json"; + public static final String MGMT_RECOVERY_CODE = "src/test/resources/mgmt/recovery_code.json"; + public static final String MGMT_IDENTITIES_LIST = "src/test/resources/mgmt/identities_list.json"; + public static final String MGMT_PROMPT = "src/test/resources/mgmt/prompt.json"; + public static final String MGMT_CUSTOM_TEXT_PROMPT = "src/test/resources/mgmt/custom_text_prompt.json"; + public static final String MGMT_PARTIALS_PROMPT = "src/test/resources/mgmt/partials_prompt.json"; + public static final String MGMT_GUARDIAN_AUTHENTICATION_POLICIES_LIST = + "src/test/resources/mgmt/guardian_authentication_policies_list.json"; + public static final String MGMT_GUARDIAN_ENROLLMENT = "src/test/resources/mgmt/guardian_enrollment.json"; + public static final String MGMT_GUARDIAN_ENROLLMENTS_LIST = + "src/test/resources/mgmt/guardian_enrollments_list.json"; + public static final String MGMT_GUARDIAN_ENROLLMENT_TICKET = + "src/test/resources/mgmt/guardian_enrollment_ticket.json"; + public static final String MGMT_GUARDIAN_FACTOR = "src/test/resources/mgmt/guardian_factor.json"; + public static final String MGMT_GUARDIAN_FACTORS_LIST = "src/test/resources/mgmt/guardian_factors_list.json"; + public static final String MGMT_GUARDIAN_TEMPLATES = "src/test/resources/mgmt/guardian_templates.json"; + public static final String MGMT_GUARDIAN_SNS_FACTOR_PROVIDER_EMPTY = + "src/test/resources/mgmt/guardian_sns_factor_provider_empty.json"; + public static final String MGMT_GUARDIAN_SNS_FACTOR_PROVIDER = + "src/test/resources/mgmt/guardian_sns_factor_provider.json"; + public static final String MGMT_GUARDIAN_TWILIO_FACTOR_PROVIDER_EMPTY = + "src/test/resources/mgmt/guardian_twilio_factor_provider_empty.json"; + public static final String MGMT_GUARDIAN_TWILIO_FACTOR_PROVIDER_WITH_FROM = + "src/test/resources/mgmt/guardian_twilio_factor_provider_with_from.json"; + public static final String MGMT_GUARDIAN_TWILIO_FACTOR_PROVIDER_WITH_MSSID = + "src/test/resources/mgmt/guardian_twilio_factor_provider_with_messaging_service_sid.json"; + public static final String MGMT_TENANT = "src/test/resources/mgmt/tenant.json"; + public static final String MGMT_ACTIVE_USERS_COUNT = "src/test/resources/mgmt/active_users_count.json"; + public static final String MGMT_DAILY_STATS_LIST = "src/test/resources/mgmt/daily_stats_list.json"; + public static final String MGMT_PASSWORD_CHANGE_TICKET = "src/test/resources/mgmt/password_change_ticket.json"; + public static final String MGMT_EMAIL_VERIFICATION_TICKET = + "src/test/resources/mgmt/email_verification_ticket.json"; + public static final String MGMT_EMPTY_LIST = "src/test/resources/mgmt/empty_list.json"; + public static final String MGMT_JOB = "src/test/resources/mgmt/job.json"; + public static final String MGMT_JOB_POST_VERIFICATION_EMAIL = + "src/test/resources/mgmt/job_post_verification_email.json"; + public static final String MGMT_JOB_POST_USERS_EXPORTS = "src/test/resources/mgmt/job_post_users_exports.json"; + public static final String MGMT_JOB_POST_USERS_IMPORTS = "src/test/resources/mgmt/job_post_users_imports.json"; + public static final String MGMT_JOB_POST_USERS_IMPORTS_INPUT = + "src/test/resources/mgmt/job_post_users_imports_input.json"; + public static final String MGMT_JOB_ERROR_DETAILS = "src/test/resources/mgmt/job_error_details.json"; + public static final String MGMT_NETWORK_ACLS_LIST = "src/test/resources/mgmt/network_acls_list.json"; + public static final String MGMT_NETWORK_ACLS = "src/test/resources/mgmt/network_acls.json"; + public static final String MGMT_USER_ATTRIBUTE_PROFILES_LIST = + "src/test/resources/mgmt/user_attribute_profiles_list.json"; + public static final String MGMT_USER_ATTRIBUTE_PROFILE = "src/test/resources/mgmt/user_attribute_profile.json"; + public static final String MGMT_USER_ATTRIBUTE_PROFILE_TEMPLATE = + "src/test/resources/mgmt/user_attribute_profile_template.json"; + public static final String MGMT_USER_ATTRIBUTE_PROFILE_TEMPLATES_LIST = + "src/test/resources/mgmt/user_attribute_profile_templates_list.json"; + public static final String MULTIPART_SAMPLE = "src/test/resources/mgmt/multipart_sample.json"; + public static final String PASSWORDLESS_EMAIL_RESPONSE = "src/test/resources/auth/passwordless_email.json"; + public static final String PASSWORDLESS_SMS_RESPONSE = "src/test/resources/auth/passwordless_sms.json"; + public static final String PUSHED_AUTHORIZATION_RESPONSE = + "src/test/resources/auth/pushed_authorization_response.json"; + public static final String BACK_CHANNEL_AUTHORIZE_RESPONSE = + "src/test/resources/auth/back_channel_authorize_response.json"; + public static final String BACK_CHANNEL_LOGIN_STATUS_RESPONSE = + "src/test/resources/auth/back_channel_login_status_response.json"; + public static final String AUTHENTICATOR_METHOD_BY_ID = "src/test/resources/mgmt/authenticator_method_by_id.json"; + public static final String AUTHENTICATOR_METHOD_CREATE = "src/test/resources/mgmt/authenticator_method_create.json"; + public static final String AUTHENTICATOR_METHOD_LIST = "src/test/resources/mgmt/authenticator_method_list.json"; + public static final String AUTHENTICATOR_METHOD_PAGED_LIST = + "src/test/resources/mgmt/authenticator_method_paged_list.json"; + public static final String AUTHENTICATOR_METHOD_UPDATE = "src/test/resources/mgmt/authenticator_method_update.json"; + public static final String AUTHENTICATOR_METHOD_UPDATE_BY_ID = + "src/test/resources/mgmt/authenticator_method_update_by_id.json"; + public static final String ORGANIZATION = "src/test/resources/mgmt/organization.json"; + public static final String ORGANIZATIONS_LIST = "src/test/resources/mgmt/organizations_list.json"; + public static final String ORGANIZATIONS_PAGED_LIST = "src/test/resources/mgmt/organizations_paged_list.json"; + public static final String ORGANIZATIONS_CHECKPOINT_PAGED_LIST = + "src/test/resources/mgmt/organizations_checkpoint_paged_list.json"; + public static final String ORGANIZATION_MEMBERS_LIST = "src/test/resources/mgmt/organization_members_list.json"; + public static final String ORGANIZATION_MEMBERS_PAGED_LIST = + "src/test/resources/mgmt/organization_members_paged_list.json"; + public static final String ORGANIZATION_MEMBERS_CHECKPOINT_PAGED_LIST = + "src/test/resources/mgmt/organization_members_checkpoint_paged_list.json"; + public static final String ORGANIZATION_CONNECTIONS_LIST = + "src/test/resources/mgmt/organization_connections_list.json"; + public static final String ORGANIZATION_CONNECTIONS_PAGED_LIST = + "src/test/resources/mgmt/organization_connections_paged_list.json"; + public static final String ORGANIZATION_CONNECTION = "src/test/resources/mgmt/organization_connection.json"; + public static final String ORGANIZATION_MEMBER_ROLES_LIST = + "src/test/resources/mgmt/organization_member_roles_list.json"; + public static final String ORGANIZATION_MEMBER_ROLES_PAGED_LIST = + "src/test/resources/mgmt/organization_member_roles_paged_list.json"; + public static final String ORGANIZATION_CLIENT_GRANTS = "src/test/resources/mgmt/organization_client_grants.json"; + public static final String ORGANIZATION_CLIENT_GRANTS_PAGED_LIST = + "src/test/resources/mgmt/organization_client_grants_paged_list.json"; + public static final String ORGANIZATION_CLIENT_GRANT = "src/test/resources/mgmt/organization_client_grant.json"; + public static final String INVITATION = "src/test/resources/mgmt/invitation.json"; + public static final String INVITATIONS_LIST = "src/test/resources/mgmt/invitations_list.json"; + public static final String INVITATIONS_PAGED_LIST = "src/test/resources/mgmt/invitations_paged_list.json"; + public static final String ACTION = "src/test/resources/mgmt/action.json"; + public static final String ACTION_TRIGGERS = "src/test/resources/mgmt/action_triggers.json"; + public static final String ACTION_VERSION = "src/test/resources/mgmt/action_version.json"; + public static final String ACTION_EXECUTION = "src/test/resources/mgmt/action_execution.json"; + public static final String ACTIONS_LIST = "src/test/resources/mgmt/actions_list.json"; + public static final String ACTION_VERSIONS_LIST = "src/test/resources/mgmt/action_versions_list.json"; + public static final String ACTION_TRIGGER_BINDINGS = "src/test/resources/mgmt/action_trigger_bindings.json"; + public static final String BREACHED_PASSWORD_SETTINGS = "src/test/resources/mgmt/breached_password_settings.json"; + public static final String BRUTE_FORCE_CONFIGURATION = "src/test/resources/mgmt/brute_force_configuration.json"; + public static final String SUSPICIOUS_IP_THROTTLING_CONFIGURATION = + "src/test/resources/mgmt/suspicious_ip_throttling_configuration.json"; + public static final String KEY = "src/test/resources/mgmt/key.json"; + public static final String KEY_LIST = "src/test/resources/mgmt/key_list.json"; + public static final String KEY_REVOKE = "src/test/resources/mgmt/key_revoke.json"; + public static final String KEY_ROTATE = "src/test/resources/mgmt/key_rotate.json"; + public static final String ENCRYPTION_KEY = "src/test/resources/mgmt/encryption_key.json"; + public static final String ENCRYPTION_KEYS_LIST = "src/test/resources/mgmt/encryption_keys_list.json"; + public static final String RATE_LIMIT_ERROR = "src/test/resources/mgmt/rate_limit_error.json"; + public static final String SELF_SERVICE_PROFILES_LIST = "src/test/resources/mgmt/self_service_profiles_list.json"; + public static final String SELF_SERVICE_PROFILE_RESPONSE = + "src/test/resources/mgmt/self_service_profile_response.json"; + public static final String SELF_SERVICE_PROFILE_CUSTOM_TEXT = + "src/test/resources/mgmt/self_service_profile_custom_text.json"; + public static final String SELF_SERVICE_PROFILE_SSO_TICKET = + "src/test/resources/mgmt/self_service_profile_sso_ticket.json"; + + private final MockWebServer server; + + public MockServer() throws Exception { + server = new MockWebServer(); + server.start(); + } + + public void stop() throws IOException { + server.shutdown(); + } + + public String getBaseUrl() { + return server.url("/").toString(); + } + + public RecordedRequest takeRequest() throws InterruptedException { + return server.takeRequest(); + } + + private String readTextFile(String path) throws IOException { + return new String(Files.readAllBytes(Paths.get(path))); + } + + public void jsonResponse(String path, int statusCode) throws IOException { + MockResponse response = new MockResponse() + .setResponseCode(statusCode) + .addHeader("Content-Type", "application/json") + .setBody(readTextFile(path)); + server.enqueue(response); + } + + public void noContentResponse() { + MockResponse response = new MockResponse() + .setResponseCode(204) + .addHeader("Content-Type", "application/json") + .setBody(""); + server.enqueue(response); + } + + public void rateLimitReachedResponse(long limit, long remaining, long reset) throws IOException { + rateLimitReachedResponse(limit, remaining, reset, null); + } + + public void rateLimitReachedResponse(long limit, long remaining, long reset, String path) throws IOException { + MockResponse response = new MockResponse().setResponseCode(429); + if (limit != -1) { + response.addHeader("x-ratelimit-limit", String.valueOf(limit)); + } + if (remaining != -1) { + response.addHeader("x-ratelimit-remaining", String.valueOf(remaining)); + } + if (reset != -1) { + response.addHeader("x-ratelimit-reset", String.valueOf(reset)); + } + if (path != null) { + response.addHeader("Content-Type", "application/json").setBody(readTextFile(path)); + } + server.enqueue(response); + } + + public void rateLimitReachedResponse( + long limit, + long remaining, + long reset, + String clientQuotaLimit, + String organizationQuotaLimit, + long retryAfter) + throws IOException { + rateLimitReachedResponse(limit, remaining, reset, null, clientQuotaLimit, organizationQuotaLimit, retryAfter); + } + + public void rateLimitReachedResponse( + long limit, + long remaining, + long reset, + String path, + String clientQuotaLimit, + String organizationQuotaLimit, + long retryAfter) + throws IOException { + MockResponse response = new MockResponse().setResponseCode(429); + if (limit != -1) { + response.addHeader("x-ratelimit-limit", String.valueOf(limit)); + } + if (remaining != -1) { + response.addHeader("x-ratelimit-remaining", String.valueOf(remaining)); + } + if (reset != -1) { + response.addHeader("x-ratelimit-reset", String.valueOf(reset)); + } + if (clientQuotaLimit != null) { + response.addHeader("auth0-client-quota-limit", clientQuotaLimit); + } + if (organizationQuotaLimit != null) { + response.addHeader("auth0-organization-quota-limit", organizationQuotaLimit); + } + if (retryAfter != -1) { + response.addHeader("retry-after", String.valueOf(retryAfter)); + } + if (path != null) { + response.addHeader("Content-Type", "application/json").setBody(readTextFile(path)); + } + server.enqueue(response); + } + + public void textResponse(String path, int statusCode) throws IOException { + MockResponse response = new MockResponse() + .setResponseCode(statusCode) + .addHeader("Content-Type", "text/plain") + .setBody(readTextFile(path)); + server.enqueue(response); + } + + public void emptyResponse(int statusCode) { + MockResponse response = new MockResponse().setResponseCode(statusCode); + server.enqueue(response); + } + + public static Map bodyFromRequest(RecordedRequest request) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + MapType mapType = mapper.getTypeFactory().constructMapType(HashMap.class, String.class, Object.class); + try (Buffer body = request.getBody()) { + return mapper.readValue(body.inputStream(), mapType); + } + } + + public static List bodyListFromRequest(RecordedRequest request) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + CollectionType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Object.class); + try (Buffer body = request.getBody()) { + return mapper.readValue(body.inputStream(), listType); + } + } + + public static String readFromRequest(RecordedRequest request) { + try (Buffer body = request.getBody()) { + return body.readUtf8(); + } + } +} diff --git a/src/test/java/com/auth0/client/RecordedRequestMatcher.java b/src/test/java/com/auth0/client/RecordedRequestMatcher.java new file mode 100644 index 000000000..ea830cf91 --- /dev/null +++ b/src/test/java/com/auth0/client/RecordedRequestMatcher.java @@ -0,0 +1,156 @@ +package com.auth0.client; + +import com.auth0.net.client.HttpMethod; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.RecordedRequest; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +public class RecordedRequestMatcher extends TypeSafeDiagnosingMatcher { + + private static final int METHOD_PATH = 0; + private static final int HEADER = 1; + private static final int QUERY_PARAMETER = 2; + private static final int QUERY_PARAMETER_PRESENCE = 3; + + private final int checkingOption; + private final String first; + private final String second; + + private RecordedRequestMatcher(String first, String second, int checkingOption) { + this.checkingOption = checkingOption; + this.first = first; + this.second = second; + } + + @Override + protected boolean matchesSafely(RecordedRequest item, Description mismatchDescription) { + if (item == null) { + mismatchDescription.appendText("was null"); + return false; + } + + switch (checkingOption) { + default: + case METHOD_PATH: + return matchesMethodAndPath(item, mismatchDescription); + case HEADER: + return matchesHeader(item, mismatchDescription); + case QUERY_PARAMETER: + return matchesQueryParameter(item, mismatchDescription); + case QUERY_PARAMETER_PRESENCE: + return hasQueryParameter(item, mismatchDescription); + } + } + + private boolean matchesMethodAndPath(RecordedRequest item, Description mismatchDescription) { + if (!item.getMethod().equalsIgnoreCase(first)) { + mismatchDescription.appendText("method was ").appendValue(item.getMethod()); + return false; + } + String path = item.getPath(); + boolean hasQuery = path.indexOf("?") > 0; + if (hasQuery) { + path = path.substring(0, path.indexOf("?")); + } + if (!path.equals(second)) { + mismatchDescription.appendText("path was ").appendValue(path); + return false; + } + + return true; + } + + private boolean matchesHeader(RecordedRequest item, Description mismatchDescription) { + String value = item.getHeader(first); + if (value != null && !value.equals(second) || value == null && second != null) { + mismatchDescription.appendText(first).appendText(" header was ").appendValue(value); + return false; + } + + return true; + } + + private boolean matchesQueryParameter(RecordedRequest item, Description mismatchDescription) { + HttpUrl requestUrl = item.getRequestUrl(); + if (requestUrl.querySize() == 0) { + mismatchDescription.appendText(" query was empty"); + return false; + } + + String queryParamValue = requestUrl.queryParameter(first); + if (second.equals(queryParamValue)) { + return true; + } + + mismatchDescription.appendValueList( + "Query parameters were {", ", ", "}.", requestUrl.query().split("&")); + return false; + } + + private boolean hasQueryParameter(RecordedRequest item, Description mismatchDescription) { + String path = item.getPath(); + boolean hasQuery = path.indexOf("?") > 0; + if (!hasQuery) { + mismatchDescription.appendText(" query was empty"); + return false; + } + + String query = path.substring(path.indexOf("?") + 1); + String[] parameters = query.split("&"); + for (String p : parameters) { + if (p.startsWith(String.format("%s=", first))) { + return true; + } + } + mismatchDescription.appendValueList("Query parameters were {", ", ", "}.", parameters); + return false; + } + + @Override + public void describeTo(Description description) { + switch (checkingOption) { + default: + case METHOD_PATH: + description + .appendText("A request with method ") + .appendValue(first) + .appendText(" and path ") + .appendValue(second); + break; + case HEADER: + description + .appendText("A request containing header ") + .appendValue(first) + .appendText(" with value ") + .appendValue(second); + break; + case QUERY_PARAMETER: + description + .appendText("A request containing query parameter ") + .appendValue(first) + .appendText(" with value ") + .appendValue(second); + break; + case QUERY_PARAMETER_PRESENCE: + description.appendText("A request containing query parameter ").appendValue(first); + break; + } + } + + public static RecordedRequestMatcher hasMethodAndPath(HttpMethod method, String path) { + return new RecordedRequestMatcher(method.toString(), path, METHOD_PATH); + } + + public static RecordedRequestMatcher hasHeader(String name, String value) { + return new RecordedRequestMatcher(name, value, HEADER); + } + + public static RecordedRequestMatcher hasQueryParameter(String name, String value) { + return new RecordedRequestMatcher(name, value, QUERY_PARAMETER); + } + + public static RecordedRequestMatcher hasQueryParameter(String name) { + return new RecordedRequestMatcher(name, null, QUERY_PARAMETER_PRESENCE); + } +} diff --git a/src/test/java/com/auth0/client/UrlMatcher.java b/src/test/java/com/auth0/client/UrlMatcher.java new file mode 100644 index 000000000..758331ce3 --- /dev/null +++ b/src/test/java/com/auth0/client/UrlMatcher.java @@ -0,0 +1,154 @@ +package com.auth0.client; + +import java.util.Arrays; +import okhttp3.HttpUrl; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +public class UrlMatcher extends TypeSafeDiagnosingMatcher { + + private static final int BASE_URL = 0; + private static final int QUERY_PARAMETER = 1; + private static final int ENCODED_QUERY = 2; + private final int checkingOption; + + private String encodedQueryContains; + private String scheme; + private String host; + private String path; + private String paramKey; + private String paramValue; + + private UrlMatcher(String scheme, String host, String path) { + this.checkingOption = BASE_URL; + this.scheme = scheme; + this.host = host; + this.path = path; + } + + private UrlMatcher(String paramKey, String paramValue) { + this.checkingOption = QUERY_PARAMETER; + this.paramKey = paramKey; + this.paramValue = paramValue; + } + + private UrlMatcher(String encodedQueryContains) { + this.checkingOption = ENCODED_QUERY; + this.encodedQueryContains = encodedQueryContains; + } + + @Override + protected boolean matchesSafely(String item, Description mismatchDescription) { + if (item == null) { + mismatchDescription.appendText("was null"); + return false; + } + HttpUrl url = HttpUrl.parse(item); + if (url == null) { + mismatchDescription.appendText("was not a valid url"); + return false; + } + + switch (checkingOption) { + default: + case BASE_URL: + return matchesBaseUrl(url, mismatchDescription); + case QUERY_PARAMETER: + return matchesParameter(url, mismatchDescription); + case ENCODED_QUERY: + return matchesEncodedQuery(url, mismatchDescription); + } + } + + private boolean matchesEncodedQuery(HttpUrl url, Description mismatchDescription) { + if (!url.encodedQuery().contains(encodedQueryContains)) { + mismatchDescription.appendText("encoded query was ").appendValue(url.encodedQuery()); + return false; + } + + return true; + } + + private boolean matchesBaseUrl(HttpUrl url, Description mismatchDescription) { + if (!url.scheme().equals(scheme)) { + mismatchDescription.appendText("scheme was ").appendValue(url.scheme()); + return false; + } + if (!url.host().equals(host)) { + mismatchDescription.appendText("host was ").appendValue(url.host()); + return false; + } + if (path == null) { + return true; + } + if (path.startsWith("/")) { + path = path.substring(1); + } + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + if (!url.pathSegments().equals(Arrays.asList(path.split("/")))) { + StringBuilder sb = new StringBuilder(); + for (String p : url.pathSegments()) { + sb.append(p).append("/"); + } + sb.deleteCharAt(sb.length() - 1); + mismatchDescription.appendText("path was ").appendValue(sb.toString()); + return false; + } + + return true; + } + + private boolean matchesParameter(HttpUrl url, Description mismatchDescription) { + String value = url.queryParameter(paramKey); + if (value != null && !value.equals(paramValue) || value == null && paramValue != null) { + mismatchDescription.appendText("value was ").appendValue(value); + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + switch (checkingOption) { + default: + case BASE_URL: + description + .appendText("An url with scheme ") + .appendValue(scheme) + .appendText(", host ") + .appendValue(host) + .appendText("and path ") + .appendValue(path); + break; + case QUERY_PARAMETER: + description + .appendText("An url with the query parameter ") + .appendValue(paramKey) + .appendText(" with value ") + .appendValue(paramValue); + break; + case ENCODED_QUERY: + description.appendText("An url with encoded query containing ").appendValue(encodedQueryContains); + break; + } + } + + public static UrlMatcher isUrl(String scheme, String host, String path) { + return new UrlMatcher(scheme, host, path); + } + + public static UrlMatcher isUrl(String scheme, String host) { + return new UrlMatcher(scheme, host, null); + } + + public static UrlMatcher hasQueryParameter(String key, String value) { + return new UrlMatcher(key, value); + } + + public static UrlMatcher encodedQueryContains(String text) { + return new UrlMatcher(text); + } +} diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java new file mode 100644 index 000000000..0e154ae87 --- /dev/null +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -0,0 +1,2407 @@ +package com.auth0.client.auth; + +import static com.auth0.AssertsUtil.verifyThrows; +import static com.auth0.client.MockServer.*; +import static com.auth0.client.RecordedRequestMatcher.hasHeader; +import static com.auth0.client.RecordedRequestMatcher.hasMethodAndPath; +import static com.auth0.client.UrlMatcher.hasQueryParameter; +import static com.auth0.client.UrlMatcher.isUrl; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.collection.IsMapContaining.hasKey; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.auth0.client.MockServer; +import com.auth0.exception.APIException; +import com.auth0.json.ObjectMapperProvider; +import com.auth0.json.auth.*; +import com.auth0.net.BaseRequest; +import com.auth0.net.Request; +import com.auth0.net.SignUpRequest; +import com.auth0.net.TokenRequest; +import com.auth0.net.client.Auth0HttpClient; +import com.auth0.net.client.Auth0HttpRequest; +import com.auth0.net.client.Auth0HttpResponse; +import com.auth0.net.client.HttpMethod; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.FileReader; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class AuthAPITest { + + private static final String DOMAIN = "domain.auth0.com"; + private static final String CLIENT_ID = "clientId"; + private static final String CLIENT_SECRET = "clientSecret"; + private static final String PASSWORD_STRENGTH_ERROR_RESPONSE_NONE = + "src/test/resources/auth/password_strength_error_none.json"; + private static final String PASSWORD_STRENGTH_ERROR_RESPONSE_SOME = + "src/test/resources/auth/password_strength_error_some.json"; + + private MockServer server; + private AuthAPI api; + private AuthAPI apiNoClientAuthentication; + + @BeforeEach + public void setUp() throws Exception { + server = new MockServer(); + api = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET).build(); + apiNoClientAuthentication = + AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID).build(); + } + + @AfterEach + public void tearDown() throws Exception { + server.stop(); + } + + // Configuration + + @Test + public void shouldAcceptHttpClient() { + Auth0HttpClient httpClient = new Auth0HttpClient() { + @Override + public Auth0HttpResponse sendRequest(Auth0HttpRequest request) { + return null; + } + + @Override + public CompletableFuture sendRequestAsync(Auth0HttpRequest request) { + return null; + } + }; + + AuthAPI api = AuthAPI.newBuilder(DOMAIN, CLIENT_ID, CLIENT_SECRET) + .withHttpClient(httpClient) + .build(); + + assertThat(api, is(notNullValue())); + assertThat(api.getHttpClient(), is(notNullValue())); + } + + @Test + public void shouldAcceptDomainWithNoScheme() { + AuthAPI api = + AuthAPI.newBuilder("me.something.com", CLIENT_ID, CLIENT_SECRET).build(); + + assertThat(api.getBaseUrl(), is(notNullValue())); + assertThat(api.getBaseUrl().toString(), isUrl("https", "me.something.com")); + } + + @Test + public void shouldAcceptDomainWithHttpScheme() { + AuthAPI api = AuthAPI.newBuilder("http://me.something.com", CLIENT_ID, CLIENT_SECRET) + .build(); + + assertThat(api.getBaseUrl(), is(notNullValue())); + assertThat(api.getBaseUrl().toString(), isUrl("http", "me.something.com")); + } + + @Test + public void shouldThrowWhenDomainIsInvalid() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthAPI.newBuilder("", CLIENT_ID, CLIENT_SECRET).build(), + "The domain had an invalid format and couldn't be parsed as an URL."); + } + + @Test + public void shouldThrowWhenDomainIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthAPI.newBuilder(null, CLIENT_ID, CLIENT_SECRET).build(), + "'domain' cannot be null!"); + } + + @Test + public void shouldThrowWhenClientIdIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthAPI.newBuilder(DOMAIN, null, CLIENT_SECRET).build(), + "'client id' cannot be null!"); + } + + @Test + public void shouldAcceptNullClientSecret() { + assertThat(AuthAPI.newBuilder(DOMAIN, CLIENT_ID, (String) null).build(), is(notNullValue())); + } + + // Authorize + + @Test + public void shouldThrowWhenAuthorizeUrlBuilderRedirectUriIsNull() { + verifyThrows( + IllegalArgumentException.class, () -> api.authorizeUrl(null), "'redirect uri' must be a valid URL!"); + } + + @Test + public void shouldThrowWhenAuthorizeUrlBuilderRedirectUriIsNotValidURL() { + verifyThrows( + IllegalArgumentException.class, + () -> api.authorizeUrl("notvalid.url"), + "'redirect uri' must be a valid URL!"); + } + + @Test + public void shouldGetAuthorizeUrlBuilder() { + AuthorizeUrlBuilder builder = api.authorizeUrl("https://domain.auth0.com/callback"); + assertThat(builder, is(notNullValue())); + } + + @Test + public void shouldSetAuthorizeUrlBuilderDefaultValues() { + AuthAPI api = + AuthAPI.newBuilder("domain.auth0.com", CLIENT_ID, CLIENT_SECRET).build(); + String url = api.authorizeUrl("https://domain.auth0.com/callback").build(); + + assertThat(url, isUrl("https", "domain.auth0.com", "/authorize")); + assertThat(url, hasQueryParameter("response_type", "code")); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + assertThat(url, hasQueryParameter("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(url, hasQueryParameter("connection", null)); + } + + // Logout + + @Test + public void shouldThrowWhenLogoutUrlBuilderReturnToUrlIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.logoutUrl(null, true), + "'return to url' must be a valid URL!"); + } + + @Test + public void shouldThrowWhenLogoutUrlBuilderRedirectUriIsNotValidURL() { + verifyThrows( + IllegalArgumentException.class, + () -> api.logoutUrl("notvalid.url", true), + "'return to url' must be a valid URL!"); + } + + @Test + public void shouldGetLogoutUrlBuilder() { + LogoutUrlBuilder builder = api.logoutUrl("https://domain.auth0.com/callback", true); + assertThat(builder, is(notNullValue())); + } + + @Test + public void shouldSetLogoutUrlBuilderDefaultValues() { + AuthAPI api = + AuthAPI.newBuilder("domain.auth0.com", CLIENT_ID, CLIENT_SECRET).build(); + String url = api.logoutUrl("https://my.domain.com/welcome", false).build(); + + assertThat(url, isUrl("https", "domain.auth0.com", "/v2/logout")); + assertThat(url, hasQueryParameter("client_id", null)); + assertThat(url, hasQueryParameter("returnTo", "https://my.domain.com/welcome")); + } + + @Test + public void shouldSetLogoutUrlBuilderDefaultValuesAndClientId() { + AuthAPI api = + AuthAPI.newBuilder("domain.auth0.com", CLIENT_ID, CLIENT_SECRET).build(); + String url = api.logoutUrl("https://my.domain.com/welcome", true).build(); + + assertThat(url, isUrl("https", "domain.auth0.com", "/v2/logout")); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + assertThat(url, hasQueryParameter("returnTo", "https://my.domain.com/welcome")); + } + + // UserInfo + + @Test + public void shouldThrowOnUserInfoWithNullAccessToken() { + verifyThrows(IllegalArgumentException.class, () -> api.userInfo(null), "'access token' cannot be null!"); + } + + @Test + public void shouldCreateUserInfoRequest() throws Exception { + Request request = api.userInfo("accessToken"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_USER_INFO, 200); + UserInfo response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/userinfo")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer accessToken")); + + assertThat(response, is(notNullValue())); + assertThat(response.getValues(), is(notNullValue())); + assertThat(response.getValues(), hasEntry("email_verified", false)); + assertThat(response.getValues(), hasEntry("email", "test.account@userinfo.com")); + assertThat(response.getValues(), hasEntry("clientID", "q2hnj2iu...")); + assertThat(response.getValues(), hasEntry("updated_at", "2016-12-05T15:15:40.545Z")); + assertThat(response.getValues(), hasEntry("name", "test.account@userinfo.com")); + assertThat(response.getValues(), hasEntry("picture", "https://s.gravatar.com/avatar/dummy.png")); + assertThat(response.getValues(), hasEntry("user_id", "auth0|58454...")); + assertThat(response.getValues(), hasEntry("nickname", "test.account")); + assertThat(response.getValues(), hasEntry("created_at", "2016-12-05T11:16:59.640Z")); + assertThat(response.getValues(), hasEntry("sub", "auth0|58454...")); + assertThat(response.getValues(), hasKey("identities")); + @SuppressWarnings("unchecked") + List> identities = + (List>) response.getValues().get("identities"); + assertThat(identities, hasSize(1)); + assertThat(identities.get(0), hasEntry("user_id", "58454...")); + assertThat(identities.get(0), hasEntry("provider", "auth0")); + assertThat(identities.get(0), hasEntry("connection", "Username-Password-Authentication")); + assertThat(identities.get(0), hasEntry("isSocial", false)); + } + + // Reset Password + + @Test + public void shouldThrowOnResetPasswordWithNullEmail() { + verifyThrows( + IllegalArgumentException.class, + () -> api.resetPassword(null, "my-connection"), + "'email' cannot be null!"); + } + + @Test + public void shouldThrowOnResetPasswordWithNullConnection() { + verifyThrows( + IllegalArgumentException.class, + () -> api.resetPassword("me@auth0.com", null), + "'connection' cannot be null!"); + } + + @Test + public void shouldCreateResetPasswordRequest() throws Exception { + Request request = api.resetPassword("me@auth0.com", "db-connection"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_RESET_PASSWORD, 200); + Void response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/change_password")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, not(hasKey("password"))); + + assertThat(response, is(nullValue())); + } + + @Test + public void shouldCreateResetPasswordRequestWithSpecifiedClientId() throws Exception { + Request request = api.resetPassword("CLIENT-ID", "me@auth0.com", "db-connection"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_RESET_PASSWORD, 200); + Void response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/change_password")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", "CLIENT-ID")); + assertThat(body, not(hasKey("password"))); + + assertThat(response, is(nullValue())); + } + + @Test + public void shouldCreateResetPasswordRequestWithSpecifiedClientIdWithOrganization() throws Exception { + Request request = api.resetPassword("CLIENT-ID", "me@auth0.com", "db-connection", "ORGANIZATION-ID"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_RESET_PASSWORD, 200); + Void response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/change_password")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", "CLIENT-ID")); + assertThat(body, hasEntry("organization", "ORGANIZATION-ID")); + assertThat(body, not(hasKey("password"))); + + assertThat(response, is(nullValue())); + } + + // Sign Up + + @Test + public void shouldThrowOnSignUpWithNullEmail() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp(null, new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "my-connection"), + "'email' cannot be null!"); + } + + @Test + public void shouldThrowOnSignUpWithNullPasswordCharArray() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp("me@auth0.com", (char[]) null, "my-connection"), + "'password' cannot be null!"); + } + + @Test + public void shouldThrowOnSignUpWithNullConnection() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp("me@auth0.com", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, null), + "'connection' cannot be null!"); + } + + @Test + public void shouldThrowOnUsernameSignUpWithNullEmail() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp(null, "me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "my-connection"), + "'email' cannot be null!"); + } + + @Test + public void shouldThrowOnUsernameSignUpWithNullUsername() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp( + "me@auth0.com", null, new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "my-connection"), + "'username' cannot be null!"); + } + + @Test + public void shouldThrowOnUsernameSignUpWithNullPasswordCharArray() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp("me@auth0.com", "me", (char[]) null, "my-connection"), + "'password' cannot be null!"); + } + + @Test + public void shouldThrowOnUsernameSignUpWithNullConnection() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp("me@auth0.com", "me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, null), + "'connection' cannot be null!"); + } + + @Test + public void shouldThrowOnUsernameAndPhoneNumberSignUpWithNullPhoneNumber() { + verifyThrows( + IllegalArgumentException.class, + () -> api.signUp( + "me@auth0.com", + "me", + new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, + "my-connection", + null), + "'phone number' cannot be null!"); + } + + @Test + public void shouldHaveNotStrongPasswordWithDetailedDescription() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + FileReader fr = new FileReader(PASSWORD_STRENGTH_ERROR_RESPONSE_NONE); + Map mapPayload = mapper.readValue(fr, new TypeReference>() {}); + APIException ex = new APIException(mapPayload, 400); + assertThat(ex.getError(), is("invalid_password")); + String expectedDescription = + "At least 10 characters in length; Contain at least 3 of the following 4 types of characters: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*); Should contain: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*); No more than 2 identical characters in a row (e.g., \"aaa\" not allowed)"; + assertThat(ex.getDescription(), is(expectedDescription)); + } + + @Test + public void shouldHaveNotStrongPasswordWithShortDetailedDescription() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + FileReader fr = new FileReader(PASSWORD_STRENGTH_ERROR_RESPONSE_SOME); + Map mapPayload = mapper.readValue(fr, new TypeReference>() {}); + APIException ex = new APIException(mapPayload, 400); + assertThat(ex.getError(), is("invalid_password")); + String expectedDescription = + "Should contain: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*)"; + assertThat(ex.getDescription(), is(expectedDescription)); + } + + @Test + public void shouldCreateSignUpRequestWithUsernameAndPhoneNumber() throws Exception { + SignUpRequest request = api.signUp( + "me@auth0.com", + "me", + new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, + "db-connection", + "1234567890"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_SIGN_UP_USERNAME, 200); + CreatedUser response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/signup")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("phone_number", "1234567890")); + + assertThat(response, is(notNullValue())); + assertThat(response.getUserId(), is("58457fe6b27")); + assertThat(response.getEmail(), is("me@auth0.com")); + assertThat(response.isEmailVerified(), is(false)); + assertThat(response.getUsername(), is("me")); + assertThat(response.getPhoneNumber(), is("1234567890")); + } + + @Test + public void shouldCreateSignUpRequestWithUsername() throws Exception { + SignUpRequest request = + api.signUp("me@auth0.com", "me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "db-connection"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_SIGN_UP_USERNAME, 200); + CreatedUser response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/signup")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + + assertThat(response, is(notNullValue())); + assertThat(response.getUserId(), is("58457fe6b27")); + assertThat(response.getEmail(), is("me@auth0.com")); + assertThat(response.isEmailVerified(), is(false)); + assertThat(response.getUsername(), is("me")); + } + + @Test + public void shouldCreateSignUpRequest() throws Exception { + SignUpRequest request = + api.signUp("me@auth0.com", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "db-connection"); + + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_SIGN_UP, 200); + CreatedUser response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/signup")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, not(hasKey("username"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getUserId(), is("58457fe6b27")); + assertThat(response.getEmail(), is("me@auth0.com")); + assertThat(response.isEmailVerified(), is(false)); + assertThat(response.getUsername(), is(nullValue())); + } + + @Test + public void shouldCreateSignUpRequestWithCustomParameters() throws Exception { + SignUpRequest request = + api.signUp("me@auth0.com", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "db-connection"); + assertThat(request, is(notNullValue())); + Map customFields = new HashMap<>(); + customFields.put("age", "25"); + customFields.put("address", "123, fake street"); + request.setCustomFields(customFields); + + server.jsonResponse(AUTH_SIGN_UP, 200); + CreatedUser response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/dbconnections/signup")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("email", "me@auth0.com")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("connection", "db-connection")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasKey("user_metadata")); + @SuppressWarnings("unchecked") + Map metadata = (Map) body.get("user_metadata"); + assertThat(metadata, hasEntry("age", "25")); + assertThat(metadata, hasEntry("address", "123, fake street")); + assertThat(body, not(hasKey("username"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getUserId(), is("58457fe6b27")); + assertThat(response.getEmail(), is("me@auth0.com")); + assertThat(response.isEmailVerified(), is(false)); + assertThat(response.getUsername(), is(nullValue())); + } + + // Log In with AuthorizationCode Grant + + @Test + public void shouldThrowOnLogInWithAuthorizationCodeGrantAndRedirectUriWithNullCode() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeCode(null, "https://domain.auth0.com/callback"), + "'code' cannot be null!"); + } + + @Test + public void shouldThrowOnLogInWithAuthorizationCodeGrantAndRedirectUriWithNullRedirectUri() { + verifyThrows( + IllegalArgumentException.class, () -> api.exchangeCode("code", null), "'redirect uri' cannot be null!"); + } + + @Test + public void shouldCreateLogInWithAuthorizationCodeGrantRequest() throws Exception { + TokenRequest request = api.exchangeCode("code123", "https://domain.auth0.com/callback"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("code", "code123")); + assertThat(body, hasEntry("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(body, hasEntry("grant_type", "authorization_code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void authorizationCodeGrantRequestRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.exchangeCode("code", "https://domain.auth0.com/callback"), + "A client secret or client assertion signing key is required for this operation"); + } + + @Test + public void shouldCreateLogInWithAuthorizationCodeGrantRequestWithCustomParameters() throws Exception { + TokenRequest request = api.exchangeCode("code123", "https://domain.auth0.com/callback"); + assertThat(request, is(notNullValue())); + request.setAudience("https://myapi.auth0.com/users"); + request.setRealm("dbconnection"); + request.setScope("profile photos contacts"); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("code", "code123")); + assertThat(body, hasEntry("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(body, hasEntry("grant_type", "authorization_code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, hasEntry("realm", "dbconnection")); + assertThat(body, hasEntry("scope", "profile photos contacts")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + // Log In with Password grant + + @Test + public void shouldThrowOnLogInWithPasswordWithNullUsername() { + verifyThrows( + IllegalArgumentException.class, + () -> api.login(null, new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}), + "'email or username' cannot be null!"); + } + + @Test + public void shouldThrowOnLogInWithCharPasswordWithNullPassword() { + verifyThrows( + IllegalArgumentException.class, () -> api.login("me", (char[]) null), "'password' cannot be null!"); + } + + @Test + public void shouldCreateLogInWithPasswordGrantRequest() throws Exception { + TokenRequest request = api.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "password")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void passwordGrantRequestRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}), + "A client secret or client assertion signing key is required for this operation"); + } + + @Test + public void shouldCreateLogInWithPasswordGrantRequestWithCustomParameters() throws Exception { + TokenRequest request = api.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}); + assertThat(request, is(notNullValue())); + request.setRealm("dbconnection"); + request.setScope("profile photos contacts"); + request.setAudience("https://myapi.auth0.com/users"); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "password")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("realm", "dbconnection")); + assertThat(body, hasEntry("scope", "profile photos contacts")); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldSetCustomHeaderWithPasswordlessRealmRequest() throws Exception { + TokenRequest request = api.login("me", new char[] {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}); + request.addHeader("some-header", "some-value"); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("some-header", "some-value")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "password")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "password")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + // Log In with PasswordRealm grant + + @Test + public void shouldThrowOnLogInWithPasswordRealmWithNullUsername() { + verifyThrows( + IllegalArgumentException.class, + () -> api.login(null, new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "realm"), + "'email or username' cannot be null!"); + } + + @Test + public void shouldThrowOnLogInWithPasswordRealmWithNullPasswordCharArray() { + verifyThrows( + IllegalArgumentException.class, + () -> api.login("me", (char[]) null, "realm"), + "'password' cannot be null!"); + } + + @Test + public void shouldThrowOnLogInWithPasswordRealmWithNullRealm() { + verifyThrows( + IllegalArgumentException.class, + () -> api.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, null), + "'realm' cannot be null!"); + } + + @Test + public void shouldCreateLogInWithPasswordRealmGrantRequest() throws Exception { + TokenRequest request = api.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "realm"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/password-realm")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("realm", "realm")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void passwordRealmGrantRequestRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.login( + "me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "realm"), + "A client secret or client assertion signing key is required for this operation"); + } + + @Test + public void shouldCreateLogInWithPasswordRealmGrantRequestWithCustomParameters() throws Exception { + TokenRequest request = api.login("me", new char[] {'p', '4', '5', '5', 'w', '0', 'r', 'd'}, "realm"); + assertThat(request, is(notNullValue())); + request.setAudience("https://myapi.auth0.com/users"); + request.setRealm("dbconnection"); + request.setScope("profile photos contacts"); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/password-realm")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("username", "me")); + assertThat(body, hasEntry("password", "p455w0rd")); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, hasEntry("realm", "dbconnection")); + assertThat(body, hasEntry("scope", "profile photos contacts")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + // Log In with ClientCredentials grant + + @Test + public void shouldThrowOnLogInWithClientCredentialsWithNullAudience() { + verifyThrows(IllegalArgumentException.class, () -> api.requestToken(null), "'audience' cannot be null!"); + } + + @Test + public void shouldCreateLogInWithClientCredentialsGrantRequest() throws Exception { + TokenRequest request = api.requestToken("https://myapi.auth0.com/users"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "client_credentials")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, not(hasEntry("organization", any(String.class)))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldCreateLogInWithClientCredentialsGrantRequestWithOrg() throws Exception { + TokenRequest request = api.requestToken("https://myapi.auth0.com/users", "org_123"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "client_credentials")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, hasEntry("organization", "org_123")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldCreateLogInWithClientCredentialsGrantRequestWithoutOrgWhenEmpty() throws Exception { + TokenRequest request = api.requestToken("https://myapi.auth0.com/users", " "); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "client_credentials")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, not(hasKey("organization"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void clientCredentialsGrantRequestRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.requestToken("https://myapi.auth0.com/users"), + "A client secret or client assertion signing key is required for this operation"); + } + + // Custom Token Exchange + + @Test + public void shouldThrowOnExchangeTokenWithNullSubjectToken() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeToken(null, "urn:mycompany:m2m-test-token"), + "'subject token' cannot be null!"); + } + + @Test + public void shouldThrowOnExchangeTokenWithNullSubjectTokenType() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeToken("test-user123-john@example.com", null), + "'subject token type' cannot be null!"); + } + + @Test + public void shouldCreateTokenExchangeRequest() throws Exception { + TokenRequest request = api.exchangeToken("test-user123-john@example.com", "urn:mycompany:m2m-test-token") + .setAudience("https://myapi.auth0.com/users") + .setScope("openid profile email"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("subject_token", "test-user123-john@example.com")); + assertThat(body, hasEntry("subject_token_type", "urn:mycompany:m2m-test-token")); + assertThat(body, hasEntry("audience", "https://myapi.auth0.com/users")); + assertThat(body, hasEntry("scope", "openid profile email")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + } + + @Test + public void tokenExchangeRequestRequiresClientAuthentication() { + verifyThrows( + IllegalStateException.class, + () -> apiNoClientAuthentication.exchangeToken( + "test-user123-john@example.com", "urn:mycompany:m2m-test-token"), + "A client secret or client assertion signing key is required for this operation"); + } + + @Test + public void shouldCreateTokenExchangeRequestWithClientAssertion() throws Exception { + AuthAPI authAPI = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET) + .withClientAssertionSigner(new TestAssertionSigner("token")) + .build(); + TokenRequest request = authAPI + .exchangeToken("test-user123-john@example.com", "urn:mycompany:m2m-test-token") + .setAudience("https://myapi.auth0.com/users") + .setScope("openid profile email"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("subject_token", "test-user123-john@example.com")); + assertThat(body, hasEntry("subject_token_type", "urn:mycompany:m2m-test-token")); + assertThat(body, not(hasEntry("client_secret", CLIENT_SECRET))); + assertThat(body, hasEntry("client_assertion", "token")); + assertThat(body, hasEntry("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + } + + // Login with Passwordless + + @Test + public void shouldCreateStartEmailPasswordlessFlowRequest() throws Exception { + Request request = + api.startPasswordlessEmailFlow("user@domain.com", PasswordlessEmailType.CODE); + assertThat(request, is(notNullValue())); + + emailPasswordlessRequest(request, true); + } + + @Test + public void shouldCreateStartEmailPasswordlessFlowRequestWithoutClientAuthentication() throws Exception { + Request request = + apiNoClientAuthentication.startPasswordlessEmailFlow("user@domain.com", PasswordlessEmailType.CODE); + assertThat(request, is(notNullValue())); + + emailPasswordlessRequest(request, false); + } + + private void emailPasswordlessRequest(Request request, boolean secretSent) + throws Exception { + server.jsonResponse(PASSWORDLESS_EMAIL_RESPONSE, 200); + PasswordlessEmailResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/passwordless/start")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("connection", "email")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("email", "user@domain.com")); + + assertThat(response, is(notNullValue())); + assertThat(response.getEmail(), not(emptyOrNullString())); + assertThat(response.getId(), not(emptyOrNullString())); + assertThat(response.isEmailVerified(), is(notNullValue())); + } + + @Test + public void startPasswordlessEmailFlowShouldThrowWhenEmailIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.startPasswordlessEmailFlow(null, PasswordlessEmailType.CODE), + "'email' cannot be null!"); + } + + @Test + public void startPasswordlessEmailFlowShouldThrowWhenTypeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.startPasswordlessEmailFlow("user@domain.com", null), + "'type' cannot be null!"); + } + + @Test + public void shouldCreateStartEmailPasswordlessFlowRequestWithCustomParams() throws Exception { + Map authParams = new HashMap<>(); + authParams.put("scope", "openid profile email"); + authParams.put("state", "abc123"); + + BaseRequest request = api.startPasswordlessEmailFlow( + "user@domain.com", PasswordlessEmailType.CODE) + .addParameter("authParams", authParams); + + // verify that connection parameter can be overridden for custom connection types + request.addParameter("connection", "custom-email"); + + assertThat(request, is(notNullValue())); + + server.jsonResponse(PASSWORDLESS_EMAIL_RESPONSE, 200); + PasswordlessEmailResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/passwordless/start")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("connection", "custom-email")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("email", "user@domain.com")); + assertThat(body, hasKey("authParams")); + @SuppressWarnings("unchecked") + Map authParamsSent = (Map) body.get("authParams"); + assertThat(authParamsSent, hasEntry("scope", authParams.get("scope"))); + assertThat(authParamsSent, hasEntry("state", authParams.get("state"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getEmail(), not(emptyOrNullString())); + assertThat(response.getId(), not(emptyOrNullString())); + assertThat(response.isEmailVerified(), is(notNullValue())); + } + + @Test + public void shouldCreateStartSmsPasswordlessFlowRequest() throws Exception { + Request request = api.startPasswordlessSmsFlow("+16511234567"); + assertThat(request, is(notNullValue())); + + smsPasswordlessFlow(request, true); + } + + @Test + public void shouldCreateStartSmsPasswordlessFlowRequestWithoutClientAuthentication() throws Exception { + Request request = apiNoClientAuthentication.startPasswordlessSmsFlow("+16511234567"); + assertThat(request, is(notNullValue())); + + smsPasswordlessFlow(request, false); + } + + private void smsPasswordlessFlow(Request request, boolean secretSent) throws Exception { + server.jsonResponse(PASSWORDLESS_SMS_RESPONSE, 200); + PasswordlessSmsResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/passwordless/start")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("connection", "sms")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("phone_number", "+16511234567")); + + assertThat(response, is(notNullValue())); + assertThat(response.getPhoneNumber(), not(emptyOrNullString())); + assertThat(response.getId(), not(emptyOrNullString())); + assertThat(response.isPhoneVerified(), is(notNullValue())); + assertThat(response.getRequestLanguage(), is(nullValue())); + } + + @Test + public void shouldCreateStartSmsPasswordlessFlowRequestWithCustomConnection() throws Exception { + BaseRequest request = api.startPasswordlessSmsFlow("+16511234567"); + + // verify that connection parameter can be overridden for custom connection types + request.addParameter("connection", "custom-sms"); + + assertThat(request, is(notNullValue())); + + server.jsonResponse(PASSWORDLESS_SMS_RESPONSE, 200); + PasswordlessSmsResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/passwordless/start")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("connection", "custom-sms")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("phone_number", "+16511234567")); + + assertThat(response, is(notNullValue())); + assertThat(response.getPhoneNumber(), not(emptyOrNullString())); + assertThat(response.getId(), not(emptyOrNullString())); + assertThat(response.isPhoneVerified(), is(notNullValue())); + assertThat(response.getRequestLanguage(), is(nullValue())); + } + + @Test + public void startPasswordlessSmsFlowShouldThrowWhenPhoneIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.startPasswordlessSmsFlow(null), + "'phoneNumber' cannot be null!"); + } + + @Test + public void shouldCreateLoginWithPasswordlessCodeRequest() throws Exception { + TokenRequest request = api.exchangePasswordlessOtp("+16511234567", "email", "otp".toCharArray()); + assertThat(request, is(notNullValue())); + + passwordlessCodeRequest(request, true); + } + + @Test + public void shouldCreateLoginWithPasswordlessCodeRequestWithoutClientAuthentication() throws Exception { + TokenRequest request = + apiNoClientAuthentication.exchangePasswordlessOtp("+16511234567", "email", "otp".toCharArray()); + assertThat(request, is(notNullValue())); + + passwordlessCodeRequest(request, false); + } + + private void passwordlessCodeRequest(TokenRequest request, boolean secretSent) throws Exception { + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("realm", "email")); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/passwordless/otp")); + assertThat(body, hasEntry("otp", "otp")); + + assertThat(response, is(notNullValue())); + assertThat(response.getScope(), is(nullValue())); + assertThat(response.getAccessToken(), is(notNullValue())); + assertThat(response.getExpiresIn(), is(notNullValue())); + assertThat(response.getIdToken(), is(notNullValue())); + assertThat(response.getTokenType(), is(notNullValue())); + } + + // Revoke a Token + + @Test + public void shouldThrowOnRevokeTokenWithNullToken() { + verifyThrows(IllegalArgumentException.class, () -> api.revokeToken(null), "'refresh token' cannot be null!"); + } + + @Test + public void shouldCreateRevokeTokenRequest() throws Exception { + Request request = api.revokeToken("2679NfkaBn62e6w5E8zNEzjr"); + assertThat(request, is(notNullValue())); + + revokeTokenRequest(request, true); + } + + @Test + public void shouldCreateRevokeTokenRequestWithoutClientAuthentication() throws Exception { + Request request = apiNoClientAuthentication.revokeToken("2679NfkaBn62e6w5E8zNEzjr"); + assertThat(request, is(notNullValue())); + + revokeTokenRequest(request, false); + } + + private void revokeTokenRequest(Request request, boolean secretSent) throws Exception { + server.emptyResponse(200); + Void response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/revoke")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("token", "2679NfkaBn62e6w5E8zNEzjr")); + + assertThat(response, is(nullValue())); + } + + // Renew Authentication using Refresh Token + + @Test + public void shouldThrowOnRenewAuthWithNullRefreshToken() { + verifyThrows(IllegalArgumentException.class, () -> api.renewAuth(null), "'refresh token' cannot be null!"); + } + + @Test + public void shouldCreateRenewTokenRequest() throws Exception { + TokenRequest request = api.renewAuth("ej2E8zNEzjrcSD2edjaE"); + assertThat(request, is(notNullValue())); + + renewTokenRequest(request, true); + } + + @Test + public void shouldCreateRenewTokenRequestWithoutClientAuthentication() throws Exception { + TokenRequest request = apiNoClientAuthentication.renewAuth("ej2E8zNEzjrcSD2edjaE"); + assertThat(request, is(notNullValue())); + + renewTokenRequest(request, false); + } + + private void renewTokenRequest(TokenRequest request, boolean secretSent) throws Exception { + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "refresh_token")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("clieht_secret"))); + } + assertThat(body, hasEntry("refresh_token", "ej2E8zNEzjrcSD2edjaE")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + // PKCE + + @Test + public void shouldCreateLogInWithAuthorizationCodeGrantWithPKCERequest() throws Exception { + TokenRequest request = api.exchangeCodeWithVerifier("code123", "verifier", "https://domain.auth0.com/callback"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("code", "code123")); + assertThat(body, hasEntry("code_verifier", "verifier")); + assertThat(body, hasEntry("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(body, hasEntry("grant_type", "authorization_code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldThrowWhenVerifierNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeCodeWithVerifier("code", null, "https://domain.auth0.com/callback"), + "'verifier' cannot be null!"); + } + + // MFA + + @Test + public void shouldThrowWhenExchangeMfaOtpCalledWithNullMfaToken() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeMfaOtp(null, new char[] {'o', 't', 'p'}), + "'mfa token' cannot be null!"); + } + + @Test + public void shouldThrowWhenExchangeMfaOtpCalledWithNullOtp() { + verifyThrows( + IllegalArgumentException.class, () -> api.exchangeMfaOtp("mfaToken", null), "'otp' cannot be null!"); + } + + @Test + public void shouldCreateExchangeMfaOtpRequest() throws Exception { + TokenRequest request = api.exchangeMfaOtp("mfaToken", new char[] {'o', 't', 'p'}); + assertThat(request, is(notNullValue())); + + mfaOtpRequest(request, true); + } + + @Test + public void shouldCreateExchangeMfaOtpRequestWithoutClientAuthentication() throws Exception { + TokenRequest request = apiNoClientAuthentication.exchangeMfaOtp("mfaToken", new char[] {'o', 't', 'p'}); + assertThat(request, is(notNullValue())); + + mfaOtpRequest(request, false); + } + + private void mfaOtpRequest(TokenRequest request, boolean secretSent) throws Exception { + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/mfa-otp")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("mfa_token", "mfaToken")); + assertThat(body, hasEntry("otp", "otp")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldThrowWhenExchangeMfaOobCalledWithNullMfaToken() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeMfaOob(null, new char[] {'o', 't', 'p'}, null), + "'mfa token' cannot be null!"); + } + + @Test + public void shouldThrowWhenExchangeMfaOobCalledWithNullOoob() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeMfaOob("mfaToken", null, null), + "'OOB code' cannot be null!"); + } + + @Test + public void shouldCreateExchangeMfaOobRequest() throws Exception { + TokenRequest request = api.exchangeMfaOob("mfaToken", new char[] {'o', 'o', 'b'}, null); + assertThat(request, is(notNullValue())); + + mfaOobExchangeRequest(request, null, true); + } + + @Test + public void shouldCreateExchangeMfaOobRequestWithoutSecret() throws Exception { + TokenRequest request = apiNoClientAuthentication.exchangeMfaOob( + "mfaToken", new char[] {'o', 'o', 'b'}, new char[] {'b', 'o', 'b'}); + assertThat(request, is(notNullValue())); + + mfaOobExchangeRequest(request, "bob", false); + } + + private void mfaOobExchangeRequest(TokenRequest request, String bindingCode, boolean secretSent) throws Exception { + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/mfa-oob")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + + if (bindingCode != null) { + assertThat(body, hasEntry("binding_code", bindingCode)); + } else { + assertThat(body, not(hasKey("binding_code"))); + } + + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("mfa_token", "mfaToken")); + assertThat(body, hasEntry("oob_code", "oob")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldThrowWhenExchangeMfaRecoveryCodeCalledWithNullMfaToken() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeMfaRecoveryCode(null, new char[] {'c', 'o', 'd', 'e'}), + "'mfa token' cannot be null!"); + } + + @Test + public void shouldThrowWhenExchangeMfaRecoveryCodeCalledWithNullCode() { + verifyThrows( + IllegalArgumentException.class, + () -> api.exchangeMfaRecoveryCode("mfaToken", null), + "'recovery code' cannot be null!"); + } + + @Test + public void shouldCreateExchangeMfaRecoveryCodeRequest() throws Exception { + TokenRequest request = api.exchangeMfaRecoveryCode("mfaToken", new char[] {'c', 'o', 'd', 'e'}); + assertThat(request, is(notNullValue())); + + mfaRecoveryCodeExchangeRequest(request, true); + } + + @Test + public void shouldCreateExchangeMfaRecoveryCodeRequestWithoutSecret() throws Exception { + TokenRequest request = + apiNoClientAuthentication.exchangeMfaRecoveryCode("mfaToken", new char[] {'c', 'o', 'd', 'e'}); + assertThat(request, is(notNullValue())); + + mfaRecoveryCodeExchangeRequest(request, false); + } + + private void mfaRecoveryCodeExchangeRequest(TokenRequest request, boolean secretSent) throws Exception { + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("grant_type", "http://auth0.com/oauth/grant-type/mfa-recovery-code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + + if (secretSent) { + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + } else { + assertThat(body, not(hasKey("client_secret"))); + } + assertThat(body, hasEntry("mfa_token", "mfaToken")); + assertThat(body, hasEntry("recovery_code", "code")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void addOtpAuthenticatorThrowsWhenTokenNull() { + verifyThrows( + IllegalArgumentException.class, () -> api.addOtpAuthenticator(null), "'mfa token' cannot be null!"); + } + + @Test + public void addOtpAuthenticatorRequest() throws Exception { + Request request = api.addOtpAuthenticator("mfaToken"); + + server.jsonResponse(AUTH_OTP_AUTHENTICATOR_RESPONSE, 200); + CreatedOtpResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("otp"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getSecret(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorThrowsWhenTokenNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator(null, Collections.singletonList("auth0"), null, null), + "'mfa token' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, null), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithPhoneNumber() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, "phone-number", null), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenChannelsNullWithEmail() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", null, null, "email-address"), + "'OOB channels' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), null, null), + "'phone number' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenPhoneNumberNullWithVoiceChannel() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("voice"), null, null), + "'phone number' cannot be null!"); + } + + @Test + public void addOobAuthenticatorThrowsWhenEmailNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, null), + "'email address' cannot be null!"); + } + + @Test + public void addOobAuthenticatorRequestWithPhoneNumber() throws Exception { + Request request = + api.addOobAuthenticator("mfaToken", Collections.singletonList("sms"), "phone-number", null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("sms"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithEmail() throws Exception { + Request request = + api.addOobAuthenticator("mfaToken", Collections.singletonList("email"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("email"))); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithNoContactInfo() throws Exception { + Request request = + api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, null); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithMultipleChannels() throws Exception { + Request request = api.addOobAuthenticator( + "mfaToken", Arrays.asList("sms", "email", "auth0"), "phone-number", "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Arrays.asList("sms", "email", "auth0"))); + assertThat(body, hasEntry("phone_number", "phone-number")); + assertThat(body, hasEntry("email", "email-address")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void addOobAuthenticatorRequestWithAuth0Channel() throws Exception { + Request request = + api.addOobAuthenticator("mfaToken", Collections.singletonList("auth0"), null, "email-address"); + + server.jsonResponse(AUTH_OOB_AUTHENTICATOR_RESPONSE, 200); + CreatedOobResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/associate")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("authenticator_types", Collections.singletonList("oob"))); + assertThat(body, hasEntry("oob_channels", Collections.singletonList("auth0"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthenticatorType(), not(emptyOrNullString())); + assertThat(response.getOobChannel(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + assertThat(response.getBarcodeUri(), not(emptyOrNullString())); + assertThat(response.getRecoveryCodes(), notNullValue()); + } + + @Test + public void listAuthenticatorsThrowsWhenTokenNull() { + verifyThrows( + IllegalArgumentException.class, () -> api.listAuthenticators(null), "'access token' cannot be null!"); + } + + @Test + public void listAuthenticatorsRequest() throws Exception { + Request> request = api.listAuthenticators("token"); + + server.jsonResponse(AUTH_LIST_AUTHENTICATORS_RESPONSE, 200); + List response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.GET, "/mfa/authenticators")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer token")); + + assertThat(response, is(notNullValue())); + } + + @Test + public void deleteAuthenticatorThrowsWhenTokenNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.deleteAuthenticator(null, "authenticatorId"), + "'access token' cannot be null!"); + } + + @Test + public void deleteAuthenticatorThrowsWhenAuthenticatorIdNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.deleteAuthenticator("Bearer accessToken", null), + "'authenticator id' cannot be null!"); + } + + @Test + public void deleteAuthenticatorRequest() throws Exception { + Request request = api.deleteAuthenticator("accessToken", "authenticatorId"); + + server.jsonResponse(AUTH_LIST_AUTHENTICATORS_RESPONSE, 200); + Void response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.DELETE, "/mfa/authenticators/authenticatorId")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + assertThat(recordedRequest, hasHeader("Authorization", "Bearer accessToken")); + + assertThat(response, is(nullValue())); + } + + @Test + public void challengeRequestThrowsWhenTokenNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.mfaChallengeRequest(null, "otp", "authenticatorId"), + "'mfa token' cannot be null!"); + } + + @Test + public void challengeRequest() throws Exception { + Request request = api.mfaChallengeRequest("mfaToken", "otp", "authenticatorId"); + + server.jsonResponse(AUTH_CHALLENGE_RESPONSE, 200); + MfaChallengeResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/mfa/challenge")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("mfa_token", "mfaToken")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, hasEntry("client_secret", CLIENT_SECRET)); + assertThat(body, hasEntry("challenge_type", "otp")); + assertThat(body, hasEntry("authenticator_id", "authenticatorId")); + + assertThat(response, is(notNullValue())); + assertThat(response.getChallengeType(), not(emptyOrNullString())); + assertThat(response.getBindingMethod(), not(emptyOrNullString())); + assertThat(response.getOobCode(), not(emptyOrNullString())); + } + + // Client Assertion tests + @Test + public void shouldAddAndPreferClientAuthentication() throws Exception { + AuthAPI authAPI = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET) + .withClientAssertionSigner(new TestAssertionSigner("token")) + .build(); + TokenRequest request = authAPI.exchangeCode("code123", "https://domain.auth0.com/callback"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("code", "code123")); + assertThat(body, hasEntry("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(body, hasEntry("grant_type", "authorization_code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, not(hasEntry("client_secret", CLIENT_SECRET))); + assertThat(body, hasEntry("client_assertion", "token")); + assertThat(body, hasEntry("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void shouldNotAddAnyParamsIfNoSecretOrAssertion() throws Exception { + AuthAPI authAPI = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID).build(); + TokenRequest request = + authAPI.exchangeCodeWithVerifier("code123", "verifier", "https://domain.auth0.com/callback"); + + assertThat(request, is(notNullValue())); + + server.jsonResponse(AUTH_TOKENS, 200); + TokenHolder response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/json")); + + Map body = bodyFromRequest(recordedRequest); + assertThat(body, hasEntry("code", "code123")); + assertThat(body, hasEntry("code_verifier", "verifier")); + assertThat(body, hasEntry("redirect_uri", "https://domain.auth0.com/callback")); + assertThat(body, hasEntry("grant_type", "authorization_code")); + assertThat(body, hasEntry("client_id", CLIENT_ID)); + assertThat(body, not(hasEntry("client_secret", CLIENT_SECRET))); + assertThat(body, not(hasEntry("client_assertion", "token"))); + assertThat( + body, not(hasEntry("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getRefreshToken(), not(emptyOrNullString())); + assertThat(response.getTokenType(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), is(notNullValue())); + } + + @Test + public void authorizeUrlWithPARShouldThrowWhenRequestUriNull() { + verifyThrows( + IllegalArgumentException.class, () -> api.authorizeUrlWithPAR(null), "'request uri' cannot be null!"); + } + + @Test + public void shouldBuildAuthorizeUrlWithPAR() { + AuthAPI api = + AuthAPI.newBuilder("domain.auth0.com", CLIENT_ID, CLIENT_SECRET).build(); + String url = api.authorizeUrlWithPAR("urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2"); + assertThat(url, is(notNullValue())); + assertThat(url, isUrl("https", "domain.auth0.com", "/authorize")); + + assertThat(url, hasQueryParameter("request_uri", "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2")); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + } + + @Test + public void pushedAuthorizationRequestShouldThrowWhenRedirectUriIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.pushedAuthorizationRequest(null, "code", Collections.emptyMap()), + "'redirect uri' must be a valid URL!"); + } + + @Test + public void pushedAuthorizationRequestShouldThrowWhenResponseTypeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.pushedAuthorizationRequest("https://domain.com/callback", null, Collections.emptyMap()), + "'response type' cannot be null!"); + } + + @Test + public void shouldCreatePushedAuthorizationRequestWithNullAdditionalParams() throws Exception { + Request request = + api.pushedAuthorizationRequest("https://domain.com/callback", "code", null); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("redirect_uri=" + "https%3A%2F%2Fdomain.com%2Fcallback")); + assertThat(body, containsString("response_type=" + "code")); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + public void shouldCreatePushedAuthorizationRequestWithAdditionalParams() throws Exception { + Map additionalParams = new HashMap<>(); + additionalParams.put("audience", "aud"); + additionalParams.put("connection", "conn"); + Request request = + api.pushedAuthorizationRequest("https://domain.com/callback", "code", additionalParams); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("redirect_uri=" + "https%3A%2F%2Fdomain.com%2Fcallback")); + assertThat(body, containsString("response_type=" + "code")); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + assertThat(body, containsString("audience=" + "aud")); + assertThat(body, containsString("connection=" + "conn")); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldCreatePushedAuthorizationRequestWithAuthDetails() throws Exception { + Map authorizationDetails = new HashMap<>(); + authorizationDetails.put("type", "account information"); + authorizationDetails.put("locations", Collections.singletonList("https://example.com/customers")); + authorizationDetails.put("actions", Arrays.asList("read", "write")); + List> authDetailsList = Collections.singletonList(authorizationDetails); + + Request request = + api.pushedAuthorizationRequest("https://domain.com/callback", "code", null, authDetailsList); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("redirect_uri=" + "https%3A%2F%2Fdomain.com%2Fcallback")); + assertThat(body, containsString("response_type=" + "code")); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + + String authDetailsParam = getQueryMap(body).get("authorization_details"); + String decodedAuthDetails = URLDecoder.decode(authDetailsParam, StandardCharsets.UTF_8.name()); + TypeReference>> typeReference = new TypeReference>>() {}; + List> deserialized = + ObjectMapperProvider.getMapper().readValue(decodedAuthDetails, typeReference); + assertThat(deserialized, notNullValue()); + assertThat(deserialized, hasSize(1)); + assertThat(deserialized.get(0).get("type"), is("account information")); + + List locations = (List) deserialized.get(0).get("locations"); + List actions = (List) deserialized.get(0).get("actions"); + + assertThat(locations, hasSize(1)); + assertThat(locations.get(0), is("https://example.com/customers")); + assertThat(actions, hasSize(2)); + assertThat(actions, contains("read", "write")); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + public void shouldThrowWhenCreatePushedAuthorizationRequestWithInvalidAuthDetails() { + // force Jackson to throw error on serialization + // see https://stackoverflow.com/questions/26716020/how-to-get-a-jsonprocessingexception-using-jackson + @SuppressWarnings("unchecked") + List> mockList = mock(List.class); + when(mockList.toString()).thenReturn(mockList.getClass().getName()); + + IllegalArgumentException e = verifyThrows( + IllegalArgumentException.class, + () -> api.pushedAuthorizationRequest("https://domain.com/callback", "code", null, mockList)); + + assertThat(e.getMessage(), is("'authorizationDetails' must be a list that can be serialized to JSON")); + assertThat(e.getCause(), instanceOf(JsonProcessingException.class)); + } + + @Test + public void shouldCreatePushedAuthorizationRequestWithoutSecret() throws Exception { + AuthAPI api = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID).build(); + Request request = + api.pushedAuthorizationRequest("https://domain.com/callback", "code", null); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("redirect_uri=" + "https%3A%2F%2Fdomain.com%2Fcallback")); + assertThat(body, containsString("response_type=" + "code")); + assertThat(body, not(containsString("client_secret"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + public void authorizeUrlWithJARShouldThrowWhenRequestNull() { + verifyThrows(IllegalArgumentException.class, () -> api.authorizeUrlWithJAR(null), "'request' cannot be null!"); + } + + @Test + public void shouldBuildAuthorizeUrlWithJAR() { + String requestJwt = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"; + AuthAPI api = + AuthAPI.newBuilder("domain.auth0.com", CLIENT_ID, CLIENT_SECRET).build(); + String url = api.authorizeUrlWithJAR( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"); + assertThat(url, is(notNullValue())); + assertThat(url, isUrl("https", "domain.auth0.com", "/authorize")); + + assertThat(url, hasQueryParameter("request", requestJwt)); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + } + + @Test + public void pushedAuthorizationRequestShouldThrowWhenRequestIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.pushedAuthorizationRequestWithJAR(null), + "'request' cannot be null!"); + } + + @Test + public void shouldCreatePushedAuthorizationJarRequest() throws Exception { + String requestJwt = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"; + Request request = api.pushedAuthorizationRequestWithJAR(requestJwt); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("request=" + requestJwt)); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + public void shouldCreatePushedAuthorizationJarRequestWithoutSecret() throws Exception { + String requestJwt = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"; + AuthAPI api = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID).build(); + Request request = api.pushedAuthorizationRequestWithJAR(requestJwt); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("request=" + requestJwt)); + assertThat(body, not(containsString("client_secret"))); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldCreatePushedAuthorizationJarRequestWithoutAuthDetails() throws Exception { + String requestJwt = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"; + Map authorizationDetails = new HashMap<>(); + authorizationDetails.put("type", "account information"); + authorizationDetails.put("locations", Collections.singletonList("https://example.com/customers")); + authorizationDetails.put("actions", Arrays.asList("read", "write")); + List> authDetailsList = Collections.singletonList(authorizationDetails); + + Request request = + api.pushedAuthorizationRequestWithJAR(requestJwt, authDetailsList); + assertThat(request, is(notNullValue())); + + server.jsonResponse(PUSHED_AUTHORIZATION_RESPONSE, 200); + PushedAuthorizationResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/par")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = readFromRequest(recordedRequest); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("request=" + requestJwt)); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + + String authDetailsParam = getQueryMap(body).get("authorization_details"); + String decodedAuthDetails = URLDecoder.decode(authDetailsParam, StandardCharsets.UTF_8.name()); + TypeReference>> typeReference = new TypeReference>>() {}; + List> deserialized = + ObjectMapperProvider.getMapper().readValue(decodedAuthDetails, typeReference); + assertThat(deserialized, notNullValue()); + assertThat(deserialized, hasSize(1)); + assertThat(deserialized.get(0).get("type"), is("account information")); + + List locations = (List) deserialized.get(0).get("locations"); + List actions = (List) deserialized.get(0).get("actions"); + + assertThat(locations, hasSize(1)); + assertThat(locations.get(0), is("https://example.com/customers")); + assertThat(actions, hasSize(2)); + assertThat(actions, contains("read", "write")); + + assertThat(response, is(notNullValue())); + assertThat(response.getRequestURI(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldThrowWhenCreatePushedAuthorizationJarRequestWithInvalidAuthDetails() { + String requestJwt = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRfaWQiOiIxMjM0NTYiLCJyZWRpcmVjdF91cmkiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJyZXNwb25zZV90eXBlIjoiY29kZSIsIm5vbmNlIjoiMTIzNCIsInN0YXRlIjoiNzhkeXVma2poZGYifQ.UQDz8hBIabaqatY75BvqGyiPoOqNYJQIsimUKg4_VrU"; + // force Jackson to throw error on serialization + // see https://stackoverflow.com/questions/26716020/how-to-get-a-jsonprocessingexception-using-jackson + List mockList = mock(List.class); + when(mockList.toString()).thenReturn(mockList.getClass().getName()); + + IllegalArgumentException e = verifyThrows( + IllegalArgumentException.class, () -> api.pushedAuthorizationRequestWithJAR(requestJwt, mockList)); + + assertThat(e.getMessage(), is("'authorizationDetails' must be a list that can be serialized to JSON")); + assertThat(e.getCause(), instanceOf(JsonProcessingException.class)); + } + + @Test + public void authorizeBackChannelWhenScopeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.authorizeBackChannel(null, "This is binding message", getLoginHint()), + "'scope' cannot be null!"); + } + + @Test + public void authorizeBackChannelWhenBindingMessageIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.authorizeBackChannel("openid", null, getLoginHint()), + "'binding message' cannot be null!"); + } + + @Test + public void authorizeBackChannelWhenLoginHintIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.authorizeBackChannel("openid", "This is binding message", null), + "'login hint' cannot be null!"); + } + + @Test + public void authorizeBackChannel() throws Exception { + Request request = + api.authorizeBackChannel("openid", "This is binding message", getLoginHint()); + assertThat(request, is(notNullValue())); + + server.jsonResponse(BACK_CHANNEL_AUTHORIZE_RESPONSE, 200); + BackChannelAuthorizeResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/bc-authorize")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = URLDecoder.decode(readFromRequest(recordedRequest), StandardCharsets.UTF_8.name()); + assertThat(body, containsString("scope=" + "openid")); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + assertThat(body, containsString("binding_message=This is binding message")); + assertThat( + body, + containsString( + "login_hint={\"sub\":\"auth0|user1\",\"format\":\"format1\",\"iss\":\"https://auth0.com\"}")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthReqId(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + assertThat(response.getInterval(), notNullValue()); + } + + @Test + public void authorizeBackChannelWithAudienceAndRequestedExpiry() throws Exception { + Request request = api.authorizeBackChannel( + "openid", "This is binding message", getLoginHint(), "https://api.example.com", 300); + assertThat(request, is(notNullValue())); + + server.jsonResponse(BACK_CHANNEL_AUTHORIZE_RESPONSE, 200); + BackChannelAuthorizeResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/bc-authorize")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = URLDecoder.decode(readFromRequest(recordedRequest), StandardCharsets.UTF_8.name()); + assertThat(body, containsString("scope=" + "openid")); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + assertThat(body, containsString("binding_message=This is binding message")); + assertThat( + body, + containsString( + "login_hint={\"sub\":\"auth0|user1\",\"format\":\"format1\",\"iss\":\"https://auth0.com\"}")); + assertThat(body, containsString("requested_expiry=" + 300)); + assertThat(body, containsString("audience=" + "https://api.example.com")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAuthReqId(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + assertThat(response.getInterval(), notNullValue()); + } + + private Map getLoginHint() { + Map loginHint = new HashMap<>(); + loginHint.put("format", "format1"); + loginHint.put("iss", "https://auth0.com"); + loginHint.put("sub", "auth0|user1"); + return loginHint; + } + + @Test + public void getBackChannelLoginStatusWhenAuthReqIdIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.getBackChannelLoginStatus(null, "ciba"), + "'auth req id' cannot be null!"); + } + + @Test + public void getBackChannelLoginStatusWhenGrantTypeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> api.getBackChannelLoginStatus("red_id_1", null), + "'grant type' cannot be null!"); + } + + @Test + public void getBackChannelLoginStatus() throws Exception { + Request request = api.getBackChannelLoginStatus("red_id_1", "ciba"); + assertThat(request, is(notNullValue())); + + server.jsonResponse(BACK_CHANNEL_LOGIN_STATUS_RESPONSE, 200); + BackChannelTokenResponse response = request.execute().getBody(); + RecordedRequest recordedRequest = server.takeRequest(); + + assertThat(recordedRequest, hasMethodAndPath(HttpMethod.POST, "/oauth/token")); + assertThat(recordedRequest, hasHeader("Content-Type", "application/x-www-form-urlencoded")); + + String body = URLDecoder.decode(readFromRequest(recordedRequest), StandardCharsets.UTF_8.name()); + assertThat(body, containsString("client_id=" + CLIENT_ID)); + assertThat(body, containsString("client_secret=" + CLIENT_SECRET)); + assertThat(body, containsString("auth_req_id=red_id_1")); + assertThat(body, containsString("grant_type=ciba")); + + assertThat(response, is(notNullValue())); + assertThat(response.getAccessToken(), not(emptyOrNullString())); + assertThat(response.getIdToken(), not(emptyOrNullString())); + assertThat(response.getExpiresIn(), notNullValue()); + assertThat(response.getScope(), not(emptyOrNullString())); + } + + private Map getQueryMap(String input) { + String[] params = input.split("&"); + + return Arrays.stream(params).map(param -> param.split("=")).collect(Collectors.toMap(p -> p[0], p -> p[1])); + } + + static class TestAssertionSigner implements ClientAssertionSigner { + + private final String token; + + public TestAssertionSigner(String token) { + this.token = token; + } + + @Override + public String createSignedClientAssertion(String issuer, String audience, String subject) { + return token; + } + } +} diff --git a/src/test/java/com/auth0/client/auth/AuthorizeUrlBuilderTest.java b/src/test/java/com/auth0/client/auth/AuthorizeUrlBuilderTest.java new file mode 100644 index 000000000..cc72b8877 --- /dev/null +++ b/src/test/java/com/auth0/client/auth/AuthorizeUrlBuilderTest.java @@ -0,0 +1,259 @@ +package com.auth0.client.auth; + +import static com.auth0.AssertsUtil.verifyThrows; +import static com.auth0.client.UrlMatcher.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +import java.net.URLEncoder; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Test; + +public class AuthorizeUrlBuilderTest { + + private static final HttpUrl DOMAIN = HttpUrl.parse("https://domain.auth0.com"); + private static final String CLIENT_ID = "clientId"; + private static final String REDIRECT_URI = "https://domain.auth0.com/callback"; + + @Test + public void shouldThrowWhenBaseUrlIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(null, CLIENT_ID, REDIRECT_URI), + "'base url' cannot be null!"); + } + + @Test + public void shouldThrowWhenRedirectUriIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, null), + "'redirect uri' cannot be null!"); + } + + @Test + public void shouldThrowWhenClientIdIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, null, REDIRECT_URI), + "'client id' cannot be null!"); + } + + @Test + public void shouldGetNewInstance() { + AuthorizeUrlBuilder instance = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI); + assertThat(instance, is(notNullValue())); + } + + @Test + public void shouldBuildValidAuthorizeUrlWithHttp() { + HttpUrl httpBaseUrl = HttpUrl.parse("http://domain.auth0.com"); + String url = AuthorizeUrlBuilder.newInstance(httpBaseUrl, CLIENT_ID, REDIRECT_URI) + .build(); + assertThat(url, isUrl("http", "domain.auth0.com", "/authorize")); + } + + @Test + public void shouldBuildValidAuthorizeUrlWithHttps() { + HttpUrl httpsBaseUrl = HttpUrl.parse("https://domain.auth0.com"); + String url = AuthorizeUrlBuilder.newInstance(httpsBaseUrl, CLIENT_ID, REDIRECT_URI) + .build(); + assertThat(url, isUrl("https", "domain.auth0.com", "/authorize")); + } + + @Test + public void shouldAddResponseTypeCode() { + String url = + AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI).build(); + assertThat(url, hasQueryParameter("response_type", "code")); + } + + @Test + public void shouldAddClientId() { + String url = + AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI).build(); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + } + + @Test + public void shouldAddRedirectUri() { + String url = + AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI).build(); + assertThat(url, hasQueryParameter("redirect_uri", REDIRECT_URI)); + } + + @Test + public void shouldNotEncodeTwiceTheRedirectUri() throws Exception { + String encodedUrl = URLEncoder.encode("https://www.google.com/?src=her&q=ans", "UTF-8"); + String url = + AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, encodedUrl).build(); + assertThat(url, encodedQueryContains("redirect_uri=" + encodedUrl)); + } + + @Test + public void shouldSetConnection() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withConnection("my-connection") + .build(); + assertThat(url, hasQueryParameter("connection", "my-connection")); + } + + @Test + public void shouldThrowWhenConnectionIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withConnection(null), + "'connection' cannot be null!"); + } + + @Test + public void shouldSetAudience() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withAudience("https://myapi.domain.com/users") + .build(); + assertThat(url, hasQueryParameter("audience", "https://myapi.domain.com/users")); + } + + @Test + public void shouldThrowWhenAudienceIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withAudience(null), + "'audience' cannot be null!"); + } + + @Test + public void shouldSetState() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withState("1234567890") + .build(); + assertThat(url, hasQueryParameter("state", "1234567890")); + } + + @Test + public void shouldThrowWhenStateIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withState(null), + "'state' cannot be null!"); + } + + @Test + public void shouldSetScope() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withScope("profile email contacts") + .build(); + assertThat(url, hasQueryParameter("scope", "profile email contacts")); + } + + @Test + public void shouldThrowWhenScopeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withScope(null), + "'scope' cannot be null!"); + } + + @Test + public void shouldSetResponseType() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withResponseType("token id_token") + .build(); + assertThat(url, hasQueryParameter("response_type", "token id_token")); + } + + @Test + public void shouldThrowWhenResponseTypeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withResponseType(null), + "'response type' cannot be null!"); + } + + @Test + public void shouldSetCustomParameter() { + String url = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withParameter("name", "value") + .build(); + assertThat(url, hasQueryParameter("name", "value")); + } + + @Test + public void shouldThrowWhenCustomParameterNameIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withParameter(null, "value"), + "'name' cannot be null!"); + } + + @Test + public void shouldThrowWhenCustomParameterValueIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withParameter("name", null), + "'value' cannot be null!"); + } + + @Test + public void shouldAddOrganizationParameter() { + String authUrl = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withOrganization("org_abc") + .build(); + assertThat(authUrl, hasQueryParameter("organization", "org_abc")); + } + + @Test + public void shouldThrowWhenOrganizationIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withOrganization(null) + .build(), + "'organization' cannot be null!"); + } + + @Test + public void shouldAddInvitationParameter() { + String authUrl = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withInvitation("invitation_123") + .build(); + assertThat(authUrl, hasQueryParameter("invitation", "invitation_123")); + } + + @Test + public void shouldThrowWhenInvitationIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withInvitation(null) + .build(), + "'invitation' cannot be null!"); + } + + @Test + public void shouldAddCodeChallengeParameter() { + String authUrl = AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withCodeChallenge("insecure_challenge") + .build(); + assertThat(authUrl, hasQueryParameter("code_challenge", "insecure_challenge")); + assertThat(authUrl, hasQueryParameter("code_challenge_method", "S256")); + } + + @Test + public void shouldThrowWhenChallengeIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> AuthorizeUrlBuilder.newInstance(DOMAIN, CLIENT_ID, REDIRECT_URI) + .withCodeChallenge(null) + .build(), + "'challenge' cannot be null!"); + } +} diff --git a/src/test/java/com/auth0/client/auth/LogoutUrlBuilderTest.java b/src/test/java/com/auth0/client/auth/LogoutUrlBuilderTest.java new file mode 100644 index 000000000..f5a5a1860 --- /dev/null +++ b/src/test/java/com/auth0/client/auth/LogoutUrlBuilderTest.java @@ -0,0 +1,106 @@ +package com.auth0.client.auth; + +import static com.auth0.AssertsUtil.verifyThrows; +import static com.auth0.client.UrlMatcher.*; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +import java.net.URLEncoder; +import okhttp3.HttpUrl; +import org.junit.jupiter.api.Test; + +public class LogoutUrlBuilderTest { + + private static final HttpUrl DOMAIN = HttpUrl.parse("https://domain.auth0.com"); + private static final String CLIENT_ID = "clientId"; + private static final String RETURN_TO_URL = "https://domain.auth0.com/callback"; + + @Test + public void shouldThrowWhenBaseUrlIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> LogoutUrlBuilder.newInstance(null, CLIENT_ID, RETURN_TO_URL, true), + "'base url' cannot be null!"); + } + + @Test + public void shouldThrowWhenReturnToURLIsNull() { + verifyThrows( + IllegalArgumentException.class, + () -> LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, null, true), + "'return to url' cannot be null!"); + } + + @Test + public void shouldNotThrowWhenClientIdIsNull() { + LogoutUrlBuilder.newInstance(DOMAIN, null, RETURN_TO_URL, true); + } + + @Test + public void shouldGetNewInstance() { + LogoutUrlBuilder instance = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, true); + assertThat(instance, is(notNullValue())); + } + + @Test + public void shouldBuildValidLogoutUrlWithHttp() { + HttpUrl httpBaseUrl = HttpUrl.parse("http://domain.auth0.com"); + String url = LogoutUrlBuilder.newInstance(httpBaseUrl, CLIENT_ID, RETURN_TO_URL, true) + .build(); + assertThat(url, isUrl("http", "domain.auth0.com", "/v2/logout")); + } + + @Test + public void shouldBuildValidLogoutUrlWithHttps() { + HttpUrl httpsBaseUrl = HttpUrl.parse("https://domain.auth0.com"); + String url = LogoutUrlBuilder.newInstance(httpsBaseUrl, CLIENT_ID, RETURN_TO_URL, true) + .build(); + assertThat(url, isUrl("https", "domain.auth0.com", "/v2/logout")); + } + + @Test + public void shouldAddReturnToURL() { + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, true) + .build(); + assertThat(url, hasQueryParameter("returnTo", RETURN_TO_URL)); + } + + @Test + public void shouldNotEncodeTwiceTheReturnToURL() throws Exception { + String encodedUrl = URLEncoder.encode("https://www.google.com/?src=her&q=ans", "UTF-8"); + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, encodedUrl, true) + .build(); + assertThat(url, encodedQueryContains("returnTo=" + encodedUrl)); + } + + @Test + public void shouldNotAddClientId() { + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, false) + .build(); + assertThat(url, hasQueryParameter("client_id", null)); + } + + @Test + public void shouldAddClientId() { + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, true) + .build(); + assertThat(url, hasQueryParameter("client_id", CLIENT_ID)); + } + + @Test + public void shouldUseFederated() { + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, true) + .useFederated(true) + .build(); + assertThat(url, hasQueryParameter("federated", "")); + } + + @Test + public void shouldNotUseFederated() { + String url = LogoutUrlBuilder.newInstance(DOMAIN, CLIENT_ID, RETURN_TO_URL, true) + .useFederated(false) + .build(); + assertThat(url, hasQueryParameter("federated", null)); + } +} diff --git a/src/test/java/com/auth0/client/auth/RSAClientAssertionSignerTest.java b/src/test/java/com/auth0/client/auth/RSAClientAssertionSignerTest.java new file mode 100644 index 000000000..39fc02a5d --- /dev/null +++ b/src/test/java/com/auth0/client/auth/RSAClientAssertionSignerTest.java @@ -0,0 +1,165 @@ +package com.auth0.client.auth; + +import static com.auth0.AssertsUtil.verifyThrows; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.auth0.AssertsUtil; +import com.auth0.exception.ClientAssertionSigningException; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTCreator; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import java.io.File; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import org.junit.jupiter.api.Test; + +public class RSAClientAssertionSignerTest { + + private static final String PRIVATE_KEY_FILE_RSA = "src/test/resources/auth/rsa_private_key.pem"; + private static final String PUBLIC_KEY_FILE_RSA = "src/test/resources/auth/rsa_public_key.pem"; + + @Test + public void defaultsToRS256() { + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + RSAClientAssertionSigner rsa = new RSAClientAssertionSigner(privateKey); + assertThat(rsa.getAssertionSigningAlgorithm(), is(RSAClientAssertionSigner.RSASigningAlgorithm.RSA256)); + } + + @Test + public void throwsOnNullSigningKey() { + AssertsUtil.verifyThrows( + IllegalArgumentException.class, + () -> new RSAClientAssertionSigner(null), + "'assertion signing key' cannot be null!"); + } + + @Test + public void throwsOnNullSigningAlgorithm() { + RSAPrivateKey privateKey = mock(RSAPrivateKey.class); + verifyThrows( + IllegalArgumentException.class, + () -> new RSAClientAssertionSigner(privateKey, null), + "'assertion signing algorithm' cannot be null!"); + } + + @Test + public void throwsWhenErrorSigning256() { + JWTCreator.Builder mockBuilder = mock(JWTCreator.Builder.class); + RSAPrivateKey mockPrivateKey = mock(RSAPrivateKey.class); + + when(mockBuilder.sign(Algorithm.RSA256(null, mockPrivateKey))).thenThrow(JWTCreationException.class); + + ClientAssertionSigningException e = verifyThrows( + ClientAssertionSigningException.class, + () -> new RSAClientAssertionSigner(mockPrivateKey).createSignedClientAssertion("iss", "aud", "sub"), + "Error creating the JWT used for client assertion using the RSA256 signing algorithm"); + + assertThat(e.getCause(), is(instanceOf(JWTCreationException.class))); + } + + @Test + public void throwsWhenErrorSigning384() { + JWTCreator.Builder mockBuilder = mock(JWTCreator.Builder.class); + RSAPrivateKey mockPrivateKey = mock(RSAPrivateKey.class); + + when(mockBuilder.sign(Algorithm.RSA384(null, mockPrivateKey))).thenThrow(JWTCreationException.class); + + ClientAssertionSigningException e = verifyThrows( + ClientAssertionSigningException.class, + () -> new RSAClientAssertionSigner(mockPrivateKey, RSAClientAssertionSigner.RSASigningAlgorithm.RSA384) + .createSignedClientAssertion("iss", "aud", "sub"), + "Error creating the JWT used for client assertion using the RSA384 signing algorithm"); + + assertThat(e.getCause(), is(instanceOf(JWTCreationException.class))); + } + + @Test + public void createsVerifiedRSA256SigningAssertion() throws Exception { + KeyPair keyPair = getKeyPair(); + + RSAClientAssertionSigner clientAssertion = new RSAClientAssertionSigner((RSAPrivateKey) keyPair.getPrivate()); + String jwt = clientAssertion.createSignedClientAssertion("issuer", "audience", "subject"); + + DecodedJWT decodedJWT = JWT.require(Algorithm.RSA256((RSAPublicKey) keyPair.getPublic(), null)) + .build() + .verify(jwt); + + assertThat(decodedJWT.getSubject(), is("subject")); + assertThat(decodedJWT.getAudience(), hasItem("audience")); + assertThat(decodedJWT.getIssuer(), is("issuer")); + assertThat( + decodedJWT.getExpiresAtAsInstant(), + is(decodedJWT.getIssuedAtAsInstant().plusSeconds(180))); + assertThat(decodedJWT.getClaim("jti").asString(), is(notNullValue())); + + System.out.println(decodedJWT); + } + + @Test + public void createsVerifiedRSA384SigningAssertion() throws Exception { + KeyPair keyPair = getKeyPair(); + + RSAClientAssertionSigner clientAssertion = new RSAClientAssertionSigner( + (RSAPrivateKey) keyPair.getPrivate(), RSAClientAssertionSigner.RSASigningAlgorithm.RSA384); + String jwt = clientAssertion.createSignedClientAssertion("issuer", "audience", "subject"); + + DecodedJWT decodedJWT = JWT.require(Algorithm.RSA384((RSAPublicKey) keyPair.getPublic(), null)) + .build() + .verify(jwt); + + assertThat(decodedJWT.getSubject(), is("subject")); + assertThat(decodedJWT.getAudience(), hasItem("audience")); + assertThat(decodedJWT.getIssuer(), is("issuer")); + assertThat( + decodedJWT.getExpiresAtAsInstant(), + is(decodedJWT.getIssuedAtAsInstant().plusSeconds(180))); + assertThat(decodedJWT.getClaim("jti").asString(), is(notNullValue())); + + System.out.println(decodedJWT); + } + + private KeyPair getKeyPair() throws Exception { + URI fileUri = new File(PRIVATE_KEY_FILE_RSA).toURI(); + String privateKeyContent = new String(Files.readAllBytes(Paths.get(fileUri))); + privateKeyContent = privateKeyContent + .replaceAll("\\n", "") + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + + PKCS8EncodedKeySpec keySpecPKCS8 = + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyContent)); + PrivateKey privateKey = kf.generatePrivate(keySpecPKCS8); + + URI publicKeyFileUri = new File(PUBLIC_KEY_FILE_RSA).toURI(); + String publicKeyContent = new String(Files.readAllBytes(Paths.get(publicKeyFileUri))); + publicKeyContent = publicKeyContent + .replaceAll("\\n", "") + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", ""); + + byte[] encodedPublicKey = Base64.getDecoder().decode(publicKeyContent); + + EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedPublicKey); + PublicKey publicKey = kf.generatePublic(keySpec); + + return new KeyPair(publicKey, privateKey); + } +} diff --git a/src/test/resources/auth/add_oob_authenticator_response.json b/src/test/resources/auth/add_oob_authenticator_response.json new file mode 100644 index 000000000..df463c1c2 --- /dev/null +++ b/src/test/resources/auth/add_oob_authenticator_response.json @@ -0,0 +1,8 @@ +{ + "oob_code": "Fe26.2**da6....", + "binding_method":"prompt", + "authenticator_type":"oob", + "oob_channel":"sms", + "recovery_codes":["ABCDEFGDRFK75ABYR7PH8TJA"], + "barcode_uri":"otpauth://..." +} diff --git a/src/test/resources/auth/add_otp_authenticator_response.json b/src/test/resources/auth/add_otp_authenticator_response.json new file mode 100644 index 000000000..f4c71fc7f --- /dev/null +++ b/src/test/resources/auth/add_otp_authenticator_response.json @@ -0,0 +1,6 @@ +{ + "secret": "ABCDEFGMK5CE6WTZKRTTQRKUJVFXOVRF", + "barcode_uri":"otpauth://...", + "authenticator_type":"otp", + "recovery_codes":["ABCDEFGDRFK75ABYR7PH8TJA"] +} diff --git a/src/test/resources/auth/back_channel_authorize_response.json b/src/test/resources/auth/back_channel_authorize_response.json new file mode 100644 index 000000000..bc0649f08 --- /dev/null +++ b/src/test/resources/auth/back_channel_authorize_response.json @@ -0,0 +1,5 @@ +{ + "auth_req_id": "red_id_1", + "expires_in": 300, + "interval": 5 +} diff --git a/src/test/resources/auth/back_channel_login_status_response.json b/src/test/resources/auth/back_channel_login_status_response.json new file mode 100644 index 000000000..bc1676e2c --- /dev/null +++ b/src/test/resources/auth/back_channel_login_status_response.json @@ -0,0 +1,6 @@ +{ + "access_token": "eyJhbGciOiJkaXIi.....", + "id_token": "eyJhbGciOiJSUzI1NiIs.....", + "expires_in": 86400, + "scope": "openid" +} diff --git a/src/test/resources/auth/error_plaintext.json b/src/test/resources/auth/error_plaintext.json new file mode 100644 index 000000000..084a6ed4c --- /dev/null +++ b/src/test/resources/auth/error_plaintext.json @@ -0,0 +1 @@ +A plain-text error response \ No newline at end of file diff --git a/src/test/resources/auth/error_with_description.json b/src/test/resources/auth/error_with_description.json new file mode 100644 index 000000000..bd84129e6 --- /dev/null +++ b/src/test/resources/auth/error_with_description.json @@ -0,0 +1,6 @@ +{ + "name": "BadRequestError", + "code": "user_exists", + "description": "The user already exists.", + "statusCode": 400 +} \ No newline at end of file diff --git a/src/test/resources/auth/error_with_description_and_extra_properties.json b/src/test/resources/auth/error_with_description_and_extra_properties.json new file mode 100644 index 000000000..a1799ae15 --- /dev/null +++ b/src/test/resources/auth/error_with_description_and_extra_properties.json @@ -0,0 +1,5 @@ +{ + "error": "mfa_required", + "error_description": "Multifactor authentication required", + "mfa_token": "Fe26...Ha" +} \ No newline at end of file diff --git a/src/test/resources/auth/error_with_error.json b/src/test/resources/auth/error_with_error.json new file mode 100644 index 000000000..ecadf352f --- /dev/null +++ b/src/test/resources/auth/error_with_error.json @@ -0,0 +1,3 @@ +{ + "error": "missing username for Username-Password-Authentication connection with requires_username enabled" +} \ No newline at end of file diff --git a/src/test/resources/auth/error_with_error_description.json b/src/test/resources/auth/error_with_error_description.json new file mode 100644 index 000000000..1ca27286f --- /dev/null +++ b/src/test/resources/auth/error_with_error_description.json @@ -0,0 +1,4 @@ +{ + "error": "invalid_request", + "error_description": "the connection was not found" +} \ No newline at end of file diff --git a/src/test/resources/auth/list_authenticators_response.json b/src/test/resources/auth/list_authenticators_response.json new file mode 100644 index 000000000..27800f194 --- /dev/null +++ b/src/test/resources/auth/list_authenticators_response.json @@ -0,0 +1,26 @@ +[ + { + "id":"recovery-code|dev_DsvzGfZw2Fg5N3rI", + "authenticator_type":"recovery-code", + "active":true + }, + { + "id":"sms|dev_gB342kcL2K22S4yB", + "authenticator_type":"oob", + "oob_channel":"sms", + "name":"+X XXXX1234", + "active":true + }, + { + "id":"sms|dev_gB342kcL2K22S4yB", + "authenticator_type":"oob", + "oob_channel":"sms", + "name":"+X XXXX1234", + "active":false + }, + { + "id":"totp|dev_LJaKaN5O3tjRFOw2", + "authenticator_type":"otp", + "active":true + } +] diff --git a/src/test/resources/auth/mfa_challenge_request_response.json b/src/test/resources/auth/mfa_challenge_request_response.json new file mode 100644 index 000000000..469fc18e0 --- /dev/null +++ b/src/test/resources/auth/mfa_challenge_request_response.json @@ -0,0 +1,5 @@ +{ + "challenge_type":"oob", + "binding_method":"prompt", + "oob_code": "abcde...dasg" +} diff --git a/src/test/resources/auth/password_strength_error_none.json b/src/test/resources/auth/password_strength_error_none.json new file mode 100644 index 000000000..4f6e8b883 --- /dev/null +++ b/src/test/resources/auth/password_strength_error_none.json @@ -0,0 +1,85 @@ +{ + "code": "invalid_password", + "description": { + "rules": [ + { + "code": "lengthAtLeast", + "format": [ + 10 + ], + "message": "At least %d characters in length", + "verified": false + }, + { + "code": "containsAtLeast", + "format": [ + 3, + 4 + ], + "items": [ + { + "code": "lowerCase", + "message": "lower case letters (a-z)", + "verified": false + }, + { + "code": "upperCase", + "message": "upper case letters (A-Z)", + "verified": false + }, + { + "code": "numbers", + "message": "numbers (i.e. 0-9)", + "verified": false + }, + { + "code": "specialCharacters", + "message": "special characters (e.g. !@#$%^&*)", + "verified": false + } + ], + "message": "Contain at least %d of the following %d types of characters:", + "verified": false + }, + { + "code": "shouldContain", + "items": [ + { + "code": "lowerCase", + "message": "lower case letters (a-z)", + "verified": false + }, + { + "code": "upperCase", + "message": "upper case letters (A-Z)", + "verified": false + }, + { + "code": "numbers", + "message": "numbers (i.e. 0-9)", + "verified": false + }, + { + "code": "specialCharacters", + "message": "special characters (e.g. !@#$%^&*)", + "verified": false + } + ], + "message": "Should contain:", + "verified": false + }, + { + "code": "identicalChars", + "format": [ + 2, + "aaa" + ], + "message": "No more than %d identical characters in a row (e.g., \"%s\" not allowed)", + "verified": false + } + ], + "verified": false + }, + "name": "PasswordStrengthError", + "statusCode": 400 +} \ No newline at end of file diff --git a/src/test/resources/auth/password_strength_error_some.json b/src/test/resources/auth/password_strength_error_some.json new file mode 100644 index 000000000..90aea75bb --- /dev/null +++ b/src/test/resources/auth/password_strength_error_some.json @@ -0,0 +1,85 @@ +{ + "code": "invalid_password", + "description": { + "rules": [ + { + "code": "lengthAtLeast", + "format": [ + 10 + ], + "message": "At least %d characters in length", + "verified": true + }, + { + "code": "containsAtLeast", + "format": [ + 3, + 4 + ], + "items": [ + { + "code": "lowerCase", + "message": "lower case letters (a-z)", + "verified": true + }, + { + "code": "upperCase", + "message": "upper case letters (A-Z)", + "verified": true + }, + { + "code": "numbers", + "message": "numbers (i.e. 0-9)", + "verified": true + }, + { + "code": "specialCharacters", + "message": "special characters (e.g. !@#$%^&*)", + "verified": false + } + ], + "message": "Contain at least %d of the following %d types of characters:", + "verified": true + }, + { + "code": "shouldContain", + "items": [ + { + "code": "lowerCase", + "message": "lower case letters (a-z)", + "verified": false + }, + { + "code": "upperCase", + "message": "upper case letters (A-Z)", + "verified": false + }, + { + "code": "numbers", + "message": "numbers (i.e. 0-9)", + "verified": false + }, + { + "code": "specialCharacters", + "message": "special characters (e.g. !@#$%^&*)", + "verified": false + } + ], + "message": "Should contain:", + "verified": false + }, + { + "code": "identicalChars", + "format": [ + 2, + "aaa" + ], + "message": "No more than %d identical characters in a row (e.g., \"%s\" not allowed)", + "verified": true + } + ], + "verified": false + }, + "name": "PasswordStrengthError", + "statusCode": 400 +} \ No newline at end of file diff --git a/src/test/resources/auth/passwordless_email.json b/src/test/resources/auth/passwordless_email.json new file mode 100644 index 000000000..56b5940a0 --- /dev/null +++ b/src/test/resources/auth/passwordless_email.json @@ -0,0 +1,5 @@ +{ + "_id": "5f85do7a2c673038a807254c", + "email": "user@domain.com", + "email_verified": false +} \ No newline at end of file diff --git a/src/test/resources/auth/passwordless_sms.json b/src/test/resources/auth/passwordless_sms.json new file mode 100644 index 000000000..f305a8d1e --- /dev/null +++ b/src/test/resources/auth/passwordless_sms.json @@ -0,0 +1,6 @@ +{ + "_id": "5f85d99a2c673038a8111f5f", + "phone_number": "+16511234567", + "phone_verified": false, + "request_language": null +} diff --git a/src/test/resources/auth/pushed_authorization_response.json b/src/test/resources/auth/pushed_authorization_response.json new file mode 100644 index 000000000..ca1a5b744 --- /dev/null +++ b/src/test/resources/auth/pushed_authorization_response.json @@ -0,0 +1,4 @@ +{ + "request_uri": "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2", + "expires_in": 90 +} diff --git a/src/test/resources/auth/reset_password.json b/src/test/resources/auth/reset_password.json new file mode 100644 index 000000000..2b84a8cb8 --- /dev/null +++ b/src/test/resources/auth/reset_password.json @@ -0,0 +1 @@ +We've just sent you an email to reset your password. \ No newline at end of file diff --git a/src/test/resources/auth/rsa_private_key.pem b/src/test/resources/auth/rsa_private_key.pem new file mode 100644 index 000000000..1427e0d51 --- /dev/null +++ b/src/test/resources/auth/rsa_private_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4ZtdaIrd1BPIJ +tfnF0TjIK5inQAXZ3XlCrUlJdP+XHwIRxdv1FsN12XyMYO/6ymLmo9ryoQeIrsXB +XYqlET3zfAY+diwCb0HEsVvhisthwMU4gZQu6TYW2s9LnXZB5rVtcBK69hcSlA2k +ZudMZWxZcj0L7KMfO2rIvaHw/qaVOE9j0T257Z8Kp2CLF9MUgX0ObhIsdumFRLaL +DvDUmBPr2zuh/34j2XmWwn1yjN/WvGtdfhXW79Ki1S40HcWnygHgLV8sESFKUxxQ +mKvPUTwDOIwLFL5WtE8Mz7N++kgmDcmWMCHc8kcOIu73Ta/3D4imW7VbKgHZo9+K +3ESFE3RjAgMBAAECggEBAJTEIyjMqUT24G2FKiS1TiHvShBkTlQdoR5xvpZMlYbN +tVWxUmrAGqCQ/TIjYnfpnzCDMLhdwT48Ab6mQJw69MfiXwc1PvwX1e9hRscGul36 +ryGPKIVQEBsQG/zc4/L2tZe8ut+qeaK7XuYrPp8bk/X1e9qK5m7j+JpKosNSLgJj +NIbYsBkG2Mlq671irKYj2hVZeaBQmWmZxK4fw0Istz2WfN5nUKUeJhTwpR+JLUg4 +ELYYoB7EO0Cej9UBG30hbgu4RyXA+VbptJ+H042K5QJROUbtnLWuuWosZ5ATldwO +u03dIXL0SH0ao5NcWBzxU4F2sBXZRGP2x/jiSLHcqoECgYEA4qD7mXQpu1b8XO8U +6abpKloJCatSAHzjgdR2eRDRx5PMvloipfwqA77pnbjTUFajqWQgOXsDTCjcdQui +wf5XAaWu+TeAVTytLQbSiTsBhrnoqVrr3RoyDQmdnwHT8aCMouOgcC5thP9vQ8Us +rVdjvRRbnJpg3BeSNimH+u9AHgsCgYEA0EzcbOltCWPHRAY7B3Ge/AKBjBQr86Kv +TdpTlxePBDVIlH+BM6oct2gaSZZoHbqPjbq5v7yf0fKVcXE4bSVgqfDJ/sZQu9Lp +PTeV7wkk0OsAMKk7QukEpPno5q6tOTNnFecpUhVLLlqbfqkB2baYYwLJR3IRzboJ +FQbLY93E8gkCgYB+zlC5VlQbbNqcLXJoImqItgQkkuW5PCgYdwcrSov2ve5r/Acz +FNt1aRdSlx4176R3nXyibQA1Vw+ztiUFowiP9WLoM3PtPZwwe4bGHmwGNHPIfwVG +m+exf9XgKKespYbLhc45tuC08DATnXoYK7O1EnUINSFJRS8cezSI5eHcbQKBgQDC +PgqHXZ2aVftqCc1eAaxaIRQhRmY+CgUjumaczRFGwVFveP9I6Gdi+Kca3DE3F9Pq +PKgejo0SwP5vDT+rOGHN14bmGJUMsX9i4MTmZUZ5s8s3lXh3ysfT+GAhTd6nKrIE +kM3Nh6HWFhROptfc6BNusRh1kX/cspDplK5x8EpJ0QKBgQDWFg6S2je0KtbV5PYe +RultUEe2C0jYMDQx+JYxbPmtcopvZQrFEur3WKVuLy5UAy7EBvwMnZwIG7OOohJb +vkSpADK6VPn9lbqq7O8cTedEHttm6otmLt8ZyEl3hZMaL3hbuRj6ysjmoFKx6CrX +rK0/Ikt5ybqUzKCMJZg2VKGTxg== +-----END PRIVATE KEY----- diff --git a/src/test/resources/auth/rsa_public_key.pem b/src/test/resources/auth/rsa_public_key.pem new file mode 100644 index 000000000..e8d628859 --- /dev/null +++ b/src/test/resources/auth/rsa_public_key.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuGbXWiK3dQTyCbX5xdE4 +yCuYp0AF2d15Qq1JSXT/lx8CEcXb9RbDddl8jGDv+spi5qPa8qEHiK7FwV2KpRE9 +83wGPnYsAm9BxLFb4YrLYcDFOIGULuk2FtrPS512Qea1bXASuvYXEpQNpGbnTGVs +WXI9C+yjHztqyL2h8P6mlThPY9E9ue2fCqdgixfTFIF9Dm4SLHbphUS2iw7w1JgT +69s7of9+I9l5lsJ9cozf1rxrXX4V1u/SotUuNB3Fp8oB4C1fLBEhSlMcUJirz1E8 +AziMCxS+VrRPDM+zfvpIJg3JljAh3PJHDiLu902v9w+Iplu1WyoB2aPfitxEhRN0 +YwIDAQAB +-----END PUBLIC KEY----- diff --git a/src/test/resources/auth/sign_up.json b/src/test/resources/auth/sign_up.json new file mode 100644 index 000000000..3a49c99d5 --- /dev/null +++ b/src/test/resources/auth/sign_up.json @@ -0,0 +1,5 @@ +{ + "_id": "58457fe6b27", + "email_verified": false, + "email": "me@auth0.com" +} \ No newline at end of file diff --git a/src/test/resources/auth/sign_up_username.json b/src/test/resources/auth/sign_up_username.json new file mode 100644 index 000000000..83315e25e --- /dev/null +++ b/src/test/resources/auth/sign_up_username.json @@ -0,0 +1,7 @@ +{ + "_id": "58457fe6b27", + "email_verified": false, + "email": "me@auth0.com", + "username": "me", + "phone_number": "1234567890" +} diff --git a/src/test/resources/auth/tokens.json b/src/test/resources/auth/tokens.json new file mode 100644 index 000000000..3f1ed5abe --- /dev/null +++ b/src/test/resources/auth/tokens.json @@ -0,0 +1,7 @@ +{ + "id_token": "eyJ0eXAiOiJKV1Qi...", + "access_token": "A9CvPwFojaBI...", + "refresh_token": "GEbRxBN...edjnXbL", + "token_type": "bearer", + "expires_in": 86000 +} \ No newline at end of file diff --git a/src/test/resources/auth/user_info.json b/src/test/resources/auth/user_info.json new file mode 100644 index 000000000..0ac347ed5 --- /dev/null +++ b/src/test/resources/auth/user_info.json @@ -0,0 +1,20 @@ +{ + "email_verified": false, + "email": "test.account@userinfo.com", + "clientID": "q2hnj2iu...", + "updated_at": "2016-12-05T15:15:40.545Z", + "name": "test.account@userinfo.com", + "picture": "https://s.gravatar.com/avatar/dummy.png", + "user_id": "auth0|58454...", + "nickname": "test.account", + "identities": [ + { + "user_id": "58454...", + "provider": "auth0", + "connection": "Username-Password-Authentication", + "isSocial": false + } + ], + "created_at": "2016-12-05T11:16:59.640Z", + "sub": "auth0|58454..." +} \ No newline at end of file diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..ca6ee9cea --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file From 040aaab7a7fdd84d405c0eacb59e5f1da592d615 Mon Sep 17 00:00:00 2001 From: tanya732 Date: Fri, 3 Jul 2026 15:46:33 +0530 Subject: [PATCH 2/2] fix spotless check --- src/test/java/com/auth0/client/auth/AuthAPITest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/auth0/client/auth/AuthAPITest.java b/src/test/java/com/auth0/client/auth/AuthAPITest.java index 0e154ae87..8516480c0 100644 --- a/src/test/java/com/auth0/client/auth/AuthAPITest.java +++ b/src/test/java/com/auth0/client/auth/AuthAPITest.java @@ -1010,8 +1010,7 @@ public void shouldCreateTokenExchangeRequestWithClientAssertion() throws Excepti AuthAPI authAPI = AuthAPI.newBuilder(server.getBaseUrl(), CLIENT_ID, CLIENT_SECRET) .withClientAssertionSigner(new TestAssertionSigner("token")) .build(); - TokenRequest request = authAPI - .exchangeToken("test-user123-john@example.com", "urn:mycompany:m2m-test-token") + TokenRequest request = authAPI.exchangeToken("test-user123-john@example.com", "urn:mycompany:m2m-test-token") .setAudience("https://myapi.auth0.com/users") .setScope("openid profile email"); assertThat(request, is(notNullValue()));