From bc235b3ba594d0ed38c2d9f0d88cd75dd5f5234e Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Sat, 7 Feb 2015 20:54:13 +0100 Subject: [PATCH] added tests for kdb writing, fixed issue with syncing (keep UUIDs when loading again) --- src/KeePassLib2Android/IDatabaseFormat.cs | 4 +- src/KeePassLib2Android/PwDatabase.cs | 2 +- src/Kp2aBusinessLogic/database/Database.cs | 10 +- .../database/KdbDatabaseFormat.cs | 71 +++++++-- .../database/KdbxDatabaseFormat.cs | 7 +- src/Kp2aBusinessLogic/database/edit/SaveDB.cs | 6 +- src/Kp2aUnitTests/MainActivity.cs | 9 +- src/Kp2aUnitTests/TestBase.cs | 4 + src/Kp2aUnitTests/TestCreateDb.cs | 7 +- src/Kp2aUnitTests/TestFileStorage.cs | 5 + src/Kp2aUnitTests/TestKp2aApp.cs | 8 +- src/Kp2aUnitTests/TestSaveDb.cs | 147 +++++++++++++++++- .../TestSelectStorageLocation.cs | 5 + .../KP2AKdbLibrary/bin/kp2akdblibrary.jar | Bin 375534 -> 375272 bytes src/keepass2android/GroupActivity.cs | 2 +- src/keepass2android/keepass2android.csproj | 1 - src/keepass2android/views/GroupRootView.cs | 65 -------- 17 files changed, 253 insertions(+), 100 deletions(-) delete mode 100644 src/keepass2android/views/GroupRootView.cs diff --git a/src/KeePassLib2Android/IDatabaseFormat.cs b/src/KeePassLib2Android/IDatabaseFormat.cs index 5a594c83..a58b69a3 100644 --- a/src/KeePassLib2Android/IDatabaseFormat.cs +++ b/src/KeePassLib2Android/IDatabaseFormat.cs @@ -6,12 +6,14 @@ namespace KeePassLib { public interface IDatabaseFormat { - void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger); + void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger); byte[] HashOfLastStream { get; } bool CanWrite { get; } string SuccessMessage { get; } void Save(PwDatabase kpDatabase, Stream stream); + + bool CanHaveEntriesInRootGroup { get; } } } \ No newline at end of file diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index 4bfe8628..3b194722 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -600,7 +600,7 @@ namespace KeePassLib m_bModified = false; - format.PopulateDatabaseFromStream(this, pwKey, s, slLogger); + format.PopulateDatabaseFromStream(this, s, slLogger); m_pbHashOfLastIO = format.HashOfLastStream; m_pbHashOfFileOnDisk = format.HashOfLastStream; diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs index a6baec77..f4bab2ef 100644 --- a/src/Kp2aBusinessLogic/database/Database.cs +++ b/src/Kp2aBusinessLogic/database/Database.cs @@ -75,7 +75,7 @@ namespace keepass2android private bool _loaded; private bool _reloadRequested; - private IDatabaseFormat _databaseFormat; + private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); public bool ReloadRequested { @@ -132,6 +132,12 @@ namespace keepass2android /// 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) { IFileStorage fileStorage = _app.GetFileStorage(iocInfo); @@ -173,7 +179,7 @@ namespace keepass2android KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions)) { - _databaseFormat.Save(KpDatabase, trans.OpenFile()); + DatabaseFormat.Save(KpDatabase, trans.OpenFile()); trans.CommitWrite(); } diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs index f17d6905..4746024e 100644 --- a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs @@ -22,12 +22,12 @@ using Random = System.Random; namespace keepass2android { - class KdbDatabaseFormat: IDatabaseFormat + public class KdbDatabaseFormat: IDatabaseFormat { private Dictionary _groupData = new Dictionary(); 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 var importer = new Com.Keepassdroid.Database.Load.ImporterV3(); @@ -35,13 +35,13 @@ namespace keepass2android 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) - KcpPassword passwordKey = (KcpPassword)key.GetUserKey(typeof(KcpPassword)); + KcpPassword passwordKey = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword)); if (passwordKey != null) { password = passwordKey.Password.ReadString(); } - KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile)); + KcpKeyFile passwordKeyfile = (KcpKeyFile)db.MasterKey.GetUserKey(typeof(KcpKeyFile)); MemoryStream keyfileStream = null; if (passwordKeyfile != null) { @@ -91,22 +91,31 @@ namespace keepass2android private PwGroup ConvertGroup(PwGroupV3 groupV3) { 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.LastAccessTime = ConvertTime(groupV3.TLastAccess); pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod); - pwGroup.Expires = !PwGroupV3.NeverExpire.Equals(groupV3.TExpire); - if (pwGroup.Expires) - pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire); + pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire); + pwGroup.Expires = !(Math.Abs((pwGroup.ExpiryTime - _expireNever).TotalMilliseconds) < 500); ; if (groupV3.Icon != null) pwGroup.IconId = (PwIcon) groupV3.Icon.IconId; - _groupData.Add(pwGroup.Uuid, new AdditionalGroupData + _groupData[pwGroup.Uuid] = new AdditionalGroupData { Flags = groupV3.Flags, Id = groupV3.Id.Id - }); + }; for (int i = 0; i < groupV3.ChildGroups.Count;i++) @@ -124,6 +133,20 @@ namespace keepass2android 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) { PwEntry toEntry = new PwEntry(false, false); @@ -142,6 +165,7 @@ namespace keepass2android if (fromEntry.Icon != null) toEntry.IconId = (PwIcon) fromEntry.Icon.IconId; 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.UrlField, false, fromEntry.Url); SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password); @@ -223,7 +247,9 @@ namespace keepass2android } //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); @@ -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(); + hashedStream.Close(); + HashOfLastStream = hashedStream.Hash; + stream.Close(); + } + + public bool CanHaveEntriesInRootGroup + { + get { return false; } } private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary groupV3s) @@ -254,7 +289,7 @@ namespace keepass2android 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]; @@ -269,6 +304,7 @@ namespace keepass2android { PwGroupV3 toGroup = new PwGroupV3(); toGroup.Name = fromGroup.Name; + Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name); toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime)); toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime)); @@ -279,7 +315,7 @@ namespace keepass2android } else { - toGroup.TExpire = new PwDate(PwGroupV3.NeverExpire); + toGroup.TExpire = new PwDate(ConvertTime(_expireNever)); } toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId); @@ -327,9 +363,12 @@ namespace keepass2android toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId); 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.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); if (fromEntry.Binaries.Any()) { @@ -344,7 +383,7 @@ namespace keepass2android { ProtectedString protectedString = fromEntry.Strings.Get(id); if (protectedString == null) - return null; + return ""; return protectedString.ReadString(); } diff --git a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs index 8db4e88c..2fe4b0b2 100644 --- a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs @@ -15,7 +15,7 @@ namespace keepass2android _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); kdbx.DetachBinaries = db.DetachBinaries; @@ -33,5 +33,10 @@ namespace keepass2android { kpDatabase.Save(stream, null); } + + public bool CanHaveEntriesInRootGroup + { + get { return true; } + } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs index 851b2de2..1d64245f 100644 --- a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs +++ b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs @@ -218,8 +218,10 @@ namespace keepass2android pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); pwImp.MasterKey = pwDatabase.MasterKey; - KdbxFile kdbx = new KdbxFile(pwImp); - kdbx.Load(GetStreamForBaseFile(fileStorage, ioc), KdbpFile.GetFormatToUse(ioc), null); + var stream = GetStreamForBaseFile(fileStorage, ioc); + + _app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); + pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null); diff --git a/src/Kp2aUnitTests/MainActivity.cs b/src/Kp2aUnitTests/MainActivity.cs index c2ca9fed..09b574d3 100644 --- a/src/Kp2aUnitTests/MainActivity.cs +++ b/src/Kp2aUnitTests/MainActivity.cs @@ -23,14 +23,17 @@ namespace Kp2aUnitTests //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly")); - runner.AddTests(new List { typeof(TestSelectStorageLocation) }); + //runner.AddTests(new List { typeof(TestSelectStorageLocation) }); //runner.AddTests(new List { typeof(TestSynchronizeCachedDatabase)}); //runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure")); //runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure")); - //runner.AddTests(new List { typeof(TestLoadDb) }); + //runner.AddTests(new List { typeof(TestSaveDb) }); //runner.AddTests(new List { 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("TestLoadKdbpWithPasswordOnly")); //runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles")); diff --git a/src/Kp2aUnitTests/TestBase.cs b/src/Kp2aUnitTests/TestBase.cs index 7293fc9a..2b8c6d9a 100644 --- a/src/Kp2aUnitTests/TestBase.cs +++ b/src/Kp2aUnitTests/TestBase.cs @@ -136,6 +136,10 @@ namespace Kp2aUnitTests IOConnectionInfo ioc = new IOConnectionInfo {Path = filename}; Database db = app.CreateNewDatabase(); + if (filename.EndsWith(".kdb")) + { + db.DatabaseFormat = new KdbDatabaseFormat(); + } db.KpDatabase = new PwDatabase(); diff --git a/src/Kp2aUnitTests/TestCreateDb.cs b/src/Kp2aUnitTests/TestCreateDb.cs index 2b11f005..9ec352fa 100644 --- a/src/Kp2aUnitTests/TestCreateDb.cs +++ b/src/Kp2aUnitTests/TestCreateDb.cs @@ -30,9 +30,12 @@ namespace Kp2aUnitTests bool createSuccesful = false; //create the task: 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); //run it: + createDb.Run(); //check expectations: Assert.IsTrue(createSuccesful); @@ -43,7 +46,7 @@ namespace Kp2aUnitTests //ensure the the database can be loaded from file: 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 AssertDatabasesAreEqual(loadedDb, app.GetDb().KpDatabase); diff --git a/src/Kp2aUnitTests/TestFileStorage.cs b/src/Kp2aUnitTests/TestFileStorage.cs index 4629e684..4b70c200 100644 --- a/src/Kp2aUnitTests/TestFileStorage.cs +++ b/src/Kp2aUnitTests/TestFileStorage.cs @@ -144,6 +144,11 @@ namespace Kp2aUnitTests throw new NotImplementedException(); } + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) { throw new NotImplementedException(); diff --git a/src/Kp2aUnitTests/TestKp2aApp.cs b/src/Kp2aUnitTests/TestKp2aApp.cs index f6123abf..526335c8 100644 --- a/src/Kp2aUnitTests/TestKp2aApp.cs +++ b/src/Kp2aUnitTests/TestKp2aApp.cs @@ -187,8 +187,12 @@ namespace Kp2aUnitTests } } - - + + public bool CheckForDuplicateUuids + { + get { return true; } + } + public bool OnServerCertificateError(int sslPolicyErrors) { diff --git a/src/Kp2aUnitTests/TestSaveDb.cs b/src/Kp2aUnitTests/TestSaveDb.cs index c5968ddc..f07af98f 100644 --- a/src/Kp2aUnitTests/TestSaveDb.cs +++ b/src/Kp2aUnitTests/TestSaveDb.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -15,6 +16,14 @@ using keepass2android; namespace Kp2aUnitTests { + static class StringExt + { + public static bool ContainsAny(this string haystack, IEnumerable needles) + { + return needles.Any(haystack.Contains); + } + } + [TestClass] class TestSaveDb: TestBase { @@ -54,39 +63,83 @@ namespace Kp2aUnitTests } + [TestMethod] + public void TestLoadEditSaveWithSyncKdb() + { + TestSync(DefaultDirectory + "savetest.kdb"); + } + private void TestSync(string filename) { //create the default database: IKp2aApp app = SetupAppWithDatabase(filename); + DisplayGroups(app, "After create"); //save it and reload it so we have a base version + Android.Util.Log.Debug("KP2A", "-- Save first version -- "); SaveDatabase(app); + Android.Util.Log.Debug("KP2A", "-- Load DB -- "); app = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); + DisplayGroups(app, "After reload"); //load it once again: + Android.Util.Log.Debug("KP2A", "-- Load DB to app 2-- "); IKp2aApp app2 = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); + DisplayGroups(app2, "After load to app2"); //modify the database by adding a group in both databases: app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy); app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); //save the database from app 1: + Android.Util.Log.Debug("KP2A", "-- Save from app 1 (with TestGroup) -- "); SaveDatabase(app); + ((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: + Android.Util.Log.Debug("KP2A", "-- Save from app 2 (with TestGroup2, requires merge) -- "); SaveDatabase(app2); - + DisplayGroups(app2, "After save with merge"); //make sure the right question was asked Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp) app2).LastYesNoCancelQuestionTitle); //add group 2 to app 1: 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: IKp2aApp resultApp = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); - + resultApp.GetDb().KpDatabase.RootGroup.SortSubGroups(true); //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); + } + + [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[] {"", + "", + "", + "", + "", + "", + "","" //key of attachments + }.ToList(); + string[] moreStuffToRemove = new string[] + { + "", + "", + "" + }; + 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] public void TestLoadKdbxAndSaveKdbp_TestIdenticalFiles() { diff --git a/src/Kp2aUnitTests/TestSelectStorageLocation.cs b/src/Kp2aUnitTests/TestSelectStorageLocation.cs index 2a980f77..caff215f 100644 --- a/src/Kp2aUnitTests/TestSelectStorageLocation.cs +++ b/src/Kp2aUnitTests/TestSelectStorageLocation.cs @@ -99,6 +99,11 @@ namespace Kp2aUnitTests throw new NotImplementedException(); } + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) { throw new NotImplementedException(); diff --git a/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar b/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar index d3112534269cda94d279f52be269e58855c7e332..db50f3f96c35cd0a4a0472e600bff1164793e020 100644 GIT binary patch delta 13182 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

kUP;uwCV1uAX8&V2s`k4Pr_MGg04 zg)rUxqxG-vA|jJ*!N^!-pvD$z49UiTU+DmLJE$146)sY8?K%YzZU>b@PQyot6+5T` z#DoZ7wuh=iVkrS02Pi#|VGk8WiUPPDpbWs7JyZzdO$|4gK!XO-zX4%$%T)AWW?P3Kk?< zKn32e3%HLS#~+LVNIru~LI5t{2)qKF&!F58T|T(*hL{QfS}ssgpqd{E3#fGl3)3o$ zAeh4cWg_4Lj`vs+j-Nw5$iM>y(_F#UB*_2w85Qty1=ri8_zwU!;Z{*)gou2EM2tw@ z*9{!#i7FgF1cuzeG*E2df&(~wiG&T*xf z37C6A8piS&KSjE(czwqnl;A~Zqe+VHE=nW>w>J7$A0C4;OaP$T@X?-R@?+>L0 zdcDEMe$PS(b|0{qW!VT3>;oQ21Nnah2CxD*qZc5E$ZI4#AU7NwT-z5MJgyLdKNKJl zAhvlW96Qw`aBdyA1^0cybYYEfau*Qvg9<<%v>-&ZA9%X$zl94X;J^>;%UUN~m_m*} zAUtyXK@aa9xS$5gyOI7qiKqj>Nwj(qxH15W4wwW$c_FT!{tzLgB0xG2yf>=@ps2vo zDA==XKQL>?Krr&-IKs~?5KLxs3L)wOp?r{)C4_(kfmQgmj1Y!FVC59HfTJKNGte0X zrfGNp7iN&;LqH1#UNL?GbujSkIy-@DDuDR}2|VKBpxD5;J`@Eo3kD}myhhkkgQ2nz zyIX|F_>F`EEJuNf5r%+1)DWO68cGf*BS8oeRknj;3{(Wp4goie0s0?-3mDr#Ndd}G z&}SMCfz?Byf)I2GFfjcC@;?L@U?YcM1Hb>|S`Y>f7EFzBsHTFD zAliBb$G0>H%oPrHy#k|=ZT?_Mh=v_P1lvOJfrVG#x_q7?NP-sx9bk{0k;M#y!V;u-) zG9p?u2{ixnIMa#&&1)G592o<44y6boR${>Mipvp#KNiXlsjonY7qQ@TGQ1ukTpJ*$ z@J?_;Bpd$_QUF5-+}`GC;Py6%1830rfUw2Kfir}4{}EU~XBT3Fu|hI?5qRh$xV^!L zW#Nr^+{#Boi6mSSnBDl>Gl1zs12}%Xu3oa-T zf{7BD{I3AqG%#SB8et=&Mkf5zEEf*Lghb;0BTxaSbf^gAh#x^dq=Sp25Jm`a zC@4Ui0S3I3_>1^ufPJTu{fnr}AX6byLCyqIA<+KICYA|$SL^>pGBd$^B%UCMW)>7R zEemW4k>wvC34u5OiCIux$W#FQdKZ9ZgZrNXh7hVj$d~}L?tgpID;u2ha|n=_4F*_+ z!LL+6YA9Id_t{`^*m6KKN(>xR0{qd)q(F5gh+o3-=YMf~4w$1_9Kt6s7Wtod;(vFA zbS~&P6OXWY=fWSbi3l-}3m!4o{|fd*WIFhp^uJNsd0R&{y2wX%t3-G!GY{eWi3Kh^<1TG>|5J3hX zf`qOZBvv|b@&L%wM!5%nxPr$-F_Z>EX$Z$Gz;-eCex_jo7i>U+ISM7h=bwY;1vob3 z#u{#OfkZgLuap3LDY%LwPlO051*0L}2(elUm4lE6A%s#OIFv~ln843)1Su#3%`CA9 zVHg9Nsmnq0!(_m%561mR0x1^Sw`An=naaPW?I2+>&uPW$3LLbSJ` zP$L3#tHA*NPQa}ie9JTJf}81qgbygB09p-LgWjG$1Q)>ULBR%`;TWwCfhBrD$A6Oi zRs)W6+mFCBwNPb9!62fb;k95}Bu9XwM(|BTq7I4$cn^amzN!VssTuu)bs*T|aB&X^ zm;tBhsRM^NpZ<$T)PrB8WWW3&jKEMmI0Ww8AA$`qHh?Ec`TQS(1;jRh&qm?OApTeID;MzL5(O20L*fHP zP2ggUF@Xw7~CXu)X7KfJQ6$ z#QDt%?)QJrb+;C9u*cj89Lxp9`8O7h9}542( - diff --git a/src/keepass2android/views/GroupRootView.cs b/src/keepass2android/views/GroupRootView.cs deleted file mode 100644 index ca1bdaf2..00000000 --- a/src/keepass2android/views/GroupRootView.cs +++ /dev/null @@ -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 . - */ - -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; - - } - } -} -