mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 08:22:16 +03:00
initial
This commit is contained in:
parent
79de2dd2e1
commit
99e857d690
62
android.yaml
Normal file
62
android.yaml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
name: Android Alpha 2
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ feature/android-native ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ feature/android-native ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Debug apk
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Yubikit Next
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: Yubico/yubikit-android
|
||||||
|
ref: next
|
||||||
|
path: kit
|
||||||
|
|
||||||
|
- name: set up JDK 11
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
|
||||||
|
- name: Build Yubikit-android
|
||||||
|
run: ./gradlew --stacktrace check test build javadocJar publishToMavenLocal
|
||||||
|
working-directory: ./kit
|
||||||
|
|
||||||
|
- name: Install Flutter
|
||||||
|
uses: subosito/flutter-action@v1
|
||||||
|
with:
|
||||||
|
channel: 'beta'
|
||||||
|
- run: |
|
||||||
|
flutter config
|
||||||
|
flutter --version
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: 'app'
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
flutter test
|
||||||
|
flutter analyze
|
||||||
|
working-directory: ./app
|
||||||
|
|
||||||
|
- name: Build the App
|
||||||
|
run: flutter build apk --debug
|
||||||
|
working-directory: ./app
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
run: |
|
||||||
|
mkdir artifacts
|
||||||
|
mv build/app/outputs/flutter-apk/app-debug.apk artifacts/yubico-authenticator-alpha2.apk
|
||||||
|
working-directory: ./app
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: yubico-authenticator-alpha2.apk.zip
|
||||||
|
path: app/artifacts/*
|
13
android/.gitignore
vendored
Normal file
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
82
android/app/build.gradle
Normal file
82
android/app/build.gradle
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: "org.jetbrains.kotlin.plugin.serialization"
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.yubico.authenticator"
|
||||||
|
minSdkVersion project.minSdkVersion
|
||||||
|
targetSdkVersion project.targetSdkVersion
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled true
|
||||||
|
shrinkResources true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api "com.yubico.yubikit:android:$project.yubiKitVersion"
|
||||||
|
api "com.yubico.yubikit:management:$project.yubiKitVersion"
|
||||||
|
api "com.yubico.yubikit:oath:$project.yubiKitVersion"
|
||||||
|
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||||
|
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.4.1'
|
||||||
|
}
|
7
android/app/src/debug/AndroidManifest.xml
Normal file
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.yubico.authenticator">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
49
android/app/src/main/AndroidManifest.xml
Normal file
49
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.yubico.authenticator">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.usb.host"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.nfc"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_label">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:exported="true"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme" />
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
|
||||||
|
android:resource="@xml/device_filter" />
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
</manifest>
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.yubico.authenticator.api
|
||||||
|
|
||||||
|
import com.yubico.authenticator.MainViewModel
|
||||||
|
import com.yubico.authenticator.OperationContext
|
||||||
|
import com.yubico.authenticator.ParameterException
|
||||||
|
|
||||||
|
class AppApiImpl(private val modelView: MainViewModel) : Pigeon.AppApi {
|
||||||
|
|
||||||
|
override fun setContext(subPageIndex: Long?, result: Pigeon.Result<Void>?) {
|
||||||
|
|
||||||
|
result?.run {
|
||||||
|
if (subPageIndex == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val contextValue = OperationContext.getByValue(subPageIndex)
|
||||||
|
if (contextValue == OperationContext.Invalid) {
|
||||||
|
// returning success is all we can do here
|
||||||
|
result.success(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modelView.setContext(contextValue)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.yubico.authenticator.api
|
||||||
|
|
||||||
|
import com.yubico.authenticator.MainViewModel
|
||||||
|
|
||||||
|
class HDialogApiImpl(private val viewModel : MainViewModel) : Pigeon.HDialogApi {
|
||||||
|
override fun dialogClosed(result: Pigeon.Result<Void>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.onDialogClosed(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
package com.yubico.authenticator.api
|
||||||
|
|
||||||
|
import com.yubico.authenticator.MainViewModel
|
||||||
|
import com.yubico.authenticator.api.Pigeon.OathApi
|
||||||
|
import com.yubico.authenticator.api.Pigeon.Result
|
||||||
|
|
||||||
|
class OathApiImpl(private val viewModel: MainViewModel) : OathApi {
|
||||||
|
|
||||||
|
override fun reset(result: Result<Void>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.resetOathSession(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unlock(
|
||||||
|
password: String?,
|
||||||
|
remember: Boolean?,
|
||||||
|
result: Result<Boolean>?
|
||||||
|
) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.unlockOathSession(password, remember, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setPassword(
|
||||||
|
newPassword: String?,
|
||||||
|
result: Result<Void>?
|
||||||
|
) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.setOathPassword(null, newPassword, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun changePassword(
|
||||||
|
currentPassword: String?,
|
||||||
|
newPassword: String?,
|
||||||
|
result: Result<Void>?
|
||||||
|
) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.setOathPassword(currentPassword, newPassword, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun unsetPassword(currentPassword: String?, result: Result<Void>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.unsetOathPassword(currentPassword, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun forgetPassword(result: Result<Void>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.forgetPassword(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAccount(
|
||||||
|
uri: String?,
|
||||||
|
requireTouch: Boolean?,
|
||||||
|
result: Result<String>?
|
||||||
|
) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.addAccount(uri, requireTouch, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renameAccount(uri: String?, name: String?, result: Result<String>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.renameCredential(uri, name, null, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun renameAccountWithIssuer(
|
||||||
|
uri: String?,
|
||||||
|
name: String?,
|
||||||
|
issuer: String?,
|
||||||
|
result: Result<String>?
|
||||||
|
) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.renameCredential(uri, name, issuer, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deleteAccount(uri: String?, result: Result<Void>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.deleteAccount(uri, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refreshCodes(result: Result<String>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.refreshOathCodes(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calculate(uri: String?, result: Result<String>?) {
|
||||||
|
result?.run {
|
||||||
|
viewModel.calculate(uri, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,673 @@
|
|||||||
|
// Autogenerated from Pigeon (v1.0.19), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
|
||||||
|
package com.yubico.authenticator.api;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel;
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger;
|
||||||
|
import io.flutter.plugin.common.MessageCodec;
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/** Generated class from Pigeon. */
|
||||||
|
@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})
|
||||||
|
public class Pigeon {
|
||||||
|
|
||||||
|
public interface Result<T> {
|
||||||
|
void success(T result);
|
||||||
|
void error(Throwable error);
|
||||||
|
}
|
||||||
|
private static class OathApiCodec extends StandardMessageCodec {
|
||||||
|
public static final OathApiCodec INSTANCE = new OathApiCodec();
|
||||||
|
private OathApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
|
||||||
|
public interface OathApi {
|
||||||
|
void reset(Result<Void> result);
|
||||||
|
void unlock(String password, Boolean remember, Result<Boolean> result);
|
||||||
|
void setPassword(String newPassword, Result<Void> result);
|
||||||
|
void changePassword(String currentPassword, String newPassword, Result<Void> result);
|
||||||
|
void unsetPassword(String currentPassword, Result<Void> result);
|
||||||
|
void forgetPassword(Result<Void> result);
|
||||||
|
void addAccount(String uri, Boolean requireTouch, Result<String> result);
|
||||||
|
void renameAccount(String uri, String name, Result<String> result);
|
||||||
|
void renameAccountWithIssuer(String uri, String name, String issuer, Result<String> result);
|
||||||
|
void deleteAccount(String uri, Result<Void> result);
|
||||||
|
void refreshCodes(Result<String> result);
|
||||||
|
void calculate(String uri, Result<String> result);
|
||||||
|
|
||||||
|
/** The codec used by OathApi. */
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return OathApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets up an instance of `OathApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
static void setup(BinaryMessenger binaryMessenger, OathApi api) {
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.reset", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.reset(resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.unlock", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String passwordArg = (String)args.get(0);
|
||||||
|
if (passwordArg == null) {
|
||||||
|
throw new NullPointerException("passwordArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Boolean rememberArg = (Boolean)args.get(1);
|
||||||
|
if (rememberArg == null) {
|
||||||
|
throw new NullPointerException("rememberArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Boolean> resultCallback = new Result<Boolean>() {
|
||||||
|
public void success(Boolean result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.unlock(passwordArg, rememberArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.setPassword", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String newPasswordArg = (String)args.get(0);
|
||||||
|
if (newPasswordArg == null) {
|
||||||
|
throw new NullPointerException("newPasswordArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.setPassword(newPasswordArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.changePassword", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String currentPasswordArg = (String)args.get(0);
|
||||||
|
if (currentPasswordArg == null) {
|
||||||
|
throw new NullPointerException("currentPasswordArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
String newPasswordArg = (String)args.get(1);
|
||||||
|
if (newPasswordArg == null) {
|
||||||
|
throw new NullPointerException("newPasswordArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.changePassword(currentPasswordArg, newPasswordArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.unsetPassword", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String currentPasswordArg = (String)args.get(0);
|
||||||
|
if (currentPasswordArg == null) {
|
||||||
|
throw new NullPointerException("currentPasswordArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.unsetPassword(currentPasswordArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.forgetPassword", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.forgetPassword(resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.addAccount", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String uriArg = (String)args.get(0);
|
||||||
|
if (uriArg == null) {
|
||||||
|
throw new NullPointerException("uriArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Boolean requireTouchArg = (Boolean)args.get(1);
|
||||||
|
if (requireTouchArg == null) {
|
||||||
|
throw new NullPointerException("requireTouchArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<String> resultCallback = new Result<String>() {
|
||||||
|
public void success(String result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.addAccount(uriArg, requireTouchArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.renameAccount", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String uriArg = (String)args.get(0);
|
||||||
|
if (uriArg == null) {
|
||||||
|
throw new NullPointerException("uriArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
String nameArg = (String)args.get(1);
|
||||||
|
if (nameArg == null) {
|
||||||
|
throw new NullPointerException("nameArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<String> resultCallback = new Result<String>() {
|
||||||
|
public void success(String result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.renameAccount(uriArg, nameArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.renameAccountWithIssuer", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String uriArg = (String)args.get(0);
|
||||||
|
if (uriArg == null) {
|
||||||
|
throw new NullPointerException("uriArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
String nameArg = (String)args.get(1);
|
||||||
|
if (nameArg == null) {
|
||||||
|
throw new NullPointerException("nameArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
String issuerArg = (String)args.get(2);
|
||||||
|
if (issuerArg == null) {
|
||||||
|
throw new NullPointerException("issuerArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<String> resultCallback = new Result<String>() {
|
||||||
|
public void success(String result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.renameAccountWithIssuer(uriArg, nameArg, issuerArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.deleteAccount", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String uriArg = (String)args.get(0);
|
||||||
|
if (uriArg == null) {
|
||||||
|
throw new NullPointerException("uriArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.deleteAccount(uriArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.refreshCodes", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Result<String> resultCallback = new Result<String>() {
|
||||||
|
public void success(String result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.refreshCodes(resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.OathApi.calculate", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
String uriArg = (String)args.get(0);
|
||||||
|
if (uriArg == null) {
|
||||||
|
throw new NullPointerException("uriArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<String> resultCallback = new Result<String>() {
|
||||||
|
public void success(String result) {
|
||||||
|
wrapped.put("result", result);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.calculate(uriArg, resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static class AppApiCodec extends StandardMessageCodec {
|
||||||
|
public static final AppApiCodec INSTANCE = new AppApiCodec();
|
||||||
|
private AppApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
|
||||||
|
public interface AppApi {
|
||||||
|
void setContext(Long subPageIndex, Result<Void> result);
|
||||||
|
|
||||||
|
/** The codec used by AppApi. */
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return AppApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets up an instance of `AppApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
static void setup(BinaryMessenger binaryMessenger, AppApi api) {
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.AppApi.setContext", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
ArrayList<Object> args = (ArrayList<Object>)message;
|
||||||
|
Number subPageIndexArg = (Number)args.get(0);
|
||||||
|
if (subPageIndexArg == null) {
|
||||||
|
throw new NullPointerException("subPageIndexArg unexpectedly null.");
|
||||||
|
}
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.setContext(subPageIndexArg.longValue(), resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static class FOathApiCodec extends StandardMessageCodec {
|
||||||
|
public static final FOathApiCodec INSTANCE = new FOathApiCodec();
|
||||||
|
private FOathApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/
|
||||||
|
public static class FOathApi {
|
||||||
|
private final BinaryMessenger binaryMessenger;
|
||||||
|
public FOathApi(BinaryMessenger argBinaryMessenger){
|
||||||
|
this.binaryMessenger = argBinaryMessenger;
|
||||||
|
}
|
||||||
|
public interface Reply<T> {
|
||||||
|
void reply(T reply);
|
||||||
|
}
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return FOathApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSession(String sessionJsonArg, Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.FOathApi.updateSession", getCodec());
|
||||||
|
channel.send(new ArrayList<Object>(Arrays.asList(sessionJsonArg)), channelReply -> {
|
||||||
|
callback.reply(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public void updateOathCredentials(String credentialListJsonArg, Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.FOathApi.updateOathCredentials", getCodec());
|
||||||
|
channel.send(new ArrayList<Object>(Arrays.asList(credentialListJsonArg)), channelReply -> {
|
||||||
|
callback.reply(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static class FManagementApiCodec extends StandardMessageCodec {
|
||||||
|
public static final FManagementApiCodec INSTANCE = new FManagementApiCodec();
|
||||||
|
private FManagementApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/
|
||||||
|
public static class FManagementApi {
|
||||||
|
private final BinaryMessenger binaryMessenger;
|
||||||
|
public FManagementApi(BinaryMessenger argBinaryMessenger){
|
||||||
|
this.binaryMessenger = argBinaryMessenger;
|
||||||
|
}
|
||||||
|
public interface Reply<T> {
|
||||||
|
void reply(T reply);
|
||||||
|
}
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return FManagementApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateDeviceInfo(String deviceInfoJsonArg, Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.FManagementApi.updateDeviceInfo", getCodec());
|
||||||
|
channel.send(new ArrayList<Object>(Arrays.asList(deviceInfoJsonArg)), channelReply -> {
|
||||||
|
callback.reply(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static class FDialogApiCodec extends StandardMessageCodec {
|
||||||
|
public static final FDialogApiCodec INSTANCE = new FDialogApiCodec();
|
||||||
|
private FDialogApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/
|
||||||
|
public static class FDialogApi {
|
||||||
|
private final BinaryMessenger binaryMessenger;
|
||||||
|
public FDialogApi(BinaryMessenger argBinaryMessenger){
|
||||||
|
this.binaryMessenger = argBinaryMessenger;
|
||||||
|
}
|
||||||
|
public interface Reply<T> {
|
||||||
|
void reply(T reply);
|
||||||
|
}
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return FDialogApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showDialogApi(String dialogParametersJsonArg, Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.FDialogApi.showDialogApi", getCodec());
|
||||||
|
channel.send(new ArrayList<Object>(Arrays.asList(dialogParametersJsonArg)), channelReply -> {
|
||||||
|
callback.reply(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public void closeDialogApi(Reply<Void> callback) {
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.FDialogApi.closeDialogApi", getCodec());
|
||||||
|
channel.send(null, channelReply -> {
|
||||||
|
callback.reply(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static class HDialogApiCodec extends StandardMessageCodec {
|
||||||
|
public static final HDialogApiCodec INSTANCE = new HDialogApiCodec();
|
||||||
|
private HDialogApiCodec() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
|
||||||
|
public interface HDialogApi {
|
||||||
|
void dialogClosed(Result<Void> result);
|
||||||
|
|
||||||
|
/** The codec used by HDialogApi. */
|
||||||
|
static MessageCodec<Object> getCodec() {
|
||||||
|
return HDialogApiCodec.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets up an instance of `HDialogApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
static void setup(BinaryMessenger binaryMessenger, HDialogApi api) {
|
||||||
|
{
|
||||||
|
BasicMessageChannel<Object> channel =
|
||||||
|
new BasicMessageChannel<>(binaryMessenger, "dev.flutter.pigeon.HDialogApi.dialogClosed", getCodec());
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler((message, reply) -> {
|
||||||
|
Map<String, Object> wrapped = new HashMap<>();
|
||||||
|
try {
|
||||||
|
Result<Void> resultCallback = new Result<Void>() {
|
||||||
|
public void success(Void result) {
|
||||||
|
wrapped.put("result", null);
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
public void error(Throwable error) {
|
||||||
|
wrapped.put("error", wrapError(error));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
api.dialogClosed(resultCallback);
|
||||||
|
}
|
||||||
|
catch (Error | RuntimeException exception) {
|
||||||
|
wrapped.put("error", wrapError(exception));
|
||||||
|
reply.reply(wrapped);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static Map<String, Object> wrapError(Throwable exception) {
|
||||||
|
Map<String, Object> errorMap = new HashMap<>();
|
||||||
|
errorMap.put("message", exception.toString());
|
||||||
|
errorMap.put("code", exception.getClass().getSimpleName());
|
||||||
|
errorMap.put("details", "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
|
||||||
|
return errorMap;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,103 @@
|
|||||||
|
package com.yubico.authenticator
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.yubico.authenticator.api.AppApiImpl
|
||||||
|
import com.yubico.authenticator.api.HDialogApiImpl
|
||||||
|
import com.yubico.authenticator.api.OathApiImpl
|
||||||
|
import com.yubico.authenticator.api.Pigeon
|
||||||
|
import com.yubico.yubikit.android.YubiKitManager
|
||||||
|
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
|
||||||
|
import com.yubico.yubikit.android.transport.nfc.NfcNotAvailable
|
||||||
|
import com.yubico.yubikit.android.transport.usb.UsbConfiguration
|
||||||
|
import com.yubico.yubikit.core.Logger
|
||||||
|
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
|
class MainActivity : FlutterFragmentActivity() {
|
||||||
|
private val viewModel: MainViewModel by viewModels()
|
||||||
|
private val nfcConfiguration = NfcConfiguration()
|
||||||
|
|
||||||
|
private var hasNfc by Delegates.notNull<Boolean>()
|
||||||
|
|
||||||
|
private lateinit var yubikit: YubiKitManager
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
// simple logger
|
||||||
|
Logger.setLogger(object : Logger() {
|
||||||
|
override fun logDebug(message: String) {
|
||||||
|
Log.d("yubico-authenticator", message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logError(message: String, throwable: Throwable) {
|
||||||
|
Log.e("yubico-authenticator", message, throwable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yubikit = YubiKitManager(this)
|
||||||
|
|
||||||
|
viewModel.handleYubiKey.observe(this) {
|
||||||
|
if (it) {
|
||||||
|
yubikit.startUsbDiscovery(UsbConfiguration()) { device ->
|
||||||
|
viewModel.yubiKeyDevice.postValue(device)
|
||||||
|
device.setOnClosed { viewModel.yubiKeyDevice.postValue(null) }
|
||||||
|
}
|
||||||
|
hasNfc = try {
|
||||||
|
yubikit.startNfcDiscovery(nfcConfiguration, this) { device ->
|
||||||
|
viewModel.yubiKeyDevice.apply {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
value = device
|
||||||
|
postValue(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: NfcNotAvailable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yubikit.stopNfcDiscovery(this)
|
||||||
|
yubikit.stopUsbDiscovery()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.yubiKeyDevice.observe(this) { yubikey ->
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (yubikey != null) {
|
||||||
|
Logger.d("A device was connected: $yubikey")
|
||||||
|
viewModel.yubikeyAttached(yubikey)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Logger.d("A device was disconnected")
|
||||||
|
viewModel.yubikeyDetached()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
|
||||||
|
val messenger = flutterEngine.dartExecutor.binaryMessenger
|
||||||
|
|
||||||
|
viewModel.setFOathApi(Pigeon.FOathApi(messenger))
|
||||||
|
viewModel.setFManagementApi(Pigeon.FManagementApi(messenger))
|
||||||
|
viewModel.setFDialogApi(Pigeon.FDialogApi(messenger))
|
||||||
|
Pigeon.OathApi.setup(messenger, OathApiImpl(viewModel))
|
||||||
|
Pigeon.AppApi.setup(messenger, AppApiImpl(viewModel))
|
||||||
|
Pigeon.HDialogApi.setup(messenger, HDialogApiImpl(viewModel))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,523 @@
|
|||||||
|
package com.yubico.authenticator
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.yubico.authenticator.api.Pigeon
|
||||||
|
import com.yubico.yubikit.android.transport.nfc.NfcYubiKeyDevice
|
||||||
|
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||||
|
import com.yubico.yubikit.core.Logger
|
||||||
|
import com.yubico.yubikit.core.YubiKeyDevice
|
||||||
|
import com.yubico.yubikit.core.smartcard.SmartCardConnection
|
||||||
|
import com.yubico.yubikit.core.util.Result
|
||||||
|
import com.yubico.yubikit.management.ManagementSession
|
||||||
|
import com.yubico.yubikit.management.ManagementSession.FEATURE_DEVICE_INFO
|
||||||
|
import com.yubico.yubikit.oath.CredentialData
|
||||||
|
import com.yubico.yubikit.oath.OathSession
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.net.URI
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
data class YubiKeyAction(
|
||||||
|
val message: String,
|
||||||
|
val action: suspend (Result<YubiKeyDevice, Exception>) -> Unit
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class OperationContext(val value: Long) {
|
||||||
|
Oath(0), Yubikey(1), Invalid(-1);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getByValue(value: Long) = values().firstOrNull { it.value == value } ?: Invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ParameterException : Exception("Invalid parameters")
|
||||||
|
|
||||||
|
class MainViewModel : ViewModel() {
|
||||||
|
|
||||||
|
private val _handleYubiKey = MutableLiveData(true)
|
||||||
|
val handleYubiKey: LiveData<Boolean> = _handleYubiKey
|
||||||
|
|
||||||
|
val yubiKeyDevice = MutableLiveData<YubiKeyDevice?>()
|
||||||
|
private var isUsbKeyConnected: Boolean = false
|
||||||
|
|
||||||
|
private var _oathSessionPassword: CharArray? = null
|
||||||
|
|
||||||
|
private var _operationContext = OperationContext.Oath
|
||||||
|
|
||||||
|
private lateinit var _fOathApi: Pigeon.FOathApi
|
||||||
|
private lateinit var _fManagementApi: Pigeon.FManagementApi
|
||||||
|
private lateinit var _fDialogApi: Pigeon.FDialogApi
|
||||||
|
|
||||||
|
fun setContext(value: OperationContext) {
|
||||||
|
_operationContext = value
|
||||||
|
Logger.d("Operation context is now $_operationContext")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFOathApi(oathApi: Pigeon.FOathApi) {
|
||||||
|
_fOathApi = oathApi
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFManagementApi(managementApi: Pigeon.FManagementApi) {
|
||||||
|
_fManagementApi = managementApi
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFDialogApi(dialogApi: Pigeon.FDialogApi) {
|
||||||
|
_fDialogApi = dialogApi
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendDeviceInfo(device: YubiKeyDevice) {
|
||||||
|
|
||||||
|
val deviceInfoData = suspendCoroutine<String> {
|
||||||
|
device.requestConnection(SmartCardConnection::class.java) { result ->
|
||||||
|
val managementSession = ManagementSession(result.value)
|
||||||
|
|
||||||
|
if (!managementSession.supports(FEATURE_DEVICE_INFO)) {
|
||||||
|
it.resume("NO_FEATURE_DEVICE_INFO")
|
||||||
|
return@requestConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val deviceInfo = managementSession.deviceInfo
|
||||||
|
val deviceInfoData =
|
||||||
|
SerializeHelpers.serialize(deviceInfo, device is NfcYubiKeyDevice)
|
||||||
|
it.resume(deviceInfoData.toString())
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
Logger.e("Failed to get device info", cause)
|
||||||
|
it.resumeWithException(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fManagementApi.updateDeviceInfo(deviceInfoData) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendOathInfo(device: YubiKeyDevice) {
|
||||||
|
|
||||||
|
val oathSessionData = suspendCoroutine<String> {
|
||||||
|
device.requestConnection(SmartCardConnection::class.java) { result ->
|
||||||
|
val oathSession = OathSession(result.value)
|
||||||
|
val isRemembered = false
|
||||||
|
val oathSessionData = SerializeHelpers.serialize(oathSession, isRemembered)
|
||||||
|
it.resume(oathSessionData.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fOathApi.updateSession(oathSessionData) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun sendOathCodes(device: YubiKeyDevice) {
|
||||||
|
val sendOathCodes = suspendCoroutine<String> {
|
||||||
|
device.requestConnection(SmartCardConnection::class.java) { result ->
|
||||||
|
val oathSession = OathSession(result.value)
|
||||||
|
|
||||||
|
val isLocked = isOathSessionLocked(oathSession)
|
||||||
|
if (!isLocked) {
|
||||||
|
|
||||||
|
val codes = SerializeHelpers.serialize(
|
||||||
|
oathSession.deviceId,
|
||||||
|
oathSession.calculateCodes()
|
||||||
|
)
|
||||||
|
it.resume(codes.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fOathApi.updateOathCredentials(sendOathCodes) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _pendingYubiKeyAction = MutableLiveData<YubiKeyAction?>()
|
||||||
|
private val pendingYubiKeyAction: LiveData<YubiKeyAction?> = _pendingYubiKeyAction
|
||||||
|
|
||||||
|
private suspend fun provideYubiKey(result: Result<YubiKeyDevice, Exception>) =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
pendingYubiKeyAction.value?.let {
|
||||||
|
_pendingYubiKeyAction.postValue(null)
|
||||||
|
if (!isUsbKeyConnected) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
requestHideDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.action.invoke(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun yubikeyAttached(device: YubiKeyDevice) {
|
||||||
|
|
||||||
|
isUsbKeyConnected = device is UsbYubiKeyDevice
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (pendingYubiKeyAction.value != null) {
|
||||||
|
provideYubiKey(Result.success(device))
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
when (_operationContext) {
|
||||||
|
OperationContext.Oath -> {
|
||||||
|
try {
|
||||||
|
sendDeviceInfo(device)
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
Logger.e("Failed to send device info", cause)
|
||||||
|
}
|
||||||
|
sendOathInfo(device)
|
||||||
|
sendOathCodes(device)
|
||||||
|
}
|
||||||
|
OperationContext.Yubikey -> {
|
||||||
|
sendDeviceInfo(device)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun yubikeyDetached() {
|
||||||
|
|
||||||
|
if (isUsbKeyConnected) {
|
||||||
|
// forget the current password only for usb keys
|
||||||
|
_oathSessionPassword = null
|
||||||
|
_fManagementApi.updateDeviceInfo("") {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDialogClosed(result: Pigeon.Result<Void>) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
provideYubiKey(Result.failure(Exception("User canceled")))
|
||||||
|
result.success(null)
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
Logger.d("failed")
|
||||||
|
result.error(Exception("Failed to close dialog during User cancel action"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requests flutter to show a dialog
|
||||||
|
private fun requestShowDialog(message: String) =
|
||||||
|
_fDialogApi.showDialogApi(SerializeHelpers.messageParameters(message).toString())
|
||||||
|
{}
|
||||||
|
|
||||||
|
private fun requestHideDialog() {
|
||||||
|
_fDialogApi.closeDialogApi { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> withUnlockedSession(session: OathSession, block: (OathSession) -> T): T {
|
||||||
|
val isLocked = isOathSessionLocked(session)
|
||||||
|
if (isLocked) {
|
||||||
|
throw Exception("Session is locked")
|
||||||
|
}
|
||||||
|
return block(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getOathCredential(oathSession: OathSession, credentialId: String) =
|
||||||
|
oathSession.credentials.firstOrNull { credential ->
|
||||||
|
(credential != null) && SerializeHelpers.credentialIdAsString(
|
||||||
|
credential.id
|
||||||
|
) == credentialId
|
||||||
|
} ?: throw Exception("Failed to find account to delete")
|
||||||
|
|
||||||
|
|
||||||
|
fun deleteAccount(credentialId: String?, result: Pigeon.Result<Void>) {
|
||||||
|
|
||||||
|
if (credentialId == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
useOathSession("Delete account", true) { session ->
|
||||||
|
withUnlockedSession(session) {
|
||||||
|
val credential = getOathCredential(session, credentialId)
|
||||||
|
session.deleteCredential(credential)
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAccount(otpUri: String?, requireTouch: Boolean?, result: Pigeon.Result<String>) {
|
||||||
|
if (otpUri == null || requireTouch == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Add account", true) { session ->
|
||||||
|
withUnlockedSession(session) {
|
||||||
|
val credentialData: CredentialData =
|
||||||
|
CredentialData.parseUri(URI.create(otpUri))
|
||||||
|
|
||||||
|
val jsonResult = SerializeHelpers.serialize(
|
||||||
|
session.deviceId,
|
||||||
|
session.putCredential(credentialData, requireTouch)
|
||||||
|
).toString()
|
||||||
|
result.success(jsonResult)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renameCredential(
|
||||||
|
credentialId: String?,
|
||||||
|
name: String?,
|
||||||
|
issuer: String?,
|
||||||
|
result: Pigeon.Result<String>
|
||||||
|
) {
|
||||||
|
if (credentialId == null || name == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Rename", true) { session ->
|
||||||
|
withUnlockedSession(session) {
|
||||||
|
val credential = getOathCredential(session, credentialId)
|
||||||
|
|
||||||
|
// rename credential
|
||||||
|
val newCredential =
|
||||||
|
session.renameCredential(credential, name, issuer)
|
||||||
|
|
||||||
|
val resultJson = SerializeHelpers.serialize(
|
||||||
|
session.deviceId,
|
||||||
|
newCredential
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
result.success(resultJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setOathPassword(current: String?, password: String?, result: Pigeon.Result<Void>) {
|
||||||
|
if (password == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Set password", true) { session ->
|
||||||
|
if (session.isAccessKeySet) {
|
||||||
|
if (current == null) {
|
||||||
|
throw Exception("Must provide current password to be able to change it")
|
||||||
|
}
|
||||||
|
// test current password sent by the user
|
||||||
|
if (!session.unlock(current.toCharArray())) {
|
||||||
|
throw Exception("Provided current password is invalid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val newPass = password.toCharArray()
|
||||||
|
session.setPassword(newPass)
|
||||||
|
_oathSessionPassword = newPass
|
||||||
|
Logger.d("Successfully set password")
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun unsetOathPassword(currentPassword: String?, result: Pigeon.Result<Void>) {
|
||||||
|
|
||||||
|
if (currentPassword == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Unset password", true) { session ->
|
||||||
|
if (session.isAccessKeySet) {
|
||||||
|
// test current password sent by the user
|
||||||
|
if (session.unlock(currentPassword.toCharArray())) {
|
||||||
|
session.deleteAccessKey()
|
||||||
|
_oathSessionPassword = null
|
||||||
|
Logger.d("Successfully unset password")
|
||||||
|
result.success(null)
|
||||||
|
return@useOathSession
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.error(Exception("Unset password failed"))
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshOathCodes(result: Pigeon.Result<String>) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
if (!isUsbKeyConnected) {
|
||||||
|
throw Exception("Cannot refresh for nfc key")
|
||||||
|
}
|
||||||
|
|
||||||
|
useOathSession("Refresh codes", false) {
|
||||||
|
withUnlockedSession(it) { session ->
|
||||||
|
val resultJson = SerializeHelpers.serialize(
|
||||||
|
session.deviceId,
|
||||||
|
session.calculateCodes()
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
result.success(resultJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun calculate(credentialId: String?, result: Pigeon.Result<String>) {
|
||||||
|
|
||||||
|
if (credentialId == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Calculate", true) {
|
||||||
|
withUnlockedSession(it) { session ->
|
||||||
|
|
||||||
|
val credential = getOathCredential(session, credentialId)
|
||||||
|
|
||||||
|
val resultJson = SerializeHelpers.serialize(
|
||||||
|
session.calculateCode(credential)
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
result.success(resultJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unlockOathSession(
|
||||||
|
password: String?,
|
||||||
|
remember: Boolean?,
|
||||||
|
result: Pigeon.Result<Boolean>
|
||||||
|
) {
|
||||||
|
|
||||||
|
if (password == null || remember == null) {
|
||||||
|
result.error(ParameterException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
var codes: String? = null
|
||||||
|
useOathSession("Unlocking", true) {
|
||||||
|
_oathSessionPassword = password.toCharArray()
|
||||||
|
val isLocked = isOathSessionLocked(it)
|
||||||
|
|
||||||
|
if (!isLocked) {
|
||||||
|
codes = SerializeHelpers.serialize(
|
||||||
|
it.deviceId,
|
||||||
|
it.calculateCodes()
|
||||||
|
).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
result.success(!isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
codes?.let {
|
||||||
|
_fOathApi.updateOathCredentials(it) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (cause: Throwable) {
|
||||||
|
result.error(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetOathSession(result: Pigeon.Result<Void>) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
useOathSession("Reset YubiKey", true) {
|
||||||
|
// note, it is ok to reset locked session
|
||||||
|
it.reset()
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
result.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> useOathSession(
|
||||||
|
title: String,
|
||||||
|
queryUserToTap: Boolean,
|
||||||
|
action: (OathSession) -> T
|
||||||
|
) = suspendCoroutine<T> { outer ->
|
||||||
|
if (queryUserToTap && !isUsbKeyConnected) {
|
||||||
|
viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
requestShowDialog(title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pendingYubiKeyAction.postValue(YubiKeyAction(title) { yubiKey ->
|
||||||
|
outer.resumeWith(runCatching {
|
||||||
|
suspendCoroutine { inner ->
|
||||||
|
yubiKey.value.requestConnection(SmartCardConnection::class.java) {
|
||||||
|
inner.resumeWith(runCatching {
|
||||||
|
action.invoke(OathSession(it.value))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
yubiKeyDevice.value?.let {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
provideYubiKey(Result.success(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns true if the session cannot be unlocked (either we don't have a password, or the password is incorrect
|
||||||
|
*
|
||||||
|
* returns false if we can unlock the session
|
||||||
|
*/
|
||||||
|
private fun isOathSessionLocked(session: OathSession): Boolean {
|
||||||
|
if (!session.isLocked) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_oathSessionPassword == null) {
|
||||||
|
return true // we have no password to unlock
|
||||||
|
}
|
||||||
|
|
||||||
|
val unlockSucceed = session.unlock(_oathSessionPassword!!)
|
||||||
|
|
||||||
|
if (unlockSucceed) {
|
||||||
|
return false // we have everything to unlock the session
|
||||||
|
}
|
||||||
|
|
||||||
|
_oathSessionPassword = null // reset the password as well as it did not work
|
||||||
|
return true // the unlock did not work, session is locked
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forgetPassword(result: Pigeon.Result<Void>) {
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
package com.yubico.authenticator
|
||||||
|
|
||||||
|
import com.yubico.yubikit.core.Transport
|
||||||
|
import com.yubico.yubikit.core.Version
|
||||||
|
import com.yubico.yubikit.management.DeviceConfig
|
||||||
|
import com.yubico.yubikit.management.DeviceInfo
|
||||||
|
import com.yubico.yubikit.oath.Code
|
||||||
|
import com.yubico.yubikit.oath.Credential
|
||||||
|
import com.yubico.yubikit.oath.OathSession
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonNull
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
|
||||||
|
class SerializeHelpers {
|
||||||
|
companion object {
|
||||||
|
private fun serialize(config: DeviceConfig) = with(config) {
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"device_flags" to JsonPrimitive(deviceFlags),
|
||||||
|
"challenge_response_timeout" to JsonPrimitive(challengeResponseTimeout),
|
||||||
|
"auto_eject_timeout" to JsonPrimitive(autoEjectTimeout),
|
||||||
|
"enabled_capabilities" to JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"usb" to JsonPrimitive(getEnabledCapabilities(Transport.USB) ?: 0),
|
||||||
|
"nfc" to JsonPrimitive(getEnabledCapabilities(Transport.NFC) ?: 0),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun serialize(version: Version) = with(version) {
|
||||||
|
JsonArray(
|
||||||
|
listOf(
|
||||||
|
JsonPrimitive(major),
|
||||||
|
JsonPrimitive(minor),
|
||||||
|
JsonPrimitive(micro)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(data: DeviceInfo, isNfcDevice: Boolean) =
|
||||||
|
with(data) {
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"config" to serialize(config),
|
||||||
|
"serial" to JsonPrimitive(serialNumber),
|
||||||
|
"version" to serialize(version),
|
||||||
|
"form_factor" to JsonPrimitive(formFactor.value),
|
||||||
|
"is_locked" to JsonPrimitive(isLocked),
|
||||||
|
"is_sky" to JsonPrimitive(false), // FIXME return correct value
|
||||||
|
"is_fips" to JsonPrimitive(false), // FIXME return correct value
|
||||||
|
"name" to JsonPrimitive("FIXME"), // FIXME return correct value
|
||||||
|
"isNFC" to JsonPrimitive(isNfcDevice),
|
||||||
|
"supported_capabilities" to JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"usb" to JsonPrimitive(getSupportedCapabilities(Transport.USB)),
|
||||||
|
"nfc" to JsonPrimitive(getSupportedCapabilities(Transport.NFC)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun serialize(oathSession: OathSession, remembered: Boolean) =
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"deviceId" to JsonPrimitive(oathSession.deviceId),
|
||||||
|
"hasKey" to JsonPrimitive(oathSession.isAccessKeySet),
|
||||||
|
"remembered" to JsonPrimitive(remembered),
|
||||||
|
"locked" to JsonPrimitive(oathSession.isLocked)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun serialize(code: Code?) =
|
||||||
|
when (code) {
|
||||||
|
null -> JsonNull
|
||||||
|
else -> JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"value" to JsonPrimitive(code.value),
|
||||||
|
"valid_from" to JsonPrimitive(code.validFrom / 1000),
|
||||||
|
"valid_to" to JsonPrimitive(code.validUntil / 1000)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun credentialIdAsString(id: ByteArray): String = id.joinToString(
|
||||||
|
separator = ""
|
||||||
|
) { b -> "%02x".format(b) }
|
||||||
|
|
||||||
|
fun serialize(deviceId: String, credential: Credential) =
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"id" to JsonPrimitive(
|
||||||
|
credentialIdAsString(credential.id)
|
||||||
|
),
|
||||||
|
"device_id" to JsonPrimitive(deviceId),
|
||||||
|
"issuer" to JsonPrimitive(credential.issuer),
|
||||||
|
"name" to JsonPrimitive(credential.accountName),
|
||||||
|
"oath_type" to JsonPrimitive(credential.oathType.value),
|
||||||
|
"period" to JsonPrimitive(credential.period),
|
||||||
|
"touch_required" to JsonPrimitive(credential.isTouchRequired),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun serialize(deviceId: String, codes: Map<Credential, Code>) =
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"entries" to JsonArray(
|
||||||
|
codes.map { (credential, code) ->
|
||||||
|
JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"credential" to serialize(deviceId, credential),
|
||||||
|
"code" to serialize(code)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun messageParameters(message: String) = JsonObject(
|
||||||
|
mapOf(
|
||||||
|
"message" to JsonPrimitive(message)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
4
android/app/src/main/res/values/strings.xml
Normal file
4
android/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_label">Yubico AuthenticatorNG</string>
|
||||||
|
</resources>
|
18
android/app/src/main/res/values/styles.xml
Normal file
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
7
android/app/src/main/res/xml/device_filter.xml
Normal file
7
android/app/src/main/res/xml/device_filter.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<usb-device
|
||||||
|
class="11"
|
||||||
|
subclass="0"
|
||||||
|
protocol="0" />
|
||||||
|
</resources>
|
7
android/app/src/profile/AndroidManifest.xml
Normal file
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.yubico.authenticator">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
42
android/build.gradle
Normal file
42
android/build.gradle
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.6.10'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-serialization:1.6.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
mavenLocal() // TODO: Remove this before release
|
||||||
|
}
|
||||||
|
|
||||||
|
project.ext {
|
||||||
|
minSdkVersion = 21
|
||||||
|
targetSdkVersion = 31
|
||||||
|
compileSdkVersion = 31
|
||||||
|
buildToolsVersion = "30.0.3"
|
||||||
|
|
||||||
|
yubiKitVersion = "2.1.0-SNAPSHOT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
3
android/gradle.properties
Normal file
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
11
android/settings.gradle
Normal file
11
android/settings.gradle
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
Loading…
Reference in New Issue
Block a user