Skip to content

Commit d9f382f

Browse files
authored
Feature: Support passkeys (#618)
1 parent 79fe07e commit d9f382f

File tree

41 files changed

+1068
-129
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1068
-129
lines changed

app/src/main/java/de/tum/informatics/www1/artemis/native_app/android/ui/MainActivity.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,11 @@ class MainActivity : AppCompatActivity(),
234234
val lifecycleOwner = LocalLifecycleOwner.current
235235
val updateRepository = koinInject<UpdateRepository>()
236236
val updateResult by updateRepository.updateResultFlow.collectAsState()
237-
val isLoggedIn by accountService.authenticationData
238-
.map { it is AccountService.AuthenticationData.LoggedIn }
239-
.collectAsState(initial = false)
240237
val serverUrl by serverConfigurationService.serverUrl.collectAsState(initial = "")
241238

242239
LaunchedEffect(serverUrl, lifecycleOwner) {
243240
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
244-
while (isLoggedIn) {
241+
while (true) {
245242
updateRepository.triggerUpdateCheck()
246243
delay(60.seconds)
247244
}

app/src/main/res/values/asset_statements.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
<resources>
33
<string name="asset_statements" translatable="false">
44
[{
5-
\"include\": \"https://artemis.ase.in.tum.de/.well-known/assetlinks.json\"
5+
\"include\": \"https://artemis.tum.de/.well-known/assetlinks.json\"
6+
<!-- For testing purposes, we also include the assetlinks from one of the test servers. -->
7+
\"include\": \"https://artemis-test1.artemis.cit.tum.de/.well-known/assetlinks.json\"
68
}]
79
</string>
810
</resources>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package de.tum.informatics.www1.artemis.native_app.core.common
2+
3+
import android.util.Log
4+
5+
6+
enum class Feature(val rawValue: String) {
7+
CourseNotifications("CourseSpecificNotifications");
8+
}
9+
10+
enum class ActiveModuleFeature(val rawValue: String) {
11+
Passkey("passkey"),
12+
}
13+
14+
private const val TAG = "FeatureAvailability"
15+
16+
object FeatureAvailability {
17+
18+
private var availableFeatures: List<String> = emptyList()
19+
private var activeModuleFeatures: List<String> = emptyList()
20+
21+
fun setAvailableFeatures(features: List<String>) {
22+
Log.d(TAG, "Setting available features: $features")
23+
availableFeatures = features
24+
}
25+
26+
fun setActiveModuleFeatures(features: List<String>) {
27+
Log.d(TAG, "Setting active module features: $features")
28+
activeModuleFeatures = features
29+
}
30+
31+
fun isEnabled(feature: Feature): Boolean {
32+
return availableFeatures.contains(feature.rawValue)
33+
}
34+
35+
fun isEnabled(activeModuleFeature: ActiveModuleFeature): Boolean {
36+
return activeModuleFeatures.contains(activeModuleFeature.rawValue)
37+
}
38+
}

core/data/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/core/data/service/Api.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sealed class Api(
2222
data object Public : Api(*Core.path, "public")
2323
data object Courses : Api(*Core.path, "courses")
2424
data object Files : Api(*Core.path, "files")
25+
data object Passkey : Api(*Core.path, "passkey")
2526

2627
/**
2728
* This is a special case, because for the 8.0 API the image path for eg profile picture or
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package de.tum.informatics.www1.artemis.native_app.core.data.service.artemis_context
2+
3+
import de.tum.informatics.www1.artemis.native_app.core.common.artemis_context.ArtemisContext
4+
import de.tum.informatics.www1.artemis.native_app.core.common.artemis_context.ArtemisContextProvider
5+
import de.tum.informatics.www1.artemis.native_app.core.data.service.KtorProvider
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.distinctUntilChanged
8+
9+
open class ServerSelectedBasedServiceImpl(
10+
ktorProvider: KtorProvider,
11+
artemisContextProvider: ArtemisContextProvider,
12+
) : ArtemisContextBasedServiceImpl<ArtemisContext>(
13+
ktorProvider,
14+
artemisContextProvider,
15+
ArtemisContext::class
16+
) {
17+
18+
override val onArtemisContextChanged: Flow<ArtemisContext> = filteredArtemisContextFlow
19+
.distinctUntilChanged { old, new ->
20+
// Consider contexts equal if they have the same data, regardless of type
21+
// This prevents emissions when only the context type changes
22+
old.serverUrl == new.serverUrl
23+
}
24+
25+
}

core/datastore/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/core/datastore/defaults/ArtemisInstances.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,20 @@ object ArtemisInstances {
5454
type = ArtemisInstance.Type.TEST
5555
)
5656

57+
private val TumTs3 = ArtemisInstance(
58+
host = "artemis-test3.artemis.cit.tum.de",
59+
name = R.string.artemis_instance_tum_test_server_3,
60+
type = ArtemisInstance.Type.TEST
61+
)
62+
63+
private val TumTs4 = ArtemisInstance(
64+
host = "artemis-test4.artemis.cit.tum.de",
65+
name = R.string.artemis_instance_tum_test_server_4,
66+
type = ArtemisInstance.Type.TEST
67+
)
68+
5769
val legacyTumInstances = listOf(LegacyTumArtemis1, LegacyTumArtemis2)
58-
private val testInstances = listOf(TumTs0, TumTs1, TumTs2)
70+
private val testInstances = listOf(TumTs0, TumTs1, TumTs2, TumTs3, TumTs4)
5971

6072
val instances: List<ArtemisInstance> =
6173
(if (BuildConfig.DEBUG) {

core/datastore/src/main/res/values/artemis_instances.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
<string name="artemis_instance_tum_test_server_0">TUM test-server 0 </string>
99
<string name="artemis_instance_tum_test_server_1">TUM test-server 1</string>
1010
<string name="artemis_instance_tum_test_server_2">TUM test-server 2</string>
11+
<string name="artemis_instance_tum_test_server_3">TUM test-server 3</string>
12+
<string name="artemis_instance_tum_test_server_4">TUM test-server 4</string>
1113
<string name="artemis_instance_tum_legacy">TUM Artemis (Legacy)</string>
1214
</resources>

core/model/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/core/model/server_config/ProfileInfo.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package de.tum.informatics.www1.artemis.native_app.core.model.server_config
22

3+
import de.tum.informatics.www1.artemis.native_app.core.common.ActiveModuleFeature
34
import kotlinx.serialization.SerialName
45
import kotlinx.serialization.Serializable
56

@@ -24,6 +25,7 @@ data class ProfileInfo(
2425
val externalCredentialProvider: String? = null,
2526
val externalPasswordResetLinkMap: Map<String, String>? = emptyMap(),
2627
val features: List<String> = emptyList(),
28+
val activeModuleFeatures: List<String> = emptyList(),
2729
/**
2830
* Set if the server allows a saml2 based login. If not set, it is also not supported.
2931
*/
@@ -33,6 +35,7 @@ data class ProfileInfo(
3335
* If a login using a username-password combination is not possible.
3436
*/
3537
val isPasswordLoginDisabled: Boolean = saml2 != null && (saml2.passwordLoginDisabled || !saml2.enablePassword)
38+
val isPasskeyLoginEnabled: Boolean = activeModuleFeatures.contains(ActiveModuleFeature.Passkey.rawValue)
3639
val compatibleVersions: CompatibleVersions? = null
3740
}
3841

core/ui/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/core/ui/common/ArtemisSection.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.tum.informatics.www1.artemis.native_app.core.ui.common
22

33
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.ColumnScope
56
import androidx.compose.foundation.layout.padding
67
import androidx.compose.material3.Card
78
import androidx.compose.material3.MaterialTheme
@@ -21,7 +22,7 @@ fun ArtemisSection(
2122
description: String? = null,
2223
testTag: String? = null,
2324
spacing: Dp = 8.dp,
24-
content: @Composable () -> Unit
25+
content: @Composable ColumnScope.() -> Unit
2526
) {
2627
Card(
2728
modifier = modifier,

core/ui/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/core/ui/date/DateFormats.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ enum class DateFormats(val format: DateFormat) {
1212
EditTimestamp(SimpleDateFormat.getDateTimeInstance(
1313
SimpleDateFormat.SHORT,
1414
SimpleDateFormat.SHORT
15-
))
15+
)),
16+
OnlyDate(SimpleDateFormat.getDateInstance(SimpleDateFormat.LONG)),
1617
}

feature/force-update/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/feature/force_update/FeatureAvailability.kt

Lines changed: 0 additions & 19 deletions
This file was deleted.

feature/force-update/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/feature/force_update/repository/UpdateUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package de.tum.informatics.www1.artemis.native_app.feature.force_update.repository
22

3+
import de.tum.informatics.www1.artemis.native_app.core.common.FeatureAvailability
34
import de.tum.informatics.www1.artemis.native_app.core.common.app_version.NormalizedAppVersion
45
import de.tum.informatics.www1.artemis.native_app.core.data.NetworkResponse
5-
import de.tum.informatics.www1.artemis.native_app.feature.force_update.FeatureAvailability
66
import de.tum.informatics.www1.artemis.native_app.feature.force_update.service.UpdateServiceResult
77

88
object UpdateUtil {
@@ -24,6 +24,7 @@ object UpdateUtil {
2424

2525
// 🔁 Set feature list
2626
FeatureAvailability.setAvailableFeatures(data.features)
27+
FeatureAvailability.setActiveModuleFeatures(data.activeModuleFeatures)
2728

2829
// 🔍 Version check
2930
val serverMinVersion = data.minVersion

feature/force-update/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/feature/force_update/service/UpdateService.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ interface UpdateService {
1313
data class UpdateServiceResult(
1414
val minVersion: NormalizedAppVersion,
1515
val recommendedVersion: NormalizedAppVersion,
16-
val features: List<String>
16+
val features: List<String>,
17+
val activeModuleFeatures: List<String> = emptyList()
1718
)
1819

1920
class UpdateServiceImpl(
@@ -30,7 +31,8 @@ class UpdateServiceImpl(
3031
UpdateServiceResult(
3132
minVersion = NormalizedAppVersion.fromNullable(minVersionString),
3233
recommendedVersion = NormalizedAppVersion.fromNullable(recommendedVersionString),
33-
features = profileInfo.features
34+
features = profileInfo.features,
35+
activeModuleFeatures = profileInfo.activeModuleFeatures
3436
)
3537
}
3638
}

feature/force-update/src/test/kotlin/de/tum/informatics/www1/artemis/native_app/feature/force_update/UpdateUtilTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package de.tum.informatics.www1.artemis.native_app.feature.force_update
22

3+
import de.tum.informatics.www1.artemis.native_app.core.common.Feature
4+
import de.tum.informatics.www1.artemis.native_app.core.common.FeatureAvailability
35
import de.tum.informatics.www1.artemis.native_app.core.common.app_version.NormalizedAppVersion
46
import de.tum.informatics.www1.artemis.native_app.core.common.test.UnitTest
57
import de.tum.informatics.www1.artemis.native_app.core.data.NetworkResponse

feature/login/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ dependencies {
2727
testImplementation(project(":feature:login-test"))
2828

2929
implementation(libs.androidx.dataStore.preferences)
30+
31+
// Supporting passkeys
32+
implementation(libs.androidx.credentials)
33+
implementation(libs.androidx.credentials.play.services.auth)
3034
}

feature/login/src/main/kotlin/de/tum/informatics/www1/artemis/native_app/feature/login/AccountUi.kt

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.tum.informatics.www1.artemis.native_app.feature.login
22

33
import androidx.compose.animation.Crossfade
44
import androidx.compose.animation.core.tween
5+
import androidx.compose.foundation.background
56
import androidx.compose.foundation.layout.Arrangement
67
import androidx.compose.foundation.layout.Box
78
import androidx.compose.foundation.layout.Column
@@ -293,41 +294,47 @@ private fun AccountUi(
293294
onLoggedIn: () -> Unit,
294295
onClickSaml2Login: (rememberMe: Boolean) -> Unit
295296
) {
296-
Box(modifier = modifier) {
297-
Column(
298-
modifier = Modifier.imePadding()
299-
) {
300-
Spacer(modifier = Modifier.fillMaxHeight(0.05f))
297+
Column(
298+
modifier = modifier
299+
.imePadding()
300+
.systemBarsPadding()
301+
) {
302+
Spacer(modifier = Modifier.fillMaxHeight(0.05f))
301303

302-
ArtemisHeader(
303-
modifier = Modifier.fillMaxWidth(),
304-
selectedInstance = selectedInstance
305-
)
304+
ArtemisHeader(
305+
modifier = Modifier.fillMaxWidth(),
306+
selectedInstance = selectedInstance
307+
)
306308

307-
Spacer(modifier = Modifier.fillMaxHeight(0.05f))
309+
Spacer(modifier = Modifier.fillMaxHeight(0.05f))
308310

309-
RegisterLoginAccount(
310-
modifier = Modifier
311-
.weight(1f)
312-
.fillMaxWidth(),
313-
serverProfileInfo = serverProfileInfo,
314-
retryLoadServerProfileInfo = retryLoadServerProfileInfo,
315-
onLoggedIn = onLoggedIn,
316-
onNavigateToLoginScreen = onNavigateToLoginScreen,
317-
onNavigateToRegisterScreen = onNavigateToRegisterScreen,
318-
onClickSaml2Login = onClickSaml2Login
319-
)
320-
}
311+
RegisterLoginAccount(
312+
modifier = Modifier
313+
.weight(1f)
314+
.fillMaxWidth(),
315+
serverProfileInfo = serverProfileInfo,
316+
retryLoadServerProfileInfo = retryLoadServerProfileInfo,
317+
onLoggedIn = onLoggedIn,
318+
onNavigateToLoginScreen = onNavigateToLoginScreen,
319+
onNavigateToRegisterScreen = onNavigateToRegisterScreen,
320+
onClickSaml2Login = onClickSaml2Login
321+
)
321322

322323
if (canSwitchInstance) {
323-
TextButton(
324-
modifier = Modifier.align(Alignment.BottomCenter),
325-
onClick = onNavigateToInstanceSelection
324+
Box(
325+
modifier = Modifier
326+
.fillMaxWidth()
327+
.background(color = MaterialTheme.colorScheme.surface),
328+
contentAlignment = Alignment.Center
326329
) {
327-
Text(
328-
text = stringResource(id = R.string.account_change_artemis_instance_label),
329-
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.linkTextColor)
330-
)
330+
TextButton(
331+
onClick = onNavigateToInstanceSelection
332+
) {
333+
Text(
334+
text = stringResource(id = R.string.account_change_artemis_instance_label),
335+
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.linkTextColor)
336+
)
337+
}
331338
}
332339
}
333340
}

0 commit comments

Comments
 (0)