Skip to content

Commit

Permalink
Ziti view model (#192)
Browse files Browse the repository at this point in the history
* simplify MFA auth interaction

* avoid exposing CoroutineScope in ZitiContext interface

* publish ZitiContext load/remove events

* add androidx.lifecycle dependencies

* implement Ziti and ZitiContext ViewModel

* add doc
  • Loading branch information
ekoby authored May 26, 2021
1 parent 2d55371 commit 1072dc9
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package org.openziti.sample.netcat

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.openziti.Ziti
import org.openziti.ZitiAddress
import org.openziti.ZitiContext
import java.lang.System.exit
import java.nio.ByteBuffer
import java.nio.channels.AsynchronousServerSocketChannel
Expand All @@ -43,7 +45,7 @@ object NetcatHost {
val ziti = Ziti.newContext(cfg, charArrayOf())

runBlocking {
ziti.statusUpdates().collect { println(it) }
ziti.statusUpdates().takeWhile { it !is ZitiContext.Status.Active }.collect { println(it) }
}
while(true) {
try {
Expand Down
1 change: 1 addition & 0 deletions samples/ziti-enroller/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation project(path: ':ziti')
implementation deps.kotlin
implementation deps.kotlinCoroutines
implementation deps.kotlinCoroutines8
implementation deps.slf4jSimple


Expand Down
91 changes: 49 additions & 42 deletions samples/ziti-enroller/src/main/kotlin/org/openziti/ZitiEnroller.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import com.github.ajalt.clikt.parameters.options.validate
import com.github.ajalt.clikt.parameters.types.file
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.future.asDeferred
import org.openziti.api.MFAType
import org.openziti.identity.Enroller
import java.io.FileNotFoundException
Expand Down Expand Up @@ -67,56 +65,65 @@ object ZitiEnroller {
val showCodes by option(help = "display recovery codes").flag(default = false)
val newCodes by option(help = "generate new recovery codes").flag(default = false)

override fun run() {
val ztx = Ziti.newContext(idFile, charArrayOf(), object : Ziti.AuthHandler{
override fun getCode(ztx: ZitiContext, mfaType: MFAType, provider: String) =
CompletableFuture.supplyAsync {
print("Enter MFA code for $mfaType/$provider[${ztx.getId()?.name}]: ")
val code = readLine()
code!!
}
})

val j = GlobalScope.launch {
ztx.statusUpdates().collect {
println("status: $it")
when(it) {
ZitiContext.Status.Loading, ZitiContext.Status.Authenticating -> {}
ZitiContext.Status.Active -> {
println("verification success!")
cancel()
}
is ZitiContext.Status.NotAuthorized -> {
cancel("verification failed!", it.ex)
fun readMFACode(type: MFAType, provider: String): Deferred<String> {
println("Enter MFA code $type/$provider: ")
return CompletableFuture.supplyAsync {
var s: String? = null
while(s == null) s = readLine()
s
}.asDeferred()
}

override fun run() = runBlocking {
val ztx = Ziti.newContext(idFile, charArrayOf())

val j = coroutineScope {
launch {
ztx.statusUpdates().collect {
println("status: $it")
when(it) {
ZitiContext.Status.Loading -> {}
is ZitiContext.Status.NeedsAuth -> {
println("identity is enrolled in MFA")
val code = readMFACode(it.type, it.provider).await()
ztx.authenticateMFA(code)
}
ZitiContext.Status.Active -> {
println("verification success!")
cancel()
}
is ZitiContext.Status.NotAuthorized -> {
cancel("verification failed!", it.ex)
}
else -> cancel("unexpected status")
}
else -> cancel("unexpected status")
}
}
}

runBlocking {
j.join()

j.join()

val mfaEnrolled = ztx.isMFAEnrolled()
println("MFA enrolled: $mfaEnrolled")
val mfaEnrolled = ztx.isMFAEnrolled()
println("MFA enrolled: $mfaEnrolled")

if (showCodes || newCodes) {
if (mfaEnrolled) {
print("""enter OTP to ${if (newCodes) "generate" else "show"} recovery codes: """)
val code = readLine()
if (showCodes || newCodes) {
if (mfaEnrolled) {
print("""enter OTP to ${if (newCodes) "generate" else "show"} recovery codes: """)
val code = readLine()
runCatching {
val recCodes = ztx.getMFARecoveryCodes(code!!, newCodes)
for (rc in recCodes) {
println(rc)
}
} else {
println("cannot show recovery codes: not enrolled in MFA")
}.onFailure {
println("invalid code submitted")
}
} else {
println("cannot show recovery codes: not enrolled in MFA")
}

ztx.destroy()
}

ztx.destroy()
}
}

Expand All @@ -125,10 +132,10 @@ object ZitiEnroller {
it.exists()
}

override fun run() {
override fun run() = runBlocking {
val ztx = Ziti.newContext(idFile, charArrayOf())

val j = GlobalScope.launch {
val j = launch {
ztx.statusUpdates().takeWhile { it != ZitiContext.Status.Active }.collectLatest { println(it) }
val mfa = ztx.enrollMFA()
println(mfa)
Expand All @@ -137,7 +144,7 @@ object ZitiEnroller {
val code = readLine()
ztx.verifyMFA(code!!.trim())
}
runBlocking { j.join() }
j.join()

ztx.destroy()
}
Expand Down
10 changes: 8 additions & 2 deletions ziti-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,20 @@ dependencies {

implementation deps.kotlin
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}"
implementation 'androidx.core:core-ktx:1.5.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'

testImplementation deps.jupiterApi
testImplementation deps.jupiter
androidTestImplementation('com.squareup.retrofit2:retrofit:2.9.0')
androidTestImplementation('com.squareup.retrofit2:converter-gson:2.9.0')

androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
}


Expand Down
75 changes: 29 additions & 46 deletions ziti-android/src/main/java/org/openziti/android/Ziti.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,26 +25,22 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import androidx.core.content.FileProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.core.content.FileProvider
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.openziti.Ziti
import org.openziti.ZitiContext
import org.openziti.api.MFAType
import org.openziti.net.dns.DNSResolver
import org.openziti.util.Logged
import org.openziti.util.Version
import org.openziti.util.ZitiLog
import java.net.URI
import java.security.KeyStore
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionStage
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.coroutines.CoroutineContext
Expand All @@ -63,62 +59,46 @@ object Ziti: CoroutineScope, Logged by ZitiLog() {
get() = Dispatchers.IO + supervisor

internal val Impl = org.openziti.Ziti
private var enrollmentClass: Class<out Activity> = EnrollmentActivity::class.java
internal var enrollmentClass: Class<out Activity> = EnrollmentActivity::class.java

const val Ziti = "Ziti"
const val ZitiNotificationChannel = "Ziti"

lateinit var app: Context
lateinit var app: Application

lateinit var zitiPref: SharedPreferences
lateinit var keyStore: KeyStore
var currentActivity: Activity? = null

object MFAHandler: Ziti.AuthHandler {
override fun getCode(
ztx: ZitiContext,
mfaType: MFAType,
provider: String
): CompletionStage<String> {
return CompletableFuture.completedFuture("TODO")
}
}

@JvmStatic
fun getDnsResolver(): DNSResolver = Impl.getDNSResolver()

@JvmStatic
fun init(app: Context, seamless: Boolean = true) {
this.app = app

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
app.getSystemService(NotificationManager::class.java)?.createNotificationChannel(
NotificationChannel(ZitiNotificationChannel, Ziti, NotificationManager.IMPORTANCE_HIGH)
)
}

if (app is Application) {

app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks{
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}

override fun onActivityPaused(activity: Activity) {
if (activity == currentActivity) currentActivity = null
}
fun init(ctx: Context, seamless: Boolean = true) {
this.app = ctx.applicationContext as Application

app.getSystemService(NotificationManager::class.java)?.createNotificationChannel(
NotificationChannel(ZitiNotificationChannel, Ziti, NotificationManager.IMPORTANCE_HIGH)
)

app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks{
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {
currentActivity = activity
}

override fun onActivityStopped(activity: Activity) {
if (activity == currentActivity) currentActivity = null
}
override fun onActivityPaused(activity: Activity) {
if (activity == currentActivity) currentActivity = null
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})
}
override fun onActivityStopped(activity: Activity) {
if (activity == currentActivity) currentActivity = null
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {}
})

val appId = app.packageName
val appVer = app.packageManager.getPackageInfo(appId, 0).versionName
Expand Down Expand Up @@ -273,8 +253,11 @@ object Ziti: CoroutineScope, Logged by ZitiLog() {
enrollmentClass = cls
}

fun identities() = Impl.identityEvents()

@JvmStatic
fun getSocketFactory() = Impl.getSocketFactory()

@JvmStatic
fun getSSLSocketFactory() = Impl.getSSLSocketFactory()
}
Loading

0 comments on commit 1072dc9

Please sign in to comment.