Skip to content

Commit 71460dc

Browse files
committed
Added additional push related logic & support
1 parent 821a0ab commit 71460dc

File tree

7 files changed

+154
-49
lines changed

7 files changed

+154
-49
lines changed

app/src/main/java/com/sap/cdc/bitsnbytes/cdc/IdentityServiceRepository.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,9 @@ class IdentityServiceRepository private constructor(context: Context) {
265265
return authenticationService.tfa().optInForPushAuthentication()
266266
}
267267

268-
//endregion
268+
suspend fun optInForPushAuth(): IAuthResponse {
269+
return authenticationService.authenticate().registerForAuthPushNotifications()
270+
}
269271

270272
//endregion
271273

app/src/main/java/com/sap/cdc/bitsnbytes/ui/view/screens/LoginOptions.kt

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import com.sap.cdc.bitsnbytes.ui.viewmodel.LoginOptionsViewModelPreview
5555
@Composable
5656
fun LoginOptionsView(viewModel: ILoginOptionsViewModel) {
5757
val context = LocalContext.current
58+
5859
var loading by remember { mutableStateOf(false) }
5960
val executor = remember { ContextCompat.getMainExecutor(context) }
6061
var optionsError by remember { mutableStateOf("") }
@@ -77,50 +78,65 @@ fun LoginOptionsView(viewModel: ILoginOptionsViewModel) {
7778
title = "Passwordless Login",
7879
status = "Activated",
7980
actionLabel = "Deactivate",
80-
onClick = { /* Handle deactivation */ },
81+
onClick = {
82+
83+
},
8184
inverse = false
8285
)
8386
SmallVerticalSpacer()
8487
OptionCard(
85-
title = "Push 2-Factor Authentication",
88+
title = "Push Authentication",
8689
status = "",
8790
actionLabel = "Activate",
8891
onClick = {
8992
if (!notificationPermission.hasPermission) {
9093
notificationPermission.launchPermissionRequest()
9194
}
92-
loading = true
93-
viewModel.optInForPushTFA(
94-
success = {
95+
loading = true
96+
viewModel.optOnForPushAuth(success = {
9597
optionsError = ""
9698
loading = false
97-
},
98-
onFailedWith = { error ->
99+
}, onFailedWith = { error ->
99100
optionsError = error?.errorDescription!!
100101
loading = false
101-
}
102-
)
102+
})
103+
}, inverse = false
104+
)
105+
SmallVerticalSpacer()
106+
OptionCard(
107+
title = "Push 2-Factor Authentication",
108+
status = "",
109+
actionLabel = "Activate",
110+
onClick = {
111+
if (!notificationPermission.hasPermission) {
112+
notificationPermission.launchPermissionRequest()
113+
}
114+
115+
loading = true
116+
viewModel.optInForPushTFA(success = {
117+
optionsError = ""
118+
loading = false
119+
}, onFailedWith = { error ->
120+
optionsError = error?.errorDescription!!
121+
loading = false
122+
})
103123
},
104124
inverse = false
105125
)
106126
SmallVerticalSpacer()
107127
OptionCard(
108-
title = "Biometrics",
109-
status = when (viewModel.isBiometricActive()) {
128+
title = "Biometrics", status = when (viewModel.isBiometricActive()) {
110129
false -> "Deactivated"
111130
true -> "Activated"
112-
},
113-
actionLabel = when (viewModel.isBiometricActive()) {
131+
}, actionLabel = when (viewModel.isBiometricActive()) {
114132
false -> "Activate"
115133
true -> "Deactivate"
116-
},
117-
onClick = {
118-
val promptInfo = BiometricPrompt.PromptInfo.Builder()
119-
.setAllowedAuthenticators(BIOMETRIC_STRONG)
120-
.setTitle("Biometric Authentication")
121-
.setSubtitle("Authenticate using your biometric credential")
122-
.setNegativeButtonText("Use another method")
123-
.build()
134+
}, onClick = {
135+
val promptInfo =
136+
BiometricPrompt.PromptInfo.Builder().setAllowedAuthenticators(BIOMETRIC_STRONG)
137+
.setTitle("Biometric Authentication")
138+
.setSubtitle("Authenticate using your biometric credential")
139+
.setNegativeButtonText("Use another method").build()
124140

125141
when (viewModel.isBiometricActive()) {
126142
true -> {
@@ -139,8 +155,7 @@ fun LoginOptionsView(viewModel: ILoginOptionsViewModel) {
139155
)
140156
}
141157
}
142-
},
143-
inverse = !viewModel.isBiometricActive()
158+
}, inverse = !viewModel.isBiometricActive()
144159
)
145160

146161
// Biometrics lock toggle
@@ -153,28 +168,24 @@ fun LoginOptionsView(viewModel: ILoginOptionsViewModel) {
153168
) {
154169
Text(text = "Lock biometrics:")
155170
LargeHorizontalSpacer()
156-
Switch(
157-
checked = viewModel.isBiometricLocked(),
158-
onCheckedChange = { checked ->
159-
when (checked) {
160-
true -> viewModel.biometricLock()
161-
false -> {
162-
val promptInfo = BiometricPrompt.PromptInfo.Builder()
163-
.setAllowedAuthenticators(BIOMETRIC_STRONG)
164-
.setTitle("Biometric Authentication")
165-
.setSubtitle("Authenticate using your biometric credential")
166-
.setNegativeButtonText("Use another method")
167-
.build()
168-
169-
viewModel.biometricUnlock(
170-
activity = context as FragmentActivity,
171-
promptInfo = promptInfo,
172-
executor = executor
173-
)
174-
}
171+
Switch(checked = viewModel.isBiometricLocked(), onCheckedChange = { checked ->
172+
when (checked) {
173+
true -> viewModel.biometricLock()
174+
false -> {
175+
val promptInfo = BiometricPrompt.PromptInfo.Builder()
176+
.setAllowedAuthenticators(BIOMETRIC_STRONG)
177+
.setTitle("Biometric Authentication")
178+
.setSubtitle("Authenticate using your biometric credential")
179+
.setNegativeButtonText("Use another method").build()
180+
181+
viewModel.biometricUnlock(
182+
activity = context as FragmentActivity,
183+
promptInfo = promptInfo,
184+
executor = executor
185+
)
175186
}
176187
}
177-
)
188+
})
178189
}
179190

180191
LargeVerticalSpacer()
@@ -198,11 +209,7 @@ fun LoginOptionsViewPreview() {
198209

199210
@Composable
200211
private fun OptionCard(
201-
title: String,
202-
status: String,
203-
actionLabel: String,
204-
inverse: Boolean,
205-
onClick: () -> Unit
212+
title: String, status: String, actionLabel: String, inverse: Boolean, onClick: () -> Unit
206213
) {
207214
Card(
208215
//elevation = 4.dp,

app/src/main/java/com/sap/cdc/bitsnbytes/ui/viewmodel/LoginOptionsViewModel.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ interface ILoginOptionsViewModel {
5959
//Stub.
6060
}
6161

62+
fun optOnForPushAuth(
63+
success: () -> Unit,
64+
onFailedWith: (CDCError?) -> Unit
65+
) {
66+
// Stub.
67+
}
68+
6269
fun createPasskey(
6370
activity: ComponentActivity,
6471
success: () -> Unit,
@@ -87,6 +94,7 @@ class LoginOptionsViewModelPreview : ILoginOptionsViewModel {
8794
class LoginOptionsViewModel(context: Context) : BaseViewModel(context),
8895
ILoginOptionsViewModel {
8996

97+
9098
/**
9199
* Create instance of the biometric auth (no need to singleton it).
92100
*/
@@ -99,6 +107,8 @@ class LoginOptionsViewModel(context: Context) : BaseViewModel(context),
99107
.sessionSecurityLevel() == SessionSecureLevel.BIOMETRIC
100108
)
101109

110+
//region BIOMETRIC
111+
102112
/**
103113
* Check if biometric session encryption is active.
104114
* This is an application feature only to support the UI design.
@@ -185,6 +195,10 @@ class LoginOptionsViewModel(context: Context) : BaseViewModel(context),
185195
)
186196
}
187197

198+
//endregion
199+
200+
//region PUSH TFA
201+
188202
override fun optInForPushTFA(
189203
success: () -> Unit,
190204
onFailedWith: (CDCError?) -> Unit
@@ -204,6 +218,33 @@ class LoginOptionsViewModel(context: Context) : BaseViewModel(context),
204218
}
205219
}
206220

221+
//endregion
222+
223+
//region PUSH AUTH
224+
225+
override fun optOnForPushAuth(
226+
success: () -> Unit,
227+
onFailedWith: (CDCError?) -> Unit
228+
) {
229+
viewModelScope.launch {
230+
val response = identityService.optInForPushTFA()
231+
when (response.state()) {
232+
AuthState.SUCCESS -> {
233+
// Success.
234+
success()
235+
}
236+
237+
else -> {
238+
onFailedWith(response.toDisplayError())
239+
}
240+
}
241+
}
242+
}
243+
244+
//endregion
245+
246+
//region PASSKEYS
247+
207248
private var passkeysAuthenticationProvider: IPasskeysAuthenticationProvider? = null
208249

209250
override fun createPasskey(
@@ -251,5 +292,7 @@ class LoginOptionsViewModel(context: Context) : BaseViewModel(context),
251292
}
252293
}
253294
}
295+
296+
//endregion
254297
}
255298

library/src/main/java/com/sap/cdc/android/sdk/auth/Auth.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ interface IAuthApis {
199199
suspend fun clearPasskey(
200200
authenticationProvider: IPasskeysAuthenticationProvider,
201201
): IAuthResponse
202+
203+
suspend fun registerForAuthPushNotifications(): IAuthResponse
204+
205+
suspend fun verifyAuthPushNotification(vToken: String): IAuthResponse
202206
}
203207

204208
/**
@@ -291,6 +295,16 @@ internal class AuthApis(
291295
return flow.clearPasskeyCredential()
292296
}
293297

298+
override suspend fun registerForAuthPushNotifications(): IAuthResponse {
299+
val flow = AccountAuthFlow(coreClient, sessionService)
300+
return flow.registerAuthDevice()
301+
}
302+
303+
override suspend fun verifyAuthPushNotification(vToken: String): IAuthResponse {
304+
val flow = AccountAuthFlow(coreClient, sessionService)
305+
return flow.verifyAuthPush(vToken)
306+
}
307+
294308
}
295309

296310
/**

library/src/main/java/com/sap/cdc/android/sdk/auth/AuthEndpoints.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ class AuthEndpoints {
2020

2121
const val EP_ACCOUNTS_ID_TOKEN_EXCHANGE = "accounts.identity.token.exchange"
2222

23+
const val EP_ACCOUNT_AUTH_DEVICE_REGISTER = "accounts.devices.register"
24+
const val EP_ACCOUNT_AUTH_DEVICE_UNREGISTER = "accounts.devices.unregister"
25+
const val EP_ACCOUNT_AUTH_PUSH_VERIFY = "accounts.auth.push.verify"
26+
2327
const val EP_SOCIALIZE_GET_IDS = "socialize.getIDs"
2428
const val EP_SOCIALIZE_LOGIN = "socialize.login"
2529
const val EP_SOCIALIZE_LOGOUT = "socialize.logout"

library/src/main/java/com/sap/cdc/android/sdk/auth/flow/AuthFlow.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.sap.cdc.android.sdk.auth.session.SessionService
1111
import com.sap.cdc.android.sdk.core.CoreClient
1212
import com.sap.cdc.android.sdk.core.api.CDCResponse
1313
import com.sap.cdc.android.sdk.extensions.parseRequiredMissingFieldsForRegistration
14+
import kotlinx.serialization.json.Json
1415

1516
/**
1617
* Created by Tal Mirmelshtein on 10/06/2024
@@ -23,6 +24,12 @@ open class AuthFlow(val coreClient: CoreClient, val sessionService: SessionServi
2324
const val LOG_TAG = "AuthFlow"
2425
}
2526

27+
internal val json: Json = Json {
28+
prettyPrint = true
29+
isLenient = true
30+
ignoreUnknownKeys = true
31+
}
32+
2633
/**
2734
* Override if needed to dispose various objects.
2835
*/

library/src/main/java/com/sap/cdc/android/sdk/auth/flow/AuthFlowAccount.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ import com.sap.cdc.android.sdk.auth.AuthEndpoints.Companion.EP_ACCOUNTS_GET_ACCO
55
import com.sap.cdc.android.sdk.auth.AuthEndpoints.Companion.EP_ACCOUNTS_GET_CONFLICTING_ACCOUNTS
66
import com.sap.cdc.android.sdk.auth.AuthEndpoints.Companion.EP_ACCOUNTS_ID_TOKEN_EXCHANGE
77
import com.sap.cdc.android.sdk.auth.AuthEndpoints.Companion.EP_ACCOUNTS_SET_ACCOUNT_INFO
8+
import com.sap.cdc.android.sdk.auth.AuthEndpoints.Companion.EP_ACCOUNT_AUTH_DEVICE_REGISTER
89
import com.sap.cdc.android.sdk.auth.AuthResponse
910
import com.sap.cdc.android.sdk.auth.AuthenticationApi
11+
import com.sap.cdc.android.sdk.auth.AuthenticationService.Companion.CDC_AUTHENTICATION_SERVICE_SECURE_PREFS
12+
import com.sap.cdc.android.sdk.auth.AuthenticationService.Companion.CDC_DEVICE_INFO
1013
import com.sap.cdc.android.sdk.auth.IAuthResponse
1114
import com.sap.cdc.android.sdk.auth.session.SessionService
1215
import com.sap.cdc.android.sdk.core.CoreClient
16+
import com.sap.cdc.android.sdk.extensions.getEncryptedPreferences
1317

1418
/**
1519
* Created by Tal Mirmelshtein on 10/06/2024
@@ -85,4 +89,28 @@ class AccountAuthFlow(coreClient: CoreClient, sessionService: SessionService) :
8589
return AuthResponse(tokenExchange)
8690
}
8791

92+
suspend fun registerAuthDevice(): IAuthResponse {
93+
// Obtain device info from secure storage.
94+
val esp = coreClient.siteConfig.applicationContext.getEncryptedPreferences(
95+
CDC_AUTHENTICATION_SERVICE_SECURE_PREFS
96+
)
97+
val deviceInfo = esp.getString(CDC_DEVICE_INFO, "") ?: ""
98+
99+
CDCDebuggable.log(LOG_TAG, "registerDevice: with deviceInfo:$deviceInfo")
100+
val registerDevice = AuthenticationApi(coreClient, sessionService).genericSend(
101+
EP_ACCOUNT_AUTH_DEVICE_REGISTER,
102+
mutableMapOf("deviceInfo" to deviceInfo)
103+
)
104+
return AuthResponse(registerDevice)
105+
}
106+
107+
suspend fun verifyAuthPush(vToken: String): IAuthResponse {
108+
CDCDebuggable.log(LOG_TAG, "verifyAuthPush: with vToken:$vToken")
109+
val verifyAuthPush = AuthenticationApi(coreClient, sessionService).genericSend(
110+
EP_ACCOUNT_AUTH_DEVICE_REGISTER,
111+
mutableMapOf("vToken" to vToken)
112+
)
113+
return AuthResponse(verifyAuthPush)
114+
}
115+
88116
}

0 commit comments

Comments
 (0)