diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt new file mode 100644 index 000000000..f3340cd87 --- /dev/null +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManager.kt @@ -0,0 +1,53 @@ +package org.piramalswasthya.cho.network.interceptors + +import java.util.concurrent.atomic.AtomicReference + +/** + * Thread-safe token holder for the network interceptors. + * + * The app updates these values from multiple workers and screens, so the + * storage needs to be safe for concurrent reads and writes. + */ +object AuthTokenManager { + + data class TmcCredentials( + val token: String = "", + val jwt: String = "" + ) + + private val tmcCredentials = AtomicReference(TmcCredentials()) + private val abhaToken = AtomicReference("") + private val abhaXToken = AtomicReference("") + private val eSanjeevaniToken = AtomicReference("") + + fun setTmcCredentials(token: String, jwt: String) { + tmcCredentials.set(TmcCredentials(token, jwt)) + } + + fun getTmcCredentials(): TmcCredentials = tmcCredentials.get() + + fun setAbhaToken(token: String?) { + abhaToken.set(token.orEmpty()) + } + + fun getAbhaToken(): String = abhaToken.get() + + fun setAbhaXToken(token: String?) { + abhaXToken.set(token.orEmpty()) + } + + fun getAbhaXToken(): String = abhaXToken.get() + + fun setESanjeevaniToken(token: String) { + eSanjeevaniToken.set(token) + } + + fun getESanjeevaniToken(): String = eSanjeevaniToken.get() + + fun clear() { + tmcCredentials.set(TmcCredentials()) + abhaToken.set("") + abhaXToken.set("") + eSanjeevaniToken.set("") + } +} diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt index c90b3a41b..c4b95d3a9 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenESanjeevaniInterceptor.kt @@ -7,13 +7,12 @@ import timber.log.Timber class TokenESanjeevaniInterceptor : Interceptor{ companion object { - private var TOKEN: String = "" fun setToken(iToken: String) { - TOKEN = iToken + AuthTokenManager.setESanjeevaniToken(iToken) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getESanjeevaniToken() } } @@ -22,10 +21,10 @@ class TokenESanjeevaniInterceptor : Interceptor{ if (request.header("No-Auth") == null) { request = request .newBuilder() - .addHeader("Authorization", "Bearer $TOKEN") + .addHeader("Authorization", "Bearer ${AuthTokenManager.getESanjeevaniToken()}") .build() } Timber.d("Request : $request") return chain.proceed(request) } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt index 8f34e8367..0abef4864 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertAbhaInterceptor.kt @@ -7,22 +7,20 @@ import timber.log.Timber class TokenInsertAbhaInterceptor : Interceptor { companion object { - private var TOKEN: String = "" - private var XToken: String = "" fun setToken(iToken: String?) { - TOKEN = iToken ?: "" + AuthTokenManager.setAbhaToken(iToken) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getAbhaToken() } fun setXToken(xToken: String?) { - XToken = xToken ?: "" + AuthTokenManager.setAbhaXToken(xToken) } fun getXToken(): String { - return XToken + return AuthTokenManager.getAbhaXToken() } } @@ -33,18 +31,19 @@ class TokenInsertAbhaInterceptor : Interceptor { .newBuilder() .addHeader( "Authorization", - "Bearer $TOKEN" + "Bearer ${AuthTokenManager.getAbhaToken()}" ) .build() } val url = request.url.toString() if (url.contains("getCard") || url.contains("getPngCard") || url.contains("abha-card")) { - if (XToken.isNotEmpty()) { + val xToken = AuthTokenManager.getAbhaXToken() + if (xToken.isNotEmpty()) { request = request .newBuilder() .addHeader( "x-token", - "Bearer $XToken" + "Bearer $xToken" ) .build() } else { diff --git a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt index 7e0d60182..c8f3c8928 100644 --- a/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt +++ b/app/src/main/java/org/piramalswasthya/cho/network/interceptors/TokenInsertTmcInterceptor.kt @@ -8,22 +8,26 @@ class TokenInsertTmcInterceptor : Interceptor{ companion object { - private var TOKEN: String = "" + fun setCredentials(token: String, jwt: String) { + AuthTokenManager.setTmcCredentials(token, jwt) + } + fun setToken(iToken: String) { - TOKEN = iToken + val credentials = AuthTokenManager.getTmcCredentials() + AuthTokenManager.setTmcCredentials(iToken, credentials.jwt) } fun getToken(): String { - return TOKEN + return AuthTokenManager.getTmcCredentials().token } - private var JWT: String = "" fun setJwt(iJWT: String) { - JWT = iJWT + val credentials = AuthTokenManager.getTmcCredentials() + AuthTokenManager.setTmcCredentials(credentials.token, iJWT) } fun getJwt(): String { - return JWT + return AuthTokenManager.getTmcCredentials().jwt } } @@ -31,13 +35,14 @@ class TokenInsertTmcInterceptor : Interceptor{ override fun intercept(chain: Interceptor.Chain): Response { var request = chain.request() if (request.header("No-Auth") == null) { + val tmcCredentials = AuthTokenManager.getTmcCredentials() request = request .newBuilder() - .addHeader("Authorization", TOKEN) - .addHeader("Jwttoken" , JWT) + .addHeader("Authorization", tmcCredentials.token) + .addHeader("Jwttoken" , tmcCredentials.jwt) .build() } Timber.d("Request : $request") return chain.proceed(request) } -} \ No newline at end of file +} diff --git a/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt new file mode 100644 index 000000000..633747dd8 --- /dev/null +++ b/app/src/test/java/org/piramalswasthya/cho/network/interceptors/AuthTokenManagerTest.kt @@ -0,0 +1,71 @@ +package org.piramalswasthya.cho.network.interceptors + +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class AuthTokenManagerTest { + + @Before + fun setUp() { + AuthTokenManager.clear() + } + + @After + fun tearDown() { + AuthTokenManager.clear() + } + + @Test + fun storesValuesIndependentlyForEachInterceptor() { + AuthTokenManager.setTmcCredentials("tmc-token", "tmc-jwt") + AuthTokenManager.setAbhaToken("abha-token") + AuthTokenManager.setAbhaXToken("abha-x-token") + AuthTokenManager.setESanjeevaniToken("esanjeevani-token") + + assertEquals("tmc-token", AuthTokenManager.getTmcCredentials().token) + assertEquals("tmc-jwt", AuthTokenManager.getTmcCredentials().jwt) + assertEquals("abha-token", AuthTokenManager.getAbhaToken()) + assertEquals("abha-x-token", AuthTokenManager.getAbhaXToken()) + assertEquals("esanjeevani-token", AuthTokenManager.getESanjeevaniToken()) + } + + @Test + fun concurrentWritesRemainReadable() { + val executor = Executors.newFixedThreadPool(4) + try { + val ready = CountDownLatch(1) + val tasks = (0 until 32).map { index -> + executor.submit { + check(ready.await(5, TimeUnit.SECONDS)) + AuthTokenManager.setTmcCredentials("tmc-$index", "jwt-$index") + AuthTokenManager.setAbhaToken("abha-$index") + AuthTokenManager.setAbhaXToken("x-$index") + AuthTokenManager.setESanjeevaniToken("es-$index") + } + } + + ready.countDown() + tasks.forEach { it.get(5, TimeUnit.SECONDS) } + } finally { + executor.shutdownNow() + executor.awaitTermination(5, TimeUnit.SECONDS) + } + + val tmcCredentials = AuthTokenManager.getTmcCredentials() + assertTrue(tmcCredentials.token.startsWith("tmc-")) + assertTrue(tmcCredentials.jwt.startsWith("jwt-")) + assertEquals( + tmcCredentials.token.substringAfter('-'), + tmcCredentials.jwt.substringAfter('-') + ) + assertTrue(AuthTokenManager.getAbhaToken().startsWith("abha-")) + assertTrue(AuthTokenManager.getAbhaXToken().startsWith("x-")) + assertTrue(AuthTokenManager.getESanjeevaniToken().startsWith("es-")) + } +}