Widgets: handle scalar token

This commit is contained in:
ganfra 2020-05-11 20:00:50 +02:00
parent ce884ac577
commit 01d6b52a60
10 changed files with 263 additions and 5 deletions

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class ScalarTokenEntity(
@PrimaryKey var serverUrl: String = "",
var token: String = ""
) : RealmObject() {
companion object
}

View file

@ -54,6 +54,7 @@ import io.realm.annotations.RealmModule
HomeServerCapabilitiesEntity::class,
RoomMemberSummaryEntity::class,
CurrentStateEventEntity::class,
UserAccountDataEntity::class
UserAccountDataEntity::class,
ScalarTokenEntity::class
])
internal class SessionRealmModule

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.model.ScalarTokenEntity
import im.vector.matrix.android.internal.database.model.ScalarTokenEntityFields
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
internal fun ScalarTokenEntity.Companion.where(realm: Realm, serverUrl: String): RealmQuery<ScalarTokenEntity> {
return realm
.where<ScalarTokenEntity>()
.equalTo(ScalarTokenEntityFields.SERVER_URL, serverUrl)
}

View file

@ -55,6 +55,7 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineEventDecr
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.session.widgets.WidgetManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -99,6 +100,7 @@ internal class DefaultSession @Inject constructor(
private val accountService: Lazy<AccountService>,
private val timelineEventDecryptor: TimelineEventDecryptor,
private val integrationManager: IntegrationManager,
private val widgetManager: WidgetManager,
private val shieldTrustUpdater: ShieldTrustUpdater)
: Session,
RoomService by roomService.get(),
@ -136,6 +138,7 @@ internal class DefaultSession @Inject constructor(
timelineEventDecryptor.start()
shieldTrustUpdater.start()
integrationManager.start()
widgetManager.start()
}
override fun requireBackgroundSync() {
@ -179,6 +182,7 @@ internal class DefaultSession @Inject constructor(
eventBus.unregister(this)
shieldTrustUpdater.stop()
integrationManager.stop()
widgetManager.stop()
}
override fun getSyncStateLive(): LiveData<SyncState> {

View file

@ -53,6 +53,7 @@ import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.session.user.UserModule
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
import im.vector.matrix.android.internal.session.widgets.WidgetModule
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -72,6 +73,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
CryptoModule::class,
PushersModule::class,
OpenIdModule::class,
WidgetModule::class,
AccountDataModule::class,
ProfileModule::class,
SessionAssistedInjectModule::class,

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.widgets
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.internal.session.widgets.token.DefaultGetScalarTokenTask
import im.vector.matrix.android.internal.session.widgets.token.GetScalarTokenTask
import retrofit2.Retrofit
@Module
internal abstract class WidgetModule {
@Module
companion object {
@JvmStatic
@Provides
fun providesWidgetsAPI(retrofit: Retrofit): WidgetsAPI {
return retrofit.create(WidgetsAPI::class.java)
}
}
@Binds
abstract fun bindCreateWidgetTask(task: DefaultCreateWidgetTask): CreateWidgetTask
@Binds
abstract fun bindGetScalarTokenTask(task: DefaultGetScalarTokenTask): GetScalarTokenTask
}

View file

@ -15,21 +15,22 @@
*/
package im.vector.matrix.android.internal.session.widgets
import im.vector.matrix.android.internal.session.openid.RequestOpenIdTokenResponse
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
internal interface WidgetsAPI {
/**
* register to the server
*
* @param requestOpenIdTokenResponse the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
*/
//TODO Require work on RequestOpenIdTokenResponse from Benoit
@POST("register")
//fun register(@Body requestOpenIdTokenResponse: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>
fun register(@Body requestOpenIdTokenResponse: RequestOpenIdTokenResponse, @Query("v") version: String?): Call<RegisterWidgetResponse>
@GET("account")
fun validateToken(@Query("scalar_token") scalarToken: String?, @Query("v") version: String?): Call<Unit>

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.widgets
import dagger.Lazy
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.OkHttpClient
import javax.inject.Inject
@SessionScope
internal class WidgetsAPIProvider @Inject constructor(@Unauthenticated private val okHttpClient: Lazy<OkHttpClient>,
private val retrofitFactory: RetrofitFactory) {
// Map to keep one WidgetAPI instance by serverUrl
private val widgetsAPIs = mutableMapOf<String, WidgetsAPI>()
fun get(serverUrl: String): WidgetsAPI {
return widgetsAPIs.getOrPut(serverUrl) {
retrofitFactory.create(okHttpClient, serverUrl).create(WidgetsAPI::class.java)
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.widgets.token
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.openid.GetOpenIdTokenTask
import im.vector.matrix.android.internal.session.widgets.RegisterWidgetResponse
import im.vector.matrix.android.internal.session.widgets.WidgetsAPI
import im.vector.matrix.android.internal.session.widgets.WidgetsAPIProvider
import im.vector.matrix.android.internal.task.Task
import java.net.HttpURLConnection
import javax.inject.Inject
internal interface GetScalarTokenTask : Task<GetScalarTokenTask.Params, String> {
data class Params(
val serverUrl: String
)
}
private const val WIDGET_API_VERSION = "1.1"
internal class DefaultGetScalarTokenTask @Inject constructor(private val widgetsAPIProvider: WidgetsAPIProvider,
private val scalarTokenStore: ScalarTokenStore,
private val getOpenIdTokenTask: GetOpenIdTokenTask) : GetScalarTokenTask {
override suspend fun execute(params: GetScalarTokenTask.Params): String {
val widgetsAPI = widgetsAPIProvider.get(params.serverUrl)
val scalarToken = scalarTokenStore.getToken(params.serverUrl)
return if (scalarToken == null) {
getNewScalarToken(widgetsAPI, params.serverUrl)
} else {
validateToken(widgetsAPI, params.serverUrl, scalarToken)
}
}
private suspend fun getNewScalarToken(widgetsAPI: WidgetsAPI, serverUrl: String): String {
val openId = getOpenIdTokenTask.execute(Unit)
val registerWidgetResponse = executeRequest<RegisterWidgetResponse>(null) {
apiCall = widgetsAPI.register(openId, WIDGET_API_VERSION)
}
if (registerWidgetResponse.scalarToken == null) {
// Should not happen
throw IllegalStateException("Scalar token is null")
}
scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken)
widgetsAPI.validateToken(registerWidgetResponse.scalarToken, WIDGET_API_VERSION)
return registerWidgetResponse.scalarToken
}
private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {
return try {
widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION)
scalarToken
} catch (failure: Throwable) {
if (failure.isScalarTokenError()) {
scalarTokenStore.clearToken(serverUrl)
getNewScalarToken(widgetsAPI, serverUrl)
} else {
throw failure
}
}
}
private fun Throwable.isScalarTokenError(): Boolean {
return this is Failure.ServerError && this.httpCode == HttpURLConnection.HTTP_FORBIDDEN
}
}

View file

@ -16,5 +16,33 @@
package im.vector.matrix.android.internal.session.widgets.token
internal class ScalarTokenStore {
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.ScalarTokenEntity
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.awaitTransaction
import im.vector.matrix.android.internal.util.fetchCopyMap
import javax.inject.Inject
internal class ScalarTokenStore @Inject constructor(private val monarchy: Monarchy) {
fun getToken(apiUrl: String): String? {
return monarchy.fetchCopyMap({ realm ->
ScalarTokenEntity.where(realm, apiUrl).findFirst()
}, { scalarToken, _ ->
scalarToken.serverUrl
})
}
suspend fun setToken(apiUrl: String, token: String) {
monarchy.awaitTransaction { realm ->
val scalarTokenEntity = ScalarTokenEntity(apiUrl, token)
realm.insertOrUpdate(scalarTokenEntity)
}
}
suspend fun clearToken(apiUrl: String) {
monarchy.awaitTransaction { realm ->
ScalarTokenEntity.where(realm, apiUrl).findFirst()?.deleteFromRealm()
}
}
}