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">
|
||||
</activity>
|
||||
<activity android:name=".scanner.QRScannerActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.CringeAuthenticator.None">
|
||||
</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>
|
||||
|
||||
</manifest>
|
@ -7,6 +7,7 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
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.SettingsFragment;
|
||||
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.SettingsUtil;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPAlgorithm;
|
||||
import com.cringe_studios.cringe_authenticator_library.OTPType;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@ -76,15 +79,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
launchApp();
|
||||
|
||||
startQRCodeScan = registerForActivityResult(new QRScannerContract(), obj -> {
|
||||
if(obj == null) { // Got some error TODO: show error message
|
||||
Toast.makeText(this, "Failed to scan code", Toast.LENGTH_LONG).show();
|
||||
if(obj == null) return; // Cancelled
|
||||
|
||||
if(!obj.isSuccess()) {
|
||||
Toast.makeText(this, "Failed to scan code: " + obj.getErrorMessage(), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||
if(fragment instanceof DynamicFragment) {
|
||||
DynamicFragment frag = (DynamicFragment) fragment;
|
||||
frag.addOTP(obj);
|
||||
frag.addOTP(obj.getData());
|
||||
}
|
||||
Log.i("AMOGUS", "Actually got something bruh" + obj);
|
||||
});
|
||||
@ -180,13 +185,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void showTOTPDialog() {
|
||||
// TODO: checksum option
|
||||
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
||||
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}));
|
||||
showCodeDialog(binding.getRoot(), () -> {
|
||||
// TODO: handle input
|
||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||
if(!(fragment instanceof DynamicFragment)) return;
|
||||
if(!(fragment instanceof DynamicFragment)) return true;
|
||||
|
||||
try {
|
||||
String name = binding.inputName.getText().toString();
|
||||
@ -195,31 +200,52 @@ public class MainActivity extends AppCompatActivity {
|
||||
int digits = (int) binding.inputDigits.getSelectedItem();
|
||||
int period = Integer.parseInt(binding.inputPeriod.getText().toString());
|
||||
|
||||
OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0);
|
||||
if(!data.validate()) {
|
||||
// TODO: error
|
||||
return;
|
||||
// TODO: checksum
|
||||
OTPData data = new OTPData(name, OTPType.TOTP, secret, algorithm, digits, period, 0, false);
|
||||
|
||||
String errorMessage = data.validate();
|
||||
if(errorMessage != null) {
|
||||
showErrorDialog(errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
((DynamicFragment) fragment).addOTP(data);
|
||||
return true;
|
||||
}catch(NumberFormatException e) {
|
||||
// TODO: error
|
||||
return;
|
||||
showErrorDialog("Invalid number entered");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showHOTPDialog() {
|
||||
DialogInputCodeTotpBinding binding = DialogInputCodeTotpBinding.inflate(getLayoutInflater());
|
||||
showCodeDialog(binding.getRoot(), () -> {});
|
||||
showCodeDialog(binding.getRoot(), () -> true);
|
||||
}
|
||||
|
||||
private void showCodeDialog(View view, Runnable ok) {
|
||||
new AlertDialog.Builder(this)
|
||||
private void showCodeDialog(View view, DialogCallback ok) {
|
||||
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||
.setTitle("Input Code")
|
||||
.setView(view)
|
||||
.setPositiveButton("Ok", (btnView, which) -> ok.run())
|
||||
.setPositiveButton("Ok", (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();
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,12 @@ public class OTPData implements Serializable {
|
||||
private int digits;
|
||||
private int period;
|
||||
private long counter;
|
||||
private boolean checksum;
|
||||
|
||||
// Cached
|
||||
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.type = type;
|
||||
this.secret = secret;
|
||||
@ -31,6 +32,7 @@ public class OTPData implements Serializable {
|
||||
this.digits = digits;
|
||||
this.period = period;
|
||||
this.counter = counter;
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@ -70,19 +72,19 @@ public class OTPData implements Serializable {
|
||||
this.counter = getOTP().getCounter();
|
||||
}
|
||||
|
||||
public boolean validate() {
|
||||
public String validate() {
|
||||
try {
|
||||
getOTP();
|
||||
return true;
|
||||
return null;
|
||||
}catch(IllegalArgumentException | OTPException e) {
|
||||
return false;
|
||||
return e.getMessage() != null ? e.getMessage() : e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private OTP getOTP() {
|
||||
// TODO: checksum
|
||||
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
|
||||
@ -96,6 +98,7 @@ public class OTPData implements Serializable {
|
||||
", digits=" + digits +
|
||||
", period=" + period +
|
||||
", counter=" + counter +
|
||||
", checksum=" + checksum +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,12 @@ package com.cringe_studios.cringe_authenticator.scanner;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.Image;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -16,19 +17,22 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.camera.core.Camera;
|
||||
import androidx.camera.core.CameraSelector;
|
||||
import androidx.camera.core.ExperimentalGetImage;
|
||||
import androidx.camera.core.FocusMeteringAction;
|
||||
import androidx.camera.core.ImageAnalysis;
|
||||
import androidx.camera.core.ImageProxy;
|
||||
import androidx.camera.core.MeteringPointFactory;
|
||||
import androidx.camera.core.Preview;
|
||||
import androidx.camera.core.SurfaceOrientedMeteringPointFactory;
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||
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.OTPType;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
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.common.Barcode;
|
||||
import com.google.mlkit.vision.common.InputImage;
|
||||
@ -37,6 +41,8 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class QRScannerActivity extends AppCompatActivity {
|
||||
|
||||
public static final int RESULT_ERROR = -2;
|
||||
|
||||
private ActivityQrScannerBinding binding;
|
||||
|
||||
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
|
||||
@ -47,7 +53,7 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
protected void onCreate(@Nullable Bundle 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);
|
||||
}
|
||||
|
||||
@ -56,7 +62,6 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
|
||||
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
||||
cameraProviderFuture.addListener(() -> {
|
||||
Log.i("AMOGUS", "Got something");
|
||||
try {
|
||||
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
|
||||
bindPreview(cameraProvider);
|
||||
@ -66,13 +71,14 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(this));
|
||||
|
||||
BarcodeScannerOptions opts = new BarcodeScannerOptions.Builder()
|
||||
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
|
||||
.build();
|
||||
|
||||
scanner = BarcodeScanning.getClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
|
||||
void bindPreview(@NonNull ProcessCameraProvider cameraProvider) {
|
||||
Preview preview = new Preview.Builder()
|
||||
.build();
|
||||
@ -84,15 +90,24 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
preview.setSurfaceProvider(binding.preview.getSurfaceProvider());
|
||||
|
||||
ImageAnalysis analysis = new ImageAnalysis.Builder()
|
||||
//.setTargetResolution(new Size(1280, 720))
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build();
|
||||
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), new Amogus());
|
||||
analysis.setAnalyzer(ContextCompat.getMainExecutor(this), new QRAnalyzer());
|
||||
|
||||
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)
|
||||
@Override
|
||||
@ -101,13 +116,11 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
if(mediaImage != null) {
|
||||
InputImage input = InputImage.fromMediaImage(mediaImage, image.getImageInfo().getRotationDegrees());
|
||||
scanner.process(input).addOnSuccessListener(barcodes -> {
|
||||
//Log.i("AMOGUS", "found " + barcodes.size() + " codes");
|
||||
image.close();
|
||||
|
||||
if(barcodes.size() >= 1) {
|
||||
Barcode code = null;
|
||||
|
||||
Log.i("AMOGUS", "TYPE " + barcodes.get(0).getValueType());
|
||||
for(Barcode c : barcodes) {
|
||||
if(c.getValueType() == Barcode.TYPE_TEXT) {
|
||||
code = c;
|
||||
@ -118,58 +131,10 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
if(code == null) return;
|
||||
|
||||
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 {
|
||||
fType = OTPType.valueOf(type.toUpperCase());
|
||||
success(OTPParser.parse(uri));
|
||||
}catch(IllegalArgumentException e) {
|
||||
Log.i("AMOGUS", e.toString());
|
||||
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;
|
||||
error(e.getMessage());
|
||||
}
|
||||
}
|
||||
}).addOnFailureListener(e -> {});
|
||||
@ -185,7 +150,9 @@ public class QRScannerActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void error(String errorMessage) {
|
||||
setResult(Activity.RESULT_CANCELED, null);
|
||||
Intent result = new Intent();
|
||||
result.putExtra("error", errorMessage);
|
||||
setResult(RESULT_ERROR, result);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator.OTPData;
|
||||
|
||||
public class QRScannerContract extends ActivityResultContract<Void, OTPData> {
|
||||
public class QRScannerContract extends ActivityResultContract<Void, ScannerResult> {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
@ -19,12 +19,16 @@ public class QRScannerContract extends ActivityResultContract<Void, OTPData> {
|
||||
}
|
||||
|
||||
@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(intent != null) {
|
||||
return new ScannerResult(intent.getStringExtra("error"));
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// TODO: refactor
|
||||
public static List<OTPData> getOTPs(SharedPreferences prefs, String group) {
|
||||
String currentOTPs = prefs.getString("group." + group, "[]");
|
||||
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));
|
||||
}
|
||||
|
||||
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">
|
||||
<!-- Base application theme. -->
|
||||
<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="colorSecondary">@color/colorSecondary</item>
|
||||
<item name="colorSecondary">?attr/colorTheme2</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
|
||||
<item name="colorTheme1">#FF00FF</item>
|
||||
|
Loading…
Reference in New Issue
Block a user