Skip to content

Commit d1fea1f

Browse files
committed
implement OOB token request handling
Added oobResponseAsState extension to process out-of-band authentication API responses. Updated error handling for OOB and token responses, supporting OAuth2 and API errors. Introduced polling context for OOB authentication flow.
1 parent 52d7fd3 commit d1fea1f

18 files changed

+1240
-81
lines changed

okta-direct-auth/src/androidHostTest/kotlin/com/okta/directauth/DirectAuthenticationFlowImplTest.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package com.okta.directauth
22

33
import com.okta.directauth.http.KtorHttpExecutor
4+
import com.okta.directauth.model.Continuation
45
import com.okta.directauth.model.PrimaryFactor
56
import com.okta.directauth.model.DirectAuthenticationError
67
import com.okta.directauth.model.DirectAuthenticationState
8+
import com.okta.directauth.model.OobChannel
79
import io.ktor.client.HttpClient
810
import kotlinx.coroutines.test.runTest
911
import kotlinx.serialization.SerializationException
1012
import org.hamcrest.CoreMatchers.equalTo
1113
import org.hamcrest.CoreMatchers.instanceOf
1214
import org.hamcrest.MatcherAssert.assertThat
1315
import org.junit.Test
16+
import kotlin.jvm.java
1417

1518
class DirectAuthenticationFlowImplTest {
1619

@@ -67,9 +70,27 @@ class DirectAuthenticationFlowImplTest {
6770
}.getOrThrow()
6871

6972
val state = flow.start("test_user", PrimaryFactor.Password("test_password"))
70-
73+
7174
assertThat(state, instanceOf(DirectAuthenticationError.InternalError::class.java))
7275
assertThat((state as DirectAuthenticationError.InternalError).throwable, instanceOf(SerializationException::class.java))
7376
assertThat(flow.authenticationState.value, equalTo(state))
7477
}
78+
79+
@Test
80+
fun start_withOobFactor_returnsContinuationPollState() = runTest {
81+
val flow = DirectAuthenticationFlowBuilder.create(issuerUrl, clientId, scope) {
82+
apiExecutor = KtorHttpExecutor(HttpClient(oobAuthenticatePushResponseMockEngine))
83+
}.getOrThrow()
84+
85+
val state = flow.start("test_user", PrimaryFactor.Oob(OobChannel.PUSH))
86+
87+
assertThat(state, instanceOf(Continuation.OobPending::class.java))
88+
val oobState = state as Continuation.OobPending
89+
assertThat(oobState.bindingContext.oobCode, equalTo("example_oob_code"))
90+
assertThat(oobState.bindingContext.channel, equalTo(OobChannel.PUSH))
91+
assertThat(oobState.bindingContext.expiresIn, equalTo(120))
92+
assertThat(oobState.bindingContext.interval, equalTo(5))
93+
94+
assertThat(flow.authenticationState.value, equalTo(state))
95+
}
7596
}

okta-direct-auth/src/androidHostTest/kotlin/com/okta/directauth/MockEngineUtil.kt

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,36 @@ import io.ktor.utils.io.ByteReadChannel
99

