From 10ed4350a679b600dab867a0da348340bd125359 Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Thu, 12 Mar 2026 15:44:53 +0530 Subject: [PATCH 1/4] breaking: Remove the Management API support --- .../android/callback/ManagementCallback.kt | 5 - .../android/management/ManagementException.kt | 90 ---- .../android/management/UsersAPIClient.kt | 256 ---------- .../com/auth0/android/result/UserProfile.kt | 2 +- .../management/ManagementExceptionTest.kt | 25 - .../android/management/UsersAPIClientTest.kt | 466 ------------------ .../util/ManagementCallbackMatcher.java | 74 --- .../android/util/MockManagementCallback.java | 40 -- .../com/auth0/sample/DatabaseLoginFragment.kt | 130 +---- .../res/layout/fragment_database_login.xml | 52 -- 10 files changed, 9 insertions(+), 1131 deletions(-) delete mode 100644 auth0/src/main/java/com/auth0/android/callback/ManagementCallback.kt delete mode 100644 auth0/src/main/java/com/auth0/android/management/ManagementException.kt delete mode 100755 auth0/src/main/java/com/auth0/android/management/UsersAPIClient.kt delete mode 100644 auth0/src/test/java/com/auth0/android/management/ManagementExceptionTest.kt delete mode 100755 auth0/src/test/java/com/auth0/android/management/UsersAPIClientTest.kt delete mode 100755 auth0/src/test/java/com/auth0/android/util/ManagementCallbackMatcher.java delete mode 100755 auth0/src/test/java/com/auth0/android/util/MockManagementCallback.java diff --git a/auth0/src/main/java/com/auth0/android/callback/ManagementCallback.kt b/auth0/src/main/java/com/auth0/android/callback/ManagementCallback.kt deleted file mode 100644 index 11969adb..00000000 --- a/auth0/src/main/java/com/auth0/android/callback/ManagementCallback.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.auth0.android.callback - -import com.auth0.android.management.ManagementException - -public interface ManagementCallback : Callback \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/management/ManagementException.kt b/auth0/src/main/java/com/auth0/android/management/ManagementException.kt deleted file mode 100644 index 96cc52d9..00000000 --- a/auth0/src/main/java/com/auth0/android/management/ManagementException.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.auth0.android.management - -import com.auth0.android.Auth0Exception -import com.auth0.android.NetworkErrorException - - -@Deprecated( - """ManagementException is deprecated and will be removed in the next major version of the SDK. """, - level = DeprecationLevel.WARNING -) -public class ManagementException @JvmOverloads constructor( - message: String, - exception: Auth0Exception? = null -) : Auth0Exception(message, exception) { - private var code: String? = null - private var description: String? = null - - /** - * Http Response status code. Can have value of 0 if not set. - * - * @return the status code. - */ - public var statusCode: Int = 0 - private set - private var values: Map? = null - - public constructor(payload: String?, statusCode: Int) : this(DEFAULT_MESSAGE) { - code = if (payload != null) NON_JSON_ERROR else EMPTY_BODY_ERROR - description = payload ?: EMPTY_RESPONSE_BODY_DESCRIPTION - this.statusCode = statusCode - } - - public constructor(values: Map) : this(DEFAULT_MESSAGE) { - this.values = values - val codeValue = - (if (values.containsKey(ERROR_KEY)) values[ERROR_KEY] else values[CODE_KEY]) as String? - code = codeValue ?: UNKNOWN_ERROR - description = - (if (values.containsKey(DESCRIPTION_KEY)) values[DESCRIPTION_KEY] else values[ERROR_DESCRIPTION_KEY]) as String? - } - - /** - * Auth0 error code if the server returned one or an internal library code (e.g.: when the server could not be reached) - * - * @return the error code. - */ - @Suppress("MemberVisibilityCanBePrivate") - public fun getCode(): String { - return if (code != null) code!! else UNKNOWN_ERROR - } - - /** - * Description of the error. - * important: You should avoid displaying description to the user, it's meant for debugging only. - * - * @return the error description. - */ - @Suppress("unused") - public fun getDescription(): String { - if (description != null) { - return description!! - } - return if (UNKNOWN_ERROR == getCode()) { - String.format("Received error with code %s", getCode()) - } else "Failed with unknown error" - } - - /** - * Returns a value from the error map, if any. - * - * @param key key of the value to return - * @return the value if found or null - */ - public fun getValue(key: String): Any? { - return values?.get(key) - } - - // When the request failed due to network issues - public val isNetworkError: Boolean - get() = cause is NetworkErrorException - - private companion object { - private const val ERROR_KEY = "error" - private const val CODE_KEY = "code" - private const val DESCRIPTION_KEY = "description" - private const val ERROR_DESCRIPTION_KEY = "error_description" - private const val DEFAULT_MESSAGE = - "An error occurred when trying to authenticate with the server." - } -} \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/management/UsersAPIClient.kt b/auth0/src/main/java/com/auth0/android/management/UsersAPIClient.kt deleted file mode 100755 index 4967e364..00000000 --- a/auth0/src/main/java/com/auth0/android/management/UsersAPIClient.kt +++ /dev/null @@ -1,256 +0,0 @@ -package com.auth0.android.management - -import androidx.annotation.VisibleForTesting -import com.auth0.android.Auth0 -import com.auth0.android.Auth0Exception -import com.auth0.android.NetworkErrorException -import com.auth0.android.authentication.ParameterBuilder -import com.auth0.android.request.ErrorAdapter -import com.auth0.android.request.JsonAdapter -import com.auth0.android.request.NetworkingClient -import com.auth0.android.request.Request -import com.auth0.android.request.internal.BaseRequest -import com.auth0.android.request.internal.GsonAdapter -import com.auth0.android.request.internal.GsonAdapter.Companion.forListOf -import com.auth0.android.request.internal.GsonAdapter.Companion.forMap -import com.auth0.android.request.internal.GsonProvider -import com.auth0.android.request.internal.RequestFactory -import com.auth0.android.request.internal.ResponseUtils.isNetworkError -import com.auth0.android.result.UserIdentity -import com.auth0.android.result.UserProfile -import com.google.gson.Gson -import okhttp3.HttpUrl.Companion.toHttpUrl -import java.io.IOException -import java.io.Reader - -/** - * API client for Auth0 Management API. - * ``` - * val auth0 = Auth0.getInstance("your_client_id", "your_domain") - * val client = UsersAPIClient(auth0) - * ``` - * - * @see [Auth API docs](https://auth0.com/docs/api/management/v2) - */ - -@Deprecated( - """UsersAPIClient is deprecated and will be removed in the next major version of the SDK.""", - level = DeprecationLevel.WARNING -) -public class UsersAPIClient @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal constructor( - private val auth0: Auth0, - private val factory: RequestFactory, - private val gson: Gson -) { - /** - * Creates a new API client instance providing the Auth0 account info and the access token. - * - * @param auth0 account information - * @param token of the primary identity - */ - public constructor( - auth0: Auth0, - token: String - ) : this( - auth0, - factoryForToken(token, auth0.networkingClient), - GsonProvider.gson - ) - - public val clientId: String - get() = auth0.clientId - public val baseURL: String - get() = auth0.getDomainUrl() - - /** - * Link a user identity calling ['/api/v2/users/:primaryUserId/identities'](https://auth0.com/docs/link-accounts#the-management-api) endpoint - * Example usage: - * ``` - * client.link("{auth0 primary user id}", "{user secondary token}") - * .start(object: Callback, ManagementException> { - * override fun onSuccess(result: List) { } - * override fun onFailure(error: ManagementException) { } - * }) - * ``` - * - * @param primaryUserId of the identity to link - * @param secondaryToken of the secondary identity obtained after login - * @return a request to start - */ - public fun link( - primaryUserId: String, - secondaryToken: String - ): Request, ManagementException> { - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(API_PATH) - .addPathSegment(V2_PATH) - .addPathSegment(USERS_PATH) - .addPathSegment(primaryUserId) - .addPathSegment(IDENTITIES_PATH) - .build() - val parameters = ParameterBuilder.newBuilder() - .set(LINK_WITH_KEY, secondaryToken) - .asDictionary() - val userIdentitiesAdapter: JsonAdapter> = forListOf( - UserIdentity::class.java, gson - ) - return factory.post(url.toString(), userIdentitiesAdapter) - .addParameters(parameters) - } - - /** - * Unlink a user identity calling ['/api/v2/users/:primaryToken/identities/secondaryProvider/secondaryUserId'](https://auth0.com/docs/link-accounts#unlinking-accounts) endpoint - * Example usage: - * ``` - * client.unlink("{auth0 primary user id}", {auth0 secondary user id}, "{secondary provider}") - * .start(object: Callback, ManagementException> { - * override fun onSuccess(result: List) { } - * override fun onFailure(error: ManagementException) {} - * }) - * ``` - * - * @param primaryUserId of the primary identity to unlink - * @param secondaryUserId of the secondary identity you wish to unlink from the main one. - * @param secondaryProvider of the secondary identity you wish to unlink from the main one. - * @return a request to start - */ - public fun unlink( - primaryUserId: String, - secondaryUserId: String, - secondaryProvider: String - ): Request, ManagementException> { - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(API_PATH) - .addPathSegment(V2_PATH) - .addPathSegment(USERS_PATH) - .addPathSegment(primaryUserId) - .addPathSegment(IDENTITIES_PATH) - .addPathSegment(secondaryProvider) - .addPathSegment(secondaryUserId) - .build() - val userIdentitiesAdapter: JsonAdapter> = forListOf( - UserIdentity::class.java, gson - ) - return factory.delete(url.toString(), userIdentitiesAdapter) - } - - /** - * Update the user_metadata calling ['/api/v2/users/:userId'](https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id) endpoint - * Example usage: - * ``` - * client.updateMetadata("{user id}", "{user metadata}") - * .start(object: Callback { - * override fun onSuccess(result: UserProfile) { } - * override fun onFailure(error: ManagementException) { } - * }) - * ``` - * - * @param userId of the primary identity to unlink - * @param userMetadata to merge with the existing one - * @return a request to start - */ - public fun updateMetadata( - userId: String, - userMetadata: Map - ): Request { - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(API_PATH) - .addPathSegment(V2_PATH) - .addPathSegment(USERS_PATH) - .addPathSegment(userId) - .build() - val userProfileAdapter: JsonAdapter = GsonAdapter( - UserProfile::class.java, gson - ) - val patch = factory.patch( - url.toString(), - userProfileAdapter - ) as BaseRequest - patch.addParameter(USER_METADATA_KEY, userMetadata) - return patch - } - - /** - * Get the User Profile calling ['/api/v2/users/:userId'](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id) endpoint - * Example usage: - * ``` - * client.getProfile("{user id}") - * .start(object: Callback { - * override fun onSuccess(result: UserProfile) { } - * override fun onFailure(error: ManagementException) { } - * }) - * ``` - * - * @param userId identity of the user - * @return a request to start - */ - public fun getProfile(userId: String): Request { - val url = auth0.getDomainUrl().toHttpUrl().newBuilder() - .addPathSegment(API_PATH) - .addPathSegment(V2_PATH) - .addPathSegment(USERS_PATH) - .addPathSegment(userId) - .build() - val userProfileAdapter: JsonAdapter = GsonAdapter( - UserProfile::class.java, gson - ) - return factory.get(url.toString(), userProfileAdapter) - } - - private companion object { - private const val LINK_WITH_KEY = "link_with" - private const val API_PATH = "api" - private const val V2_PATH = "v2" - private const val USERS_PATH = "users" - private const val IDENTITIES_PATH = "identities" - private const val USER_METADATA_KEY = "user_metadata" - - private fun createErrorAdapter(): ErrorAdapter { - val mapAdapter = forMap(GsonProvider.gson) - return object : ErrorAdapter { - override fun fromRawResponse( - statusCode: Int, - bodyText: String, - headers: Map> - ): ManagementException { - return ManagementException(bodyText, statusCode) - } - - @Throws(IOException::class) - override fun fromJsonResponse( - statusCode: Int, - reader: Reader - ): ManagementException { - val values = mapAdapter.fromJson(reader) - return ManagementException(values) - } - - override fun fromException(cause: Throwable): ManagementException { - if (isNetworkError(cause)) { - return ManagementException( - "Failed to execute the network request", - NetworkErrorException(cause) - ) - } - return ManagementException( - "Something went wrong", - Auth0Exception("Something went wrong", cause) - ) - } - } - } - - private fun factoryForToken( - token: String, - client: NetworkingClient - ): RequestFactory { - val factory = RequestFactory(client, createErrorAdapter()) - factory.setHeader("Authorization", "Bearer $token") - return factory - } - } - - init { - factory.setAuth0ClientInfo(auth0.auth0UserAgent.value) - } -} \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/result/UserProfile.kt b/auth0/src/main/java/com/auth0/android/result/UserProfile.kt index 2f490313..911165e0 100755 --- a/auth0/src/main/java/com/auth0/android/result/UserProfile.kt +++ b/auth0/src/main/java/com/auth0/android/result/UserProfile.kt @@ -5,7 +5,7 @@ import java.util.* /** * Class that holds the information of a user's profile in Auth0. - * Used both in [com.auth0.android.management.UsersAPIClient] and [com.auth0.android.authentication.AuthenticationAPIClient]. + * Used in [com.auth0.android.authentication.AuthenticationAPIClient]. */ public class UserProfile( private val id: String?, diff --git a/auth0/src/test/java/com/auth0/android/management/ManagementExceptionTest.kt b/auth0/src/test/java/com/auth0/android/management/ManagementExceptionTest.kt deleted file mode 100644 index 9a97d132..00000000 --- a/auth0/src/test/java/com/auth0/android/management/ManagementExceptionTest.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.auth0.android.management - -import com.auth0.android.NetworkErrorException -import org.hamcrest.CoreMatchers -import org.hamcrest.MatcherAssert -import org.junit.Test -import java.io.IOException - -public class ManagementExceptionTest { - @Test - public fun shouldNotHaveNetworkError() { - val ex = ManagementException("Something else happened") - MatcherAssert.assertThat(ex.isNetworkError, CoreMatchers.`is`(false)) - } - - @Test - public fun shouldHaveNetworkError() { - val ex = ManagementException( - "Request has definitely failed", NetworkErrorException( - IOException() - ) - ) - MatcherAssert.assertThat(ex.isNetworkError, CoreMatchers.`is`(true)) - } -} \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/management/UsersAPIClientTest.kt b/auth0/src/test/java/com/auth0/android/management/UsersAPIClientTest.kt deleted file mode 100755 index 7c1d370e..00000000 --- a/auth0/src/test/java/com/auth0/android/management/UsersAPIClientTest.kt +++ /dev/null @@ -1,466 +0,0 @@ -package com.auth0.android.management - -import android.content.Context -import android.content.res.Resources -import com.auth0.android.Auth0 -import com.auth0.android.authentication.AuthenticationAPIClient -import com.auth0.android.authentication.AuthenticationAPIClientTest -import com.auth0.android.request.HttpMethod.GET -import com.auth0.android.request.NetworkingClient -import com.auth0.android.request.RequestOptions -import com.auth0.android.request.ServerResponse -import com.auth0.android.request.internal.RequestFactory -import com.auth0.android.request.internal.ThreadSwitcherShadow -import com.auth0.android.result.UserIdentity -import com.auth0.android.result.UserProfile -import com.auth0.android.util.* -import com.auth0.android.util.SSLTestUtils.testClient -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken -import org.mockito.kotlin.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import okhttp3.mockwebserver.RecordedRequest -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers -import org.hamcrest.Matchers.instanceOf -import org.hamcrest.Matchers.notNullValue -import org.hamcrest.collection.IsMapContaining -import org.hamcrest.collection.IsMapWithSize -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -import org.robolectric.shadows.ShadowLooper -import java.io.ByteArrayInputStream -import java.io.InputStream -import java.util.* - - -@RunWith(RobolectricTestRunner::class) -@Config(shadows = [ThreadSwitcherShadow::class]) -public class UsersAPIClientTest { - private lateinit var client: UsersAPIClient - private lateinit var gson: Gson - private lateinit var mockAPI: UsersAPIMockServer - - @Before - public fun setUp() { - mockAPI = UsersAPIMockServer() - val domain = mockAPI.domain - val auth0 = Auth0.getInstance(CLIENT_ID, domain, domain) - auth0.networkingClient = testClient - client = UsersAPIClient(auth0, TOKEN_PRIMARY) - gson = GsonBuilder().serializeNulls().create() - } - - @After - public fun tearDown() { - mockAPI.shutdown() - } - - @Test - public fun shouldUseCustomNetworkingClient() { - val account = Auth0.getInstance("client-id", "https://tenant.auth0.com/") - val jsonResponse = """{"id": "undercover"}""" - val inputStream: InputStream = ByteArrayInputStream(jsonResponse.toByteArray()) - val response = ServerResponse(200, inputStream, emptyMap()) - val networkingClient: NetworkingClient = mock() - - whenever(networkingClient.load(any(), any())).thenReturn(response) - account.networkingClient = networkingClient - val client = UsersAPIClient(account, "token.token") - val request = client.getProfile("undercover") - - request.execute() - ShadowLooper.idleMainLooper() - argumentCaptor().apply { - verify(networkingClient).load(eq("https://tenant.auth0.com/api/v2/users/undercover"), capture()) - assertThat(firstValue, Matchers.`is`(notNullValue())) - assertThat(firstValue.method, Matchers.`is`(instanceOf(GET::class.java))) - assertThat(firstValue.parameters, IsMapWithSize.anEmptyMap()) - assertThat(firstValue.headers, IsMapContaining.hasKey("Auth0-Client")) - } - } - - @Test - public fun shouldSetAuth0UserAgentIfPresent() { - val auth0UserAgent: Auth0UserAgent = mock() - val factory: RequestFactory = mock() - val account = Auth0.getInstance(CLIENT_ID, DOMAIN) - - whenever(auth0UserAgent.value).thenReturn("the-user-agent-data") - account.auth0UserAgent = auth0UserAgent - UsersAPIClient(account, factory, gson) - - verify(factory).setAuth0ClientInfo("the-user-agent-data") - } - - @Test - public fun shouldCreateClientWithAccountInfo() { - val client = UsersAPIClient(Auth0.getInstance(CLIENT_ID, DOMAIN), TOKEN_PRIMARY) - assertThat(client, Matchers.`is`(notNullValue())) - assertThat(client.clientId, Matchers.equalTo(CLIENT_ID)) - assertThat(client.baseURL, Matchers.equalTo("https://$DOMAIN/")) - } - - @Test - public fun shouldCreateClientWithContextInfo() { - val context: Context = mock() - val resources: Resources = mock() - - whenever(context.packageName).thenReturn("com.myapp") - whenever(context.resources).thenReturn(resources) - whenever(resources.getIdentifier( - eq("com_auth0_client_id"), - eq("string"), - eq("com.myapp") - )).thenReturn(222) - whenever(resources.getIdentifier( - eq("com_auth0_domain"), - eq("string"), - eq("com.myapp") - )).thenReturn(333) - whenever(context.getString(eq(222))).thenReturn(CLIENT_ID) - whenever(context.getString(eq(333))).thenReturn(DOMAIN) - - val client = UsersAPIClient(Auth0.getInstance(context), TOKEN_PRIMARY) - assertThat(client, Matchers.`is`(notNullValue())) - assertThat(client.clientId, Matchers.`is`(CLIENT_ID)) - assertThat(client.baseURL, Matchers.equalTo("https://$DOMAIN/")) - } - - @Test - public fun shouldLinkAccount() { - mockAPI.willReturnSuccessfulLink() - val callback = MockManagementCallback>() - client.link(USER_ID_PRIMARY, TOKEN_SECONDARY) - .start(callback) - ShadowLooper.idleMainLooper() - - val request = mockAPI.takeRequest() - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_POST)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasEntry(KEY_LINK_WITH, TOKEN_SECONDARY)) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(callback, ManagementCallbackMatcher.hasPayloadOfType(typeToken)) - assertThat(callback.payload.size, Matchers.`is`(2)) - } - - @Test - public fun shouldLinkAccountSync() { - mockAPI.willReturnSuccessfulLink() - val result = client.link(USER_ID_PRIMARY, TOKEN_SECONDARY) - .execute() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_POST)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasEntry(KEY_LINK_WITH, TOKEN_SECONDARY)) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(result, TypeTokenMatcher.isA(typeToken)) - assertThat(result.size, Matchers.`is`(2)) - } - - @Test - @ExperimentalCoroutinesApi - public fun shouldAwaitLinkAccount(): Unit = runTest { - mockAPI.willReturnSuccessfulLink() - val result = client.link(USER_ID_PRIMARY, TOKEN_SECONDARY) - .await() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_POST)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasEntry(KEY_LINK_WITH, TOKEN_SECONDARY)) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(result, TypeTokenMatcher.isA(typeToken)) - assertThat(result.size, Matchers.`is`(2)) - } - - - @Test - public fun shouldUnlinkAccount() { - mockAPI.willReturnSuccessfulUnlink() - val callback = MockManagementCallback>() - client.unlink(USER_ID_PRIMARY, USER_ID_SECONDARY, PROVIDER) - .start(callback) - ShadowLooper.idleMainLooper() - - val request = mockAPI.takeRequest() - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities/$PROVIDER/$USER_ID_SECONDARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_DELETE)) - - val body = bodyFromRequest(request) - assertThat(body, IsMapWithSize.anEmptyMap()) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(callback, ManagementCallbackMatcher.hasPayloadOfType(typeToken)) - assertThat(callback.payload.size, Matchers.`is`(1)) - } - - @Test - public fun shouldUnlinkAccountSync() { - mockAPI.willReturnSuccessfulUnlink() - val result = client.unlink(USER_ID_PRIMARY, USER_ID_SECONDARY, PROVIDER) - .execute() - - val request = mockAPI.takeRequest() - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities/$PROVIDER/$USER_ID_SECONDARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_DELETE)) - - val body = bodyFromRequest(request) - assertThat(body, IsMapWithSize.anEmptyMap()) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(result, TypeTokenMatcher.isA(typeToken)) - assertThat(result.size, Matchers.`is`(1)) - } - - @Test - @ExperimentalCoroutinesApi - public fun shouldAwaitUnlinkAccount(): Unit = runTest { - mockAPI.willReturnSuccessfulUnlink() - val result = client.unlink(USER_ID_PRIMARY, USER_ID_SECONDARY, PROVIDER) - .await() - - val request = mockAPI.takeRequest() - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY/identities/$PROVIDER/$USER_ID_SECONDARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_DELETE)) - - val body = bodyFromRequest(request) - assertThat(body, IsMapWithSize.anEmptyMap()) - - val typeToken: TypeToken> = object : TypeToken>() {} - assertThat(result, TypeTokenMatcher.isA(typeToken)) - assertThat(result.size, Matchers.`is`(1)) - } - - @Test - public fun shouldUpdateUserMetadata() { - mockAPI.willReturnUserProfile() - val metadata: MutableMap = HashMap() - metadata["boolValue"] = true - metadata["name"] = "my_name" - metadata["list"] = listOf("my", "name", "is") - val callback = MockManagementCallback() - client.updateMetadata(USER_ID_PRIMARY, metadata) - .start(callback) - ShadowLooper.idleMainLooper() - - val request = mockAPI.takeRequest() - assertThat(request.path, Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY")) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_PATCH)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasKey(KEY_USER_METADATA)) - assertThat(body[KEY_USER_METADATA], Matchers.`is`(Matchers.equalTo(metadata))) - assertThat( - callback, - ManagementCallbackMatcher.hasPayloadOfType(UserProfile::class.java) - ) - } - - @Test - public fun shouldUpdateUserMetadataSync() { - mockAPI.willReturnUserProfile() - val metadata: MutableMap = HashMap() - metadata["boolValue"] = true - metadata["name"] = "my_name" - metadata["list"] = listOf("my", "name", "is") - - val result = client.updateMetadata(USER_ID_PRIMARY, metadata) - .execute() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_PATCH)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasKey(KEY_USER_METADATA)) - assertThat(body[KEY_USER_METADATA], Matchers.`is`(Matchers.equalTo(metadata))) - assertThat(result, Matchers.isA(UserProfile::class.java)) - } - - @Test - @ExperimentalCoroutinesApi - public fun shouldAwaitUpdateUserMetadata(): Unit = runTest { - mockAPI.willReturnUserProfile() - val metadata: MutableMap = HashMap() - metadata["boolValue"] = true - metadata["name"] = "my_name" - metadata["list"] = listOf("my", "name", "is") - - val result = client.updateMetadata(USER_ID_PRIMARY, metadata) - .await() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat(request.method, Matchers.equalTo(METHOD_PATCH)) - - val body = bodyFromRequest(request) - assertThat(body, Matchers.hasKey(KEY_USER_METADATA)) - assertThat(body[KEY_USER_METADATA], Matchers.`is`(Matchers.equalTo(metadata))) - assertThat(result, Matchers.isA(UserProfile::class.java)) - } - - @Test - public fun shouldGetUserProfileSync() { - mockAPI.willReturnUserProfile() - val result = client.getProfile(USER_ID_PRIMARY) - .execute() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat( - request.method, - Matchers.equalTo(METHOD_GET) - ) - assertThat(result, Matchers.isA(UserProfile::class.java)) - } - - @Test - @ExperimentalCoroutinesApi - public fun shouldAwaitGetUserProfile(): Unit = runTest { - mockAPI.willReturnUserProfile() - val result = client.getProfile(USER_ID_PRIMARY) - .await() - val request = mockAPI.takeRequest() - - assertThat( - request.path, - Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY") - ) - assertThat( - request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY) - ) - assertThat( - request.method, - Matchers.equalTo(METHOD_GET) - ) - assertThat(result, Matchers.isA(UserProfile::class.java)) - } - - @Test - public fun shouldGetUserProfile() { - mockAPI.willReturnUserProfile() - val callback = MockManagementCallback() - client.getProfile(USER_ID_PRIMARY) - .start(callback) - ShadowLooper.idleMainLooper() - - val request = mockAPI.takeRequest() - assertThat(request.path, Matchers.equalTo("/api/v2/users/$USER_ID_PRIMARY")) - assertThat(request.getHeader(HEADER_AUTHORIZATION), - Matchers.equalTo(BEARER + TOKEN_PRIMARY)) - assertThat(request.method, Matchers.equalTo(METHOD_GET)) - assertThat( - callback, ManagementCallbackMatcher.hasPayloadOfType( - UserProfile::class.java - ) - ) - } - - private inline fun bodyFromRequest(request: RecordedRequest): Map { - val mapType = object : TypeToken>() {}.type - return gson.fromJson(request.body.readUtf8(), mapType) - } - - private companion object { - private const val CLIENT_ID = "CLIENTID" - private const val DOMAIN = "samples.auth0.com" - private const val USER_ID_PRIMARY = "primaryUserId" - private const val USER_ID_SECONDARY = "secondaryUserId" - private const val TOKEN_PRIMARY = "primaryToken" - private const val TOKEN_SECONDARY = "secondaryToken" - private const val PROVIDER = "provider" - private const val HEADER_AUTHORIZATION = "Authorization" - private const val BEARER = "Bearer " - private const val METHOD_POST = "POST" - private const val METHOD_DELETE = "DELETE" - private const val METHOD_PATCH = "PATCH" - private const val METHOD_GET = "GET" - private const val KEY_LINK_WITH = "link_with" - private const val KEY_USER_METADATA = "user_metadata" - } -} \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/util/ManagementCallbackMatcher.java b/auth0/src/test/java/com/auth0/android/util/ManagementCallbackMatcher.java deleted file mode 100755 index 2f5bf4c9..00000000 --- a/auth0/src/test/java/com/auth0/android/util/ManagementCallbackMatcher.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.auth0.android.util; - -import com.auth0.android.callback.ManagementCallback; -import com.auth0.android.management.ManagementException; -import com.google.gson.reflect.TypeToken; -import com.jayway.awaitility.core.ConditionTimeoutException; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; - -import static com.jayway.awaitility.Awaitility.await; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isA; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; - -public class ManagementCallbackMatcher extends BaseMatcher> { - private final Matcher payloadMatcher; - private final Matcher errorMatcher; - - public ManagementCallbackMatcher(Matcher payloadMatcher, Matcher errorMatcher) { - this.payloadMatcher = payloadMatcher; - this.errorMatcher = errorMatcher; - } - - @Override - @SuppressWarnings("unchecked") - public boolean matches(Object item) { - MockManagementCallback callback = (MockManagementCallback) item; - try { - await().until(callback.payload(), payloadMatcher); - await().until(callback.error(), errorMatcher); - return true; - } catch (ConditionTimeoutException e) { - return false; - } - } - - @Override - public void describeTo(Description description) { - description - .appendText("successful method be called"); - } - - public static Matcher> hasPayloadOfType(Class tClazz) { - return new ManagementCallbackMatcher<>(isA(tClazz), is(nullValue(ManagementException.class))); - } - - public static Matcher> hasPayloadOfType(TypeToken typeToken) { - return new ManagementCallbackMatcher<>(TypeTokenMatcher.isA(typeToken), is(nullValue(ManagementException.class))); - } - - public static Matcher> hasPayload(T payload) { - return new ManagementCallbackMatcher<>(equalTo(payload), is(nullValue(ManagementException.class))); - } - - public static Matcher> hasNoPayloadOfType(Class tClazz) { - return new ManagementCallbackMatcher<>(is(nullValue(tClazz)), is(notNullValue(ManagementException.class))); - } - - public static Matcher> hasNoPayloadOfType(TypeToken typeToken) { - return new ManagementCallbackMatcher<>(TypeTokenMatcher.isA(typeToken), is(nullValue(ManagementException.class))); - } - - public static Matcher> hasNoError() { - return new ManagementCallbackMatcher<>(is(notNullValue(Void.class)), is(nullValue(ManagementException.class))); - } - - public static Matcher> hasError() { - return new ManagementCallbackMatcher<>(is(nullValue(Void.class)), is(notNullValue(ManagementException.class))); - } -} diff --git a/auth0/src/test/java/com/auth0/android/util/MockManagementCallback.java b/auth0/src/test/java/com/auth0/android/util/MockManagementCallback.java deleted file mode 100755 index 74002f3d..00000000 --- a/auth0/src/test/java/com/auth0/android/util/MockManagementCallback.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.auth0.android.util; - -import androidx.annotation.NonNull; - -import com.auth0.android.callback.ManagementCallback; -import com.auth0.android.management.ManagementException; - -import java.util.concurrent.Callable; - -public class MockManagementCallback implements ManagementCallback { - - private ManagementException error; - private T payload; - - @Override - public void onFailure(@NonNull ManagementException error) { - this.error = error; - } - - @Override - public void onSuccess(@NonNull T result) { - this.payload = result; - } - - public Callable error() { - return () -> error; - } - - public Callable payload() { - return () -> payload; - } - - public ManagementException getError() { - return error; - } - - public T getPayload() { - return payload; - } -} diff --git a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt index c4e54212..31084315 100644 --- a/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt +++ b/sample/src/main/java/com/auth0/sample/DatabaseLoginFragment.kt @@ -27,8 +27,6 @@ import com.auth0.android.authentication.storage.LocalAuthenticationOptions import com.auth0.android.authentication.storage.SecureCredentialsManager import com.auth0.android.authentication.storage.SharedPreferencesStorage import com.auth0.android.callback.Callback -import com.auth0.android.management.ManagementException -import com.auth0.android.management.UsersAPIClient import com.auth0.android.provider.WebAuthProvider import com.auth0.android.request.DefaultClient import com.auth0.android.request.PublicKeyCredentials @@ -36,7 +34,6 @@ import com.auth0.android.request.UserData import com.auth0.android.result.Credentials import com.auth0.android.result.PasskeyChallenge import com.auth0.android.result.PasskeyRegistrationChallenge -import com.auth0.android.result.UserProfile import com.auth0.sample.databinding.FragmentDatabaseLoginBinding import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson @@ -50,7 +47,7 @@ import java.util.concurrent.Executors */ class DatabaseLoginFragment : Fragment() { - private val scope = "openid profile email read:current_user update:current_user_metadata" + private val scope = "openid profile email offline_access" private val account: Auth0 by lazy { // -- REPLACE this credentials with your own Auth0 app credentials! @@ -104,7 +101,7 @@ class DatabaseLoginFragment : Fragment() { .setDeviceCredentialFallback(true) .build() - private val callback = object: Callback { + private val callback = object : Callback { override fun onSuccess(result: Credentials) { credentialsManager.saveCredentials(result) Snackbar.make( @@ -187,22 +184,6 @@ class DatabaseLoginFragment : Fragment() { getCredsAsync() } } - binding.btGetProfile.setOnClickListener { - getProfile() - } - binding.btGetProfileAsync.setOnClickListener { - launchAsync { - getProfileAsync() - } - } - binding.btUpdateMeta.setOnClickListener { - updateMeta() - } - binding.btUpdateMetaAsync.setOnClickListener { - launchAsync { - updateMetaAsync() - } - } return binding.root } @@ -346,7 +327,8 @@ class DatabaseLoginFragment : Fragment() { } private fun getCreds() { - credentialsManager.getCredentials(null, + credentialsManager.getCredentials( + null, 300, emptyMap(), emptyMap(), @@ -409,104 +391,6 @@ class DatabaseLoginFragment : Fragment() { } } - private fun getProfile() { - credentialsManager.getCredentials(object : - Callback { - override fun onSuccess(result: Credentials) { - val users = UsersAPIClient(account, result.accessToken) - users.getProfile(result.user.getId()!!) - .start(object : Callback { - override fun onFailure(error: ManagementException) { - Snackbar.make( - requireView(), error.getDescription(), Snackbar.LENGTH_LONG - ).show() - } - - override fun onSuccess(result: UserProfile) { - Snackbar.make( - requireView(), - "Got profile for ${result.name}", - Snackbar.LENGTH_LONG - ).show() - } - }) - } - - override fun onFailure(error: CredentialsManagerException) { - Snackbar.make(requireView(), "${error.message}", Snackbar.LENGTH_LONG).show() - } - }) - } - - private suspend fun getProfileAsync() { - try { - val credentials = credentialsManager.awaitCredentials() - val users = UsersAPIClient(account, credentials.accessToken) - val user = users.getProfile(credentials.user.getId()!!).await() - Snackbar.make( - requireView(), "Got profile for ${user.name}", Snackbar.LENGTH_LONG - ).show() - } catch (error: CredentialsManagerException) { - Snackbar.make(requireView(), "${error.message}", Snackbar.LENGTH_LONG).show() - } catch (error: ManagementException) { - Snackbar.make(requireView(), error.getDescription(), Snackbar.LENGTH_LONG).show() - } - } - - private fun updateMeta() { - val metadata = mapOf( - "random" to (0..100).random(), - ) - - credentialsManager.getCredentials(object : - Callback { - override fun onSuccess(result: Credentials) { - val users = UsersAPIClient(account, result.accessToken) - users.updateMetadata(result.user.getId()!!, metadata) - .start(object : Callback { - override fun onFailure(error: ManagementException) { - Snackbar.make( - requireView(), error.getDescription(), Snackbar.LENGTH_LONG - ).show() - } - - override fun onSuccess(result: UserProfile) { - Snackbar.make( - requireView(), - "Updated metadata for ${result.name} to ${result.getUserMetadata()}", - Snackbar.LENGTH_LONG - ).show() - } - }) - } - - override fun onFailure(error: CredentialsManagerException) { - Snackbar.make(requireView(), "${error.message}", Snackbar.LENGTH_LONG).show() - } - }) - } - - private suspend fun updateMetaAsync() { - val metadata = mapOf( - "random" to (0..100).random(), - ) - - try { - val credentials = credentialsManager.awaitCredentials() - val users = UsersAPIClient(account, credentials.accessToken) - val user = users.updateMetadata(credentials.user.getId()!!, metadata).await() - Snackbar.make( - requireView(), - "Updated metadata for ${user.name} to ${user.getUserMetadata()}", - Snackbar.LENGTH_LONG - ).show() - } catch (error: CredentialsManagerException) { - Snackbar.make(requireView(), "${error.message}", Snackbar.LENGTH_LONG).show() - } catch (error: ManagementException) { - Snackbar.make(requireView(), error.getDescription(), Snackbar.LENGTH_LONG).show() - } - } - private fun launchAsync(runnable: suspend () -> Unit) { //Use a better scope like lifecycleScope or viewModelScope GlobalScope.launch(Dispatchers.Main) { @@ -529,7 +413,8 @@ class DatabaseLoginFragment : Fragment() { ) var response: CreatePublicKeyCredentialResponse? - credentialManager.createCredentialAsync(requireContext(), + credentialManager.createCredentialAsync( + requireContext(), request, CancellationSignal(), Executors.newSingleThreadExecutor(), @@ -596,7 +481,8 @@ class DatabaseLoginFragment : Fragment() { listOf(request) ) - credentialManager.getCredentialAsync(requireContext(), + credentialManager.getCredentialAsync( + requireContext(), getCredRequest, CancellationSignal(), Executors.newSingleThreadExecutor(), diff --git a/sample/src/main/res/layout/fragment_database_login.xml b/sample/src/main/res/layout/fragment_database_login.xml index 5d3731b2..f3cc258e 100644 --- a/sample/src/main/res/layout/fragment_database_login.xml +++ b/sample/src/main/res/layout/fragment_database_login.xml @@ -146,17 +146,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btWebLogoutAsync" /> - -