From cb539bfc8a9ca1df9a14cbc64bb8d1b4d199eed3 Mon Sep 17 00:00:00 2001 From: Christian Melchior Date: Mon, 20 Dec 2021 09:03:46 +0100 Subject: [PATCH 1/2] Add unit test trying to reproduce the problem --- .../kotlin/io/realm/AppTests.kt | 116 ++++++++++++++++++ .../kotlin/io/realm/admin/ServerAdmin.kt | 3 + 2 files changed, 119 insertions(+) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 438807f06b..48cba14d3f 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -20,8 +20,14 @@ import androidx.test.platform.app.InstrumentationRegistry import io.realm.admin.ServerAdmin import io.realm.entities.SyncStringOnly import io.realm.exceptions.RealmFileException +import io.realm.internal.network.OkHttpNetworkTransport +import io.realm.internal.objectstore.OsJavaNetworkTransport +import io.realm.kotlin.syncSession +import io.realm.log.LogLevel +import io.realm.log.RealmLog import io.realm.mongodb.* import io.realm.mongodb.sync.SyncConfiguration +import io.realm.mongodb.sync.SyncSession import io.realm.mongodb.sync.testSchema import io.realm.rule.BlockingLooperThread import org.bson.codecs.StringCodec @@ -30,6 +36,7 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith import java.io.File +import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicReference import kotlin.test.assertFailsWith @@ -45,6 +52,7 @@ class AppTests { @Before fun setUp() { Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + RealmLog.setLevel(LogLevel.DEBUG) app = TestApp() admin = ServerAdmin(app) } @@ -197,6 +205,114 @@ class AppTests { assertNull(app.currentUser()) } + @Test + fun currentUser_availableIfJustExpired() { + app.close() + Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) + app = TestApp(object: OsJavaNetworkTransport() { + override fun sendRequestAsync(method: String, + url: String, + timeoutMs: Long, + headers: MutableMap, + body: String, + completionPtr: Long) { + val response = executeRequest(method, url, timeoutMs, headers, body) + handleResponse(response, completionPtr) + } + + override fun executeRequest( + method: String, + url: String, + timeoutMs: Long, + headers: MutableMap, + body: String + ): Response { + var result = "" + when { + url.endsWith("/providers/${Credentials.Provider.ANONYMOUS.id}/login") -> { + // This token expires on Sunday 10. May 2020 22:23:28 + result = """ + { + "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ", + "refresh_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA", + "user_id": "5e6964e0afea63254581c1a1", + "device_id": "000000000000000000000000" + } + """.trimIndent() + } + url.endsWith("/auth/profile") -> { + result = """ + { + "user_id": "5e6964e0afea63254581c1a1", + "domain_id": "000000000000000000000000", + "identities": [ + { + "id": "5e68f51ade5ba998bb17500d", + "provider_type": "local-userpass", + "provider_id": "000000000000000000000003", + "provider_data": { + "email": "unique_user@domain.com" + } + } + ], + "data": { + "email": "unique_user@domain.com" + }, + "type": "normal", + "roles": [ + { + "role_name": "GROUP_OWNER", + "group_id": "5e68f51e087b1b33a53f56d5" + } + ] + } + """.trimIndent() + } + url.endsWith("/location") -> { + result = """ + { "deployment_model" : "GLOBAL", + "location": "US-VA", + "hostname": "http://localhost:9090", + "ws_hostname": "ws://localhost:9090" + } + """.trimIndent() + } + else -> { + fail("Unexpected request url: $url") + } + } + val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) + return OkHttpNetworkTransport.Response.httpResponse(200, successHeaders, result) + } + + override fun sendStreamingRequest(request: Request): Response { + throw IllegalAccessError() + } + }) + + val creds = Credentials.anonymous() + val user: User = app.login(creds) + assertEquals(User.State.LOGGED_IN, user.state) // TODO: Should ideally be LOGGED_OUT, but only happens after interaction with server + } + + @Test + fun currentUser_nullWhenSyncAuthenticationFails() = looperThread.runBlocking { + val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") + admin.disableUser(user) + val waiter = CountDownLatch(1) + val syncConfig = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) + .errorHandler { session: SyncSession, error: AppException -> + RealmLog.error(error.toString()) + assertEquals(User.State.LOGGED_OUT, session.user.state) + assertNull(app.currentUser()) + looperThread.testComplete() + } + .build() + val realm = Realm.getInstance(syncConfig) + looperThread.closeAfterTest(realm) + } + @Test fun switchUser_nullThrows() { try { diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt index 6bda27f205..f6a253c6fd 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/admin/ServerAdmin.kt @@ -173,6 +173,9 @@ class ServerAdmin(private val app: App) { val JSON = MediaType.parse("application/json; charset=utf-8") + /** + * Disable an existing user on the server. + */ fun disableUser(user: User) { var request = Request.Builder() .url("$baseUrl/groups/$groupId/apps/$appId/users/${user.id}/disable") From 2f9f83e3fde74cdf2c4af4a39d5e02fdcf34626e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus=20R=C3=B8rbech?= Date: Mon, 20 Dec 2021 18:48:02 +0100 Subject: [PATCH 2/2] Add test case for refresh token error --- .../kotlin/io/realm/AppTests.kt | 32 +++++++++++++++---- .../network/OkHttpNetworkTransport.java | 1 - 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt index 48cba14d3f..ec8780b176 100644 --- a/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt +++ b/realm/realm-library/src/androidTestObjectServer/kotlin/io/realm/AppTests.kt @@ -22,13 +22,12 @@ import io.realm.entities.SyncStringOnly import io.realm.exceptions.RealmFileException import io.realm.internal.network.OkHttpNetworkTransport import io.realm.internal.objectstore.OsJavaNetworkTransport -import io.realm.kotlin.syncSession import io.realm.log.LogLevel import io.realm.log.RealmLog import io.realm.mongodb.* +import io.realm.mongodb.sync.testSchema import io.realm.mongodb.sync.SyncConfiguration import io.realm.mongodb.sync.SyncSession -import io.realm.mongodb.sync.testSchema import io.realm.rule.BlockingLooperThread import org.bson.codecs.StringCodec import org.bson.codecs.configuration.CodecRegistries @@ -36,7 +35,6 @@ import org.junit.* import org.junit.Assert.* import org.junit.runner.RunWith import java.io.File -import java.util.concurrent.CountDownLatch import java.util.concurrent.atomic.AtomicReference import kotlin.test.assertFailsWith @@ -206,7 +204,7 @@ class AppTests { } @Test - fun currentUser_availableIfJustExpired() { + fun currentUser_availableIfJustExpired() = looperThread.runBlocking { app.close() Realm.init(InstrumentationRegistry.getInstrumentation().targetContext) app = TestApp(object: OsJavaNetworkTransport() { @@ -228,6 +226,7 @@ class AppTests { body: String ): Response { var result = "" + var code = 200 when { url.endsWith("/providers/${Credentials.Provider.ANONYMOUS.id}/login") -> { // This token expires on Sunday 10. May 2020 22:23:28 @@ -277,12 +276,16 @@ class AppTests { } """.trimIndent() } + url.endsWith("/session") -> { + code = 401 + result = "fake: refresh token could not be refreshed" + } else -> { fail("Unexpected request url: $url") } } val successHeaders: Map = mapOf(Pair("Content-Type", "application/json")) - return OkHttpNetworkTransport.Response.httpResponse(200, successHeaders, result) + return OkHttpNetworkTransport.Response.httpResponse(code, successHeaders, result) } override fun sendStreamingRequest(request: Request): Response { @@ -292,14 +295,29 @@ class AppTests { val creds = Credentials.anonymous() val user: User = app.login(creds) - assertEquals(User.State.LOGGED_IN, user.state) // TODO: Should ideally be LOGGED_OUT, but only happens after interaction with server + assertNotNull(app.currentUser()) + assertEquals(1, app.allUsers().size) + + val syncConfig = configFactory.createSyncConfigurationBuilder(user) + .testSchema(SyncStringOnly::class.java) + .errorHandler { session: SyncSession, error: AppException -> + assertEquals(ErrorCode.BAD_AUTHENTICATION, error.errorCode) + assertEquals("Unable to refresh the user access token.", error.errorMessage) + assertFalse(session.user.isLoggedIn) + assertEquals(User.State.REMOVED, session.user.state) + assertNull(app.currentUser()) + assertEquals(0, app.allUsers().size) + looperThread.testComplete() + } + .build() + val realm = Realm.getInstance(syncConfig) + looperThread.closeAfterTest(realm) } @Test fun currentUser_nullWhenSyncAuthenticationFails() = looperThread.runBlocking { val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") admin.disableUser(user) - val waiter = CountDownLatch(1) val syncConfig = configFactory.createSyncConfigurationBuilder(user) .testSchema(SyncStringOnly::class.java) .errorHandler { session: SyncSession, error: AppException -> diff --git a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java index 8dd1b8bd19..6a358c5806 100644 --- a/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java +++ b/realm/realm-library/src/objectServer/java/io/realm/internal/network/OkHttpNetworkTransport.java @@ -271,4 +271,3 @@ public boolean isOpen() { } } -