mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-22 09:12:17 -05:00
Improvements to PasswordActivity:
* Refactoring (removed duplicate code for loading Challenge/OTP aux files) * Added possibility to select aux file location manually if automatic "detection" (by constructing file paths) doesn't work. This is required so that aux files can be used with SAF which doesn't allow file path construction (maybe with OPEN_DOCUMENT_TREE, but that would involve additional user action and would require more permissions than required). Manually selected aux file location is stored in preferences * Fixed bug that OTP/Challenge mode wasn't correctly remembered
This commit is contained in:
parent
80b6a1d287
commit
6c3795ff1a
@ -90,6 +90,7 @@ namespace keepass2android
|
|||||||
private const int RequestCodeChallengeYubikey = 1002;
|
private const int RequestCodeChallengeYubikey = 1002;
|
||||||
private const int RequestCodeSelectKeyfile = 1003;
|
private const int RequestCodeSelectKeyfile = 1003;
|
||||||
private const int RequestCodePrepareKeyFile = 1004;
|
private const int RequestCodePrepareKeyFile = 1004;
|
||||||
|
private const int RequestCodeSelectAuxFile = 1005;
|
||||||
|
|
||||||
|
|
||||||
private Task<MemoryStream> _loadDbTask;
|
private Task<MemoryStream> _loadDbTask;
|
||||||
@ -128,6 +129,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
private bool _starting;
|
private bool _starting;
|
||||||
private OtpInfo _otpInfo;
|
private OtpInfo _otpInfo;
|
||||||
|
private IOConnectionInfo _otpAuxIoc;
|
||||||
private ChallengeInfo _chalInfo;
|
private ChallengeInfo _chalInfo;
|
||||||
private byte[] _challengeSecret;
|
private byte[] _challengeSecret;
|
||||||
private KeeChallengeProv _challengeProv;
|
private KeeChallengeProv _challengeProv;
|
||||||
@ -313,16 +315,22 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
if (requestCode == RequestCodePrepareOtpAuxFile)
|
if (requestCode == RequestCodePrepareOtpAuxFile)
|
||||||
{
|
{
|
||||||
if (_keyFileOrProvider == KeyProviderIdChallenge)
|
GetAuxFileLoader().LoadAuxFile(true);
|
||||||
{
|
|
||||||
LoadChalFile();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
LoadOtpFile ();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (requestCode == RequestCodeSelectAuxFile && resultCode == Result.Ok)
|
||||||
|
{
|
||||||
|
IOConnectionInfo auxFileIoc = new IOConnectionInfo();
|
||||||
|
SetIoConnectionFromIntent(auxFileIoc, data);
|
||||||
|
|
||||||
|
PreferenceManager.GetDefaultSharedPreferences(this).Edit()
|
||||||
|
.PutString("KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(_ioConnection),
|
||||||
|
IOConnectionInfo.SerializeToString(auxFileIoc))
|
||||||
|
.Apply();
|
||||||
|
|
||||||
|
GetAuxFileLoader().LoadAuxFile(false);
|
||||||
|
}
|
||||||
if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok)
|
if (requestCode == RequestCodeChallengeYubikey && resultCode == Result.Ok)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -382,7 +390,17 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AuxFileLoader GetAuxFileLoader()
|
||||||
|
{
|
||||||
|
if (_keyFileOrProvider == KeyProviderIdChallenge)
|
||||||
|
{
|
||||||
|
return new ChallengeAuxFileLoader(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new OtpAuxFileLoader(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
private void UpdateKeyfileIocView()
|
private void UpdateKeyfileIocView()
|
||||||
{
|
{
|
||||||
//store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file
|
//store keyfile in the view so that we can show the selected keyfile again if the user switches to another key provider and back to key file
|
||||||
@ -416,101 +434,205 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void LoadOtpFile()
|
|
||||||
|
private abstract class AuxFileLoader
|
||||||
{
|
{
|
||||||
new LoadingDialog<object, object, object>(this, true,
|
protected readonly PasswordActivity Activity;
|
||||||
//doInBackground
|
|
||||||
delegate
|
protected AuxFileLoader(PasswordActivity activity)
|
||||||
{
|
{
|
||||||
try
|
Activity = activity;
|
||||||
{
|
|
||||||
_otpInfo = OathHotpKeyProv.LoadOtpInfo(new KeyProviderQueryContext(_ioConnection, false, false));
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Kp2aLog.Log(e.ToString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
public void LoadAuxFile(bool triggerSelectAuxManuallyOnFailure)
|
||||||
},
|
|
||||||
//onPostExecute
|
|
||||||
delegate
|
|
||||||
{
|
{
|
||||||
if (_otpInfo == null)
|
new LoadingDialog<object, object, object>(Activity, true,
|
||||||
{
|
|
||||||
Toast.MakeText(this,
|
|
||||||
GetString(Resource.String.CouldntLoadOtpAuxFile) + " " + GetString(Resource.String.CouldntLoadOtpAuxFile_Hint)
|
|
||||||
, ToastLength.Long).Show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IList<string> prefilledOtps = _pendingOtps;
|
|
||||||
ShowOtpEntry(prefilledOtps);
|
|
||||||
_pendingOtps.Clear();
|
|
||||||
|
|
||||||
}
|
|
||||||
).Execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadChalFile()
|
|
||||||
{
|
|
||||||
new LoadingDialog<object, object, object>(this, true,
|
|
||||||
//doInBackground
|
//doInBackground
|
||||||
delegate
|
delegate
|
||||||
{
|
{
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IFileStorage fileStorage =
|
var iocAux = GetDefaultAuxLocation();
|
||||||
App.Kp2a.GetOtpAuxFileStorage(_ioConnection);
|
LoadFile(iocAux);
|
||||||
IOConnectionInfo iocAux =
|
|
||||||
fileStorage.GetFilePath(
|
|
||||||
fileStorage.GetParentPath(_ioConnection),
|
|
||||||
fileStorage.GetFilenameWithoutPathAndExt(_ioConnection) +
|
|
||||||
".xml");
|
|
||||||
|
|
||||||
_chalInfo = ChallengeInfo.Load(iocAux);
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
|
//retry with saved ioc
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var savedManualIoc = IOConnectionInfo.UnserializeFromString(
|
||||||
|
PreferenceManager.GetDefaultSharedPreferences(Activity).GetString(
|
||||||
|
"KP2A.PasswordAct.AuxFileIoc" + IOConnectionInfo.SerializeToString(Activity._ioConnection), null));
|
||||||
|
|
||||||
|
LoadFile((savedManualIoc));
|
||||||
|
}
|
||||||
|
catch (Exception e2)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log(e2.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
, delegate
|
, delegate
|
||||||
{
|
{
|
||||||
if (_chalInfo == null)
|
if (!AuxDataLoaded)
|
||||||
{
|
{
|
||||||
Toast.MakeText(this,
|
if (triggerSelectAuxManuallyOnFailure)
|
||||||
GetString(Resource.String.CouldntLoadChalAuxFile) +
|
|
||||||
" " +
|
|
||||||
GetString(
|
|
||||||
Resource.String.CouldntLoadChalAuxFile_Hint)
|
|
||||||
, ToastLength.Long).Show();
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE");
|
|
||||||
chalIntent.PutExtra("challenge", _chalInfo.Challenge);
|
|
||||||
chalIntent.PutExtra("slot", 2);
|
|
||||||
IList<ResolveInfo> activities = PackageManager.QueryIntentActivities(chalIntent, 0);
|
|
||||||
bool isIntentSafe = activities.Count > 0;
|
|
||||||
if (isIntentSafe)
|
|
||||||
{
|
{
|
||||||
StartActivityForResult(chalIntent, RequestCodeChallengeYubikey);
|
Intent intent = new Intent(Activity, typeof(SelectStorageLocationActivity));
|
||||||
|
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppGet, true);
|
||||||
|
intent.PutExtra(FileStorageSelectionActivity.AllowThirdPartyAppSend, false);
|
||||||
|
intent.PutExtra(FileStorageSetupDefs.ExtraIsForSave, false);
|
||||||
|
intent.PutExtra(SelectStorageLocationActivity.ExtraKeyWritableRequirements, (int)SelectStorageLocationActivity.WritableRequirements.WriteDemanded);
|
||||||
|
Activity.StartActivityForResult(intent, RequestCodeSelectAuxFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AlertDialog.Builder b = new AlertDialog.Builder(this);
|
Toast.MakeText(Activity,GetErrorMessage(), ToastLength.Long).Show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
}
|
||||||
|
HandleSuccess();
|
||||||
|
}).Execute();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract bool AuxDataLoaded { get; }
|
||||||
|
|
||||||
|
protected abstract void LoadFile(IOConnectionInfo iocAux);
|
||||||
|
|
||||||
|
protected abstract void HandleSuccess();
|
||||||
|
|
||||||
|
protected abstract string GetErrorMessage();
|
||||||
|
|
||||||
|
protected abstract IOConnectionInfo GetDefaultAuxLocation();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OtpAuxFileLoader : AuxFileLoader
|
||||||
|
{
|
||||||
|
public OtpAuxFileLoader(PasswordActivity activity) : base(activity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AuxDataLoaded
|
||||||
|
{
|
||||||
|
get { return Activity._otpInfo != null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadFile(IOConnectionInfo iocAux)
|
||||||
|
{
|
||||||
|
Activity._otpInfo = OtpInfo.Load(iocAux);
|
||||||
|
Activity._otpAuxIoc = iocAux;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc)
|
||||||
|
{
|
||||||
|
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc);
|
||||||
|
var parentPath = fileStorage.GetParentPath(databaseIoc);
|
||||||
|
var filename = fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + OathHotpKeyProv.AuxFileExt;
|
||||||
|
IOConnectionInfo iocAux = fileStorage.GetFilePath(parentPath, filename);
|
||||||
|
return iocAux;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
||||||
|
{
|
||||||
|
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
||||||
|
var iocAux = GetAuxFileIoc(ioc);
|
||||||
|
|
||||||
|
return iocAux;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleSuccess()
|
||||||
|
{
|
||||||
|
IList<string> prefilledOtps = Activity._pendingOtps;
|
||||||
|
Activity.ShowOtpEntry(prefilledOtps);
|
||||||
|
Activity._pendingOtps.Clear();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string GetErrorMessage()
|
||||||
|
{
|
||||||
|
return Activity.GetString(Resource.String.CouldntLoadOtpAuxFile) + " " +
|
||||||
|
Activity.GetString(Resource.String.CouldntLoadOtpAuxFile_Hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IOConnectionInfo GetDefaultAuxLocation()
|
||||||
|
{
|
||||||
|
return GetAuxFileIoc(Activity._ioConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChallengeAuxFileLoader : AuxFileLoader
|
||||||
|
{
|
||||||
|
public ChallengeAuxFileLoader(PasswordActivity activity) : base(activity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void HandleSuccess()
|
||||||
|
{
|
||||||
|
Intent chalIntent = new Intent("com.yubichallenge.NFCActivity.CHALLENGE");
|
||||||
|
chalIntent.PutExtra("challenge", Activity._chalInfo.Challenge);
|
||||||
|
chalIntent.PutExtra("slot", 2);
|
||||||
|
IList<ResolveInfo> activities = Activity.PackageManager.QueryIntentActivities(chalIntent, 0);
|
||||||
|
bool isIntentSafe = activities.Count > 0;
|
||||||
|
if (isIntentSafe)
|
||||||
|
{
|
||||||
|
Activity.StartActivityForResult(chalIntent, RequestCodeChallengeYubikey);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AlertDialog.Builder b = new AlertDialog.Builder(Activity);
|
||||||
b.SetMessage(Resource.String.YubiChallengeNotInstalled);
|
b.SetMessage(Resource.String.YubiChallengeNotInstalled);
|
||||||
b.SetPositiveButton(Android.Resource.String.Ok, delegate {
|
b.SetPositiveButton(Android.Resource.String.Ok,
|
||||||
Util.GotoUrl(this, GetString(Resource.String.MarketURL) + "com.yubichallenge");
|
delegate
|
||||||
|
{
|
||||||
|
Util.GotoUrl(Activity, Activity.GetString(Resource.String.MarketURL) + "com.yubichallenge");
|
||||||
});
|
});
|
||||||
b.SetNegativeButton(Resource.String.cancel, delegate { });
|
b.SetNegativeButton(Resource.String.cancel, delegate { });
|
||||||
b.Create().Show();
|
b.Create().Show();
|
||||||
}
|
}
|
||||||
}).Execute();
|
}
|
||||||
|
|
||||||
|
protected override string GetErrorMessage()
|
||||||
|
{
|
||||||
|
return Activity.GetString(Resource.String.CouldntLoadChalAuxFile) +
|
||||||
|
" " +
|
||||||
|
Activity.GetString(
|
||||||
|
Resource.String.CouldntLoadChalAuxFile_Hint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool AuxDataLoaded
|
||||||
|
{
|
||||||
|
get { return Activity._chalInfo != null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadFile(IOConnectionInfo iocAux)
|
||||||
|
{
|
||||||
|
Activity._chalInfo = ChallengeInfo.Load(iocAux);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected override IOConnectionInfo GetDefaultAuxLocation()
|
||||||
|
{
|
||||||
|
IFileStorage fileStorage =
|
||||||
|
App.Kp2a.GetOtpAuxFileStorage(Activity._ioConnection);
|
||||||
|
IOConnectionInfo iocAux =
|
||||||
|
fileStorage.GetFilePath(
|
||||||
|
fileStorage.GetParentPath(Activity._ioConnection),
|
||||||
|
fileStorage.GetFilenameWithoutPathAndExt(Activity._ioConnection) +
|
||||||
|
".xml");
|
||||||
|
return iocAux;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowOtpEntry(IList<string> prefilledOtps)
|
private void ShowOtpEntry(IList<string> prefilledOtps)
|
||||||
@ -1352,6 +1474,8 @@ namespace keepass2android
|
|||||||
if (String.IsNullOrEmpty(keyfile))
|
if (String.IsNullOrEmpty(keyfile))
|
||||||
return null; //signal no key file
|
return null; //signal no key file
|
||||||
|
|
||||||
|
if (KeyProviderType == KeyProviders.KeyFile)
|
||||||
|
{
|
||||||
//test if the filename is properly encoded.
|
//test if the filename is properly encoded.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -1369,6 +1493,7 @@ namespace keepass2android
|
|||||||
return serializedKeyFile;
|
return serializedKeyFile;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return keyfile;
|
return keyfile;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
@ -1537,11 +1662,11 @@ namespace keepass2android
|
|||||||
StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);
|
StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);
|
||||||
|
|
||||||
KeyProviderQueryContext ctx = new KeyProviderQueryContext(_act._ioConnection, false, false);
|
KeyProviderQueryContext ctx = new KeyProviderQueryContext(_act._ioConnection, false, false);
|
||||||
IOConnectionInfo auxFileIoc = OathHotpKeyProv.GetAuxFileIoc(_act._ioConnection);
|
|
||||||
if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, ctx, auxFileIoc))
|
if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, ctx, _act._otpAuxIoc))
|
||||||
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile), ToastLength.Long).Show();
|
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile), ToastLength.Long).Show();
|
||||||
|
|
||||||
App.Kp2a.GetDb().OtpAuxFileIoc = auxFileIoc;
|
App.Kp2a.GetDb().OtpAuxFileIoc = _act._otpAuxIoc;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<activity android:name="com.dropbox.client2.android.AuthActivity" android:launchMode="singleTask" android:configChanges="orientation|keyboard">
|
<activity android:name="com.dropbox.client2.android.AuthActivity" android:launchMode="singleTask" android:configChanges="orientation|keyboard">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<data android:scheme="db-i8shu7v1hgh7ynt" />
|
<data android:scheme="db-2gormiq7iq1jls1" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
@ -32,7 +32,7 @@ namespace OtpKeyProv
|
|||||||
public sealed class OathHotpKeyProv
|
public sealed class OathHotpKeyProv
|
||||||
/*removed base class KeyProvider because "synchronous" interface is not suitable on Android*/
|
/*removed base class KeyProvider because "synchronous" interface is not suitable on Android*/
|
||||||
{
|
{
|
||||||
private const string AuxFileExt = ".otp.xml";
|
public const string AuxFileExt = ".otp.xml";
|
||||||
private const string ProvType = "OATH HOTP / RFC 4226";
|
private const string ProvType = "OATH HOTP / RFC 4226";
|
||||||
private const string ProvVersion = "2.0"; // File version, not OtpKeyProv version
|
private const string ProvVersion = "2.0"; // File version, not OtpKeyProv version
|
||||||
|
|
||||||
@ -46,26 +46,7 @@ namespace OtpKeyProv
|
|||||||
public const string ProductName = "OtpKeyProv KeePass Plugin";
|
public const string ProductName = "OtpKeyProv KeePass Plugin";
|
||||||
|
|
||||||
|
|
||||||
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
|
||||||
{
|
|
||||||
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
|
||||||
var iocAux = GetAuxFileIoc(ioc);
|
|
||||||
|
|
||||||
return iocAux;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc)
|
|
||||||
{
|
|
||||||
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc);
|
|
||||||
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(databaseIoc),
|
|
||||||
fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + AuxFileExt);
|
|
||||||
return iocAux;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OtpInfo LoadOtpInfo(KeyProviderQueryContext ctx)
|
|
||||||
{
|
|
||||||
return OtpInfo.Load(GetAuxFileIoc(ctx));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
private static byte[] Open(KeyProviderQueryContext ctx, OtpInfo otpInfo)
|
private static byte[] Open(KeyProviderQueryContext ctx, OtpInfo otpInfo)
|
||||||
|
Loading…
Reference in New Issue
Block a user