Better error handling, Add URI handler, Add support for checksum
This commit is contained in:
parent
4154e0e026
commit
eb57876fe3
17
.idea/deploymentTargetDropDown.xml
Normal file
17
.idea/deploymentTargetDropDown.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="deploymentTargetDropDown">
|
||||||
|
<runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<Target>
|
||||||
|
<type value="RUNNING_DEVICE_TARGET" />
|
||||||
|
<deviceKey>
|
||||||
|
<Key>
|
||||||
|
<type value="SERIAL_NUMBER" />
|
||||||
|
<value value="R38N50464FV" />
|
||||||
|
</Key>
|
||||||
|
</deviceKey>
|
||||||
|
</Target>
|
||||||
|
</runningDeviceTargetSelectedWithDropDown>
|
||||||
|
<timeTargetWasSelectedWithDropDown value="2023-06-25T19:29:01.472337342Z" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -36,8 +36,18 @@
|
|||||||
android:theme="@style/Theme.CringeAuthenticator.None">
|
android:theme="@style/Theme.CringeAuthenticator.None">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".scanner.QRScannerActivity"
|
<activity android:name=".scanner.QRScannerActivity"
|
||||||
|
android:exported="true"
|
||||||
android:theme="@style/Theme.CringeAuthenticator.None">
|
android:theme="@style/Theme.CringeAuthenticator.None">
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name=".urihandler.URIHandlerActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="otpauth" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -7,6 +7,7 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@ -28,12 +29,14 @@ import com.cringe_studios.cringe_authenticator.fragment.HomeFragment;
|
|||||||
import com.cringe_studios.cringe_authenticator.fragment.MenuFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.MenuFragment;
|
||||||
import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
|
import com.cringe_studios.cringe_authenticator.fragment.SettingsFragment;
|
||||||
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
|
import com.cringe_studios.cringe_authenticator.scanner.QRScannerContract;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.DialogCallback;
|
||||||
import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
|
import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
|
||||||
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||||
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;
|
||||||
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@ -76,15 +79,17 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
launchApp();
|
launchApp();
|
||||||
|
|
||||||
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
|
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
|
||||||
if(obj == null) { // Got some error TODO: show error message
|
if(obj == null) return; // Cancelled
|
||||||
Toast.makeText(this, "Failed to scan code", Toast.LENGTH_LONG).show();
|
|
||||||
|
if(!obj.isSuccess()) {
|
||||||
|
Toast.makeText(this, "Failed to scan code: " + obj.getErrorMessage(), Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||||
if(fragment instanceof DynamicFragment) {
|
if(fragment instanceof DynamicFragment) {
|
||||||
DynamicFragment frag = (DynamicFragment) fragment;
|
DynamicFragment frag = (DynamicFragment) fragment;
|
||||||
frag.addOTP(obj);
|
frag.addOTP(obj.getData());
|
||||||
}
|
}
|
||||||
Log.i("AMOGUS", "Actually got something bruh" + obj);
|
Log.i("AMOGUS", "Actually got something bruh" + obj);
|
||||||
});
|
});
|
||||||
@ -180,13 +185,13 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showTOTPDialog() {
|
private void showTOTPDialog() {
|
||||||
|
// TODO: checksum option
|
||||||
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
||||||
binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, OTPAlgorithm.values()));
|
binding.inputAlgorithm.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, OTPAlgorithm.values()));
|
||||||
binding.inputDigits.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new Integer[]{6, 7, 8, 9, 10, 11, 12}));
|
binding.inputDigits.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new Integer[]{6, 7, 8, 9, 10, 11, 12}));
|
||||||
showCodeDialog(binding.getRoot(), () -> {
|
showCodeDialog(binding.getRoot(), () -> {
|
||||||
// TODO: handle input
|
|
||||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||||
if(!(fragment instanceof DynamicFragment)) return;
|
if(!(fragment instanceof DynamicFragment)) return true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String name = binding.inputName.getText().toString();
|
String name = binding.inputName.getText().toString();
|
||||||
@ -195,31 +200,52 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
int digits = (int) binding.inputDigits.getSelectedItem();
|
int digits = (int) binding.inputDigits.getSelectedItem();
|
||||||
int period = Integer.parseInt(binding.inputPeriod.getText().toString());
|
int period = Integer.parseInt(binding.inputPeriod.getText().toString());
|
||||||
|
|
||||||
OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0);
|
// TODO: checksum
|
||||||
if(!data.validate()) {
|
OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, false);
|
||||||
// TODO: error
|
|
||||||
return;
|
String errorMessage = data.validate();
|
||||||
|
if(errorMessage != null) {
|
||||||
|
showErrorDialog(errorMessage);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
((DynamicFragment) fragment).addOTP(data);
|
((DynamicFragment) fragment).addOTP(data);
|
||||||
|
return true;
|
||||||
}catch(NumberFormatException e) {
|
}catch(NumberFormatException e) {
|
||||||
// TODO: error
|
showErrorDialog("Invalid number entered");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showHOTPDialog() {
|
private void showHOTPDialog() {
|
||||||
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
||||||
showCodeDialog(binding.getRoot(), () -> {});
|
showCodeDialog(binding.getRoot(), () -> true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showCodeDialog(View view, Runnable ok) {
|
private void showCodeDialog(View view, DialogCallback ok) {
|
||||||
new AlertDialog.Builder(this)
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
.setTitle("Input Code")
|
.setTitle("Input Code")
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setPositiveButton("Ok", (btnView, which) -> ok.run())
|
.setPositiveButton("Ok", (btnView, which) -> {})
|
||||||
.setNegativeButton("Cancel", (btnView, which) -> {})
|
.setNegativeButton("Cancel", (btnView, which) -> {})
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.setOnShowListener(d -> {
|
||||||
|
Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
okButton.setOnClickListener(v -> {
|
||||||
|
if(ok.callback()) dialog.dismiss();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showErrorDialog(String errorMessage) {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Failed to add code")
|
||||||
|
.setMessage(errorMessage)
|
||||||
|
.setPositiveButton("Ok", (dialog, which) -> {})
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,11 +19,12 @@ public class OTPData implements Serializable {
|
|||||||
private int digits;
|
private int digits;
|
||||||
private int period;
|
private int period;
|
||||||
private long counter;
|
private long counter;
|
||||||
|
private boolean checksum;
|
||||||
|
|
||||||
// Cached
|
// Cached
|
||||||
private transient OTP otp;
|
private transient OTP otp;
|
||||||
|
|
||||||
public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter) {
|
public OTPData(String name, OTPType type, String secret, OTPAlgorithm algorithm, int digits, int period, long counter, boolean checksum) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
@ -31,6 +32,7 @@ public class OTPData implements Serializable {
|
|||||||
this.digits = digits;
|
this.digits = digits;
|
||||||
this.period = period;
|
this.period = period;
|
||||||
this.counter = counter;
|
this.counter = counter;
|
||||||
|
this.checksum = checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -70,19 +72,19 @@ public class OTPData implements Serializable {
|
|||||||
this.counter = getOTP().getCounter();
|
this.counter = getOTP().getCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validate() {
|
public String validate() {
|
||||||
try {
|
try {
|
||||||
getOTP();
|
getOTP();
|
||||||
return true;
|
return null;
|
||||||
}catch(IllegalArgumentException | OTPException e) {
|
}catch(IllegalArgumentException | OTPException e) {
|
||||||
return false;
|
return e.getMessage() != null ? e.getMessage() : e.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private OTP getOTP() {
|
private OTP getOTP() {
|
||||||
// TODO: checksum
|
// TODO: checksum
|
||||||
if(otp != null) return otp;
|
if(otp != null) return otp;
|
||||||
return otp = OTP.createNewOTP(type, secret, algorithm, digits, counter, period, false);
|
return otp = OTP.createNewOTP(type, secret, algorithm, digits, counter, period, checksum);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -96,6 +98,7 @@ public class OTPData implements Serializable {
|
|||||||
", digits=" + digits +
|
", digits=" + digits +
|
||||||
", period=" + period +
|
", period=" + period +
|
||||||
", counter=" + counter +
|
", counter=" + counter +
|
||||||
|
", checksum=" + checksum +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,12 @@ package com.cringe_studios.cringe_authenticator.scanner;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.media.Image;
|
import android.media.Image;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -16,19 +17,22 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import androidx.camera.core.Camera;
|
import androidx.camera.core.Camera;
|
||||||
import androidx.camera.core.CameraSelector;
|
import androidx.camera.core.CameraSelector;
|
||||||
import androidx.camera.core.ExperimentalGetImage;
|
import androidx.camera.core.ExperimentalGetImage;
|
||||||
|
import androidx.camera.core.FocusMeteringAction;
|
||||||
import androidx.camera.core.ImageAnalysis;
|
import androidx.camera.core.ImageAnalysis;
|
||||||
import androidx.camera.core.ImageProxy;
|
import androidx.camera.core.ImageProxy;
|
||||||
|
import androidx.camera.core.MeteringPointFactory;
|
||||||
import androidx.camera.core.Preview;
|
import androidx.camera.core.Preview;
|
||||||
|
import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.OTPData;
|
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||||
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
|
import com.cringe_studios.cringe_authenticator.databinding.ActivityQrScannerBinding;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.OTPParser;
|
||||||
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;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanner;
|
import com.google.mlkit.vision.barcode.BarcodeScanner;
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
|
|
||||||
import com.google.mlkit.vision.barcode.BarcodeScanning;
|
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;
|
||||||
@ -37,6 +41,8 @@ import java.util.concurrent.ExecutionException;
|
|||||||
|
|
||||||
public class QRScannerActivity extends AppCompatActivity {
|
public class QRScannerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final int RESULT_ERROR = -2;
|
||||||
|
|
||||||
private ActivityQrScannerBinding binding;
|
private ActivityQrScannerBinding binding;
|
||||||
|
|
||||||
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
|
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
|
||||||
@ -47,7 +53,7 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
|
||||||
requestPermissions(new String[] {Manifest.permission.CAMERA}, 1234);
|
requestPermissions(new String[] {Manifest.permission.CAMERA}, 1234);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +62,6 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
||||||
cameraProviderFuture.addListener(() -> {
|
cameraProviderFuture.addListener(() -> {
|
||||||
Log.i("AMOGUS", "Got something");
|
|
||||||
try {
|
try {
|
||||||
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
|
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
|
||||||
bindPreview(cameraProvider);
|
bindPreview(cameraProvider);
|
||||||
@ -66,13 +71,14 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}, ContextCompat.getMainExecutor(this));
|
}, ContextCompat.getMainExecutor(this));
|
||||||
|
|
||||||
BarcodeScannerOptions opts = new BarcodeScannerOptions.Builder()
|
|
||||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
scanner = BarcodeScanning.getClient();
|
scanner = BarcodeScanning.getClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
}
|
||||||
|
|
||||||
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
||||||
Preview preview = new Preview.Builder()
|
Preview preview = new Preview.Builder()
|
||||||
.build();
|
.build();
|
||||||
@ -84,15 +90,24 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
preview.setSurfaceProvider(binding.preview.getSurfaceProvider());
|
preview.setSurfaceProvider(binding.preview.getSurfaceProvider());
|
||||||
|
|
||||||
ImageAnalysis analysis = new ImageAnalysis.Builder()
|
ImageAnalysis analysis = new ImageAnalysis.Builder()
|
||||||
//.setTargetResolution(new Size(1280, 720))
|
|
||||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||||
.build();
|
.build();
|
||||||
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), new Amogus());
|
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), new QRAnalyzer());
|
||||||
|
|
||||||
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, analysis);
|
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, analysis);
|
||||||
|
|
||||||
|
binding.preview.setOnTouchListener((view, event) -> {
|
||||||
|
if(event.getAction() != MotionEvent.ACTION_DOWN) return true;
|
||||||
|
view.performClick();
|
||||||
|
|
||||||
|
MeteringPointFactory factory = new SurfaceOrientedMeteringPointFactory(binding.preview.getWidth(), binding.preview.getHeight());
|
||||||
|
camera.getCameraControl().startFocusAndMetering(new FocusMeteringAction.Builder(factory.createPoint(event.getX(), event.getY())).build());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Amogus implements ImageAnalysis.Analyzer {
|
private class QRAnalyzer implements ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
@OptIn(markerClass = ExperimentalGetImage.class)
|
@OptIn(markerClass = ExperimentalGetImage.class)
|
||||||
@Override
|
@Override
|
||||||
@ -101,13 +116,11 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
if(mediaImage != null) {
|
if(mediaImage != null) {
|
||||||
InputImage input = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees());
|
InputImage input = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees());
|
||||||
scanner.process(input).addOnSuccessListener(barcodes -> {
|
scanner.process(input).addOnSuccessListener(barcodes -> {
|
||||||
//Log.i("AMOGUS", "found " + barcodes.size() + " codes");
|
|
||||||
image.close();
|
image.close();
|
||||||
|
|
||||||
if(barcodes.size() >= 1) {
|
if(barcodes.size() >= 1) {
|
||||||
Barcode code = null;
|
Barcode code = null;
|
||||||
|
|
||||||
Log.i("AMOGUS", "TYPE " + barcodes.get(0).getValueType());
|
|
||||||
for(Barcode c : barcodes) {
|
for(Barcode c : barcodes) {
|
||||||
if(c.getValueType() == Barcode.TYPE_TEXT) {
|
if(c.getValueType() == Barcode.TYPE_TEXT) {
|
||||||
code = c;
|
code = c;
|
||||||
@ -118,58 +131,10 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
if(code == null) return;
|
if(code == null) return;
|
||||||
|
|
||||||
Uri uri = Uri.parse(code.getRawValue());
|
Uri uri = Uri.parse(code.getRawValue());
|
||||||
Log.i("AMOGUS", code.getRawValue());
|
|
||||||
Log.i("AMOGUS", uri.getHost());
|
|
||||||
Log.i("AMOGUS", uri.getPath());
|
|
||||||
|
|
||||||
String type = uri.getHost();
|
|
||||||
String accountName = uri.getPath();
|
|
||||||
String secret = uri.getQueryParameter("secret");
|
|
||||||
String algorithm = uri.getQueryParameter("algorithm");
|
|
||||||
String digits = uri.getQueryParameter("digits");
|
|
||||||
String period = uri.getQueryParameter("period");
|
|
||||||
String counter = uri.getQueryParameter("counter");
|
|
||||||
|
|
||||||
if(type == null || secret == null) {
|
|
||||||
error("Missing params");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
OTPType fType;
|
|
||||||
try {
|
try {
|
||||||
fType = OTPType.valueOf(type.toUpperCase());
|
success(OTPParser.parse(uri));
|
||||||
}catch(IllegalArgumentException e) {
|
}catch(IllegalArgumentException e) {
|
||||||
Log.i("AMOGUS", e.toString());
|
error(e.getMessage());
|
||||||
error("Failed to parse OTP parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(fType == OTPType.HOTP && counter == null) {
|
|
||||||
error("Missing params");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(accountName == null || accountName.length() < 2 /* Because path is /accName, so 2 letters for acc with 1 letter name */) {
|
|
||||||
// TODO: error
|
|
||||||
Log.i("AMOGUS", "Missing params");
|
|
||||||
error("Missing OTP parameters");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
accountName = accountName.substring(1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 0 or null for defaults (handled by Cringe-Authenticator-Library)
|
|
||||||
OTPAlgorithm fAlgorithm = algorithm == null ? null : OTPAlgorithm.valueOf(algorithm.toUpperCase());
|
|
||||||
int fDigits = digits == null ? 0 : Integer.parseInt(digits);
|
|
||||||
int fPeriod = period == null ? 0 : Integer.parseInt(period);
|
|
||||||
int fCounter = counter == null ? 0 : Integer.parseInt(counter);
|
|
||||||
|
|
||||||
success(new OTPData(accountName, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter));
|
|
||||||
}catch(IllegalArgumentException e) {
|
|
||||||
Log.i("AMOGUS", e.toString());
|
|
||||||
error("Failed to parse OTP parameters");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).addOnFailureListener(e -> {});
|
}).addOnFailureListener(e -> {});
|
||||||
@ -185,7 +150,9 @@ public class QRScannerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void error(String errorMessage) {
|
private void error(String errorMessage) {
|
||||||
setResult(Activity.RESULT_CANCELED, null);
|
Intent result = new Intent();
|
||||||
|
result.putExtra("error", errorMessage);
|
||||||
|
setResult(RESULT_ERROR, result);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.cringe_studios.cringe_authenticator.OTPData;
|
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||||
|
|
||||||
public class QRScannerContract extends ActivityResultContract<Void, OTPData> {
|
public class QRScannerContract extends ActivityResultContract<Void, ScannerResult> {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@ -19,12 +19,16 @@ public class QRScannerContract extends ActivityResultContract<Void, OTPData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable OTPData parseResult(int result, @Nullable Intent intent) {
|
public @Nullable ScannerResult parseResult(int result, @Nullable Intent intent) {
|
||||||
if(result != Activity.RESULT_OK || intent == null) {
|
if(result != Activity.RESULT_OK || intent == null) {
|
||||||
|
if(intent != null) {
|
||||||
|
return new ScannerResult(intent.getStringExtra("error"));
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (OTPData) intent.getSerializableExtra("data");
|
return new ScannerResult((OTPData) intent.getSerializableExtra("data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.scanner;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||||
|
|
||||||
|
public class ScannerResult {
|
||||||
|
|
||||||
|
private OTPData data;
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
public ScannerResult(@NonNull OTPData data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScannerResult(@Nullable String errorMessage) {
|
||||||
|
this.errorMessage = errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return data != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OTPData getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMessage() {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.urihandler;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||||
|
import com.cringe_studios.cringe_authenticator.util.OTPParser;
|
||||||
|
|
||||||
|
public class URIHandlerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Intent intent = getIntent();
|
||||||
|
if(intent == null) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
OTPData data = OTPParser.parse(intent.getData());
|
||||||
|
// TODO: choose group, add code
|
||||||
|
Toast.makeText(this, "Code received", Toast.LENGTH_LONG).show();
|
||||||
|
finish();
|
||||||
|
}catch(IllegalArgumentException e) {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setTitle("Failed to add code")
|
||||||
|
.setMessage(e.getMessage())
|
||||||
|
.setPositiveButton("Ok", (d, which) -> finish())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dialog.setOnDismissListener(d -> finish());
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.util;
|
||||||
|
|
||||||
|
public interface DialogCallback {
|
||||||
|
|
||||||
|
public boolean callback();
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
package com.cringe_studios.cringe_authenticator.util;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||||
|
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
||||||
|
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||||
|
|
||||||
|
public class OTPParser {
|
||||||
|
|
||||||
|
public static OTPData parse(Uri uri) throws IllegalArgumentException {
|
||||||
|
if(!"otpauth".equals(uri.getScheme())) {
|
||||||
|
throw new IllegalArgumentException("Wrong URI scheme");
|
||||||
|
}
|
||||||
|
|
||||||
|
String type = uri.getHost();
|
||||||
|
String accountName = uri.getPath();
|
||||||
|
String secret = uri.getQueryParameter("secret");
|
||||||
|
String algorithm = uri.getQueryParameter("algorithm");
|
||||||
|
String digits = uri.getQueryParameter("digits");
|
||||||
|
String period = uri.getQueryParameter("period");
|
||||||
|
String counter = uri.getQueryParameter("counter");
|
||||||
|
String checksum = uri.getQueryParameter("checksum");
|
||||||
|
|
||||||
|
if(type == null || secret == null) {
|
||||||
|
throw new IllegalArgumentException("Missing params");
|
||||||
|
}
|
||||||
|
|
||||||
|
OTPType fType;
|
||||||
|
try {
|
||||||
|
fType = OTPType.valueOf(type.toUpperCase());
|
||||||
|
}catch(IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to parse OTP parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(fType == OTPType.HOTP && counter == null) {
|
||||||
|
throw new IllegalArgumentException("Missing required parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(accountName == null || accountName.length() < 2 /* Because path is /accName, so 2 letters for acc with 1 letter name */) {
|
||||||
|
throw new IllegalArgumentException("Missing required parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
accountName = accountName.substring(1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 0 or null for defaults (handled by Cringe-Authenticator-Library)
|
||||||
|
OTPAlgorithm fAlgorithm = algorithm == null ? null : OTPAlgorithm.valueOf(algorithm.toUpperCase());
|
||||||
|
int fDigits = digits == null ? 0 : Integer.parseInt(digits);
|
||||||
|
int fPeriod = period == null ? 0 : Integer.parseInt(period);
|
||||||
|
int fCounter = counter == null ? 0 : Integer.parseInt(counter);
|
||||||
|
boolean fChecksum = false;
|
||||||
|
if(checksum != null) {
|
||||||
|
switch(checksum) {
|
||||||
|
case "true": fChecksum = true; break;
|
||||||
|
case "false": break;
|
||||||
|
default: throw new IllegalArgumentException("Checksum must be set to 'true' or 'false'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OTPData data = new OTPData(accountName, fType, secret, fAlgorithm, fDigits, fPeriod, fCounter, fChecksum);
|
||||||
|
|
||||||
|
String errorMessage = data.validate();
|
||||||
|
if(errorMessage != null) {
|
||||||
|
throw new IllegalArgumentException(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}catch(IllegalArgumentException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to parse OTP parameters", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -41,6 +41,7 @@ public class SettingsUtil {
|
|||||||
|
|
||||||
private static final Gson GSON = new Gson();
|
private static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
// TODO: refactor
|
||||||
public static List<OTPData> getOTPs(SharedPreferences prefs, String group) {
|
public static List<OTPData> getOTPs(SharedPreferences prefs, String group) {
|
||||||
String currentOTPs = prefs.getString("group." + group, "[]");
|
String currentOTPs = prefs.getString("group." + group, "[]");
|
||||||
return Arrays.asList(GSON.fromJson(currentOTPs, OTPData[].class));
|
return Arrays.asList(GSON.fromJson(currentOTPs, OTPData[].class));
|
||||||
@ -73,4 +74,12 @@ public class SettingsUtil {
|
|||||||
return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("theme", THEME_NAMES.get(0));
|
return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getString("theme", THEME_NAMES.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void enableSuperSecretHamburgers(Context ctx) {
|
||||||
|
ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).edit().putBoolean("iLikeHamburgers", true).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isSuperSecretHamburgersEnabled(Context ctx) {
|
||||||
|
return ctx.getSharedPreferences(GENERAL_PREFS_NAME, Context.MODE_PRIVATE).getBoolean("iLikeHamburgers", false);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Base.Theme.CringeAuthenticator" parent="Theme.Material3.DayNight.NoActionBar">
|
<style name="Base.Theme.CringeAuthenticator" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">?attr/colorTheme1</item>
|
||||||
<item name="colorPrimaryDark">@android:color/transparent</item>
|
<item name="colorPrimaryDark">@android:color/transparent</item>
|
||||||
<item name="colorSecondary">@color/colorSecondary</item>
|
<item name="colorSecondary">?attr/colorTheme2</item>
|
||||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
|
||||||
<item name="colorTheme1">#FF00FF</item>
|
<item name="colorTheme1">#FF00FF</item>
|
||||||
|
Loading…
Reference in New Issue
Block a user