Add support for multi-code migrations

This commit is contained in:
MrLetsplay 2023-08-16 21:59:35 +02:00
parent 37a7eef5e1
commit 5af785804f
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
7 changed files with 109 additions and 26 deletions

View File

@ -38,7 +38,8 @@
</activity> </activity>
<activity android:name=".scanner.QRScannerActivity" <activity android:name=".scanner.QRScannerActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.CringeAuthenticator.None"> android:theme="@style/Theme.CringeAuthenticator.None"
android:configChanges="orientation|screenSize">
</activity> </activity>
<activity android:name=".urihandler.URIHandlerActivity" <activity android:name=".urihandler.URIHandlerActivity"
android:exported="true"> android:exported="true">

View File

@ -0,0 +1,26 @@
package com.cringe_studios.cringe_authenticator.model;
public class OTPMigrationPart {
private OTPData[] otps;
private int batchIndex;
private int batchSize;
public OTPMigrationPart(OTPData[] otps, int batchIndex, int batchSize) {
this.otps = otps;
this.batchIndex = batchIndex;
this.batchSize = batchSize;
}
public OTPData[] getOTPs() {
return otps;
}
public int getBatchIndex() {
return batchIndex;
}
public int getBatchSize() {
return batchSize;
}
}

View File

@ -2,7 +2,6 @@ package com.cringe_studios.cringe_authenticator.scanner;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.media.Image; import android.media.Image;
@ -11,6 +10,7 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -31,6 +31,7 @@ import androidx.core.content.ContextCompat;
import com.cringe_studios.cringe_authenticator.R; import com.cringe_studios.cringe_authenticator.R;
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding; import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
import com.cringe_studios.cringe_authenticator.model.OTPData; import com.cringe_studios.cringe_authenticator.model.OTPData;
import com.cringe_studios.cringe_authenticator.model.OTPMigrationPart;
import com.cringe_studios.cringe_authenticator.util.OTPParser; import com.cringe_studios.cringe_authenticator.util.OTPParser;
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder; import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
import com.cringe_studios.cringe_authenticator.util.ThemeUtil; import com.cringe_studios.cringe_authenticator.util.ThemeUtil;
@ -40,6 +41,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanning;
import com.google.mlkit.vision.barcode.common.Barcode; import com.google.mlkit.vision.barcode.common.Barcode;
import com.google.mlkit.vision.common.InputImage; import com.google.mlkit.vision.common.InputImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
public class QRScannerActivity extends AppCompatActivity { public class QRScannerActivity extends AppCompatActivity {
@ -54,6 +58,12 @@ public class QRScannerActivity extends AppCompatActivity {
private boolean process = true; private boolean process = true;
private List<OTPData> currentCodes;
private OTPMigrationPart lastPart;
private Toast t;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -69,6 +79,9 @@ public class QRScannerActivity extends AppCompatActivity {
binding = ActivityQrScannerBinding.inflate(getLayoutInflater()); binding = ActivityQrScannerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
currentCodes = new ArrayList<>();
lastPart = null;
cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> { cameraProviderFuture.addListener(() -> {
try { try {
@ -81,11 +94,8 @@ public class QRScannerActivity extends AppCompatActivity {
}, ContextCompat.getMainExecutor(this)); }, ContextCompat.getMainExecutor(this));
scanner = BarcodeScanning.getClient(); scanner = BarcodeScanning.getClient();
} t = Toast.makeText(this, "0", Toast.LENGTH_LONG);
t.show();
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
} }
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) { void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
@ -121,7 +131,10 @@ public class QRScannerActivity extends AppCompatActivity {
@OptIn(markerClass = ExperimentalGetImage.class) @OptIn(markerClass = ExperimentalGetImage.class)
@Override @Override
public void analyze(@NonNull ImageProxy image) { public void analyze(@NonNull ImageProxy image) {
if(!process) return; if(!process) {
image.close();
return;
}
Image mediaImage = image.getImage(); Image mediaImage = image.getImage();
if(mediaImage != null) { if(mediaImage != null) {
@ -152,19 +165,56 @@ public class QRScannerActivity extends AppCompatActivity {
private void importUri(Uri uri) { private void importUri(Uri uri) {
if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) { if("otpauth-migration".equalsIgnoreCase(uri.getScheme())) {
Dialog dialog = new StyledDialogBuilder(this) OTPMigrationPart part;
try {
part = OTPParser.parseMigration(uri);
}catch(IllegalArgumentException e) {
error(e.getMessage());
return;
}
if((lastPart != null && part.getBatchIndex() != lastPart.getBatchIndex() + 1) || (lastPart == null && part.getBatchIndex() > 0)) {
// Not next batch, or first batch (if nothing was scanned yet), keep looking
process = true;
return;
}
if(part.getBatchIndex() == 0) {
new StyledDialogBuilder(this)
.setTitle(R.string.qr_scanner_migration_title) .setTitle(R.string.qr_scanner_migration_title)
.setMessage(R.string.qr_scanner_migration_message) .setMessage(R.string.qr_scanner_migration_message)
.setPositiveButton(R.string.yes, (d, which) -> { .setPositiveButton(R.string.yes, (d, which) -> {
try { if(part.getBatchSize() == 1) {
success(OTPParser.parseMigration(uri)); success(part.getOTPs());
}catch(IllegalArgumentException e) { }else {
error(e.getMessage()); currentCodes.addAll(Arrays.asList(part.getOTPs()));
lastPart = part;
Toast.makeText(this, getString(R.string.qr_scanner_migration_part, part.getBatchIndex()+ 1, part.getBatchSize()), Toast.LENGTH_LONG).show();
process = true;
} }
}) })
.setNegativeButton(R.string.no, (d, which) -> cancel()) .setNegativeButton(R.string.no, (d, which) -> cancel())
.setOnDismissListener(d -> cancel()) .show()
.show(); .setCanceledOnTouchOutside(false);
}else {
currentCodes.addAll(Arrays.asList(part.getOTPs()));
Toast.makeText(this, getString(R.string.qr_scanner_migration_part, part.getBatchIndex()+ 1, part.getBatchSize()), Toast.LENGTH_LONG).show();
if(part.getBatchIndex() == part.getBatchSize() - 1) {
success(currentCodes.toArray(new OTPData[0]));
}else {
process = true;
}
}
return;
}
if(lastPart != null) {
// Migration is being imported
process = true;
return; return;
} }

View File

@ -43,7 +43,8 @@ public class URIHandlerActivity extends AppCompatActivity {
importCodes(OTPParser.parse(uri)); importCodes(OTPParser.parse(uri));
break; break;
case "otpauth-migration": case "otpauth-migration":
importCodes(OTPParser.parseMigration(uri)); importCodes(OTPParser.parseMigration(uri).getOTPs());
// TODO: notify user if there are multiple codes?
break; break;
} }
}catch(IllegalArgumentException e) { }catch(IllegalArgumentException e) {

View File

@ -4,6 +4,7 @@ import android.net.Uri;
import android.util.Base64; import android.util.Base64;
import com.cringe_studios.cringe_authenticator.model.OTPData; import com.cringe_studios.cringe_authenticator.model.OTPData;
import com.cringe_studios.cringe_authenticator.model.OTPMigrationPart;
import com.cringe_studios.cringe_authenticator.proto.OTPMigration; import com.cringe_studios.cringe_authenticator.proto.OTPMigration;
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm; import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
import com.cringe_studios.cringe_authenticator_library.OTPType; import com.cringe_studios.cringe_authenticator_library.OTPType;
@ -12,7 +13,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
public class OTPParser { public class OTPParser {
public static OTPData[] parseMigration(Uri uri) throws IllegalArgumentException { public static OTPMigrationPart parseMigration(Uri uri) throws IllegalArgumentException {
if(!"otpauth-migration".equals(uri.getScheme())) { if(!"otpauth-migration".equals(uri.getScheme())) {
throw new IllegalArgumentException("Wrong URI scheme"); throw new IllegalArgumentException("Wrong URI scheme");
} }
@ -27,15 +28,16 @@ public class OTPParser {
} }
byte[] dataBytes = Base64.decode(data, Base64.DEFAULT); byte[] dataBytes = Base64.decode(data, Base64.DEFAULT);
try { try {
OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes); OTPMigration.MigrationPayload payload = OTPMigration.MigrationPayload.parseFrom(dataBytes);
int count = payload.getOtpParametersCount(); int count = payload.getOtpParametersCount();
OTPData[] otps = new OTPData[count]; OTPData[] otps = new OTPData[count];
for(int i = 0; i < payload.getOtpParametersCount(); i++) { for(int i = 0; i < payload.getOtpParametersCount(); i++) {
OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i); OTPMigration.MigrationPayload.OtpParameters params = payload.getOtpParameters(i);
// TODO: issuer
String name = params.getName(); String name = params.getName();
String issuer = params.getIssuer(); String issuer = params.getIssuer();
@ -96,7 +98,8 @@ public class OTPParser {
otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum); otps[i] = new OTPData(name, issuer, type, secret, algorithm, digits, period, counter, checksum);
} }
return otps;
return new OTPMigrationPart(otps, payload.getBatchIndex(), payload.getBatchSize());
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException("Failed to parse migration data", e); throw new IllegalArgumentException("Failed to parse migration data", e);
} }

View File

@ -48,6 +48,7 @@
<string name="otp_add_missing_name">Name fehlt</string> <string name="otp_add_missing_name">Name fehlt</string>
<string name="qr_scanner_migration_title">OTP-Migration</string> <string name="qr_scanner_migration_title">OTP-Migration</string>
<string name="qr_scanner_migration_message">Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren?</string> <string name="qr_scanner_migration_message">Du scheinst zu versuchen, Codes aus einer anderen App zu importieren. Willst du alle Codes in diese Gruppe importieren?</string>
<string name="qr_scanner_migration_part">Code %d von %d gescannt</string>
<string-array name="view_edit_move_delete"> <string-array name="view_edit_move_delete">
<item>Anzeigen</item> <item>Anzeigen</item>
<item>Bearbeiten</item> <item>Bearbeiten</item>

View File

@ -48,6 +48,7 @@
<string name="otp_add_missing_name">Missing name</string> <string name="otp_add_missing_name">Missing name</string>
<string name="qr_scanner_migration_title">OTP Migration</string> <string name="qr_scanner_migration_title">OTP Migration</string>
<string name="qr_scanner_migration_message">It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group?</string> <string name="qr_scanner_migration_message">It seems like you\'re trying to import OTP codes from another app. Do you want to import all codes into this group?</string>
<string name="qr_scanner_migration_part">Code %d of %d scanned</string>
<string-array name="view_edit_move_delete"> <string-array name="view_edit_move_delete">
<item>View</item> <item>View</item>
<item>Edit</item> <item>Edit</item>