added tests for kdb writing, fixed issue with syncing (keep UUIDs when loading again)
This commit is contained in:
@@ -6,12 +6,14 @@ namespace KeePassLib
|
|||||||
{
|
{
|
||||||
public interface IDatabaseFormat
|
public interface IDatabaseFormat
|
||||||
{
|
{
|
||||||
void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger);
|
void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger);
|
||||||
|
|
||||||
byte[] HashOfLastStream { get; }
|
byte[] HashOfLastStream { get; }
|
||||||
|
|
||||||
bool CanWrite { get; }
|
bool CanWrite { get; }
|
||||||
string SuccessMessage { get; }
|
string SuccessMessage { get; }
|
||||||
void Save(PwDatabase kpDatabase, Stream stream);
|
void Save(PwDatabase kpDatabase, Stream stream);
|
||||||
|
|
||||||
|
bool CanHaveEntriesInRootGroup { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,7 +600,7 @@ namespace KeePassLib
|
|||||||
|
|
||||||
m_bModified = false;
|
m_bModified = false;
|
||||||
|
|
||||||
format.PopulateDatabaseFromStream(this, pwKey, s, slLogger);
|
format.PopulateDatabaseFromStream(this, s, slLogger);
|
||||||
|
|
||||||
m_pbHashOfLastIO = format.HashOfLastStream;
|
m_pbHashOfLastIO = format.HashOfLastStream;
|
||||||
m_pbHashOfFileOnDisk = format.HashOfLastStream;
|
m_pbHashOfFileOnDisk = format.HashOfLastStream;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace keepass2android
|
|||||||
private bool _loaded;
|
private bool _loaded;
|
||||||
|
|
||||||
private bool _reloadRequested;
|
private bool _reloadRequested;
|
||||||
private IDatabaseFormat _databaseFormat;
|
private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default);
|
||||||
|
|
||||||
public bool ReloadRequested
|
public bool ReloadRequested
|
||||||
{
|
{
|
||||||
@@ -132,6 +132,12 @@ namespace keepass2android
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanWrite { get; set; }
|
public bool CanWrite { get; set; }
|
||||||
|
|
||||||
|
public IDatabaseFormat DatabaseFormat
|
||||||
|
{
|
||||||
|
get { return _databaseFormat; }
|
||||||
|
set { _databaseFormat = value; }
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
|
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
|
||||||
{
|
{
|
||||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||||
@@ -173,7 +179,7 @@ namespace keepass2android
|
|||||||
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
|
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
|
||||||
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions))
|
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions))
|
||||||
{
|
{
|
||||||
_databaseFormat.Save(KpDatabase, trans.OpenFile());
|
DatabaseFormat.Save(KpDatabase, trans.OpenFile());
|
||||||
|
|
||||||
trans.CommitWrite();
|
trans.CommitWrite();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ using Random = System.Random;
|
|||||||
|
|
||||||
namespace keepass2android
|
namespace keepass2android
|
||||||
{
|
{
|
||||||
class KdbDatabaseFormat: IDatabaseFormat
|
public class KdbDatabaseFormat: IDatabaseFormat
|
||||||
{
|
{
|
||||||
private Dictionary<PwUuid, AdditionalGroupData> _groupData = new Dictionary<PwUuid, AdditionalGroupData>();
|
private Dictionary<PwUuid, AdditionalGroupData> _groupData = new Dictionary<PwUuid, AdditionalGroupData>();
|
||||||
private static readonly DateTime _expireNever = new DateTime(2999,12,28,23,59,59);
|
private static readonly DateTime _expireNever = new DateTime(2999,12,28,23,59,59);
|
||||||
|
|
||||||
public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
|
public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger)
|
||||||
{
|
{
|
||||||
#if !EXCLUDE_KEYTRANSFORM
|
#if !EXCLUDE_KEYTRANSFORM
|
||||||
var importer = new Com.Keepassdroid.Database.Load.ImporterV3();
|
var importer = new Com.Keepassdroid.Database.Load.ImporterV3();
|
||||||
@@ -35,13 +35,13 @@ namespace keepass2android
|
|||||||
var hashingStream = new HashingStreamEx(s, false, new SHA256Managed());
|
var hashingStream = new HashingStreamEx(s, false, new SHA256Managed());
|
||||||
|
|
||||||
string password = "";//no need to distinguish between null and "" because empty passwords are invalid (and null is not allowed)
|
string password = "";//no need to distinguish between null and "" because empty passwords are invalid (and null is not allowed)
|
||||||
KcpPassword passwordKey = (KcpPassword)key.GetUserKey(typeof(KcpPassword));
|
KcpPassword passwordKey = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword));
|
||||||
if (passwordKey != null)
|
if (passwordKey != null)
|
||||||
{
|
{
|
||||||
password = passwordKey.Password.ReadString();
|
password = passwordKey.Password.ReadString();
|
||||||
}
|
}
|
||||||
|
|
||||||
KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile));
|
KcpKeyFile passwordKeyfile = (KcpKeyFile)db.MasterKey.GetUserKey(typeof(KcpKeyFile));
|
||||||
MemoryStream keyfileStream = null;
|
MemoryStream keyfileStream = null;
|
||||||
if (passwordKeyfile != null)
|
if (passwordKeyfile != null)
|
||||||
{
|
{
|
||||||
@@ -91,22 +91,31 @@ namespace keepass2android
|
|||||||
private PwGroup ConvertGroup(PwGroupV3 groupV3)
|
private PwGroup ConvertGroup(PwGroupV3 groupV3)
|
||||||
{
|
{
|
||||||
PwGroup pwGroup = new PwGroup(true, false);
|
PwGroup pwGroup = new PwGroup(true, false);
|
||||||
pwGroup.Name = groupV3.Name;
|
pwGroup.Uuid = CreateUuidFromGroupId(groupV3.Id.Id);
|
||||||
|
|
||||||
|
//check if we have group data for this group already (from loading in a previous pass).
|
||||||
|
//then use the same UUID (important for merging)
|
||||||
|
var gdForGroup = _groupData.Where(g => g.Value.Id == groupV3.Id.Id).ToList();
|
||||||
|
if (gdForGroup.Count == 1)
|
||||||
|
{
|
||||||
|
pwGroup.Uuid = gdForGroup.Single().Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
pwGroup.Name = groupV3.Name;
|
||||||
|
Android.Util.Log.Debug("KP2A", "load kdb: group " + groupV3.Name);
|
||||||
pwGroup.CreationTime = ConvertTime(groupV3.TCreation);
|
pwGroup.CreationTime = ConvertTime(groupV3.TCreation);
|
||||||
pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess);
|
pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess);
|
||||||
pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod);
|
pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod);
|
||||||
pwGroup.Expires = !PwGroupV3.NeverExpire.Equals(groupV3.TExpire);
|
pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire);
|
||||||
if (pwGroup.Expires)
|
pwGroup.Expires = !(Math.Abs((pwGroup.ExpiryTime - _expireNever).TotalMilliseconds) < 500); ;
|
||||||
pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire);
|
|
||||||
|
|
||||||
if (groupV3.Icon != null)
|
if (groupV3.Icon != null)
|
||||||
pwGroup.IconId = (PwIcon) groupV3.Icon.IconId;
|
pwGroup.IconId = (PwIcon) groupV3.Icon.IconId;
|
||||||
_groupData.Add(pwGroup.Uuid, new AdditionalGroupData
|
_groupData[pwGroup.Uuid] = new AdditionalGroupData
|
||||||
{
|
{
|
||||||
Flags = groupV3.Flags,
|
Flags = groupV3.Flags,
|
||||||
Id = groupV3.Id.Id
|
Id = groupV3.Id.Id
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
||||||
for (int i = 0; i < groupV3.ChildGroups.Count;i++)
|
for (int i = 0; i < groupV3.ChildGroups.Count;i++)
|
||||||
@@ -124,6 +133,20 @@ namespace keepass2android
|
|||||||
return pwGroup;
|
return pwGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PwUuid CreateUuidFromGroupId(int id)
|
||||||
|
{
|
||||||
|
byte[] template = new byte[] { 0xd2, 0x18, 0x22, 0x93,
|
||||||
|
0x8e, 0xa4, 0x43, 0xf2,
|
||||||
|
0xb4, 0xb5, 0x2a, 0x49,
|
||||||
|
0x00, 0x00, 0x00, 0x00};
|
||||||
|
byte[] idBytes = BitConverter.GetBytes(id);
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
template[i + 12] = idBytes[i];
|
||||||
|
}
|
||||||
|
return new PwUuid(template);
|
||||||
|
}
|
||||||
|
|
||||||
private PwEntry ConvertEntry(PwEntryV3 fromEntry)
|
private PwEntry ConvertEntry(PwEntryV3 fromEntry)
|
||||||
{
|
{
|
||||||
PwEntry toEntry = new PwEntry(false, false);
|
PwEntry toEntry = new PwEntry(false, false);
|
||||||
@@ -142,6 +165,7 @@ namespace keepass2android
|
|||||||
if (fromEntry.Icon != null)
|
if (fromEntry.Icon != null)
|
||||||
toEntry.IconId = (PwIcon) fromEntry.Icon.IconId;
|
toEntry.IconId = (PwIcon) fromEntry.Icon.IconId;
|
||||||
SetFieldIfAvailable(toEntry, PwDefs.TitleField, false, fromEntry.Title);
|
SetFieldIfAvailable(toEntry, PwDefs.TitleField, false, fromEntry.Title);
|
||||||
|
Android.Util.Log.Debug("KP2A", "load kdb: entry " + toEntry.Strings.ReadSafe(PwDefs.TitleField));
|
||||||
SetFieldIfAvailable(toEntry, PwDefs.UserNameField, false, fromEntry.Username);
|
SetFieldIfAvailable(toEntry, PwDefs.UserNameField, false, fromEntry.Username);
|
||||||
SetFieldIfAvailable(toEntry, PwDefs.UrlField, false, fromEntry.Url);
|
SetFieldIfAvailable(toEntry, PwDefs.UrlField, false, fromEntry.Url);
|
||||||
SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password);
|
SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password);
|
||||||
@@ -223,7 +247,9 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
//traverse again and assign parents
|
//traverse again and assign parents
|
||||||
db.RootGroup = new PwGroupV3() { Level = -1};
|
db.RootGroup = ConvertGroup(kpDatabase.RootGroup, db);
|
||||||
|
db.RootGroup.Level = -1;
|
||||||
|
|
||||||
AssignParent(kpDatabase.RootGroup, db, groupV3s);
|
AssignParent(kpDatabase.RootGroup, db, groupV3s);
|
||||||
|
|
||||||
|
|
||||||
@@ -238,8 +264,17 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PwDbV3Output output = new PwDbV3Output(db, stream);
|
HashingStreamEx hashedStream = new HashingStreamEx(stream, true, null);
|
||||||
|
PwDbV3Output output = new PwDbV3Output(db, hashedStream);
|
||||||
output.Output();
|
output.Output();
|
||||||
|
hashedStream.Close();
|
||||||
|
HashOfLastStream = hashedStream.Hash;
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHaveEntriesInRootGroup
|
||||||
|
{
|
||||||
|
get { return false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary<int, PwGroupV3> groupV3s)
|
private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary<int, PwGroupV3> groupV3s)
|
||||||
@@ -254,7 +289,7 @@ namespace keepass2android
|
|||||||
parentV3 = groupV3s[_groupData[kpParent.Uuid].Id];
|
parentV3 = groupV3s[_groupData[kpParent.Uuid].Id];
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (PwGroup g in kpParent.Groups)
|
foreach (PwGroup g in kpParent.Groups.OrderBy(g => g.Name))
|
||||||
{
|
{
|
||||||
PwGroupV3 groupV3 = groupV3s[_groupData[g.Uuid].Id];
|
PwGroupV3 groupV3 = groupV3s[_groupData[g.Uuid].Id];
|
||||||
|
|
||||||
@@ -269,6 +304,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
PwGroupV3 toGroup = new PwGroupV3();
|
PwGroupV3 toGroup = new PwGroupV3();
|
||||||
toGroup.Name = fromGroup.Name;
|
toGroup.Name = fromGroup.Name;
|
||||||
|
Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name);
|
||||||
|
|
||||||
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
|
toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
|
||||||
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
|
toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
|
||||||
@@ -279,7 +315,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
toGroup.TExpire = new PwDate(PwGroupV3.NeverExpire);
|
toGroup.TExpire = new PwDate(ConvertTime(_expireNever));
|
||||||
}
|
}
|
||||||
|
|
||||||
toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId);
|
toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId);
|
||||||
@@ -327,9 +363,12 @@ namespace keepass2android
|
|||||||
|
|
||||||
toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId);
|
toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId);
|
||||||
toEntry.SetTitle(GetString(fromEntry, PwDefs.TitleField), dbTo);
|
toEntry.SetTitle(GetString(fromEntry, PwDefs.TitleField), dbTo);
|
||||||
|
Android.Util.Log.Debug("KP2A", "save kdb: entry " + fromEntry.Strings.ReadSafe(PwDefs.TitleField));
|
||||||
toEntry.SetUsername(GetString(fromEntry, PwDefs.UserNameField), dbTo);
|
toEntry.SetUsername(GetString(fromEntry, PwDefs.UserNameField), dbTo);
|
||||||
toEntry.SetUrl(GetString(fromEntry, PwDefs.UrlField), dbTo);
|
toEntry.SetUrl(GetString(fromEntry, PwDefs.UrlField), dbTo);
|
||||||
toEntry.SetPassword(GetString(fromEntry, PwDefs.PasswordField), dbTo);
|
var pwd = GetString(fromEntry, PwDefs.PasswordField);
|
||||||
|
if (pwd != null)
|
||||||
|
toEntry.SetPassword(pwd, dbTo);
|
||||||
toEntry.SetNotes(GetString(fromEntry, PwDefs.NotesField), dbTo);
|
toEntry.SetNotes(GetString(fromEntry, PwDefs.NotesField), dbTo);
|
||||||
if (fromEntry.Binaries.Any())
|
if (fromEntry.Binaries.Any())
|
||||||
{
|
{
|
||||||
@@ -344,7 +383,7 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
ProtectedString protectedString = fromEntry.Strings.Get(id);
|
ProtectedString protectedString = fromEntry.Strings.Get(id);
|
||||||
if (protectedString == null)
|
if (protectedString == null)
|
||||||
return null;
|
return "";
|
||||||
return protectedString.ReadString();
|
return protectedString.ReadString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace keepass2android
|
|||||||
_format = format;
|
_format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
|
public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger)
|
||||||
{
|
{
|
||||||
KdbxFile kdbx = new KdbxFile(db);
|
KdbxFile kdbx = new KdbxFile(db);
|
||||||
kdbx.DetachBinaries = db.DetachBinaries;
|
kdbx.DetachBinaries = db.DetachBinaries;
|
||||||
@@ -33,5 +33,10 @@ namespace keepass2android
|
|||||||
{
|
{
|
||||||
kpDatabase.Save(stream, null);
|
kpDatabase.Save(stream, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanHaveEntriesInRootGroup
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,8 +218,10 @@ namespace keepass2android
|
|||||||
pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
|
pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
|
||||||
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
||||||
pwImp.MasterKey = pwDatabase.MasterKey;
|
pwImp.MasterKey = pwDatabase.MasterKey;
|
||||||
KdbxFile kdbx = new KdbxFile(pwImp);
|
var stream = GetStreamForBaseFile(fileStorage, ioc);
|
||||||
kdbx.Load(GetStreamForBaseFile(fileStorage, ioc), KdbpFile.GetFormatToUse(ioc), null);
|
|
||||||
|
_app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null);
|
||||||
|
|
||||||
|
|
||||||
pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);
|
pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null);
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,17 @@ namespace Kp2aUnitTests
|
|||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
|
||||||
|
|
||||||
|
|
||||||
runner.AddTests(new List<Type> { typeof(TestSelectStorageLocation) });
|
//runner.AddTests(new List<Type> { typeof(TestSelectStorageLocation) });
|
||||||
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase)});
|
//runner.AddTests(new List<Type> { typeof(TestSynchronizeCachedDatabase)});
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure"));
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure"));
|
||||||
|
|
||||||
//runner.AddTests(new List<Type> { typeof(TestLoadDb) });
|
//runner.AddTests(new List<Type> { typeof(TestSaveDb) });
|
||||||
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
|
//runner.AddTests(new List<Type> { typeof(TestCachingFileStorage) });
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly"));
|
||||||
|
runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSaveWithSyncKdb"));
|
||||||
|
runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadAndSave_TestIdenticalFiles_kdb"));
|
||||||
|
runner.AddTests(typeof(TestSaveDb).GetMethod("TestCreateSaveAndLoad_TestIdenticalFiles_kdb"));
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadAndSaveFromRemote1And1Ftp"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("LoadAndSaveFromRemote1And1Ftp"));
|
||||||
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
//runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly"));
|
||||||
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles"));
|
//runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles"));
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
IOConnectionInfo ioc = new IOConnectionInfo {Path = filename};
|
IOConnectionInfo ioc = new IOConnectionInfo {Path = filename};
|
||||||
Database db = app.CreateNewDatabase();
|
Database db = app.CreateNewDatabase();
|
||||||
|
if (filename.EndsWith(".kdb"))
|
||||||
|
{
|
||||||
|
db.DatabaseFormat = new KdbDatabaseFormat();
|
||||||
|
}
|
||||||
|
|
||||||
db.KpDatabase = new PwDatabase();
|
db.KpDatabase = new PwDatabase();
|
||||||
|
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ namespace Kp2aUnitTests
|
|||||||
//create the task:
|
//create the task:
|
||||||
CreateDb createDb = new CreateDb(app, Application.Context, ioc, new ActionOnFinish((success, message) =>
|
CreateDb createDb = new CreateDb(app, Application.Context, ioc, new ActionOnFinish((success, message) =>
|
||||||
{ createSuccesful = success;
|
{ createSuccesful = success;
|
||||||
|
if (!success)
|
||||||
|
Android.Util.Log.Debug("KP2A_Test", message);
|
||||||
}), false);
|
}), false);
|
||||||
//run it:
|
//run it:
|
||||||
|
|
||||||
createDb.Run();
|
createDb.Run();
|
||||||
//check expectations:
|
//check expectations:
|
||||||
Assert.IsTrue(createSuccesful);
|
Assert.IsTrue(createSuccesful);
|
||||||
@@ -43,7 +46,7 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
//ensure the the database can be loaded from file:
|
//ensure the the database can be loaded from file:
|
||||||
PwDatabase loadedDb = new PwDatabase();
|
PwDatabase loadedDb = new PwDatabase();
|
||||||
loadedDb.Open(ioc, new CompositeKey(), null, new KdbxDatabaseLoader(KdbxFormat.Default));
|
loadedDb.Open(ioc, new CompositeKey(), null, new KdbxDatabaseFormat(KdbxFormat.Default));
|
||||||
|
|
||||||
//Check whether the databases are equal
|
//Check whether the databases are equal
|
||||||
AssertDatabasesAreEqual(loadedDb, app.GetDb().KpDatabase);
|
AssertDatabasesAreEqual(loadedDb, app.GetDb().KpDatabase);
|
||||||
|
|||||||
@@ -144,6 +144,11 @@ namespace Kp2aUnitTests
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckForDuplicateUuids
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool OnServerCertificateError(int sslPolicyErrors)
|
public bool OnServerCertificateError(int sslPolicyErrors)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
@@ -15,6 +16,14 @@ using keepass2android;
|
|||||||
|
|
||||||
namespace Kp2aUnitTests
|
namespace Kp2aUnitTests
|
||||||
{
|
{
|
||||||
|
static class StringExt
|
||||||
|
{
|
||||||
|
public static bool ContainsAny(this string haystack, IEnumerable<string> needles)
|
||||||
|
{
|
||||||
|
return needles.Any(haystack.Contains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
class TestSaveDb: TestBase
|
class TestSaveDb: TestBase
|
||||||
{
|
{
|
||||||
@@ -54,39 +63,83 @@ namespace Kp2aUnitTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadEditSaveWithSyncKdb()
|
||||||
|
{
|
||||||
|
TestSync(DefaultDirectory + "savetest.kdb");
|
||||||
|
}
|
||||||
|
|
||||||
private void TestSync(string filename)
|
private void TestSync(string filename)
|
||||||
{
|
{
|
||||||
//create the default database:
|
//create the default database:
|
||||||
IKp2aApp app = SetupAppWithDatabase(filename);
|
IKp2aApp app = SetupAppWithDatabase(filename);
|
||||||
|
DisplayGroups(app, "After create");
|
||||||
//save it and reload it so we have a base version
|
//save it and reload it so we have a base version
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Save first version -- ");
|
||||||
SaveDatabase(app);
|
SaveDatabase(app);
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Load DB -- ");
|
||||||
app = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
app = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
||||||
|
DisplayGroups(app, "After reload");
|
||||||
//load it once again:
|
//load it once again:
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Load DB to app 2-- ");
|
||||||
IKp2aApp app2 = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
IKp2aApp app2 = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
||||||
|
DisplayGroups(app2, "After load to app2");
|
||||||
|
|
||||||
//modify the database by adding a group in both databases:
|
//modify the database by adding a group in both databases:
|
||||||
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
|
app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true);
|
||||||
var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy);
|
var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy);
|
||||||
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
|
app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
|
||||||
//save the database from app 1:
|
//save the database from app 1:
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Save from app 1 (with TestGroup) -- ");
|
||||||
SaveDatabase(app);
|
SaveDatabase(app);
|
||||||
|
|
||||||
|
|
||||||
((TestKp2aApp) app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes);
|
((TestKp2aApp) app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes);
|
||||||
|
|
||||||
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
|
//save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync:
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Save from app 2 (with TestGroup2, requires merge) -- ");
|
||||||
SaveDatabase(app2);
|
SaveDatabase(app2);
|
||||||
|
DisplayGroups(app2, "After save with merge");
|
||||||
//make sure the right question was asked
|
//make sure the right question was asked
|
||||||
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp) app2).LastYesNoCancelQuestionTitle);
|
Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp) app2).LastYesNoCancelQuestionTitle);
|
||||||
|
|
||||||
//add group 2 to app 1:
|
//add group 2 to app 1:
|
||||||
app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
|
app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true);
|
||||||
|
app.GetDb().KpDatabase.RootGroup.SortSubGroups(true);
|
||||||
|
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Load DB to new app -- ");
|
||||||
//load database to a new app instance:
|
//load database to a new app instance:
|
||||||
IKp2aApp resultApp = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
IKp2aApp resultApp = LoadDatabase(filename, DefaultPassword, DefaultKeyfile);
|
||||||
|
resultApp.GetDb().KpDatabase.RootGroup.SortSubGroups(true);
|
||||||
//ensure the sync was successful:
|
//ensure the sync was successful:
|
||||||
AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
string kdbxXml = DatabaseToXml(app);
|
||||||
|
string kdbxResultXml = DatabaseToXml(resultApp);
|
||||||
|
|
||||||
|
RemoveKdbLines(ref kdbxXml, true);
|
||||||
|
RemoveKdbLines(ref kdbxResultXml, true);
|
||||||
|
|
||||||
|
Assert.AreEqual(kdbxXml, kdbxResultXml);
|
||||||
|
|
||||||
|
//AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayGroups(IKp2aApp app, string name)
|
||||||
|
{
|
||||||
|
LogDebug("Groups display: " + name);
|
||||||
|
DisplayGroupRecursive(0, app.GetDb().Root);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayGroupRecursive(int level, PwGroup g)
|
||||||
|
{
|
||||||
|
LogDebug("Group name="+g.Name+", exp: " + g.Expires+ ", expTime="+g.ExpiryTime.ToString(CultureInfo.InvariantCulture));
|
||||||
|
foreach (var ch in g.Groups)
|
||||||
|
DisplayGroupRecursive(level + 1, ch);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LogDebug(string text, int indent=0)
|
||||||
|
{
|
||||||
|
Android.Util.Log.Debug("KP2A", text.PadLeft(indent*2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -293,10 +346,98 @@ namespace Kp2aUnitTests
|
|||||||
|
|
||||||
Assert.AreEqual(kdbxXml,kdbxReloadedXml);
|
Assert.AreEqual(kdbxXml,kdbxReloadedXml);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestLoadAndSave_TestIdenticalFiles_kdb()
|
||||||
|
{
|
||||||
|
IKp2aApp app = LoadDatabase(DefaultDirectory + "complexDb.kdb", "test", null);
|
||||||
|
app.GetDb().Root.SortSubGroups(true);
|
||||||
|
string kdbxXml = DatabaseToXml(app);
|
||||||
|
|
||||||
|
newFilename = TestDbDirectory + "tmp_complexDb.kdb";
|
||||||
|
if (File.Exists(newFilename))
|
||||||
|
File.Delete(newFilename);
|
||||||
|
app.GetDb().KpDatabase.IOConnectionInfo.Path = newFilename;
|
||||||
|
app.GetDb().SaveData(Application.Context);
|
||||||
|
|
||||||
|
|
||||||
|
IKp2aApp appReloaded = LoadDatabase(newFilename, "test", null);
|
||||||
|
appReloaded.GetDb().Root.SortSubGroups(true);
|
||||||
|
string kdbxReloadedXml = DatabaseToXml(appReloaded);
|
||||||
|
|
||||||
|
RemoveKdbLines(ref kdbxReloadedXml);
|
||||||
|
RemoveKdbLines(ref kdbxXml);
|
||||||
|
|
||||||
|
Assert.AreEqual(kdbxXml, kdbxReloadedXml);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void TestCreateSaveAndLoad_TestIdenticalFiles_kdb()
|
||||||
|
{
|
||||||
|
string filename = DefaultDirectory + "createsaveandload.kdb";
|
||||||
|
IKp2aApp app = SetupAppWithDatabase(filename);
|
||||||
|
string kdbxXml = DatabaseToXml(app);
|
||||||
|
//save it and reload it
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Save DB -- ");
|
||||||
|
SaveDatabase(app);
|
||||||
|
Android.Util.Log.Debug("KP2A", "-- Load DB -- ");
|
||||||
|
|
||||||
|
|
||||||
|
PwDatabase pwImp = new PwDatabase();
|
||||||
|
PwDatabase pwDatabase = app.GetDb().KpDatabase;
|
||||||
|
pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
|
||||||
|
pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
|
||||||
|
pwImp.MasterKey = pwDatabase.MasterKey;
|
||||||
|
|
||||||
|
IOConnectionInfo ioc = new IOConnectionInfo() {Path = filename};
|
||||||
|
using (Stream s = app.GetFileStorage(ioc).OpenFileForRead(ioc))
|
||||||
|
{
|
||||||
|
app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, s, null);
|
||||||
|
}
|
||||||
|
string kdbxReloadedXml = DatabaseToXml(app);
|
||||||
|
|
||||||
|
RemoveKdbLines(ref kdbxReloadedXml);
|
||||||
|
RemoveKdbLines(ref kdbxXml);
|
||||||
|
|
||||||
|
Assert.AreEqual(kdbxXml, kdbxReloadedXml);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
private void RemoveKdbLines(ref string databaseXml, bool removeForAfterSync=false)
|
||||||
|
{
|
||||||
|
//these values are not part of .kdb and thus cannot be the same when comparing two .kdb files
|
||||||
|
// -> remove them from the .xml
|
||||||
|
var stuffToRemove = new string[] {"<DatabaseNameChanged>",
|
||||||
|
"<DatabaseDescriptionChanged>",
|
||||||
|
"<DefaultUserNameChanged>",
|
||||||
|
"<MasterKeyChanged>",
|
||||||
|
"<RecycleBinChanged>",
|
||||||
|
"<EntryTemplatesGroupChanged>",
|
||||||
|
"<Key>","<Key />" //key of attachments
|
||||||
|
}.ToList();
|
||||||
|
string[] moreStuffToRemove = new string[]
|
||||||
|
{
|
||||||
|
"<UUID>",
|
||||||
|
"<ExpiryTime>",
|
||||||
|
"<LocationChanged>"
|
||||||
|
};
|
||||||
|
if (removeForAfterSync)
|
||||||
|
{
|
||||||
|
stuffToRemove.AddRange(moreStuffToRemove);
|
||||||
|
}
|
||||||
|
string[] lines = databaseXml.Split(new char[] {'\n'});
|
||||||
|
databaseXml = lines
|
||||||
|
.Where(line => !line.ContainsAny(stuffToRemove))
|
||||||
|
.Aggregate("", (current, line) => current + (line + "\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void TestLoadKdbxAndSaveKdbp_TestIdenticalFiles()
|
public void TestLoadKdbxAndSaveKdbp_TestIdenticalFiles()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ namespace Kp2aUnitTests
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
|||||||
Binary file not shown.
@@ -85,7 +85,7 @@ namespace keepass2android
|
|||||||
}
|
}
|
||||||
private bool AddEntryEnabled
|
private bool AddEntryEnabled
|
||||||
{
|
{
|
||||||
get { return App.Kp2a.GetDb().CanWrite; }
|
get { return App.Kp2a.GetDb().CanWrite && ((this.Group.ParentGroup != null) || App.Kp2a.GetDb().DatabaseFormat.CanHaveEntriesInRootGroup); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,6 @@
|
|||||||
<Compile Include="views\ClickView.cs" />
|
<Compile Include="views\ClickView.cs" />
|
||||||
<Compile Include="views\FileStorageViewKp2a.cs" />
|
<Compile Include="views\FileStorageViewKp2a.cs" />
|
||||||
<Compile Include="views\GroupView.cs" />
|
<Compile Include="views\GroupView.cs" />
|
||||||
<Compile Include="views\GroupRootView.cs" />
|
|
||||||
<Compile Include="PwGroupListAdapter.cs" />
|
<Compile Include="PwGroupListAdapter.cs" />
|
||||||
<Compile Include="views\FileStorageView.cs" />
|
<Compile Include="views\FileStorageView.cs" />
|
||||||
<Compile Include="views\Kp2aShortHelpView.cs" />
|
<Compile Include="views\Kp2aShortHelpView.cs" />
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
|
|
||||||
|
|
||||||
Keepass2Android is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Keepass2Android is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Util;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Widget;
|
|
||||||
|
|
||||||
namespace keepass2android.view
|
|
||||||
{
|
|
||||||
public class GroupRootView : RelativeLayout
|
|
||||||
{
|
|
||||||
public GroupRootView (IntPtr javaReference, JniHandleOwnership transfer)
|
|
||||||
: base(javaReference, transfer)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public GroupRootView (Context context) :
|
|
||||||
base (context)
|
|
||||||
{
|
|
||||||
Initialize ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public GroupRootView (Context context, IAttributeSet attrs) :
|
|
||||||
base (context, attrs)
|
|
||||||
{
|
|
||||||
Initialize ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public GroupRootView (Context context, IAttributeSet attrs, int defStyle) :
|
|
||||||
base (context, attrs, defStyle)
|
|
||||||
{
|
|
||||||
Initialize ();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Initialize ()
|
|
||||||
{
|
|
||||||
LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService);
|
|
||||||
inflater.Inflate(Resource.Layout.group_add_entry, this);
|
|
||||||
|
|
||||||
View addEntry = FindViewById(Resource.Id.add_entry);
|
|
||||||
addEntry.Visibility = ViewStates.Invisible;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user