1010
const val TOKEN_RESPONSE_JSON =
1111
"""{"access_token":"example_access_token","token_type":"Bearer","expires_in":3600,"scope":"openid email profile offline_access","refresh_token":"example_refresh_token","id_token":"example_id_token"}"""
12-
const val AUTHORIZATION_PENDING_JSON =
13-
"""{"error":"authorization_pending","error_description":"The user has not yet approved the push notification."}"""
14-
const val OAUTH2_ERROR_JSON =
15-
"""{"error":"invalid_grant","error_description":"The password was invalid."}"""
16-
const val SERVER_ERROR_JSON =
17-
"""{"errorCode":"E00000","errorSummary":"Internal Server Error"}"""
18-
const val MFA_REQUIRED_JSON =
19-
"""{"error":"mfa_required","error_description":"MFA is required for this transaction.","mfa_token":"example_mfa_token"}"""
20-
const val INVALID_MFA_REQUIRED_JSON =
21-
"""{"error":"mfa_required","error_description":"MFA is required for this transaction."}"""
12+
const val AUTHORIZATION_PENDING_JSON = """{"error":"authorization_pending","error_description":"The user has not yet approved the push notification."}"""
13+
const val OAUTH2_ERROR_JSON = """{"error":"invalid_grant","error_description":"The password was invalid."}"""
14+
const val SERVER_ERROR_JSON = """{"errorCode":"E00000","errorSummary":"Internal Server Error"}"""
15+
const val MFA_REQUIRED_JSON = """{"error":"mfa_required","error_description":"MFA is required for this transaction.","mfa_token":"example_mfa_token"}"""
16+
const val INVALID_MFA_REQUIRED_JSON = """{"error":"mfa_required","error_description":"MFA is required for this transaction."}"""
2217
const val UNKNOWN_JSON_TYPE = """{"unknown_json_type":"bad response"}"""
18+
const val OOB_AUTHENTICATE_PUSH_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"push","binding_method":"none","expires_in":120,"interval":5}"""
19+
const val OOB_AUTHENTICATE_SMS_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"sms","binding_method":"prompt","expires_in":120}"""
20+
const val OOB_AUTHENTICATE_VOICE_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"voice","binding_method":"prompt","expires_in":120}"""
21+
const val OOB_AUTHENTICATE_TRANSFER_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"push","binding_method":"transfer","binding_code":"95","expires_in":120,"interval":5}"""
22+
const val OOB_AUTHENTICATE_TRANSFER_NO_BINDING_CODE_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"push","binding_method":"transfer","expires_in":120,"interval":5}"""
23+
24+
// email is not a unsupported channel
25+
const val OOB_AUTHENTICATE_EMAIL_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"email","binding_method":"prompt","expires_in":120}"""
26+
const val OOB_AUTHENTICATE_INVALID_BINDING_RESPONSE_JSON = """{"oob_code":"example_oob_code","channel":"push","binding_method":"bluetooth","expires_in":120}"""
27+
28+
const val OOB_AUTHENTICATE_OAUTH2_ERROR_JSON = """{"error":"invalid_request","error_description":"abc is not a valid channel hint"}"""
29+
2330

2431
// Malformed JSON with trailing comma
2532
const val MALFORMED_JSON = """{"access_token": "token","token_type": "Bearer",}"""
2633
const val MALFORMED_JSON_ERROR_CODE = """{"errorCode": "error","errorSummary": "error description",}"""
2734
const val MALFORMED_JSON_ERROR = """{"error": "access_denied","error_description": "error description",}"""
2835

36+
val contentType = headersOf("Content-Type", "application/json")
37+
2938
fun createMockEngine(
3039
jsonResponse: String,
3140
httpStatusCode: HttpStatusCode,
32-
headers: Headers = headersOf("Content-Type", "application/json")
41+
headers: Headers = contentType
3342
): MockEngine = MockEngine {
3443
respond(
3544
content = ByteReadChannel(jsonResponse),
@@ -64,6 +73,24 @@ val emptyResponseOkMockEngine = createMockEngine("", HttpStatusCode.OK)
6473

6574
val malformedJsonOkMockEngine = createMockEngine(MALFORMED_JSON, HttpStatusCode.OK)
6675

76+
val malformedJsonClientMockEngine = createMockEngine(MALFORMED_JSON_ERROR_CODE, HttpStatusCode.TooManyRequests)
77+
6778
val malformedJsonErrorMockEngine = createMockEngine(MALFORMED_JSON_ERROR, HttpStatusCode.BadRequest)
6879

6980
val malformedJsonErrorCodeMockEngine = createMockEngine(MALFORMED_JSON_ERROR_CODE, HttpStatusCode.BadRequest)
81+
82+
val oobAuthenticatePushResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_PUSH_RESPONSE_JSON, HttpStatusCode.OK)
83+
84+
val oobAuthenticateSmsResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_SMS_RESPONSE_JSON, HttpStatusCode.OK)
85+
86+
val oobAuthenticateVoiceResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_VOICE_RESPONSE_JSON, HttpStatusCode.OK)
87+
88+
val oobAuthenticateTransferResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_TRANSFER_RESPONSE_JSON, HttpStatusCode.OK)
89+
90+
val oobAuthenticateTransferNoBindingCodeResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_TRANSFER_NO_BINDING_CODE_RESPONSE_JSON, HttpStatusCode.OK)
91+
92+
val oobAuthenticateEmailResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_EMAIL_RESPONSE_JSON, HttpStatusCode.OK)
93+
94+
val oobAuthenticateInvalidBindingResponseMockEngine = createMockEngine(OOB_AUTHENTICATE_INVALID_BINDING_RESPONSE_JSON, HttpStatusCode.OK)
95+
96+
val oobAuthenticateOauth2ErrorMockEngine = createMockEngine(OOB_AUTHENTICATE_OAUTH2_ERROR_JSON, HttpStatusCode.BadRequest)

0 commit comments

Comments
 (0)