mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-28 20:12:21 -05:00
extended implementation of OTP
This commit is contained in:
parent
66cd05b9f4
commit
aeaba47573
@ -60,7 +60,7 @@ namespace keepass2android.Io
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CachingFileStorage: IFileStorage
|
public class CachingFileStorage: IFileStorage
|
||||||
{
|
{
|
||||||
private readonly IFileStorage _cachedStorage;
|
protected readonly IFileStorage _cachedStorage;
|
||||||
private readonly ICacheSupervisor _cacheSupervisor;
|
private readonly ICacheSupervisor _cacheSupervisor;
|
||||||
private readonly string _streamCacheDir;
|
private readonly string _streamCacheDir;
|
||||||
|
|
||||||
@ -179,14 +179,21 @@ namespace keepass2android.Io
|
|||||||
{
|
{
|
||||||
if (TryUpdateRemoteFile(localData, ioc, true, hash))
|
if (TryUpdateRemoteFile(localData, ioc, true, hash))
|
||||||
_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc);
|
_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc);
|
||||||
|
return File.OpenRead(cachedFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//conflict: both files changed.
|
//conflict: both files changed.
|
||||||
//signal that we're loading from local
|
return OpenFileForReadWithConflict(ioc, cachedFilePath);
|
||||||
_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
|
||||||
|
{
|
||||||
|
//signal that we're loading from local
|
||||||
|
_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
|
||||||
return File.OpenRead(cachedFilePath);
|
return File.OpenRead(cachedFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,29 +221,17 @@ namespace keepass2android.Io
|
|||||||
|
|
||||||
private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
|
private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
|
||||||
{
|
{
|
||||||
//open stream:
|
|
||||||
using (Stream file = _cachedStorage.OpenFileForRead(ioc))
|
//remember current hash
|
||||||
{
|
string previousHash = null;
|
||||||
|
string baseVersionFilePath = BaseVersionFilePath(ioc);
|
||||||
|
if (File.Exists(baseVersionFilePath))
|
||||||
|
previousHash = File.ReadAllText(baseVersionFilePath);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//copy to cache:
|
//copy to cache:
|
||||||
//note: we might use the file version to check if it's already in the cache and if copying is required.
|
var fileHash = UpdateCacheFromRemote(ioc, cachedFilePath);
|
||||||
//However, this is safer.
|
|
||||||
string fileHash;
|
|
||||||
using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
|
|
||||||
{
|
|
||||||
file.CopyTo(cachedFile);
|
|
||||||
cachedFile.Close();
|
|
||||||
fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
//remember current hash
|
|
||||||
string previousHash = null;
|
|
||||||
string baseVersionFilePath = BaseVersionFilePath(ioc);
|
|
||||||
if (File.Exists(baseVersionFilePath))
|
|
||||||
previousHash = File.ReadAllText(baseVersionFilePath);
|
|
||||||
|
|
||||||
//save hash in cache files:
|
|
||||||
File.WriteAllText(VersionFilePath(ioc), fileHash);
|
|
||||||
File.WriteAllText(baseVersionFilePath, fileHash);
|
|
||||||
|
|
||||||
//notify supervisor what we did:
|
//notify supervisor what we did:
|
||||||
if (previousHash != fileHash)
|
if (previousHash != fileHash)
|
||||||
@ -245,8 +240,35 @@ namespace keepass2android.Io
|
|||||||
_cacheSupervisor.LoadedFromRemoteInSync(ioc);
|
_cacheSupervisor.LoadedFromRemoteInSync(ioc);
|
||||||
|
|
||||||
return File.OpenRead(cachedFilePath);
|
return File.OpenRead(cachedFilePath);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// copies the file in ioc to the local cache. Updates the cache version files and returns the new file hash.
|
||||||
|
/// </summary>
|
||||||
|
protected string UpdateCacheFromRemote(IOConnectionInfo ioc, string cachedFilePath)
|
||||||
|
{
|
||||||
|
//note: we might use the file version to check if it's already in the cache and if copying is required.
|
||||||
|
//However, this is safer.
|
||||||
|
string fileHash;
|
||||||
|
|
||||||
|
//open stream:
|
||||||
|
using (Stream remoteFile = _cachedStorage.OpenFileForRead(ioc))
|
||||||
|
{
|
||||||
|
|
||||||
|
using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
|
||||||
|
{
|
||||||
|
remoteFile.CopyTo(cachedFile);
|
||||||
|
cachedFile.Close();
|
||||||
|
fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//save hash in cache files:
|
||||||
|
File.WriteAllText(VersionFilePath(ioc), fileHash);
|
||||||
|
File.WriteAllText(BaseVersionFilePath(ioc), fileHash);
|
||||||
|
return fileHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
||||||
@ -266,7 +288,7 @@ namespace keepass2android.Io
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
protected void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
||||||
{
|
{
|
||||||
//try to write to remote:
|
//try to write to remote:
|
||||||
using (
|
using (
|
||||||
|
@ -44,6 +44,8 @@ namespace keepass2android
|
|||||||
CheckingDatabaseForChanges,
|
CheckingDatabaseForChanges,
|
||||||
RemoteDatabaseUnchanged,
|
RemoteDatabaseUnchanged,
|
||||||
CannotMoveGroupHere,
|
CannotMoveGroupHere,
|
||||||
ErrorOcurred
|
ErrorOcurred,
|
||||||
|
SynchronizingOtpAuxFile,
|
||||||
|
SavingOtpAuxFile
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -44,6 +44,13 @@ namespace keepass2android
|
|||||||
return KpDatabase == null ? null : KpDatabase.IOConnectionInfo;
|
return KpDatabase == null ? null : KpDatabase.IOConnectionInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// if an OTP key was used, this property tells the location of the OTP auxiliary file.
|
||||||
|
/// Must be set after loading.
|
||||||
|
/// </summary>
|
||||||
|
public IOConnectionInfo OtpAuxFileIoc { get; set; }
|
||||||
|
|
||||||
public string LastFileVersion;
|
public string LastFileVersion;
|
||||||
public SearchDbHelper SearchHelper;
|
public SearchDbHelper SearchHelper;
|
||||||
|
|
||||||
@ -192,6 +199,7 @@ namespace keepass2android
|
|||||||
KpDatabase = null;
|
KpDatabase = null;
|
||||||
_loaded = false;
|
_loaded = false;
|
||||||
_reloadRequested = false;
|
_reloadRequested = false;
|
||||||
|
OtpAuxFileIoc = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MarkAllGroupsAsDirty() {
|
public void MarkAllGroupsAsDirty() {
|
||||||
|
@ -25,6 +25,7 @@ using Android.Widget;
|
|||||||
using KeePassLib;
|
using KeePassLib;
|
||||||
using Android.Preferences;
|
using Android.Preferences;
|
||||||
using KeePassLib.Interfaces;
|
using KeePassLib.Interfaces;
|
||||||
|
using KeePassLib.Serialization;
|
||||||
using KeePassLib.Utility;
|
using KeePassLib.Utility;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
using keepass2android.database.edit;
|
using keepass2android.database.edit;
|
||||||
@ -367,6 +368,37 @@ namespace keepass2android
|
|||||||
return base.OnOptionsItemSelected(item);
|
return base.OnOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SyncOtpAuxFile: OnFinish
|
||||||
|
{
|
||||||
|
private readonly IOConnectionInfo _ioc;
|
||||||
|
|
||||||
|
public SyncOtpAuxFile(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_ioc = ioc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Run()
|
||||||
|
{
|
||||||
|
if (Handler != null)
|
||||||
|
{
|
||||||
|
Handler.Post(DoSyncOtpAuxFile);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DoSyncOtpAuxFile();
|
||||||
|
base.Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoSyncOtpAuxFile()
|
||||||
|
{
|
||||||
|
StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile);
|
||||||
|
//simply open the file. The file storage does a complete sync.
|
||||||
|
using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Synchronize()
|
private void Synchronize()
|
||||||
{
|
{
|
||||||
var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc);
|
var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc);
|
||||||
|
@ -3,6 +3,7 @@ using Android.App;
|
|||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Android.Widget;
|
||||||
using Java.Util.Regex;
|
using Java.Util.Regex;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
@ -54,8 +55,30 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
i.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
i.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
i.PutExtra(Intents.OtpExtraKey, GetOtpFromIntent(Intent));
|
try
|
||||||
StartActivity(i);
|
{
|
||||||
|
string otp = GetOtpFromIntent(Intent);
|
||||||
|
if (otp == null)
|
||||||
|
throw new Exception("Otp must not be null!");
|
||||||
|
i.PutExtra(Intents.OtpExtraKey, otp);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log(e.ToString());
|
||||||
|
Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show();
|
||||||
|
Finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (App.Kp2a.GetDb().Loaded)
|
||||||
|
{
|
||||||
|
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_db_open), ToastLength.Long).Show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartActivity(i);
|
||||||
|
}
|
||||||
|
|
||||||
Finish();
|
Finish();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Xml.Serialization;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.Database;
|
using Android.Database;
|
||||||
@ -27,15 +30,17 @@ using Android.Views;
|
|||||||
using Android.Widget;
|
using Android.Widget;
|
||||||
using Java.Net;
|
using Java.Net;
|
||||||
using Android.Preferences;
|
using Android.Preferences;
|
||||||
using Java.IO;
|
|
||||||
using Android.Text;
|
using Android.Text;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using KeePassLib.Keys;
|
using KeePassLib.Keys;
|
||||||
using KeePassLib.Serialization;
|
using KeePassLib.Serialization;
|
||||||
|
using KeePassLib.Utility;
|
||||||
using OtpKeyProv;
|
using OtpKeyProv;
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
using keepass2android.Utils;
|
using keepass2android.Utils;
|
||||||
using Exception = System.Exception;
|
using Exception = System.Exception;
|
||||||
|
using File = Java.IO.File;
|
||||||
|
using FileNotFoundException = Java.IO.FileNotFoundException;
|
||||||
using MemoryStream = System.IO.MemoryStream;
|
using MemoryStream = System.IO.MemoryStream;
|
||||||
using Object = Java.Lang.Object;
|
using Object = Java.Lang.Object;
|
||||||
using Process = Android.OS.Process;
|
using Process = Android.OS.Process;
|
||||||
@ -55,11 +60,10 @@ namespace keepass2android
|
|||||||
//int values correspond to indices in passwordSpinner
|
//int values correspond to indices in passwordSpinner
|
||||||
None = 0,
|
None = 0,
|
||||||
KeyFile = 1,
|
KeyFile = 1,
|
||||||
Otp = 2
|
Otp = 2,
|
||||||
|
OtpRecovery = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _showPassword;
|
|
||||||
|
|
||||||
public const String KeyDefaultFilename = "defaultFileName";
|
public const String KeyDefaultFilename = "defaultFileName";
|
||||||
|
|
||||||
public const String KeyFilename = "fileName";
|
public const String KeyFilename = "fileName";
|
||||||
@ -71,19 +75,23 @@ namespace keepass2android
|
|||||||
private const String ViewIntent = "android.intent.action.VIEW";
|
private const String ViewIntent = "android.intent.action.VIEW";
|
||||||
private const string ShowpasswordKey = "ShowPassword";
|
private const string ShowpasswordKey = "ShowPassword";
|
||||||
private const string KeyProviderIdOtp = "KP2A-OTP";
|
private const string KeyProviderIdOtp = "KP2A-OTP";
|
||||||
|
private const string KeyProviderIdOtpRecovery = "KP2A-OTPSecret";
|
||||||
|
|
||||||
|
private const int RequestCodePrepareDbFile = 1000;
|
||||||
|
private const int RequestCodePrepareOtpAuxFile = 1001;
|
||||||
|
|
||||||
|
|
||||||
private Task<MemoryStream> _loadDbTask;
|
private Task<MemoryStream> _loadDbTask;
|
||||||
private IOConnectionInfo _ioConnection;
|
private IOConnectionInfo _ioConnection;
|
||||||
private String _keyFileOrProvider;
|
private String _keyFileOrProvider;
|
||||||
|
bool _showPassword;
|
||||||
|
|
||||||
internal AppTask AppTask;
|
internal AppTask AppTask;
|
||||||
private bool _killOnDestroy;
|
private bool _killOnDestroy;
|
||||||
private string _password = "";
|
private string _password = "";
|
||||||
//OTPs which should be entered into the OTP fields as soon as these become visible
|
//OTPs which should be entered into the OTP fields as soon as these become visible
|
||||||
private readonly List<String> _pendingOtps = new List<string>();
|
private List<String> _pendingOtps = new List<string>();
|
||||||
|
|
||||||
private const int RequestCodePrepareDbFile = 1000;
|
|
||||||
private const int RequestCodePrepareOtpAuxFile = 1001;
|
|
||||||
|
|
||||||
|
|
||||||
KeyProviders KeyProviderType
|
KeyProviders KeyProviderType
|
||||||
@ -94,6 +102,8 @@ namespace keepass2android
|
|||||||
return KeyProviders.None;
|
return KeyProviders.None;
|
||||||
if (_keyFileOrProvider == KeyProviderIdOtp)
|
if (_keyFileOrProvider == KeyProviderIdOtp)
|
||||||
return KeyProviders.Otp;
|
return KeyProviders.Otp;
|
||||||
|
if (_keyFileOrProvider == KeyProviderIdOtpRecovery)
|
||||||
|
return KeyProviders.OtpRecovery;
|
||||||
return KeyProviders.KeyFile;
|
return KeyProviders.KeyFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,7 +113,12 @@ namespace keepass2android
|
|||||||
|
|
||||||
private bool _starting;
|
private bool _starting;
|
||||||
private OtpInfo _otpInfo;
|
private OtpInfo _otpInfo;
|
||||||
private readonly int[] _otpTextViewIds = new int[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
|
private readonly int[] _otpTextViewIds = new[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
|
||||||
|
private const string OtpInfoKey = "OtpInfoKey";
|
||||||
|
private const string EnteredOtpsKey = "EnteredOtpsKey";
|
||||||
|
private const string PendingOtpsKey = "PendingOtpsKey";
|
||||||
|
private const string PasswordKey = "PasswordKey";
|
||||||
|
private const string KeyFileOrProviderKey = "KeyFileOrProviderKey";
|
||||||
|
|
||||||
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
|
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
|
||||||
: base(javaReference, transfer)
|
: base(javaReference, transfer)
|
||||||
@ -217,7 +232,7 @@ namespace keepass2android
|
|||||||
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
|
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
|
||||||
|
|
||||||
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
|
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
|
||||||
_keyFileOrProvider = kcpKeyfile.Path;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
App.Kp2a.LockDatabase(false);
|
App.Kp2a.LockDatabase(false);
|
||||||
@ -234,7 +249,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
|
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
|
||||||
fn.Text = filename;
|
fn.Text = filename;
|
||||||
_keyFileOrProvider = filename;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -265,51 +280,54 @@ namespace keepass2android
|
|||||||
Toast.MakeText(this, GetString(Resource.String.CouldntLoadOtpAuxFile), ToastLength.Long).Show();
|
Toast.MakeText(this, GetString(Resource.String.CouldntLoadOtpAuxFile), ToastLength.Long).Show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;
|
|
||||||
FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible;
|
|
||||||
int c = 0;
|
|
||||||
|
|
||||||
foreach (int otpId in _otpTextViewIds)
|
IList<string> prefilledOtps = _pendingOtps;
|
||||||
{
|
ShowOtpEntry(prefilledOtps);
|
||||||
c++;
|
|
||||||
var otpTextView = FindViewById<EditText>(otpId);
|
|
||||||
if (c <= _pendingOtps.Count)
|
|
||||||
{
|
|
||||||
otpTextView.Text = _pendingOtps[c-1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
otpTextView.Text = "";
|
|
||||||
}
|
|
||||||
otpTextView.Hint = GetString(Resource.String.otp_hint, new Object[] {c});
|
|
||||||
otpTextView.SetFilters(new IInputFilter[] {new InputFilterLengthFilter((int)_otpInfo.OtpLength) });
|
|
||||||
if (c > _otpInfo.OtpsRequired)
|
|
||||||
{
|
|
||||||
otpTextView.Visibility = ViewStates.Gone;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
otpTextView.TextChanged += (sender, args) =>
|
|
||||||
{
|
|
||||||
UpdateOkButtonState();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_pendingOtps.Clear();
|
_pendingOtps.Clear();
|
||||||
|
|
||||||
}
|
}
|
||||||
).Execute();
|
).Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowOtpEntry(IList<string> prefilledOtps)
|
||||||
|
{
|
||||||
|
FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;
|
||||||
|
FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible;
|
||||||
|
int c = 0;
|
||||||
|
|
||||||
|
foreach (int otpId in _otpTextViewIds)
|
||||||
|
{
|
||||||
|
c++;
|
||||||
|
var otpTextView = FindViewById<EditText>(otpId);
|
||||||
|
if (c <= prefilledOtps.Count)
|
||||||
|
{
|
||||||
|
otpTextView.Text = prefilledOtps[c - 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
otpTextView.Text = "";
|
||||||
|
}
|
||||||
|
otpTextView.Hint = GetString(Resource.String.otp_hint, new Object[] {c});
|
||||||
|
otpTextView.SetFilters(new IInputFilter[] {new InputFilterLengthFilter((int) _otpInfo.OtpLength)});
|
||||||
|
if (c > _otpInfo.OtpsRequired)
|
||||||
|
{
|
||||||
|
otpTextView.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
otpTextView.TextChanged += (sender, args) => { UpdateOkButtonState(); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
if (savedInstanceState != null)
|
|
||||||
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
|
Intent i = Intent;
|
||||||
|
|
||||||
AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
|
AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
|
||||||
|
|
||||||
Intent i = Intent;
|
|
||||||
String action = i.Action;
|
String action = i.Action;
|
||||||
|
|
||||||
_prefs = PreferenceManager.GetDefaultSharedPreferences(this);
|
_prefs = PreferenceManager.GetDefaultSharedPreferences(this);
|
||||||
@ -320,84 +338,11 @@ namespace keepass2android
|
|||||||
|
|
||||||
if (action != null && action.Equals(ViewIntent))
|
if (action != null && action.Equals(ViewIntent))
|
||||||
{
|
{
|
||||||
//started from "view" intent (e.g. from file browser)
|
if (!GetIocFromViewIntent(i)) return;
|
||||||
_ioConnection.Path = i.DataString;
|
|
||||||
|
|
||||||
if (! _ioConnection.Path.Substring(0, 7).Equals("file://"))
|
|
||||||
{
|
|
||||||
//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
|
|
||||||
Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
|
|
||||||
Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ioConnection.Path = URLDecoder.Decode(_ioConnection.Path.Substring(7));
|
|
||||||
|
|
||||||
if (_ioConnection.Path.Length == 0)
|
|
||||||
{
|
|
||||||
// No file name
|
|
||||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
|
||||||
Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File dbFile = new File(_ioConnection.Path);
|
|
||||||
if (! dbFile.Exists())
|
|
||||||
{
|
|
||||||
// File does not exist
|
|
||||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
|
||||||
Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
else if ((action != null) && (action.Equals(Intents.StartWithOtp)))
|
else if ((action != null) && (action.Equals(Intents.StartWithOtp)))
|
||||||
{
|
{
|
||||||
//create called after detecting an OTP via NFC
|
if (!GetIocFromOtpIntent(savedInstanceState, i)) return;
|
||||||
//this means the Activity was not on the back stack before, i.e. no database has been selected
|
|
||||||
|
|
||||||
_ioConnection = null;
|
|
||||||
|
|
||||||
//see if we can get a database from recent:
|
|
||||||
if (App.Kp2a.FileDbHelper.HasRecentFiles())
|
|
||||||
{
|
|
||||||
ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles();
|
|
||||||
StartManagingCursor(filesCursor);
|
|
||||||
filesCursor.MoveToFirst();
|
|
||||||
IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor);
|
|
||||||
if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false)
|
|
||||||
{
|
|
||||||
IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc);
|
|
||||||
|
|
||||||
if (!fileStorage.RequiresCredentials(ioc))
|
|
||||||
{
|
|
||||||
//ok, we can use this file
|
|
||||||
_ioConnection = ioc;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_ioConnection == null)
|
|
||||||
{
|
|
||||||
//We need to go to FileSelectActivity first.
|
|
||||||
//For security reasons: discard the OTP (otherwise the user might not select a database now and forget
|
|
||||||
//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.
|
|
||||||
|
|
||||||
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show();
|
|
||||||
GoToFileSelectActivity();
|
|
||||||
Finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//user obviously wants to use OTP:
|
|
||||||
_keyFileOrProvider = KeyProviderIdOtp;
|
|
||||||
|
|
||||||
//remember the OTP for later use
|
|
||||||
_pendingOtps.Add(Intent.GetStringExtra(Intents.OtpExtraKey));
|
|
||||||
Intent.RemoveExtra(Intents.OtpExtraKey);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -419,16 +364,17 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
SetContentView(Resource.Layout.password);
|
SetContentView(Resource.Layout.password);
|
||||||
PopulateView();
|
InitializeFilenameView();
|
||||||
|
|
||||||
EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
|
if (KeyProviderType == KeyProviders.KeyFile)
|
||||||
|
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
|
||||||
|
|
||||||
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
|
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
|
||||||
(sender, args) =>
|
(sender, args) =>
|
||||||
{
|
{
|
||||||
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
|
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
|
||||||
UpdateOkButtonState();
|
UpdateOkButtonState();
|
||||||
};
|
};
|
||||||
|
|
||||||
FindViewById<EditText>(Resource.Id.password).TextChanged +=
|
FindViewById<EditText>(Resource.Id.password).TextChanged +=
|
||||||
(sender, args) =>
|
(sender, args) =>
|
||||||
@ -437,20 +383,167 @@ namespace keepass2android
|
|||||||
UpdateOkButtonState();
|
UpdateOkButtonState();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
FindViewById<EditText>(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState();
|
||||||
|
|
||||||
|
|
||||||
|
EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
|
||||||
passwordEdit.RequestFocus();
|
passwordEdit.RequestFocus();
|
||||||
Window.SetSoftInputMode(SoftInput.StateVisible);
|
Window.SetSoftInputMode(SoftInput.StateVisible);
|
||||||
|
|
||||||
Button confirmButton = (Button)FindViewById(Resource.Id.pass_ok);
|
InitializeOkButton();
|
||||||
|
|
||||||
|
InitializePasswordModeSpinner();
|
||||||
|
|
||||||
|
InitializeOtpSecretSpinner();
|
||||||
|
|
||||||
|
UpdateOkButtonState();
|
||||||
|
|
||||||
|
InitializeTogglePasswordButton();
|
||||||
|
InitializeKeyfileBrowseButton();
|
||||||
|
|
||||||
|
InitializeQuickUnlockCheckbox();
|
||||||
|
|
||||||
|
RestoreState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeOtpSecretSpinner()
|
||||||
|
{
|
||||||
|
Spinner spinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
|
||||||
|
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, EncodingUtil.Formats);
|
||||||
|
spinner.Adapter = spinnerArrayAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetIocFromOtpIntent(Bundle savedInstanceState, Intent i)
|
||||||
|
{
|
||||||
|
//create called after detecting an OTP via NFC
|
||||||
|
//this means the Activity was not on the back stack before, i.e. no database has been selected
|
||||||
|
|
||||||
|
_ioConnection = null;
|
||||||
|
|
||||||
|
//see if we can get a database from recent:
|
||||||
|
if (App.Kp2a.FileDbHelper.HasRecentFiles())
|
||||||
|
{
|
||||||
|
ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles();
|
||||||
|
StartManagingCursor(filesCursor);
|
||||||
|
filesCursor.MoveToFirst();
|
||||||
|
IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor);
|
||||||
|
if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false)
|
||||||
|
{
|
||||||
|
IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc);
|
||||||
|
|
||||||
|
if (!fileStorage.RequiresCredentials(ioc))
|
||||||
|
{
|
||||||
|
//ok, we can use this file
|
||||||
|
_ioConnection = ioc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_ioConnection == null)
|
||||||
|
{
|
||||||
|
//We need to go to FileSelectActivity first.
|
||||||
|
//For security reasons: discard the OTP (otherwise the user might not select a database now and forget
|
||||||
|
//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.
|
||||||
|
|
||||||
|
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show();
|
||||||
|
GoToFileSelectActivity();
|
||||||
|
Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//user obviously wants to use OTP:
|
||||||
|
_keyFileOrProvider = KeyProviderIdOtp;
|
||||||
|
|
||||||
|
if (savedInstanceState == null) //only when not re-creating
|
||||||
|
{
|
||||||
|
//remember the OTP for later use
|
||||||
|
_pendingOtps.Add(i.GetStringExtra(Intents.OtpExtraKey));
|
||||||
|
i.RemoveExtra(Intents.OtpExtraKey);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetIocFromViewIntent(Intent i)
|
||||||
|
{
|
||||||
|
//started from "view" intent (e.g. from file browser)
|
||||||
|
_ioConnection.Path = i.DataString;
|
||||||
|
|
||||||
|
if (! _ioConnection.Path.Substring(0, 7).Equals("file://"))
|
||||||
|
{
|
||||||
|
//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
|
||||||
|
Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
|
||||||
|
Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ioConnection.Path = URLDecoder.Decode(_ioConnection.Path.Substring(7));
|
||||||
|
|
||||||
|
if (_ioConnection.Path.Length == 0)
|
||||||
|
{
|
||||||
|
// No file name
|
||||||
|
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||||
|
Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File dbFile = new File(_ioConnection.Path);
|
||||||
|
if (! dbFile.Exists())
|
||||||
|
{
|
||||||
|
// File does not exist
|
||||||
|
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||||
|
Finish();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeOkButton()
|
||||||
|
{
|
||||||
|
Button confirmButton = (Button) FindViewById(Resource.Id.pass_ok);
|
||||||
confirmButton.Click += (sender, e) =>
|
confirmButton.Click += (sender, e) =>
|
||||||
{
|
{
|
||||||
App.Kp2a.GetFileStorage(_ioConnection)
|
App.Kp2a.GetFileStorage(_ioConnection)
|
||||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false);
|
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
|
||||||
|
RequestCodePrepareDbFile, false);
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeTogglePasswordButton()
|
||||||
|
{
|
||||||
|
ImageButton btnTogglePassword = (ImageButton) FindViewById(Resource.Id.toggle_password);
|
||||||
|
btnTogglePassword.Click += (sender, e) =>
|
||||||
|
{
|
||||||
|
_showPassword = !_showPassword;
|
||||||
|
MakePasswordMaskedOrVisible();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeKeyfileBrowseButton()
|
||||||
|
{
|
||||||
|
ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button);
|
||||||
|
browse.Click += (sender, evt) =>
|
||||||
|
{
|
||||||
|
string filename = null;
|
||||||
|
if (!String.IsNullOrEmpty(_ioConnection.Path))
|
||||||
|
{
|
||||||
|
File keyfile = new File(_ioConnection.Path);
|
||||||
|
File parent = keyfile.ParentFile;
|
||||||
|
if (parent != null)
|
||||||
|
{
|
||||||
|
filename = parent.AbsolutePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializePasswordModeSpinner()
|
||||||
|
{
|
||||||
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
|
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
|
||||||
if (passwordModeSpinner != null)
|
if (passwordModeSpinner != null)
|
||||||
{
|
{
|
||||||
|
|
||||||
UpdateKeyProviderUiState();
|
UpdateKeyProviderUiState();
|
||||||
passwordModeSpinner.SetSelection((int) KeyProviderType);
|
passwordModeSpinner.SetSelection((int) KeyProviderType);
|
||||||
passwordModeSpinner.ItemSelected += (sender, args) =>
|
passwordModeSpinner.ItemSelected += (sender, args) =>
|
||||||
@ -466,16 +559,20 @@ namespace keepass2android
|
|||||||
case 2:
|
case 2:
|
||||||
_keyFileOrProvider = KeyProviderIdOtp;
|
_keyFileOrProvider = KeyProviderIdOtp;
|
||||||
break;
|
break;
|
||||||
|
case 3:
|
||||||
|
_keyFileOrProvider = KeyProviderIdOtpRecovery;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Unexpected position "+args.Position+" / " + ((ICursor)((AdapterView)sender).GetItemAtPosition(args.Position)).GetString(1));
|
throw new Exception("Unexpected position " + args.Position + " / " +
|
||||||
|
((ICursor) ((AdapterView) sender).GetItemAtPosition(args.Position)).GetString(1));
|
||||||
}
|
}
|
||||||
UpdateKeyProviderUiState();
|
UpdateKeyProviderUiState();
|
||||||
};
|
};
|
||||||
FindViewById(Resource.Id.init_otp).Click += (sender, args) =>
|
FindViewById(Resource.Id.init_otp).Click += (sender, args) =>
|
||||||
{
|
{
|
||||||
App.Kp2a.GetOtpAuxFileStorage(_ioConnection)
|
App.Kp2a.GetOtpAuxFileStorage(_ioConnection)
|
||||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareOtpAuxFile, false);
|
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
|
||||||
|
RequestCodePrepareOtpAuxFile, false);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -483,51 +580,35 @@ namespace keepass2android
|
|||||||
//android 2.x
|
//android 2.x
|
||||||
//TODO test
|
//TODO test
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreState(Bundle savedInstanceState)
|
||||||
UpdateOkButtonState();
|
{
|
||||||
|
if (savedInstanceState != null)
|
||||||
|
|
||||||
|
|
||||||
/*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password);
|
|
||||||
// Show or hide password
|
|
||||||
checkBox.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs e) {
|
|
||||||
|
|
||||||
TextView password = (TextView) FindViewById(Resource.Id.password);
|
|
||||||
if ( e.IsChecked ) {
|
|
||||||
password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
|
|
||||||
} else {
|
|
||||||
password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
ImageButton btnTogglePassword = (ImageButton)FindViewById(Resource.Id.toggle_password);
|
|
||||||
btnTogglePassword.Click += (sender, e) =>
|
|
||||||
{
|
|
||||||
_showPassword = !_showPassword;
|
|
||||||
MakePasswordMaskedOrVisible();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ImageButton browse = (ImageButton)FindViewById(Resource.Id.browse_button);
|
|
||||||
browse.Click += (sender, evt) =>
|
|
||||||
{
|
{
|
||||||
string filename = null;
|
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
|
||||||
if (!String.IsNullOrEmpty(_ioConnection.Path))
|
MakePasswordMaskedOrVisible();
|
||||||
|
|
||||||
|
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text = savedInstanceState.GetString(KeyFileOrProviderKey);
|
||||||
|
_password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey);
|
||||||
|
|
||||||
|
_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
|
||||||
|
|
||||||
|
string otpInfoString = savedInstanceState.GetString(OtpInfoKey);
|
||||||
|
if (otpInfoString != null)
|
||||||
{
|
{
|
||||||
File keyfile = new File(_ioConnection.Path);
|
|
||||||
File parent = keyfile.ParentFile;
|
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||||
if (parent != null)
|
_otpInfo = (OtpInfo)xs.Deserialize(new StringReader(otpInfoString));
|
||||||
{
|
|
||||||
filename = parent.AbsolutePath;
|
var enteredOtps = savedInstanceState.GetStringArrayList(EnteredOtpsKey);
|
||||||
}
|
|
||||||
|
ShowOtpEntry(enteredOtps);
|
||||||
}
|
}
|
||||||
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
|
|
||||||
|
|
||||||
};
|
UpdateKeyProviderUiState();
|
||||||
|
|
||||||
RetrieveSettings();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateOkButtonState()
|
private void UpdateOkButtonState()
|
||||||
@ -562,6 +643,9 @@ namespace keepass2android
|
|||||||
|
|
||||||
FindViewById(Resource.Id.pass_ok).Enabled = enabled;
|
FindViewById(Resource.Id.pass_ok).Enabled = enabled;
|
||||||
break;
|
break;
|
||||||
|
case KeyProviders.OtpRecovery:
|
||||||
|
FindViewById(Resource.Id.pass_ok).Enabled = FindViewById<EditText>(Resource.Id.pass_otpsecret).Text != "" && _password != "";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException();
|
throw new ArgumentOutOfRangeException();
|
||||||
}
|
}
|
||||||
@ -575,6 +659,10 @@ namespace keepass2android
|
|||||||
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
|
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
|
||||||
? ViewStates.Visible
|
? ViewStates.Visible
|
||||||
: ViewStates.Gone;
|
: ViewStates.Gone;
|
||||||
|
|
||||||
|
FindViewById(Resource.Id.otpSecretLine).Visibility = KeyProviderType == KeyProviders.OtpRecovery
|
||||||
|
? ViewStates.Visible
|
||||||
|
: ViewStates.Gone;
|
||||||
if (KeyProviderType == KeyProviders.Otp)
|
if (KeyProviderType == KeyProviders.Otp)
|
||||||
{
|
{
|
||||||
FindViewById(Resource.Id.otps_pending).Visibility = _pendingOtps.Count > 0 ? ViewStates.Visible : ViewStates.Gone;
|
FindViewById(Resource.Id.otps_pending).Visibility = _pendingOtps.Count > 0 ? ViewStates.Visible : ViewStates.Gone;
|
||||||
@ -597,7 +685,8 @@ namespace keepass2android
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Kp2aLog.Log(e.ToString());
|
Kp2aLog.Log(e.ToString());
|
||||||
throw new KeyFileException();
|
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (KeyProviderType == KeyProviders.Otp)
|
else if (KeyProviderType == KeyProviders.Otp)
|
||||||
@ -605,26 +694,34 @@ namespace keepass2android
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<string> lOtps = new List<string>();
|
var lOtps = GetOtpsFromUI();
|
||||||
foreach (int otpId in _otpTextViewIds)
|
|
||||||
{
|
|
||||||
string otpText = FindViewById<EditText>(otpId).Text;
|
|
||||||
if (!String.IsNullOrEmpty(otpText))
|
|
||||||
lOtps.Add(otpText);
|
|
||||||
}
|
|
||||||
CreateOtpSecret(lOtps);
|
CreateOtpSecret(lOtps);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
const string strMain = "Failed to create OTP key!";
|
|
||||||
const string strLine1 = "Make sure you've entered the correct OTPs.";
|
|
||||||
|
|
||||||
Toast.MakeText(this, strMain + " " + strLine1, ToastLength.Long).Show();
|
Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
|
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
|
||||||
}
|
}
|
||||||
|
else if (KeyProviderType == KeyProviders.OtpRecovery)
|
||||||
|
{
|
||||||
|
Spinner stpDataFmtSpinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
|
||||||
|
EditText secretEdit = FindViewById<EditText>(Resource.Id.pass_otpsecret);
|
||||||
|
|
||||||
|
byte[] pbSecret = EncodingUtil.ParseKey(secretEdit.Text, (OtpDataFmt)stpDataFmtSpinner.SelectedItemPosition);
|
||||||
|
if (pbSecret != null)
|
||||||
|
{
|
||||||
|
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, pbSecret, true));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Toast.MakeText(this, Resource.String.CouldntParseOtpSecret, ToastLength.Long).Show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CheckBox cbQuickUnlock = (CheckBox) FindViewById(Resource.Id.enable_quickunlock);
|
CheckBox cbQuickUnlock = (CheckBox) FindViewById(Resource.Id.enable_quickunlock);
|
||||||
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
|
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
|
||||||
@ -642,6 +739,18 @@ namespace keepass2android
|
|||||||
new ProgressTask(App.Kp2a, this, task).Run();
|
new ProgressTask(App.Kp2a, this, task).Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> GetOtpsFromUI()
|
||||||
|
{
|
||||||
|
List<string> lOtps = new List<string>();
|
||||||
|
foreach (int otpId in _otpTextViewIds)
|
||||||
|
{
|
||||||
|
string otpText = FindViewById<EditText>(otpId).Text;
|
||||||
|
if (!String.IsNullOrEmpty(otpText))
|
||||||
|
lOtps.Add(otpText);
|
||||||
|
}
|
||||||
|
return lOtps;
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateOtpSecret(List<string> lOtps)
|
private void CreateOtpSecret(List<string> lOtps)
|
||||||
{
|
{
|
||||||
byte[] pbSecret;
|
byte[] pbSecret;
|
||||||
@ -785,13 +894,32 @@ namespace keepass2android
|
|||||||
base.OnSaveInstanceState(outState);
|
base.OnSaveInstanceState(outState);
|
||||||
AppTask.ToBundle(outState);
|
AppTask.ToBundle(outState);
|
||||||
outState.PutBoolean(ShowpasswordKey, _showPassword);
|
outState.PutBoolean(ShowpasswordKey, _showPassword);
|
||||||
//TODO:
|
|
||||||
// * save OTP state
|
outState.PutString(KeyFileOrProviderKey, _keyFileOrProvider);
|
||||||
|
outState.PutString(PasswordKey, _password);
|
||||||
|
outState.PutStringArrayList(PendingOtpsKey, _pendingOtps);
|
||||||
|
if (_otpInfo != null)
|
||||||
|
{
|
||||||
|
outState.PutStringArrayList(EnteredOtpsKey, GetOtpsFromUI());
|
||||||
|
|
||||||
|
var sw = new StringWriter();
|
||||||
|
|
||||||
|
var xws = OtpInfo.XmlWriterSettings();
|
||||||
|
|
||||||
|
XmlWriter xw = XmlWriter.Create(sw, xws);
|
||||||
|
|
||||||
|
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||||
|
xs.Serialize(xw, _otpInfo);
|
||||||
|
|
||||||
|
xw.Close();
|
||||||
|
|
||||||
|
outState.PutString(OtpInfoKey, sw.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
//more OTP TODO:
|
//more OTP TODO:
|
||||||
// * NfcOtp: Ask for close when db open
|
|
||||||
// * Caching of aux file
|
// * Caching of aux file
|
||||||
// * -> implement IFileStorage in JavaFileStorage based on ListFiles
|
// * -> implement IFileStorage in JavaFileStorage based on ListFiles
|
||||||
|
// * -> Sync
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
@ -896,7 +1024,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RetrieveSettings() {
|
private void InitializeQuickUnlockCheckbox() {
|
||||||
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
|
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
|
||||||
cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);
|
cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);
|
||||||
}
|
}
|
||||||
@ -909,7 +1037,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PopulateView() {
|
private void InitializeFilenameView() {
|
||||||
SetEditText(Resource.Id.filename, App.Kp2a.GetFileStorage(_ioConnection).GetDisplayName(_ioConnection));
|
SetEditText(Resource.Id.filename, App.Kp2a.GetFileStorage(_ioConnection).GetDisplayName(_ioConnection));
|
||||||
if (App.Kp2a.FileDbHelper.NumberOfRecentFiles() < 2)
|
if (App.Kp2a.FileDbHelper.NumberOfRecentFiles() < 2)
|
||||||
{
|
{
|
||||||
@ -919,8 +1047,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
FindViewById(Resource.Id.filename_group).Visibility = ViewStates.Visible;
|
FindViewById(Resource.Id.filename_group).Visibility = ViewStates.Visible;
|
||||||
}
|
}
|
||||||
if (KeyProviderType == KeyProviders.KeyFile)
|
|
||||||
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@ -937,15 +1064,6 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private void ErrorMessage(int resId)
|
|
||||||
{
|
|
||||||
Toast.MakeText(this, resId, ToastLength.Long).Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String GetEditText(int resId) {
|
|
||||||
return Util.GetEditText(this, resId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetEditText(int resId, String str) {
|
private void SetEditText(int resId, String str) {
|
||||||
TextView te = (TextView) FindViewById(resId);
|
TextView te = (TextView) FindViewById(resId);
|
||||||
//assert(te == null);
|
//assert(te == null);
|
||||||
@ -1002,13 +1120,40 @@ namespace keepass2android
|
|||||||
|
|
||||||
|
|
||||||
public override void Run() {
|
public override void Run() {
|
||||||
|
|
||||||
|
if (_act.KeyProviderType == KeyProviders.Otp)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);
|
||||||
|
|
||||||
|
if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, new KeyProviderQueryContext(_act._ioConnection, false, false)))
|
||||||
|
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile), ToastLength.Long).Show();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Kp2aLog.Log(e.Message);
|
||||||
|
|
||||||
|
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile)+" "+e.Message, ToastLength.Long).Show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ( Success )
|
if ( Success )
|
||||||
{
|
{
|
||||||
_act.SetEditText(Resource.Id.password, "");
|
_act.SetEditText(Resource.Id.password, "");
|
||||||
|
_act.SetEditText(Resource.Id.pass_otpsecret, "");
|
||||||
|
foreach (int otpId in _act._otpTextViewIds)
|
||||||
|
{
|
||||||
|
_act.SetEditText(otpId, "");
|
||||||
|
}
|
||||||
|
|
||||||
_act.LaunchNextActivity();
|
_act.LaunchNextActivity();
|
||||||
|
|
||||||
GC.Collect(); // Ensure temporary memory used while loading is collected - it will contain sensitive data such as username and password, and also the large data of the encrypted database file
|
if ((_act.KeyProviderType == KeyProviders.Otp) || (_act.KeyProviderType == KeyProviders.OtpRecovery))
|
||||||
|
App.Kp2a.GetDb().OtpAuxFileIoc = OathHotpKeyProv.GetAuxFileIoc(_act._ioConnection);
|
||||||
|
|
||||||
|
GC.Collect(); // Ensure temporary memory used while loading is collected
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
1119
src/keepass2android/Resources/Resource.designer.cs
generated
1119
src/keepass2android/Resources/Resource.designer.cs
generated
File diff suppressed because it is too large
Load Diff
@ -1,188 +1,213 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_marginLeft="12dip"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginRight="12dip"
|
>
|
||||||
android:layout_marginBottom="12dip"
|
<LinearLayout
|
||||||
android:orientation="vertical"
|
|
||||||
>
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/filename_group"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="match_parent"
|
||||||
<TextView
|
|
||||||
android:id="@+id/filename_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/TextAppearance_SmallHeading"
|
|
||||||
android:text="@string/pass_filename" />
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/divider1"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/filename_label"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:src="@android:drawable/divider_horizontal_dark" />
|
|
||||||
<HorizontalScrollView
|
|
||||||
android:id="@+id/filenamescroll"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/divider1">
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/filename"
|
|
||||||
style="@style/GroupText"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:ellipsize="none"
|
|
||||||
android:focusable="true"
|
|
||||||
android:focusableInTouchMode="true" />
|
|
||||||
</HorizontalScrollView>
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/divider2"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@id/filenamescroll"
|
|
||||||
android:scaleType="fitXY"
|
|
||||||
android:src="@android:drawable/divider_horizontal_dark" />
|
|
||||||
</RelativeLayout>
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/password_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="" />
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/password_mode_spinner"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:entries="@array/password_modes"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/passwordLine"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:hint="@string/hint_login_pass" />
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/toggle_password"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_menu_view" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/keyfileLine"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/pass_keyfile"
|
|
||||||
android:layout_width="0px"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:hint="@string/entry_keyfile" />
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/browse_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/ic_launcher_folder_small" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/otpView"
|
|
||||||
android:layout_marginLeft="12dip"
|
android:layout_marginLeft="12dip"
|
||||||
android:layout_marginRight="12dip"
|
android:layout_marginRight="12dip"
|
||||||
android:layout_width="fill_parent"
|
android:layout_marginBottom="12dip"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
>
|
>
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:id="@+id/otpInitView"
|
android:id="@+id/filename_group"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filename_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/TextAppearance_SmallHeading"
|
||||||
|
android:text="@string/pass_filename" />
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/divider1"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/filename_label"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@android:drawable/divider_horizontal_dark" />
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:id="@+id/filenamescroll"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/divider1">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/filename"
|
||||||
|
style="@style/GroupText"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:ellipsize="none"
|
||||||
|
android:focusable="true"
|
||||||
|
android:focusableInTouchMode="true" />
|
||||||
|
</HorizontalScrollView>
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/divider2"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/filenamescroll"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@android:drawable/divider_horizontal_dark" />
|
||||||
|
</RelativeLayout>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/password_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/master_key_type" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/password_mode_spinner"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
>
|
android:entries="@array/password_modes"
|
||||||
<Button
|
/>
|
||||||
android:id="@+id/init_otp"
|
|
||||||
android:text="@string/init_otp"
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/passwordLine"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_width="fill_parent"
|
||||||
<TextView
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/otps_pending"
|
android:orientation="horizontal">
|
||||||
android:text="@string/otps_pending"
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/password"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_width="0px"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="@string/hint_login_pass" />
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/toggle_password"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_menu_view" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/otpEntry"
|
android:id="@+id/keyfileLine"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:orientation="horizontal">
|
||||||
android:orientation="vertical"
|
<EditText
|
||||||
|
android:id="@+id/pass_keyfile"
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="@string/entry_keyfile" />
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/browse_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_launcher_folder_small" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/otpView"
|
||||||
|
android:layout_marginLeft="12dip"
|
||||||
|
android:layout_marginRight="12dip"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
>
|
>
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/otp_expl"
|
android:id="@+id/otpInitView"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/otp_explanation" />
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/init_otp"
|
||||||
|
android:text="@string/init_otp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/otps_pending"
|
||||||
|
android:text="@string/otps_pending"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/otpEntry"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:orientation="vertical"
|
||||||
|
>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/otp_expl"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/otp_explanation" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp1"
|
android:id="@+id/otp1"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="93317749"
|
android:text="93317749"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp2"
|
android:id="@+id/otp2"
|
||||||
android:text="54719327"
|
android:text="54719327"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp3"
|
android:id="@+id/otp3"
|
||||||
android:text="49844651"
|
android:text="49844651"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp4"
|
android:id="@+id/otp4"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp5"
|
android:id="@+id/otp5"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/otp6"
|
android:id="@+id/otp6"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:singleLine="true" />
|
android:singleLine="true" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/otpSecretLine"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/pass_otpsecret"
|
||||||
|
android:layout_width="0px"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="@string/otpsecret_hint" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/otpsecret_format_spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
/>
|
||||||
|
</LinearLayout>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/pass_ok"
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/kill_app"
|
||||||
|
android:text="@string/kill_app_label"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/enable_quickunlock"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/enable_quickunlock" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<Button
|
</ScrollView>
|
||||||
android:id="@+id/pass_ok"
|
|
||||||
android:text="@android:string/ok"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
<Button
|
|
||||||
android:id="@+id/kill_app"
|
|
||||||
android:text="@string/kill_app_label"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/enable_quickunlock"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/enable_quickunlock" />
|
|
||||||
</LinearLayout>
|
|
@ -143,6 +143,7 @@
|
|||||||
<string name="omitbackup_summary">Omit \'Backup\' and Recycle Bin group from search results</string>
|
<string name="omitbackup_summary">Omit \'Backup\' and Recycle Bin group from search results</string>
|
||||||
<string name="pass_filename">KeePass database filename</string>
|
<string name="pass_filename">KeePass database filename</string>
|
||||||
<string name="password_title">Enter database password</string>
|
<string name="password_title">Enter database password</string>
|
||||||
|
<string name="master_key_type">Select master key type:</string>
|
||||||
<string name="progress_create">Creating new database…</string>
|
<string name="progress_create">Creating new database…</string>
|
||||||
<string name="create_database">Create database</string>
|
<string name="create_database">Create database</string>
|
||||||
<string name="progress_title">Working…</string>
|
<string name="progress_title">Working…</string>
|
||||||
@ -299,9 +300,17 @@
|
|||||||
<string name="UpdatedRemoteFileOnLoad">Updated remote file.</string>
|
<string name="UpdatedRemoteFileOnLoad">Updated remote file.</string>
|
||||||
<string name="NotifyOpenFromLocalDueToConflict">Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.</string>
|
<string name="NotifyOpenFromLocalDueToConflict">Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.</string>
|
||||||
<string name="LoadedFromRemoteInSync">Remote file and cache are synchronized.</string>
|
<string name="LoadedFromRemoteInSync">Remote file and cache are synchronized.</string>
|
||||||
<string name="UpdatedCachedFileOnLoad">Updated local cache copy of database.</string>
|
<string name="UpdatedCachedFileOnLoad">Updated local cache copy of %1$s.</string>
|
||||||
<string name="RemoteDatabaseUnchanged">No changes detected.</string>
|
<string name="RemoteDatabaseUnchanged">No changes detected.</string>
|
||||||
|
|
||||||
|
<string name="ResolvedCacheConflictByUsingRemoteOtpAux">Updated cached OTP auxiliary file: Remote counter was higher.</string>
|
||||||
|
<string name="ResolvedCacheConflictByUsingLocalOtpAux">Updated remote OTP auxiliary file: Local counter was higher.</string>
|
||||||
|
|
||||||
|
<string name="SynchronizingOtpAuxFile">Synchronizing OTP auxiliary file…</string>
|
||||||
|
|
||||||
|
<string name="database_file">database file</string>
|
||||||
|
<string name="otp_aux_file">OTP auxiliary file</string>
|
||||||
|
|
||||||
<string name="ErrorOcurred">An error occured:</string>
|
<string name="ErrorOcurred">An error occured:</string>
|
||||||
|
|
||||||
<string name="synchronize_database_menu">Synchronize database…</string>
|
<string name="synchronize_database_menu">Synchronize database…</string>
|
||||||
@ -343,14 +352,19 @@
|
|||||||
|
|
||||||
<string name="error_adding_keyfile">Error while adding the keyfile!</string>
|
<string name="error_adding_keyfile">Error while adding the keyfile!</string>
|
||||||
|
|
||||||
<string name="init_otp">Enter OTPs…</string>
|
<string name="init_otp">Load OTP auxiliary file…</string>
|
||||||
<string name="otp_explanation">Enter the next One-time-passwords (OTPs). Swipe your Yubikey NEO at the back of your device to enter via NFC.</string>
|
<string name="otp_explanation">Enter the next One-time-passwords (OTPs). Swipe your Yubikey NEO at the back of your device to enter via NFC.</string>
|
||||||
<string name="otp_hint">OTP %1$d</string>
|
<string name="otp_hint">OTP %1$d</string>
|
||||||
<string name="CouldntLoadOtpAuxFile">Could not load auxiliary OTP file!</string>
|
<string name="CouldntLoadOtpAuxFile">Could not load auxiliary OTP file!</string>
|
||||||
<string name="otp_discarded_because_no_db">Please select database first. OTP is discarded for security reasons.</string>
|
<string name="otp_discarded_because_no_db">Please select database first. OTP is discarded for security reasons.</string>
|
||||||
<string name="otp_discarded_no_space">OTP discarded: All OTPs already entered!</string>
|
<string name="otp_discarded_no_space">OTP discarded: All OTPs already entered!</string>
|
||||||
|
<string name="otp_discarded_because_db_open">Please close database first. OTP is discarded.</string>
|
||||||
<string name="otps_pending">(One or more OTPs already available)</string>
|
<string name="otps_pending">(One or more OTPs already available)</string>
|
||||||
|
<string name="otpsecret_hint">OTP secret (e.g. 01 23 ab cd…)</string>
|
||||||
|
<string name="CouldntParseOtpSecret">Error parsing OTP secret!</string>
|
||||||
|
<string name="OtpKeyError">Failed to create OTP key! Make sure you have entered the correct OTPs.</string>
|
||||||
|
<string name="ErrorUpdatingOtpAuxFile">Error updating OTP auxiliary file!</string>
|
||||||
|
<string name="SavingOtpAuxFile">Saving auxiliary OTP file…</string>
|
||||||
|
|
||||||
<string name="loading">Loading…</string>
|
<string name="loading">Loading…</string>
|
||||||
|
|
||||||
@ -477,5 +491,6 @@ Initial public release
|
|||||||
<item>Password only</item>
|
<item>Password only</item>
|
||||||
<item>Password + Key file</item>
|
<item>Password + Key file</item>
|
||||||
<item>Password + OTP</item>
|
<item>Password + OTP</item>
|
||||||
|
<item>Password + OTP secret (recovery mode)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -49,13 +49,19 @@ namespace OtpKeyProv
|
|||||||
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
||||||
{
|
{
|
||||||
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
||||||
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(ioc);
|
var iocAux = GetAuxFileIoc(ioc);
|
||||||
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(ioc),
|
|
||||||
fileStorage.GetFilenameWithoutPathAndExt(ioc) + AuxFileExt);
|
|
||||||
|
|
||||||
return iocAux;
|
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)
|
public static OtpInfo LoadOtpInfo(KeyProviderQueryContext ctx)
|
||||||
{
|
{
|
||||||
return OtpInfo.Load(GetAuxFileIoc(ctx));
|
return OtpInfo.Load(GetAuxFileIoc(ctx));
|
||||||
@ -130,7 +136,7 @@ namespace OtpKeyProv
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool CreateAuxFile(OtpInfo otpInfo,
|
public static bool CreateAuxFile(OtpInfo otpInfo,
|
||||||
KeyProviderQueryContext ctx)
|
KeyProviderQueryContext ctx)
|
||||||
{
|
{
|
||||||
otpInfo.Type = ProvType;
|
otpInfo.Type = ProvType;
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using KeePassLib.Serialization;
|
||||||
|
using OtpKeyProv;
|
||||||
|
using keepass2android.Io;
|
||||||
|
|
||||||
|
namespace keepass2android.addons.OtpKeyProv
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class which provides caching for OtpInfo-files. This is an extension to CachingFileStorage required to handle conflicts directly when loading.
|
||||||
|
/// </summary>
|
||||||
|
class OtpAuxCachingFileStorage: CachingFileStorage
|
||||||
|
{
|
||||||
|
private readonly IOtpAuxCacheSupervisor _cacheSupervisor;
|
||||||
|
|
||||||
|
internal interface IOtpAuxCacheSupervisor: ICacheSupervisor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// called when there was a conflict which was resolved by using the remote file.
|
||||||
|
/// </summary>
|
||||||
|
void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// called when there was a conflict which was resolved by using the local file.
|
||||||
|
/// </summary>
|
||||||
|
void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public OtpAuxCachingFileStorage(IFileStorage cachedStorage, string cacheDir, IOtpAuxCacheSupervisor cacheSupervisor)
|
||||||
|
: base(cachedStorage, cacheDir, cacheSupervisor)
|
||||||
|
{
|
||||||
|
_cacheSupervisor = cacheSupervisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
|
||||||
|
{
|
||||||
|
OtpInfo remoteOtpInfo, localOtpInfo;
|
||||||
|
//load both files
|
||||||
|
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||||
|
localOtpInfo = (OtpInfo) xs.Deserialize(File.OpenRead(cachedFilePath));
|
||||||
|
using (Stream remoteStream = _cachedStorage.OpenFileForRead(ioc))
|
||||||
|
{
|
||||||
|
remoteOtpInfo = (OtpInfo) xs.Deserialize(remoteStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
//see which OtpInfo has the bigger Counter value and use this one:
|
||||||
|
if (localOtpInfo.Counter > remoteOtpInfo.Counter)
|
||||||
|
{
|
||||||
|
//overwrite the remote file
|
||||||
|
UpdateRemoteFile(File.OpenRead(cachedFilePath),
|
||||||
|
ioc,
|
||||||
|
App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions),
|
||||||
|
GetBaseVersionHash(ioc)
|
||||||
|
);
|
||||||
|
|
||||||
|
_cacheSupervisor.ResolvedCacheConflictByUsingRemote(ioc);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//overwrite the local file:
|
||||||
|
UpdateCacheFromRemote(ioc, cachedFilePath);
|
||||||
|
_cacheSupervisor.ResolvedCacheConflictByUsingLocal(ioc);
|
||||||
|
}
|
||||||
|
|
||||||
|
//now return the local file in any way:
|
||||||
|
return File.OpenRead(cachedFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -197,18 +197,8 @@ namespace OtpKeyProv
|
|||||||
using (var trans = App.Kp2a.GetOtpAuxFileStorage(ioc)
|
using (var trans = App.Kp2a.GetOtpAuxFileStorage(ioc)
|
||||||
.OpenWriteTransaction(ioc, App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
|
.OpenWriteTransaction(ioc, App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
|
||||||
{
|
{
|
||||||
XmlWriterSettings xws = new XmlWriterSettings();
|
var stream = trans.OpenFile();
|
||||||
xws.CloseOutput = true;
|
WriteToStream(otpInfo, stream);
|
||||||
xws.Encoding = StrUtil.Utf8;
|
|
||||||
xws.Indent = true;
|
|
||||||
xws.IndentChars = "\t";
|
|
||||||
|
|
||||||
XmlWriter xw = XmlWriter.Create(trans.OpenFile(), xws);
|
|
||||||
|
|
||||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
|
||||||
xs.Serialize(xw, otpInfo);
|
|
||||||
|
|
||||||
xw.Close();
|
|
||||||
trans.CommitWrite();
|
trans.CommitWrite();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -222,6 +212,31 @@ namespace OtpKeyProv
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void WriteToStream(OtpInfo otpInfo, Stream stream)
|
||||||
|
{
|
||||||
|
var xws = XmlWriterSettings();
|
||||||
|
|
||||||
|
XmlWriter xw = XmlWriter.Create(stream, xws);
|
||||||
|
|
||||||
|
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||||
|
xs.Serialize(xw, otpInfo);
|
||||||
|
|
||||||
|
xw.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlWriterSettings XmlWriterSettings()
|
||||||
|
{
|
||||||
|
XmlWriterSettings xws = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
CloseOutput = true,
|
||||||
|
Encoding = StrUtil.Utf8,
|
||||||
|
Indent = true,
|
||||||
|
IndentChars = "\t"
|
||||||
|
};
|
||||||
|
return xws;
|
||||||
|
}
|
||||||
|
|
||||||
public void EncryptSecret()
|
public void EncryptSecret()
|
||||||
{
|
{
|
||||||
if(m_pbSecret == null) throw new InvalidOperationException();
|
if(m_pbSecret == null) throw new InvalidOperationException();
|
||||||
|
@ -32,6 +32,7 @@ using Android.Preferences;
|
|||||||
using TwofishCipher;
|
using TwofishCipher;
|
||||||
#endif
|
#endif
|
||||||
using keepass2android.Io;
|
using keepass2android.Io;
|
||||||
|
using keepass2android.addons.OtpKeyProv;
|
||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
@ -294,8 +295,11 @@ namespace keepass2android
|
|||||||
|
|
||||||
builder.SetNegativeButton(GetResourceString(noString), noHandler);
|
builder.SetNegativeButton(GetResourceString(noString), noHandler);
|
||||||
|
|
||||||
builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
|
if (cancelHandler != null)
|
||||||
cancelHandler);
|
{
|
||||||
|
builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
|
||||||
|
cancelHandler);
|
||||||
|
}
|
||||||
|
|
||||||
Dialog dialog = builder.Create();
|
Dialog dialog = builder.Create();
|
||||||
dialog.Show();
|
dialog.Show();
|
||||||
@ -447,7 +451,7 @@ namespace keepass2android
|
|||||||
return _db;
|
return _db;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowToast(string message)
|
internal void ShowToast(string message)
|
||||||
{
|
{
|
||||||
var handler = new Handler(Looper.MainLooper);
|
var handler = new Handler(Looper.MainLooper);
|
||||||
handler.Post(() => { Toast.MakeText(Application.Context, message, ToastLength.Long).Show(); });
|
handler.Post(() => { Toast.MakeText(Application.Context, message, ToastLength.Long).Show(); });
|
||||||
@ -466,7 +470,8 @@ namespace keepass2android
|
|||||||
|
|
||||||
public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
|
public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
|
||||||
{
|
{
|
||||||
ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad));
|
ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad,
|
||||||
|
new Java.Lang.Object[] { Application.Context.GetString(Resource.String.database_file) }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
|
public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
|
||||||
@ -510,7 +515,7 @@ namespace keepass2android
|
|||||||
|
|
||||||
if (DatabaseCacheEnabled)
|
if (DatabaseCacheEnabled)
|
||||||
{
|
{
|
||||||
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
|
return new OtpAuxCachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, new OtpAuxCacheSupervisor(this));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -532,7 +537,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
|
||||||
#if NoNet
|
#if NoNet
|
||||||
[Application(Debuggable=false, Label=AppNames.AppName)]
|
[Application(Debuggable=false, Label=AppNames.AppName)]
|
||||||
#else
|
#else
|
||||||
|
59
src/keepass2android/app/OtpAuxCacheSupervisor.cs
Normal file
59
src/keepass2android/app/OtpAuxCacheSupervisor.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using Android.App;
|
||||||
|
using KeePassLib.Serialization;
|
||||||
|
using keepass2android.addons.OtpKeyProv;
|
||||||
|
|
||||||
|
namespace keepass2android
|
||||||
|
{
|
||||||
|
public class OtpAuxCacheSupervisor : OtpAuxCachingFileStorage.IOtpAuxCacheSupervisor
|
||||||
|
{
|
||||||
|
private readonly Kp2aApp _app;
|
||||||
|
|
||||||
|
public OtpAuxCacheSupervisor(Kp2aApp app)
|
||||||
|
{
|
||||||
|
_app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex)
|
||||||
|
{
|
||||||
|
_app.CouldntSaveToRemote(ioc, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex)
|
||||||
|
{
|
||||||
|
_app.CouldntOpenFromRemote(ioc, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_app.ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad,
|
||||||
|
new Java.Lang.Object[] { Application.Context.GetString(Resource.String.otp_aux_file) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_app.UpdatedRemoteFileOnLoad(ioc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
//must not be called . Conflicts should be resolved.
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadedFromRemoteInSync(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_app.LoadedFromRemoteInSync(ioc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingRemoteOtpAux));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingLocalOtpAux));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -373,7 +373,7 @@ namespace keepass2android
|
|||||||
#if !EXCLUDE_FILECHOOSER
|
#if !EXCLUDE_FILECHOOSER
|
||||||
StartFileChooser(ioc.Path);
|
StartFileChooser(ioc.Path);
|
||||||
#else
|
#else
|
||||||
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi2.kdbx"});
|
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx"});
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
||||||
|
@ -84,9 +84,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
|
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
|
||||||
<Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
|
<Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
|
||||||
|
<Compile Include="addons\OtpKeyProv\OtpAuxCachingFileStorage.cs" />
|
||||||
<Compile Include="addons\OtpKeyProv\OtpInfo.cs" />
|
<Compile Include="addons\OtpKeyProv\OtpInfo.cs" />
|
||||||
<Compile Include="addons\OtpKeyProv\OtpUtil.cs" />
|
<Compile Include="addons\OtpKeyProv\OtpUtil.cs" />
|
||||||
<Compile Include="app\NoFileStorageFoundException.cs" />
|
<Compile Include="app\NoFileStorageFoundException.cs" />
|
||||||
|
<Compile Include="app\OtpAuxCacheSupervisor.cs" />
|
||||||
<Compile Include="CreateDatabaseActivity.cs" />
|
<Compile Include="CreateDatabaseActivity.cs" />
|
||||||
<Compile Include="fileselect\FileChooserFileProvider.cs" />
|
<Compile Include="fileselect\FileChooserFileProvider.cs" />
|
||||||
<Compile Include="fileselect\FileStorageSetupActivity.cs" />
|
<Compile Include="fileselect\FileStorageSetupActivity.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user