-
Notifications
You must be signed in to change notification settings - Fork 731
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8341 from tomtit/bugfix/issue-7758
Fixes #7758: Fixed JWT token for Jitsi openidtoken-jwt authentication
- Loading branch information
Showing
4 changed files
with
175 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Fixed JWT token for Jitsi openidtoken-jwt authentication |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
vector/src/test/java/im/vector/app/features/call/conference/jwt/JitsiJWTFactoryTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* Copyright (c) 2023 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.app.features.call.conference.jwt | ||
|
||
import com.squareup.moshi.JsonAdapter | ||
import com.squareup.moshi.Moshi | ||
import com.squareup.moshi.Types | ||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertFalse | ||
import org.junit.Assert.assertTrue | ||
import org.junit.Before | ||
import org.junit.Test | ||
import org.matrix.android.sdk.api.session.openid.OpenIdToken | ||
import java.lang.reflect.ParameterizedType | ||
import java.util.Base64 | ||
import kotlin.streams.toList | ||
|
||
class JitsiJWTFactoryTest { | ||
private val base64Decoder = Base64.getUrlDecoder() | ||
private val moshi = Moshi.Builder().build() | ||
private val stringToString = Types.newParameterizedType(Map::class.java, String::class.java, String::class.java) | ||
private val stringToAny = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) | ||
private lateinit var factory: JitsiJWTFactory | ||
|
||
@Before | ||
fun init() { | ||
factory = JitsiJWTFactory() | ||
} | ||
|
||
@Test | ||
fun `token contains 3 encoded parts`() { | ||
val token = createToken() | ||
|
||
val parts = token.split(".") | ||
assertEquals(3, parts.size) | ||
parts.forEach { | ||
assertTrue("Non-empty array", base64Decoder.decode(it).isNotEmpty()) | ||
} | ||
} | ||
|
||
@Test | ||
fun `token contains unique signature`() { | ||
val signatures = listOf("one", "two").stream() | ||
.map { createToken(it) } | ||
.map { it.split(".")[2] } | ||
.map { base64Decoder.decode(it) } | ||
.toList() | ||
|
||
assertEquals(2, signatures.size) | ||
signatures.forEach { | ||
assertEquals(32, it.size) | ||
} | ||
assertFalse("Unique", signatures[0].contentEquals(signatures[1])) | ||
} | ||
|
||
@Test | ||
fun `token header contains algorithm`() { | ||
val token = createToken() | ||
|
||
assertEquals("HS256", parseTokenHeader(token)["alg"]) | ||
} | ||
|
||
@Test | ||
fun `token header contains type`() { | ||
val token = createToken() | ||
|
||
assertEquals("JWT", parseTokenHeader(token)["typ"]) | ||
} | ||
|
||
@Test | ||
fun `token body contains subject`() { | ||
val token = createToken() | ||
|
||
assertEquals("jitsi-server-domain", parseTokenBody(token)["sub"]) | ||
} | ||
|
||
@Test | ||
fun `token body contains issuer`() { | ||
val token = createToken() | ||
|
||
assertEquals("jitsi-server-domain", parseTokenBody(token)["iss"]) | ||
} | ||
|
||
@Test | ||
fun `token body contains audience`() { | ||
val token = createToken() | ||
|
||
assertEquals("https://jitsi-server-domain", parseTokenBody(token)["aud"]) | ||
} | ||
|
||
@Test | ||
fun `token body contains room claim`() { | ||
val token = createToken() | ||
|
||
assertEquals("*", parseTokenBody(token)["room"]) | ||
} | ||
|
||
@Test | ||
fun `token body contains matrix data`() { | ||
val token = createToken() | ||
|
||
assertEquals(mutableMapOf("room_id" to "room-id", "server_name" to "matrix-server-name", "token" to "matrix-token"), parseMatrixData(token)) | ||
} | ||
|
||
@Test | ||
fun `token body contains user data`() { | ||
val token = createToken() | ||
|
||
assertEquals(mutableMapOf("name" to "user-display-name", "avatar" to "user-avatar-url"), parseUserData(token)) | ||
} | ||
|
||
private fun createToken(): String { | ||
return createToken("matrix-token") | ||
} | ||
|
||
private fun createToken(accessToken: String): String { | ||
val openIdToken = OpenIdToken(accessToken, "matrix-token-type", "matrix-server-name", -1) | ||
return factory.create(openIdToken, "jitsi-server-domain", "room-id", "user-avatar-url", "user-display-name") | ||
} | ||
|
||
private fun parseTokenHeader(token: String): Map<String, String> { | ||
return parseTokenPart(token.split(".")[0], stringToString) | ||
} | ||
|
||
private fun parseTokenBody(token: String): Map<String, Any> { | ||
return parseTokenPart(token.split(".")[1], stringToAny) | ||
} | ||
|
||
private fun parseMatrixData(token: String): Map<*, *> { | ||
return (parseTokenBody(token)["context"] as Map<*, *>)["matrix"] as Map<*, *> | ||
} | ||
|
||
private fun parseUserData(token: String): Map<*, *> { | ||
return (parseTokenBody(token)["context"] as Map<*, *>)["user"] as Map<*, *> | ||
} | ||
|
||
private fun <T> parseTokenPart(value: String, type: ParameterizedType): T { | ||
val decoded = String(base64Decoder.decode(value)) | ||
val adapter: JsonAdapter<T> = moshi.adapter(type) | ||
return adapter.fromJson(decoded)!! | ||
} | ||
} |