From b16696e518b963b305247d0de28abc89ade2c759 Mon Sep 17 00:00:00 2001 From: Dmitry Zhutkov Date: Sat, 8 Aug 2020 09:06:37 +0500 Subject: [PATCH 01/15] Important Addendum 1 The search has been rewritten, it does not satisfy the quality and is difficult to expand. Added a new tool "Check Weak Passwords", which allows you to find these same passwords and fix them in one click. Also added to the search alphabetical sorter and favorites. --- .vs/OlibKey/DesignTimeBuild/.dtbcache.v2 | Bin 183542 -> 183542 bytes .vs/OlibKey/v16/.suo | Bin 218112 -> 207872 bytes App.axaml | 54 +++++- Assets/Local/lang.de-DE.axaml | 6 + Assets/Local/lang.en-US.axaml | 6 + Assets/Local/lang.fr-FR.axaml | 6 + Assets/Local/lang.hy-AM.axaml | 6 + Assets/Local/lang.ru-RU.axaml | 6 + Assets/Local/lang.uk-UA.axaml | 6 + Core/PasswordGenerator.cs | 52 ++++++ MainWindow.axaml | 6 +- MainWindowViewModel.cs | 91 ++++++---- OlibKey.csproj | 4 +- Structures/Database.cs | 2 + .../CheckingWeakPasswordsWindowViewModel.cs | 117 +++++++++++++ Views/Controls/LoginListItem.axaml | 16 +- Views/Controls/LoginListItem.axaml.cs | 33 +++- Views/Pages/CreateLoginPage.axaml.cs | 4 +- Views/Pages/EditLoginPage.axaml.cs | 4 +- Views/Windows/AboutWindow.axaml | 2 +- .../Windows/CheckingWeakPasswordsWindow.axaml | 32 ++++ .../CheckingWeakPasswordsWindow.axaml.cs | 28 ++++ .../Windows/PasswordGeneratorWindow.axaml.cs | 17 +- Views/Windows/SearchWindow.axaml | 13 +- Views/Windows/SearchWindow.axaml.cs | 157 ++++++------------ Views/Windows/SettingsWindow.axaml | 4 +- 26 files changed, 507 insertions(+), 165 deletions(-) create mode 100644 Core/PasswordGenerator.cs create mode 100644 ViewModels/Windows/CheckingWeakPasswordsWindowViewModel.cs create mode 100644 Views/Windows/CheckingWeakPasswordsWindow.axaml create mode 100644 Views/Windows/CheckingWeakPasswordsWindow.axaml.cs diff --git a/.vs/OlibKey/DesignTimeBuild/.dtbcache.v2 b/.vs/OlibKey/DesignTimeBuild/.dtbcache.v2 index 875cc6e94d1dd28d869427699dfec3ab334eec66..38e2519979cba8faeab82bf1b9a82cf96bd6c375 100644 GIT binary patch delta 11887 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 Dn^CfB delta 18503 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 DB@i?7 diff --git a/App.axaml b/App.axaml index 98915c7..79ed4d7 100644 --- a/App.axaml +++ b/App.axaml @@ -10,7 +10,7 @@ - + @@ -96,9 +96,6 @@ - @@ -106,6 +103,9 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Assets/Local/lang.de-DE.axaml b/Assets/Local/lang.de-DE.axaml index 925a95d..825226c 100644 --- a/Assets/Local/lang.de-DE.axaml +++ b/Assets/Local/lang.de-DE.axaml @@ -103,6 +103,12 @@ Sie haben keinen Repository-Pfad ausgewählt oder kein Hauptkennwort angegeben! Sie können keinen Tresor erstellen, wenn dieser Tresor bereits geöffnet ist! + Überprüfen schwacher Passwörter + Gesamtkomplexität: + Fix + Wählen Sie Alle + Hier sind schwache Passwörter, die Sie ersetzen können. Wählen Sie diejenigen aus, die Sie ändern möchten, und klicken Sie auf die Schaltfläche Fix. Der Austausch erfolgt gemäß den Einstellungen des Passwortgenerators. + Öffnen Geben Sie das Master-Passwort ein diff --git a/Assets/Local/lang.en-US.axaml b/Assets/Local/lang.en-US.axaml index 139122c..c9247ad 100644 --- a/Assets/Local/lang.en-US.axaml +++ b/Assets/Local/lang.en-US.axaml @@ -103,6 +103,12 @@ You did not select a repository path or did not provide a master password! You cannot create a vault if this vault is already open! + Checking weak passwords + Overall complexity: + Fix + Select all + Here are weak passwords that you can replace. Select the ones you want to change and click on the Fix button. The replacement will take place according to the password generator settings. + Open Enter Master Password diff --git a/Assets/Local/lang.fr-FR.axaml b/Assets/Local/lang.fr-FR.axaml index c512f5b..47d70cb 100644 --- a/Assets/Local/lang.fr-FR.axaml +++ b/Assets/Local/lang.fr-FR.axaml @@ -103,6 +103,12 @@ Vous n'avez pas sélectionné de chemin de référentiel ou n'avez pas fourni de mot de passe principal! Vous ne pouvez pas créer de coffre-fort si ce coffre-fort est déjà ouvert! + Vérification des mots de passe faibles + Complexité globale: + Réparer + Tout sélectionner + Voici les mots de passe faibles que vous pouvez remplacer. Sélectionnez ceux que vous souhaitez modifier et cliquez sur le bouton Réparer. Le remplacement aura lieu selon les paramètres du générateur de mot de passe. + Ouverte Entrez le mot de passe principal diff --git a/Assets/Local/lang.hy-AM.axaml b/Assets/Local/lang.hy-AM.axaml index ad4d332..df048f9 100644 --- a/Assets/Local/lang.hy-AM.axaml +++ b/Assets/Local/lang.hy-AM.axaml @@ -102,6 +102,12 @@ Թվային մուտքագրման դաշտերում արժեքները պետք է պարունակեն թվեր և չեն կարող զրո լինել: Դուք չեք ընտրել պահեստային ուղի կամ չեք տրամադրել վարպետ գաղտնաբառ: Դուք չեք կարող ստեղծել պահոց, եթե այս կամարն արդեն բաց է: + + Ստուգում են թույլ գաղտնաբառերը + Ընդհանուր դժվարություն. + Ուղղել + Ընտրել բոլորը + Ահա թույլ գաղտնաբառերը, որոնք կարող եք փոխարինել: Ընտրեք դրանք, որոնք ցանկանում եք փոխել և կտտացրեք ամրագրման կոճակին: Փոխումը տեղի է ունենալու ըստ գաղտնաբառի գեներատորի պարամետրերի: Բացել Գրեք վարպետության գաղտնաբառը diff --git a/Assets/Local/lang.ru-RU.axaml b/Assets/Local/lang.ru-RU.axaml index 57d5b73..900872b 100644 --- a/Assets/Local/lang.ru-RU.axaml +++ b/Assets/Local/lang.ru-RU.axaml @@ -102,6 +102,12 @@ В полях с вводом цифровых значении должны содержать числа и не могут быть равны нулю! Вы не выбрали путь хранилища или не указали мастер-пароль! Вы не сможете создать хранилище, оно уже открыто! + + Проверка слабых паролей + Общая сложность: + Исправить + Выделить все + Здесь представлены слабые пароли, которые можно заменить. Выберите те, которые вы хотите изменить, и нажмите на кнопку Исправить. Замена будет происходить согласно настройкам генератора паролей. Открыть Введите мастер-пароль diff --git a/Assets/Local/lang.uk-UA.axaml b/Assets/Local/lang.uk-UA.axaml index 12577bb..dceb407 100644 --- a/Assets/Local/lang.uk-UA.axaml +++ b/Assets/Local/lang.uk-UA.axaml @@ -102,6 +102,12 @@ У полях з введенням цифрових значенні повинні містити числа і не можуть бути рівними нулю! Ви не обрали шлях сховища або не вказали майстер-пароль! Ви не зможете створити сховище, якщо це сховище вже відкрито! + + Перевірка слабких паролів + Загальна складність: + Виправити + Виділити все + Тут представлені слабкі паролі, які можна замінити. Виберіть ті, які ви хочете змінити, і натисніть на кнопку Виправити. Заміна відбуватиметься згідно налаштувань генератора паролів. Відкрити Введіть майстер-пароль diff --git a/Core/PasswordGenerator.cs b/Core/PasswordGenerator.cs new file mode 100644 index 0000000..9915ba5 --- /dev/null +++ b/Core/PasswordGenerator.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia; +using Avalonia.Controls; +using OlibKey.Views.Windows; + +namespace OlibKey.Core +{ + public static class PasswordGenerator + { + public static string RandomPassword() + { + const string lower = "abcdefghijklmnopqrstuvwxyz"; + const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const string number = "0123456789"; + const string special = @"~!@#$%^&*():;[]{}<>,.?/\|"; + string other = App.Settings.GeneratorTextOther; + + string allowed = ""; + string password = ""; + if (App.Settings.GeneratorAllowLowercase) allowed += lower; + if (App.Settings.GeneratorAllowUppercase) allowed += upper; + if (App.Settings.GeneratorAllowNumber) allowed += number; + if (App.Settings.GeneratorAllowSpecial) allowed += special; + if (App.Settings.GeneratorAllowUnderscore && password.IndexOfAny("_".ToCharArray()) == -1) + { + allowed += "_"; + password += "_"; + } + if (App.Settings.GeneratorAllowOther) allowed += other; + int minChars = int.Parse(App.Settings.GenerationCount); + int numChars = Encryptor.RandomInteger(minChars, minChars); + while (password.Length < numChars) + password += allowed.Substring(Encryptor.RandomInteger(0, allowed.Length - 1), 1); + password = RandomizeString(password); + return password; + } + private static string RandomizeString(string str) + { + string result = ""; + while (str.Length > 0) + { + int i = Encryptor.RandomInteger(0, str.Length - 1); + result += str.Substring(i, 1); + str = str.Remove(i, 1); + } + + return result; + } + } +} diff --git a/MainWindow.axaml b/MainWindow.axaml index 192c366..011ef0a 100644 --- a/MainWindow.axaml +++ b/MainWindow.axaml @@ -10,7 +10,7 @@ Title="OlibKey" WindowStartupLocation="CenterScreen" Icon="avares://OlibKey/app.ico" - DragDrop.AllowDrop="{Binding IsActivateDnD}" + DragDrop.AllowDrop="True" Background="{DynamicResource ThemeWindowBackgroundBrush}" MinWidth="500" MinHeight="350"> @@ -52,7 +52,8 @@ - + + @@ -119,6 +120,7 @@ + diff --git a/MainWindowViewModel.cs b/MainWindowViewModel.cs index 803ac70..c4ccb4a 100644 --- a/MainWindowViewModel.cs +++ b/MainWindowViewModel.cs @@ -19,11 +19,15 @@ public class MainWindowViewModel : ReactiveObject { private bool _isUnlockDatabase; private bool _isLockDatabase; - private bool _isActivateDnD; private int _selectedTabIndex; private int _countLogins; + private ChangeMasterPasswordWindow _windowChangeMasterPassword; + private SettingsWindow _settingsWindow; + public PasswordGeneratorWindow PasswordGenerator; + public CheckingWeakPasswordsWindow CheckingWindow; + private ObservableCollection _tabItems = new ObservableCollection(); #region ReactiveCommands @@ -44,6 +48,7 @@ public class MainWindowViewModel : ReactiveObject private ReactiveCommand LockAllDatabasesCommand { get; } private ReactiveCommand SaveAllDatabasesCommand { get; } private ReactiveCommand UnlockAllDatabasesCommand { get; } + private ReactiveCommand OpenCheckingWeakPasswordsWindowCommand { get; } #endregion @@ -74,11 +79,6 @@ public int CountLogins get => _countLogins; set => this.RaiseAndSetIfChanged(ref _countLogins, value); } - public bool IsActivateDnD - { - get => _isActivateDnD; - set => this.RaiseAndSetIfChanged(ref _isActivateDnD, value); - } public DatabaseControl SelectedTabItem { get { try { return (DatabaseControl)TabItems[SelectedTabIndex].Content; } catch { return null; } } } public DatabaseTabHeader SelectedTabItemHeader { get { try { return (DatabaseTabHeader)TabItems[SelectedTabIndex].Header; } catch { return null; } } } public List OpenStorages { get; set; } @@ -103,13 +103,24 @@ public MainWindowViewModel() LockAllDatabasesCommand = ReactiveCommand.Create(LockAllDatabases); SaveAllDatabasesCommand = ReactiveCommand.Create(SaveAllDatabases); UnlockAllDatabasesCommand = ReactiveCommand.Create(UnlockAllDatabases); + OpenCheckingWeakPasswordsWindowCommand = ReactiveCommand.Create(OpenCheckingWeakPasswordsWindow); App.Autoblock.Tick += AutoblockStorage; } + private void OpenCheckingWeakPasswordsWindow() + { + if (SelectedTabItem == null || SelectedTabItem.ViewModel.IsLockDatabase) return; + CheckingWindow = new CheckingWeakPasswordsWindow(); + CheckingWindow.ShowDialog(App.MainWindow); + + SelectedTabItem.ViewModel.SelectedIndex = -1; + SelectedTabItem.ViewModel.Router.Navigate.Execute(new StartPageViewModel()); + } + public async void Loading(MainWindow mainWindow) { - foreach (var item in OpenStorages.Where(item => Path.GetExtension(item) == ".olib")) + foreach (string item in OpenStorages.Where(item => Path.GetExtension(item) == ".olib")) { App.Settings.PathDatabase = item; @@ -142,9 +153,7 @@ public async void Loading(MainWindow mainWindow) databaseTabHeader = tabHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(App.Settings.PathDatabase) } }; - IsActivateDnD = false; await passwordWindow.ShowDialog(App.MainWindow); - IsActivateDnD = true; } if (OpenStorages.Count > 0) @@ -184,14 +193,13 @@ public async void Loading(MainWindow mainWindow) databaseTabHeader = tabHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(App.Settings.PathDatabase) } }; - IsActivateDnD = false; await passwordWindow.ShowDialog(mainWindow); - IsActivateDnD = true; } } public async void OpenStorageDnD(List files) { - foreach (var item in files.Where(item => Path.GetExtension(item) == ".olib").Where(item => TabItems.All(i => ((DatabaseControl)i.Content).ViewModel.PathDatabase != item))) + foreach (string item in files.Where(item => Path.GetExtension(item) == ".olib").Where(item => + TabItems.All(i => ((DatabaseControl)i.Content).ViewModel.PathDatabase != item))) { App.Settings.PathDatabase = item; @@ -224,17 +232,21 @@ public async void OpenStorageDnD(List files) databaseTabHeader = tabHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(App.Settings.PathDatabase) } }; - IsActivateDnD = false; await passwordWindow.ShowDialog(App.MainWindow); - IsActivateDnD = true; } App.MainWindow.MessageStatusBar((string)Application.Current.FindResource("Not6")); } public void AutoblockStorage(object sender, EventArgs e) { - foreach (TabItem item in TabItems) + App.SearchWindow?.Close(); + _windowChangeMasterPassword?.Close(); + _settingsWindow?.Close(); + PasswordGenerator?.Close(); + CheckingWindow?.Close(); + for (var index = 0; index < TabItems.Count; index++) { + TabItem item = TabItems[index]; DatabaseControl db = (DatabaseControl)item.Content; DatabaseTabHeader dbHeader = (DatabaseTabHeader)item.Header; @@ -249,6 +261,7 @@ public void AutoblockStorage(object sender, EventArgs e) db.ViewModel.Router.Navigate.Execute(new StartPageViewModel(db.ViewModel)); } } + App.Autoblock.Stop(); } public void ProgramClosing(object sender, System.ComponentModel.CancelEventArgs e) @@ -309,9 +322,7 @@ private async void UnlockDatabase() databaseTabHeader = SelectedTabItemHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(SelectedTabItem.ViewModel.PathDatabase) } }; - IsActivateDnD = false; await passwordWindow.ShowDialog(App.MainWindow); - IsActivateDnD = true; } private async void UnlockAllDatabases() { @@ -326,9 +337,7 @@ private async void UnlockAllDatabases() databaseTabHeader = dbHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(db.ViewModel.PathDatabase) } }; - IsActivateDnD = false; await passwordWindow.ShowDialog(App.MainWindow); - IsActivateDnD = true; } App.MainWindow.MessageStatusBar((string)Application.Current.FindResource("Not8")); } @@ -346,8 +355,9 @@ private void LockDatabase() } private void LockAllDatabases() { - foreach (TabItem item in TabItems) + for (var index = 0; index < TabItems.Count; index++) { + TabItem item = TabItems[index]; DatabaseControl db = (DatabaseControl)item.Content; DatabaseTabHeader dbHeader = (DatabaseTabHeader)item.Header; @@ -357,6 +367,7 @@ private void LockAllDatabases() IsLockDatabase = db.ViewModel.IsLockDatabase = dbHeader.iLock.IsVisible = true; db.ViewModel.Router.Navigate.Execute(new StartPageViewModel(db.ViewModel)); } + App.MainWindow.MessageStatusBar((string)Application.Current.FindResource("Not7")); } private async void OpenDatabase() @@ -367,7 +378,8 @@ private async void OpenDatabase() if (files.Count == 0) return; - foreach (var file in files.Where(file => TabItems.All(i => ((DatabaseControl)i.Content).ViewModel.PathDatabase != file))) + foreach (string file in files.Where(file => + TabItems.All(i => ((DatabaseControl)i.Content).ViewModel.PathDatabase != file))) { App.Settings.PathDatabase = file; @@ -393,9 +405,8 @@ private async void OpenDatabase() TabItems.Add(new TabItem { Header = tabHeader, Content = db }); RequireMasterPasswordWindow requireMaster = new RequireMasterPasswordWindow { LoadStorageCallback = LoadDatabase, databaseControl = db, databaseTabHeader = tabHeader, tbNameStorage = { Text = Path.GetFileNameWithoutExtension(App.Settings.PathDatabase) } }; - IsActivateDnD = false; + await requireMaster.ShowDialog(App.MainWindow); - IsActivateDnD = true; } } private void CreateLogin() @@ -413,7 +424,12 @@ private void CreateLogin() private void LoadDatabase(DatabaseControl db, DatabaseTabHeader dbHeader) { db.ViewModel.Database = SaveAndLoadDatabase.LoadFiles(db); - foreach (Login logins in db.ViewModel.Database.Logins) db.ViewModel.AddLogin(logins); + for (var index = 0; index < db.ViewModel.Database.Logins.Count; index++) + { + Login logins = db.ViewModel.Database.Logins[index]; + db.ViewModel.AddLogin(logins); + } + db.ViewModel.IsLockDatabase = dbHeader.iLock.IsVisible = false; db.ViewModel.IsUnlockDatabase = dbHeader.iUnlock.IsVisible = true; @@ -466,16 +482,35 @@ private void ShowSearchWindow() { if (SelectedTabItem == null || SelectedTabItem.ViewModel.IsLockDatabase) return; App.SearchWindow = new SearchWindow(); - foreach (Folder folder in SelectedTabItem.ViewModel.Database.Folders) App.SearchWindow.SearchViewModel.AddFolder(folder); + for (var index = 0; index < SelectedTabItem.ViewModel.Database.Folders.Count; index++) + { + Folder folder = SelectedTabItem.ViewModel.Database.Folders[index]; + App.SearchWindow.SearchViewModel.AddFolder(folder); + } + App.SearchWindow.ShowDialog(App.MainWindow); } private void SaveAllDatabases() { - foreach (TabItem item in TabItems) SaveDatabase((DatabaseControl)item.Content); + for (var index = 0; index < TabItems.Count; index++) + { + TabItem item = TabItems[index]; + SaveDatabase((DatabaseControl)item.Content); + } + } + private void OpenSettingsWindow() + { + _settingsWindow = new SettingsWindow(); + _settingsWindow.ShowDialog(App.MainWindow); } - private void OpenSettingsWindow() => new SettingsWindow().ShowDialog(App.MainWindow); + private void CheckUpdate() => App.CheckUpdate(true); - private void ChangeMasterPassword() => new ChangeMasterPasswordWindow().ShowDialog(App.MainWindow); + private void ChangeMasterPassword() + { + _windowChangeMasterPassword = new ChangeMasterPasswordWindow(); + _windowChangeMasterPassword.ShowDialog(App.MainWindow); + } + private void ExitApplication() => App.MainWindow.Close(); private void SaveSettings() => SaveAndLoadSettings.SaveSettings(); } diff --git a/OlibKey.csproj b/OlibKey.csproj index fc9cb94..5ce5ab1 100644 --- a/OlibKey.csproj +++ b/OlibKey.csproj @@ -1,7 +1,7 @@  WinExe - net5.0 + netcoreapp3.1 AnyCPU;x64 @@ -13,7 +13,7 @@ false true app.ico - 3.1.1 + 3.2.0 Dmitry Zhutkov © Dmitry Zhutkov 2020 OlibKey - an open source password manager. diff --git a/Structures/Database.cs b/Structures/Database.cs index 0882af1..df7a4ff 100644 --- a/Structures/Database.cs +++ b/Structures/Database.cs @@ -11,6 +11,8 @@ public class Login public string TimeCreate { get; set; } public string TimeChanged { get; set; } + public bool Favorite { get; set; } + public string FolderID { get; set; } #region Login diff --git a/ViewModels/Windows/CheckingWeakPasswordsWindowViewModel.cs b/ViewModels/Windows/CheckingWeakPasswordsWindowViewModel.cs new file mode 100644 index 0000000..1bc0236 --- /dev/null +++ b/ViewModels/Windows/CheckingWeakPasswordsWindowViewModel.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reactive; +using Avalonia; +using Avalonia.Controls; +using OlibKey.Core; +using OlibKey.Structures; +using OlibKey.Views.Controls; +using OlibKey.Views.Windows; +using ReactiveUI; + +namespace OlibKey.ViewModels.Windows +{ + public class CheckingWeakPasswordsWindowViewModel : ReactiveObject + { + private ObservableCollection _loginList = new ObservableCollection(); + + private double _overallComplexity; + + #region ReactiveCommand's + + private ReactiveCommand SelectAllCommand { get; } + private ReactiveCommand CloseWindowCommand { get; } + private ReactiveCommand ChangeWeakPasswordsCommand { get; } + + #endregion + + #region Property's + + private ObservableCollection LoginList + { + get => _loginList; set => this.RaiseAndSetIfChanged(ref _loginList, value); + } + public double OverallComplexity + { + get => _overallComplexity; + set => this.RaiseAndSetIfChanged(ref _overallComplexity, value); + } + + #endregion + + public CheckingWeakPasswordsWindowViewModel() + { + SelectAllCommand = ReactiveCommand.Create(SelectAll); + CloseWindowCommand = ReactiveCommand.Create(() => { App.MainWindowViewModel.CheckingWindow.Close(); }); + ChangeWeakPasswordsCommand = ReactiveCommand.Create(ChangeWeakPassword); + + for (int index = 0; index < App.MainWindowViewModel.SelectedTabItem.ViewModel.LoginList.Count; index++) + { + LoginListItem item = App.MainWindowViewModel.SelectedTabItem.ViewModel.LoginList[index]; + if (item.LoginItem.Type == 0) + { + if (PasswordUtils.CheckPasswordStrength(item.LoginItem.Password) < 200) + Add(item.LoginItem, item.IconLogin, item.LoginID); + } + } + + double sum = 0; + + sum += App.MainWindowViewModel.SelectedTabItem.ViewModel.LoginList.Where(item => item.LoginItem.Type == 0) + .Aggregate(0, (current, item) => current + (PasswordUtils.CheckPasswordStrength(item.LoginItem.Password) > 300 + ? 300 + : PasswordUtils.CheckPasswordStrength(item.LoginItem.Password))); + + int count = App.MainWindowViewModel.SelectedTabItem.ViewModel.LoginList.Count(item => item.LoginItem.Type == 0); + + OverallComplexity = count == 0 ? 0 : sum / count; + } + + private void Add(Login a, Image i, string id) => + LoginList.Add(new LoginListItem(a) + { + LoginID = id, + IconLogin = { Source = i.Source }, + SelectedItem = { IsVisible = true}, + IsFavorite = { IsVisible = false} + }); + + private void SelectAll() + { + for (int index = 0; index < LoginList.Count; index++) + { + LoginListItem item = LoginList[index]; + item.SelectedItem.IsChecked = true; + } + } + + private async void ChangeWeakPassword() + { + try + { + for (int index = 0; index < LoginList.Count; index++) + { + LoginListItem i = LoginList[index]; + if (i.SelectedItem.IsChecked != null && (bool)i.SelectedItem.IsChecked) + { + foreach (LoginListItem item in App.MainWindowViewModel.SelectedTabItem.ViewModel.LoginList.Where( + item => item.LoginID == i.LoginID)) + { + item.LoginItem.Password = PasswordGenerator.RandomPassword(); + break; + } + } + } + await MessageBox.Show(App.MainWindowViewModel.CheckingWindow, null, (string)Application.Current.FindResource("Successfully"), (string)Application.Current.FindResource("Message"), + MessageBox.MessageBoxButtons.Ok, MessageBox.MessageBoxIcon.Information); + App.MainWindowViewModel.CheckingWindow.Close(); + } + catch + { + await MessageBox.Show(App.MainWindowViewModel.CheckingWindow, null, (string)Application.Current.FindResource("MB7"), (string)Application.Current.FindResource("Error"), + MessageBox.MessageBoxButtons.Ok, MessageBox.MessageBoxIcon.Error); + } + } + } +} diff --git a/Views/Controls/LoginListItem.axaml b/Views/Controls/LoginListItem.axaml index 28fdac7..c2d280c 100644 --- a/Views/Controls/LoginListItem.axaml +++ b/Views/Controls/LoginListItem.axaml @@ -8,19 +8,17 @@ Background="Transparent" Height="40" x:Class="OlibKey.Views.Controls.LoginListItem"> - - - - - - - - - + + + + + + + diff --git a/Views/Controls/LoginListItem.axaml.cs b/Views/Controls/LoginListItem.axaml.cs index e70ce74..e53dac3 100644 --- a/Views/Controls/LoginListItem.axaml.cs +++ b/Views/Controls/LoginListItem.axaml.cs @@ -6,12 +6,15 @@ using OlibKey.Structures; using OlibKey.Views.Windows; using System; +using Avalonia.Controls.Primitives; namespace OlibKey.Views.Controls { public class LoginListItem : UserControl { public Image IconLogin; + public CheckBox SelectedItem; + public ToggleButton IsFavorite; private readonly TextBlock _tbLoginName; private readonly TextBlock _tbUsername; @@ -30,6 +33,8 @@ public LoginListItem(Login Login) IconLogin = this.FindControl("imageIconWebSite"); _tbLoginName = this.FindControl("tbLoginName"); _tbUsername = this.FindControl("tbUsername"); + SelectedItem = this.FindControl("selectedItem"); + IsFavorite = this.FindControl("isFavorite"); LoginItem = Login; @@ -49,9 +54,23 @@ public LoginListItem(Login Login) break; } - if (LoginItem.Type == 0 && string.IsNullOrEmpty(Login.Username)) _tbUsername.Text = Login.Email; + _tbUsername.Text = LoginItem.Type switch + { + 0 when string.IsNullOrEmpty(Login.Username) => Login.Email, + 4 => LoginItem.TimeCreate, + _ => _tbUsername.Text + }; + + IsFavorite.IsChecked = LoginItem.Favorite; - if (LoginItem.Type == 4) _tbUsername.Text = LoginItem.TimeCreate; + IsFavorite.Checked += IsFavoriteChecking; + IsFavorite.Unchecked += IsFavoriteChecking; + } + + private void IsFavoriteChecking(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + bool? isChecked = ((ToggleButton)sender).IsChecked; + if (isChecked != null) LoginItem.Favorite = (bool)isChecked; } private void InitializeComponent() => AvaloniaXamlLoader.Load(this); @@ -60,11 +79,13 @@ public void EditedLogin() { if (LoginItem == null) return; _tbLoginName.Text = LoginItem.Name; - _tbUsername.Text = LoginItem.Username; - - if (LoginItem.Type == 0 && string.IsNullOrEmpty(LoginItem.Username)) _tbUsername.Text = LoginItem.Email; - if (LoginItem.Type == 4) _tbUsername.Text = LoginItem.TimeCreate; + _tbUsername.Text = LoginItem.Type switch + { + 0 when string.IsNullOrEmpty(LoginItem.Username) => LoginItem.Email, + 4 => LoginItem.TimeCreate, + _ => LoginItem.Username + }; } public async void GetIconElement() diff --git a/Views/Pages/CreateLoginPage.axaml.cs b/Views/Pages/CreateLoginPage.axaml.cs index 535d2b0..c4f0154 100644 --- a/Views/Pages/CreateLoginPage.axaml.cs +++ b/Views/Pages/CreateLoginPage.axaml.cs @@ -93,8 +93,8 @@ private void SectionChanged(object sender, SelectionChangedEventArgs e) private async void GeneratePassword(object sender, RoutedEventArgs e) { - PasswordGeneratorWindow a = new PasswordGeneratorWindow { _saveButton = { IsVisible = true } }; - if (await a.ShowDialog(App.MainWindow)) _txtPassword.Text = a._tbPassword.Text; + App.MainWindowViewModel.PasswordGenerator = new PasswordGeneratorWindow { _saveButton = { IsVisible = true } }; + if (await App.MainWindowViewModel.PasswordGenerator.ShowDialog(App.MainWindow)) _txtPassword.Text = App.MainWindowViewModel.PasswordGenerator._tbPassword.Text; } } } diff --git a/Views/Pages/EditLoginPage.axaml.cs b/Views/Pages/EditLoginPage.axaml.cs index 3cc5132..3b5f269 100644 --- a/Views/Pages/EditLoginPage.axaml.cs +++ b/Views/Pages/EditLoginPage.axaml.cs @@ -39,8 +39,8 @@ private void InitializeComponent() private async void GeneratePassword(object sender, RoutedEventArgs e) { - PasswordGeneratorWindow a = new PasswordGeneratorWindow { _saveButton = { IsVisible = true } }; - if (await a.ShowDialog(App.MainWindow)) _txtPassword.Text = a._tbPassword.Text; + App.MainWindowViewModel.PasswordGenerator = new PasswordGeneratorWindow { _saveButton = { IsVisible = true } }; + if (await App.MainWindowViewModel.PasswordGenerator.ShowDialog(App.MainWindow)) _txtPassword.Text = App.MainWindowViewModel.PasswordGenerator._tbPassword.Text; } } } diff --git a/Views/Windows/AboutWindow.axaml b/Views/Windows/AboutWindow.axaml index 3e454f4..bfc9faf 100644 --- a/Views/Windows/AboutWindow.axaml +++ b/Views/Windows/AboutWindow.axaml @@ -14,7 +14,7 @@ - + diff --git a/Views/Windows/CheckingWeakPasswordsWindow.axaml b/Views/Windows/CheckingWeakPasswordsWindow.axaml new file mode 100644 index 0000000..57cf986 --- /dev/null +++ b/Views/Windows/CheckingWeakPasswordsWindow.axaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + diff --git a/Views/Pages/CreateLoginPage.axaml b/Views/Pages/CreateLoginPage.axaml index ccd2f37..08217d7 100644 --- a/Views/Pages/CreateLoginPage.axaml +++ b/Views/Pages/CreateLoginPage.axaml @@ -188,7 +188,7 @@ -