190 Commits
V1.11 ... V1.17

Author SHA1 Message Date
Geoffroy BONNEVILLE
1488c3244f Better exception handling 2020-05-05 19:26:38 +02:00
Geoffroy BONNEVILLE
2e22a2bd92 Added some translations in file pickers
Corrected key file creation picker issue (any not allowed)
Fixed some Sonar issues
2020-05-05 17:32:07 +02:00
Geoffroy BONNEVILLE
8fb468358e Hamburger button state is now correct (no more double clicks) but it's a bit hacky
Changed Help tooltip location in New Database Settings page
Suggest Save As when opening DB when another is opened and there is a save error
2020-05-05 16:59:49 +02:00
Geoffroy BONNEVILLE
5ce0262318 TextBoxWithButton control correctly updates field value
Create Group now allows inline input of the group name
2020-05-05 15:27:34 +02:00
Geoffroy BONNEVILLE
2f30389f6c Big redesign (closer to Win10 UWP)
Replaced breadcrumb with Up button
Search button now integrated in top menu
Hamburger menu make better use of visual states
Better visual states changes when size changes
2020-05-04 20:56:19 +02:00
Geoffroy BONNEVILLE
b3c7683c12 Send a message on save to update commands can execute 2020-05-04 14:29:52 +02:00
Geoffroy BONNEVILLE
1e7662def7 Save error is now handled via Messenger instead of unhandled exception handler (which didn't work)
Save as actually works now
WIP Styles
Code cleanup
2020-05-04 12:48:27 +02:00
Geoffroy BONNEVILLE
97b10baedc Resuming correctly re-opens the previsouly opened database 2020-05-02 14:39:42 +02:00
Geoffroy BONNEVILLE
654bd6b4e5 Corrected some Sonar issues
Changed a little bit the Open and Set credentials User controls
2020-05-02 14:21:59 +02:00
Geoffroy BONNEVILLE
7b88461333 *BoxWithButton data binding actually work 2020-04-30 22:04:08 +02:00
Geoffroy BONNEVILLE
8de493f987 Username is now correctly persisted
Set credentials validation works as intended
Getting settings has default values
Add parent group in Move searchbox
Moving entries work as intended
Removed unreferenced code files
2020-04-30 19:40:48 +02:00
Geoffroy BONNEVILLE
e5353478f4 Corrected issue where entry information was not showing anymore 2020-04-30 16:39:39 +02:00
Geoffroy BONNEVILLE
1b981b00d5 Update credentials better looking 2020-04-30 15:32:42 +02:00
Geoffroy BONNEVILLE
d2814c6c67 OpenDatabase control now handles status changes with VisualStateManager instead of Vm
Opening DB shows a progress ring instead
2020-04-30 11:10:10 +02:00
Geoffroy BONNEVILLE
9fdc727787 Minor code cleanup 2020-04-29 19:20:10 +02:00
Geoffroy BONNEVILLE
14cd3ab57a WIP Windows 10
Dependencies finally installed
Removal of useless code
Big cleanup in XAML styles (override colors the correct way)
2020-04-29 16:39:20 +02:00
Geoffroy BONNEVILLE
d6529646a8 Split ViewModelLocator 2020-04-29 09:44:32 +02:00
Geoffroy BONNEVILLE
7917a8b388 Updated release notes
Moved ViewModelLocator
WIP ModernKeePass10
2020-04-28 20:14:18 +02:00
Geoffroy BONNEVILLE
b8e1bbd9d7 Move finally works
Sort entries and groups refresh page info
Stopped using breadcrumb user control - for now
Some refactoring
2020-04-28 18:54:37 +02:00
Geoffroy BONNEVILLE
f158e5aced Finally a nicer looking and working TextBoxWithButton (inspired from the SearchButton)
SearchBox field style improved
2020-04-28 15:20:47 +02:00
Geoffroy BONNEVILLE
8e06bf4bb0 Removed half-baked import feature for now
No views depend on services anymore
Dirty status fully handled by behavior
2020-04-27 11:14:29 +02:00
Geoffroy BONNEVILLE
59ab43ca9c Remove entry from mru when it does not exist anymore 2020-04-25 22:34:50 +02:00
Geoffroy BONNEVILLE
7778e45deb All VMs use viewmodellocator 2020-04-25 22:03:47 +02:00
Geoffroy BONNEVILLE
df973c5f62 StorageFile client more intelligent
Save is working again
2020-04-24 16:16:48 +02:00
Geoffroy BONNEVILLE
3967db41b3 Open from Explorer works again
Updated ModernKeePassLib
2020-04-24 15:52:46 +02:00
Geoffroy BONNEVILLE
eacb3b182e Database files are added to the mru instead of the fal
Simplify opening database workflow
Corrected opening from recent bug
2020-04-24 13:58:30 +02:00
Geoffroy BONNEVILLE
d211453553 WIP ViewModelLocator - Messenger and Recent issues
Refactoring
Code cleanup
2020-04-23 19:00:38 +02:00
Geoffroy BONNEVILLE
2b8f9bd5f0 WIP ViewModelLocator - Messenger and Recent issues
Refactoring
Code cleanup
2020-04-23 18:59:56 +02:00
Geoffroy BONNEVILLE
722790e5e4 Removed useless package 2020-04-22 19:22:36 +02:00
Geoffroy BONNEVILLE
a9ed588c9a Creation of a notification service 2020-04-22 18:12:28 +02:00
Geoffroy BONNEVILLE
61f5e9df0b Update master key works
Key file creation works
Code cleanup
2020-04-22 17:06:16 +02:00
Geoffroy BONNEVILLE
a7da427ded Create database works with new Vm
Refactoring
2020-04-22 16:21:47 +02:00
Geoffroy BONNEVILLE
a88051bc0c Restore Main and Settings Page
Entry and Group delete events converted to commands
Code cleanup
2020-04-22 11:58:06 +02:00
Geoffroy BONNEVILLE
1df9cbce1c WIP DeleteCommand
WIP DataContexts binding (Main and Settings are broken)
2020-04-21 19:12:26 +02:00
Geoffroy BONNEVILLE
0b19d8d50a Opening Databases now use a Messenger service 2020-04-21 17:13:39 +02:00
Geoffroy BONNEVILLE
004f1a35a8 Detect if current group is the recycle bin for entry and group creation 2020-04-21 13:39:53 +02:00
Geoffroy BONNEVILLE
ac66faa9e2 GroupDetailPage uses navigation service 2020-04-21 13:33:15 +02:00
Geoffroy BONNEVILLE
c81f8bc835 SavePage uses nav service 2020-04-21 13:14:56 +02:00
Geoffroy BONNEVILLE
a1085b6010 Add MVVM Light library
Updated nuget packages
2020-04-21 13:07:17 +02:00
Geoffroy BONNEVILLE
75f6e2f840 OpenDatabaseUserControl works 2020-04-21 11:26:02 +02:00
Geoffroy BONNEVILLE
310bd777b2 WIP Split composite key user control
Some refactoring
2020-04-20 20:02:43 +02:00
Geoffroy BONNEVILLE
73670e8689 Create a shared project with all Win App common files (8.1 and 10)
Finally use the dependency injected Resource Service
2020-04-17 16:56:07 +02:00
Geoffroy BONNEVILLE
2fb5b14085 Updating group raise SaveCommand can execute
No need to click twice on history menu
Skipped first history entry as it is the same as the current entry
Stored SaveException innerexception as it is read more than once
2020-04-16 19:43:17 +02:00
Geoffroy BONNEVILLE
f950564000 Update groups finally implemented
Code cleanup
2020-04-16 17:16:03 +02:00
Geoffroy BONNEVILLE
9befdc321a Entry history delete and restore work 2020-04-16 14:08:50 +02:00
Geoffroy BONNEVILLE
98ac418f62 WIP History
Restore from history works
2020-04-15 19:06:13 +02:00
Geoffroy BONNEVILLE
0063ef1977 Create entries and groups put them in Edit mode (as before)
Search now uses KeePassLib search
Code cleanup
2020-04-15 12:02:30 +02:00
Geoffroy BONNEVILLE
b66e79f97c History *works*
WIP on save on entry page doesn't show last change
2020-04-14 19:59:19 +02:00
Geoffroy BONNEVILLE
9603c1ff01 Icons work again
Colors work again
2020-04-14 17:49:29 +02:00
Geoffroy BONNEVILLE
3def21bc7d Restored title group and entries icons
Editing adds a visible border
2020-04-14 14:09:31 +02:00
Geoffroy BONNEVILLE
a2eba91a3b Added dirty behavior
Removed restore action (-> Move action wip)
Added additional check on DB size before auto saving
Code cleanup
2020-04-14 13:44:07 +02:00
Geoffroy BONNEVILLE
d972b6cb5a Added the option to close DB without saving
Changed the way recent files are retrieved
Stopped showing the DB Closed exception on suspend
Reordering entries works
Moved code from infra to application
Cleanup
2020-04-09 19:43:06 +02:00
Geoffroy BONNEVILLE
14fd4634db Entry icons now correctly show up
Auto create new recycle bin works correctly
2020-04-08 20:02:13 +02:00
Geoffroy BONNEVILLE
752e96884d Code cleanup (ter) 2020-04-08 16:38:38 +02:00
Geoffroy BONNEVILLE
1046110dd2 Code cleanup (bis) 2020-04-08 16:23:15 +02:00
Geoffroy BONNEVILLE
d1047a92ba Update build configurations 2020-04-08 15:52:25 +02:00
Geoffroy BONNEVILLE
009382ea03 Cleanup code 2020-04-08 15:27:40 +02:00
Geoffroy BONNEVILLE
4863eb9fae Create DB works correctly
Sample data moved to application
Tests updated (still not working - splat)
WIP
2020-04-07 17:29:03 +02:00
Geoffroy BONNEVILLE
1fa799bdf8 FileInfo overhaul
Opening DB works again
2020-04-07 12:48:18 +02:00
Geoffroy BONNEVILLE
56d93a5187 Moved application code to the Application layer
Imported Win10 project
Code cleanup
WIP - Use common UWP services for Win8.1 and Win10
2020-04-06 20:20:45 +02:00
Geoffroy BONNEVILLE
e795a8c3c4 Database settings now work properly 2020-04-03 18:14:44 +02:00
Geoffroy BONNEVILLE
b875f3c89d Adding entries and groups works again
Entry history almost fully functional
Some refactoring
2020-04-03 17:33:53 +02:00
Geoffroy BONNEVILLE
36aa8914fa Handle entities with id
No hierarchy is built anymore
WIP save issues after delete
2020-04-02 19:12:16 +02:00
Geoffroy BONNEVILLE
b61a9652d1 WIP change to ids 2020-04-01 19:37:30 +02:00
Geoffroy BONNEVILLE
57be6bb917 Build hierarchy instead of using Automapper
Add entities before removing them
2020-04-01 12:48:36 +02:00
Geoffroy BONNEVILLE
90c592d7ee Lots of bug corrections
WIP - still lots left
2020-03-31 19:19:02 +02:00
Geoffroy BONNEVILLE
e4bd788ed3 1st working version in clean arch
WIP Parent group mapping issues
2020-03-30 19:43:04 +02:00
Geoffroy BONNEVILLE
d1ba73ee9d Don't use mediator for App services (recent, resource, settings)
WIP in View models
2020-03-28 16:13:17 +01:00
Geoffroy BONNEVILLE
45fcf7e8ab Removed ModernKeePassLib dependency
Code cleanup
WIP on service replacement and VM use
2020-03-27 18:45:13 +01:00
Geoffroy BONNEVILLE
e3638c2f5c More commands/queries
WIP on XAML EntryVm and GroupVm
2020-03-27 13:27:29 +01:00
Geoffroy BONNEVILLE
22072bb2fe Most services are implemented as command/queries
Code cleanup
2020-03-26 19:04:51 +01:00
Geoffroy BONNEVILLE
903e7649e4 More queries/commands 2020-03-26 15:38:29 +01:00
Geoffroy BONNEVILLE
a17d6b05ae Added lots of commands
Simplified KeePass client
2020-03-26 12:25:22 +01:00
Geoffroy BONNEVILLE
4b1210f414 WIP 2020-03-24 19:14:34 +01:00
Geoffroy BONNEVILLE
f208e2d0b6 Correct package version installed
Dependency injection works
Project renaming
WIP replacement of services with CQRS
2020-03-24 17:31:34 +01:00
Geoffroy BONNEVILLE
ba8bbe045b Cleanup 2020-03-24 13:09:00 +01:00
Geoffroy BONNEVILLE
7e44d47065 WIP Clean Architecture
Windows 8.1 App Uses keepasslib v2.44 (temporarily)
2020-03-24 13:01:14 +01:00
Geoffroy BONNEVILLE
34cd4ca3d8 Allow any file type for key selection 2020-03-20 17:05:46 +01:00
638bfacadd Bug corrections 2018-09-12 16:49:01 +02:00
d341535d60 Corrected some tests
Minor code refactor
Main page now correctly shows save page even when opening a DB from Explorer
2018-09-12 12:58:32 +02:00
BONNEVILLE Geoffroy
38e2d1ac51 Updated store screenshots 2018-09-11 10:08:33 +02:00
BONNEVILLE Geoffroy
5b1039b81f Hid useless open from URL
Removed commented out code
2018-09-10 18:26:55 +02:00
BONNEVILLE Geoffroy
37deac2ab9 Basic CSV Import working 2018-09-10 17:29:52 +02:00
BONNEVILLE Geoffroy
34f6d4e793 Correct navigation to root group when creating new database 2018-09-10 11:25:57 +02:00
BONNEVILLE Geoffroy
bbae2c356a Changed implementation of sample data
Creating a new entry does not create an useless history value
WIP import data
2018-09-10 11:13:44 +02:00
deec19a60c Merge branch 'master' of https://geogeob.visualstudio.com/_git/ModernKeePass 2018-09-09 20:02:02 +02:00
b1167594db Finally corrected weird Windows 8 and RT bug
Refactor TopMenu User Control visibility with Visual State Manager
2018-09-09 20:01:56 +02:00
BONNEVILLE Geoffroy
0da6a5fc60 WIP Import mechanism 2018-09-07 18:16:40 +02:00
BONNEVILLE Geoffroy
549006036b Changed default icon from IntToSymbolConverter to a parameter
Filtered symbol list from drop down list to only show actually settable symbols
2018-09-06 18:30:30 +02:00
BONNEVILLE Geoffroy
6ed29e788c Sort menu default visibility set to Collapsed 2018-09-04 15:07:31 +02:00
BONNEVILLE Geoffroy
e437f65f83 Restored useless changes
Code cleanup
2018-08-03 17:41:52 +02:00
BONNEVILLE Geoffroy
6f96e698ec Created Import/Export page (stub)
Changed some dependency properties from Interfaces to implementations
Used CanExecute on Commands
2018-08-02 17:40:30 +02:00
BONNEVILLE Geoffroy
b2dd028fc7 Textbox button now works again correctly (but has caret instead of pointer...) 2018-07-30 16:29:28 +02:00
BONNEVILLE Geoffroy
62f2be8823 Finally corrected strange behaviour in TextBoxWithButton where text would sometimes not appear unless pointed over 2018-07-27 10:52:23 +02:00
BONNEVILLE Geoffroy
89d43e21eb Created a sub style for textbox with buttons in EntryDetailPage
Set long forgotten resources for buttons tooltips
2018-07-26 17:58:34 +02:00
BONNEVILLE Geoffroy
5d9831efb5 Clearer clipboard action toast messages
Created a top menu button style
2018-07-26 16:28:28 +02:00
BONNEVILLE Geoffroy
b65cb87e58 Minor code cleanup 2018-07-26 12:01:49 +02:00
BONNEVILLE Geoffroy
7e687f7001 Donation page now displays a loading ring while page is loaded 2018-07-26 11:49:48 +02:00
BONNEVILLE Geoffroy
1d5a4d6fab Removal of unused string resources
Minor Toast code refactor
2018-07-26 10:18:00 +02:00
BONNEVILLE Geoffroy
2a2a934006 Entry delete button now shows up correctly
Minor code refactoring
2018-07-25 18:39:03 +02:00
BONNEVILLE Geoffroy
4ae65fdbac Created an app theme instead of using the System accent colors (which doesn't work in Win 8.1)
Merged two dictionaries into one
Re-added focus on title when editing an entity
2018-07-25 18:08:59 +02:00
BONNEVILLE Geoffroy
dd347b56a2 Updated some MS packages 2018-07-24 18:06:44 +02:00
BONNEVILLE Geoffroy
ff1cdc265a Removed useless CodeAnalysis packages 2018-07-24 18:05:07 +02:00
BONNEVILLE Geoffroy
9863195684 Added CodeAnalysis package
Better entry history handling
Changing icon now adds an history entry
2018-07-24 17:52:44 +02:00
BONNEVILLE Geoffroy
d6765904a1 Slider control now uses Accent Color
ComboBox enter key now uses Accent Color
Some code cleanup
Updated release notes
2018-07-24 16:26:58 +02:00
BONNEVILLE Geoffroy
7e4d6a2836 Update release notes 2018-07-24 11:00:41 +02:00
BONNEVILLE Geoffroy
8a0b3a0870 HockeyApp correct app id 2018-07-24 10:55:31 +02:00
BONNEVILLE Geoffroy
6f53fc79bb Updated README.md 2018-07-20 17:49:22 +02:00
BONNEVILLE Geoffroy
6f161e8699 Changed search button and search box visibility behavior for a more modern and user friendly mechanism 2018-07-20 17:24:02 +02:00
BONNEVILLE Geoffroy
8f1f80ae38 New version number
New homepage URL
2018-07-20 16:33:13 +02:00
BONNEVILLE Geoffroy
aec09a6890 Updated screenshots
Widened semantic view group header to accomodate larger letters
2018-07-18 18:30:56 +02:00
BONNEVILLE Geoffroy
8e557c608e Hamburger menu expanded size set to static value
LeftIndicator list view pointed over opacity reduced
2018-07-18 16:20:02 +02:00
BONNEVILLE Geoffroy
991a678788 Hamburger button now has a hover opacity change
Search box text bottom margin removed
2018-07-13 11:30:10 +02:00
BONNEVILLE Geoffroy
b88d6131c5 Set a global XAML variable for menu and button sizes
Set global to 60 instead of 50
Changed expired entry icon
2018-07-12 18:19:26 +02:00
BONNEVILLE Geoffroy
c25ae2ad0f Group and entry icon is now shown (and editable) on the left of the title
Added new entry placeholder text in resources
Group detail page small view triggered on larger width
2018-07-12 15:32:54 +02:00
BONNEVILLE Geoffroy
2abab27d5c Updating master key now works again
Security settings page now also uses Toast Action
2018-07-12 12:30:58 +02:00
BONNEVILLE Geoffroy
a91f8b6c5c Entry page now has vertical scrollbar if needed
Removed command bar
Restore button is now correctly enabled or not depending on context
2018-07-11 18:23:52 +02:00
BONNEVILLE Geoffroy
83d37d943f Sorting now works with code behind 2018-07-11 13:48:15 +02:00
BONNEVILLE Geoffroy
81ca11a955 WIP Top Menu - sort buttons present but not working
Removed flyout from textbox with button
Generating a new password creates a new history entry
Top Menu edit mode now works as intended
2018-07-11 12:15:56 +02:00
BONNEVILLE Geoffroy
931f79ac16 WIP Top Menu - added code behind to handle visibility and enabled status in flyouts because XAML doesn't work 2018-07-10 18:26:27 +02:00
BONNEVILLE Geoffroy
2ab84ef4af WIP Top Menu - Overflow mechanism works, commands implemented 2018-07-05 18:32:42 +02:00
BONNEVILLE Geoffroy
056d1af9d0 WIP Top Menu - Button visibility ok when not in menu 2018-07-04 18:26:16 +02:00
BONNEVILLE Geoffroy
34f5f2f6c8 WIP Top Menu 2018-07-02 18:23:43 +02:00
BONNEVILLE Geoffroy
6d32161312 Small layout changes 2018-07-02 14:33:53 +02:00
BONNEVILLE Geoffroy
df6914d1e0 Added possibility to change groups and entries icons
Add new entry link now hides text depending on width
TimePicker uses main color
2018-06-26 18:14:01 +02:00
BONNEVILLE Geoffroy
6f277e7b33 Added ability to change Entries background and foreground colors 2018-06-26 15:01:02 +02:00
BONNEVILLE Geoffroy
7ffcf1c82b Navigate to URL correctly shows the error, should it happen
Entry title linked to history version
Title text boxes selection now uses the main color
2018-06-25 17:31:17 +02:00
BONNEVILLE Geoffroy
c6e27c35c8 Finally a workaround the stupid CommandBar DataContext issues
Delete and Restore actions now work as intended
Renamed styles folder
2018-06-22 18:31:55 +02:00
BONNEVILLE Geoffroy
a6f82b4541 WIP Delete and restore entities as Actions 2018-06-21 18:40:44 +02:00
BONNEVILLE Geoffroy
d533abada5 Better suspend/resume handling 2018-06-21 16:40:04 +02:00
BONNEVILLE Geoffroy
4393aada3e Textbox selection now uses Accent color 2018-06-21 12:16:35 +02:00
BONNEVILLE Geoffroy
559fbe65c3 New toast action to show toast messages from XAML
Code cleanup for Sonar
Tests corrections
2018-06-21 11:13:40 +02:00
BONNEVILLE Geoffroy
803dab0fb5 Replaced almost all CallMethodActions with RelayCommands (allows using async)
SonarQube bug correction
2018-06-20 18:41:56 +02:00
BONNEVILLE Geoffroy
ca377a6684 SonarQube async method return type set to Task 2018-06-20 17:20:15 +02:00
BONNEVILLE Geoffroy
188233cc81 Database file is now handled by each page, instead of a central place
Opening a database while another one is open triggers a warning message
2018-06-20 11:52:17 +02:00
BONNEVILLE Geoffroy
408b4eed90 WIP removing global databaseFile property 2018-06-19 18:47:37 +02:00
BONNEVILLE Geoffroy
b456e56789 Making modifications in an Entry creates a new History entry 2018-06-18 18:40:00 +02:00
BONNEVILLE Geoffroy
9225732c1a Removal of unused License Service 2018-06-18 16:19:56 +02:00
BONNEVILLE Geoffroy
e9601e8d13 Removed unused anti corruption code 2018-06-18 15:39:01 +02:00
BONNEVILLE Geoffroy
b1ded11fa5 Sonar code smells corrections 2018-06-18 14:58:01 +02:00
BONNEVILLE Geoffroy
978929ba48 Correct Sonar vulnerability 2018-06-18 11:25:21 +02:00
BONNEVILLE Geoffroy
7e337c4a40 History list fully functional in Entry page
Viewing a historic entry disables controls on the page
Enhancements in the hamburger menu
2018-06-15 18:07:44 +02:00
BONNEVILLE Geoffroy
62c9719a77 Hamburger menu button visibility is now a property
Hamburger menu now has a display member path property
2018-06-15 10:10:48 +02:00
BONNEVILLE Geoffroy
2b48b64f2f Hamburger button menu is now a User Control
WIP History in Entry page
2018-06-14 18:38:05 +02:00
BONNEVILLE Geoffroy
799b896bfe Breadcrumb finished and working 2018-06-14 10:50:16 +02:00
BONNEVILLE Geoffroy
8a5db88225 Entries and groups icons are now handled with int
Static mapping is changed to a converter
2018-06-14 10:20:00 +02:00
BONNEVILLE Geoffroy
cc65c56042 WIP Breadcrumb
Breadcrumb is now a working User Control (removal of the Templated Control)
AssemblyInfo updated
Some code cleanup
2018-06-13 18:58:28 +02:00
BONNEVILLE Geoffroy
0a4df01354 WIP Breadcrumb 2018-06-12 18:40:54 +02:00
BONNEVILLE Geoffroy
25945b8b3b Add update script version
GroupVM new breadcrumb code
2018-06-12 09:42:08 +02:00
BONNEVILLE Geoffroy
9921a4db52 Correct AppID in HockeyApp 2018-06-11 18:55:55 +02:00
BONNEVILLE Geoffroy
2c6530f3b0 Removed AppCenter and included HockeyApp 2018-06-11 18:42:50 +02:00
BONNEVILLE Geoffroy
f158191e52 Increase version number
WIP new breadcrumb (with hyperlinks)
2018-06-08 18:46:07 +02:00
BONNEVILLE Geoffroy
a7f2ae91a7 Reference ModernKeePassLib as a Nuget package (v2.39.1)
Correct bug when selecting search result
2018-06-08 15:39:28 +02:00
BONNEVILLE Geoffroy
49e1b709f7 ComboBox (including those in DatePicker) now use Accent Color 2018-06-08 14:27:35 +02:00
BONNEVILLE Geoffroy
59c903b635 Search box now recursively search sub groups 2018-06-08 12:08:06 +02:00
BONNEVILLE Geoffroy
c9d599216d List view now set Accent Color on selected item text 2018-06-08 11:22:00 +02:00
BONNEVILLE Geoffroy
8fa3a75a63 ToggleSwitch now also uses Accent Color 2018-06-08 11:12:54 +02:00
BONNEVILLE Geoffroy
e31d1edb99 Use of System Accent Color instead of purple (ToggleButtons still need to be styled) 2018-06-07 18:27:50 +02:00
BONNEVILLE Geoffroy
915cad8c85 Display a title on groups header menu 2018-06-06 18:26:05 +02:00
BONNEVILLE Geoffroy
7bd6c0dd09 XAML code cleanup 2018-06-06 12:21:11 +02:00
BONNEVILLE Geoffroy
0aa8e886ce Entry icon size reduced to 50%, with no side effects 2018-06-06 12:17:28 +02:00
BONNEVILLE Geoffroy
1334b266b9 Updated release notes
Finally a working zoomed out view for the Semantic Zoom
2018-06-06 12:13:25 +02:00
BONNEVILLE Geoffroy
9566c9a719 Open databases errors are cleaner
WIP SemanticView ListView Width issue
2018-06-05 18:40:23 +02:00
BONNEVILLE Geoffroy
0643701c4a Typing a password auto checks the check box
Correct hit test area on entries in group detail page
Correct zoomed out entries (filter and group)
2018-06-05 16:23:09 +02:00
BONNEVILLE Geoffroy
4d86c25411 ModernKeePass lib version 2.39.1 seemeegly functional
WIP in some pages
2018-06-04 18:38:48 +02:00
BONNEVILLE Geoffroy
ad02740d8a WIP Lib version 2.39.1 2018-05-22 18:27:44 +02:00
BONNEVILLE Geoffroy
0b95669db0 Projects renamed 2018-05-22 14:52:31 +02:00
BONNEVILLE Geoffroy
4aefbcb8b9 RecycleBin now uses resources
RecycleBin bugs correction
2018-03-12 17:30:03 +01:00
BONNEVILLE Geoffroy
56129253d9 Use ModernKeePass Lib 2.38.2
Changed suspension save errors handling
2018-03-12 12:45:12 +01:00
BONNEVILLE Geoffroy
7613629d87 Deactivation of buggy Splat custom icon implementation 2018-03-12 12:40:07 +01:00
BONNEVILLE Geoffroy
fb0eab00c2 Create group and delete current group hidden in Recycle Bin 2018-03-12 11:27:15 +01:00
BONNEVILLE Geoffroy
5b8c3b9b11 Typo in restore button 2018-03-12 11:18:42 +01:00
BONNEVILLE Geoffroy
700f76679a Corrected RecycleBin group bug
Database Settings page now has radio button for recycle bin
2018-03-12 10:21:36 +01:00
BONNEVILLE Geoffroy
e7d731bb04 KeepassLib version 2.38
Added a new settings page to allow auto-save or not
App now resumes correctly from suspend
Settings service now allows getting default values
Removed french special characters from metadata
Code cleanup
2018-03-09 18:06:06 +01:00
BONNEVILLE Geoffroy
49637fcc3b KeepassLib version update to 2.38 2018-03-09 17:49:47 +01:00
BONNEVILLE Geoffroy
fc25d7ea93 Update some packages 2018-03-07 18:39:56 +01:00
BONNEVILLE Geoffroy
cca6579274 Removed useless code in Donate page 2018-02-23 18:13:44 +01:00
BONNEVILLE Geoffroy
7dbf93fe7b Changed most services to singletons
Refactor the Database Service (no more enum, ...)
Restored the Donate page with Paypal web page
Added (but not working) MS App Center integration
Corrected tests accordingly
WIP AOP to detect database changes
2018-02-23 18:09:21 +01:00
BONNEVILLE Geoffroy
b46ab8db51 Code cleanup
Popup discard action now works
2018-01-09 18:40:11 +01:00
BONNEVILLE Geoffroy
a19519fa73 Removed database status in favor of much cleaner code
Implemented (but deactivated) anti corruption mechanism
WIP detect changes and save them if opening another database
2018-01-08 18:52:03 +01:00
BONNEVILLE Geoffroy
4a3f36d38b Version bump to 1.13
New group button is now at the bottom of the listview
2018-01-08 11:02:53 +01:00
BONNEVILLE Geoffroy
4ae02fc07b Corrected test case to reflect page removal 2018-01-03 11:37:09 +01:00
BONNEVILLE Geoffroy
047fca32bf Hid donations pages (need to implement 3rd party API) 2018-01-03 11:29:51 +01:00
BONNEVILLE Geoffroy
abbff449c0 Removed User Account from composite key (probably never going to work as intended)
Changed copy URL to navigate to URL in entry quick menu
2017-12-26 17:54:13 +01:00
BONNEVILLE Geoffroy
fba668860b WIP user accounts - not working at all 2017-12-21 18:24:01 +01:00
BONNEVILLE Geoffroy
acb196d9c2 WIP Windows User Accounts Composite Key integration 2017-12-20 18:49:11 +01:00
BONNEVILLE Geoffroy
dfa3a21e6b Removed an useless converter
Groups in menu now use instead a Template selector (depending on IIsSelected)
2017-12-19 18:44:35 +01:00
BONNEVILLE Geoffroy
7ff6bccbc4 Added some tests
Removed false first group, replaced it a button in the header
Code refactor
2017-12-18 18:53:42 +01:00
BONNEVILLE Geoffroy
88e5b80778 Version bump to 1.12
Added small menu on entries list to copy login, password and URL
2017-12-18 14:09:04 +01:00
BONNEVILLE Geoffroy
d127431396 Third try on store special character escaping 2017-12-18 11:52:49 +01:00
630 changed files with 51779 additions and 39888 deletions

3
.gitignore vendored
View File

@@ -38,4 +38,5 @@ packages/
project.lock.json project.lock.json
AppPackages/ AppPackages/
BundleArtifacts/ BundleArtifacts/
*.DotSettings *.DotSettings
/ModernKeePass/Win81App_StoreKey.pfx

View File

@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{42353562-5E43-459C-8E3E-2F21E575261D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernKeePass.Application</RootNamespace>
<AssemblyName>ModernKeePass.Application</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Behaviors\DirtyStatusBehavior.cs" />
<Compile Include="Common\Interfaces\ICryptographyClient.cs" />
<Compile Include="Common\Interfaces\IDatabaseProxy.cs" />
<Compile Include="Common\Interfaces\IEntityVm.cs" />
<Compile Include="Common\Interfaces\IFileProxy.cs" />
<Compile Include="Common\Interfaces\IImportFormat.cs" />
<Compile Include="Common\Interfaces\ICredentialsProxy.cs" />
<Compile Include="Common\Interfaces\INotificationService.cs" />
<Compile Include="Common\Interfaces\IRecentProxy.cs" />
<Compile Include="Common\Interfaces\IResourceProxy.cs" />
<Compile Include="Common\Interfaces\ISettingsProxy.cs" />
<Compile Include="Common\Mappings\IMapFrom.cs" />
<Compile Include="Common\Mappings\MappingProfile.cs" />
<Compile Include="Entry\Commands\AddHistory\AddHistoryCommand.cs" />
<Compile Include="Entry\Commands\DeleteHistory\DeleteHistoryCommand.cs" />
<Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" />
<Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" />
<Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" />
<Compile Include="Group\Commands\DeleteGroup\DeleteGroupCommand.cs" />
<Compile Include="Group\Commands\UpdateGroup\UpdateGroupCommand.cs" />
<Compile Include="Group\Queries\GetAllGroups\GetAllGroupsQuery.cs" />
<Compile Include="Group\Queries\GetGroup\GetGroupQuery.cs" />
<Compile Include="Group\Queries\SearchEntries\SearchEntriesQuery.cs" />
<Compile Include="Parameters\Commands\SetCipher\SetCipherCommand.cs" />
<Compile Include="Parameters\Commands\SetCompression\SetCompressionCommand.cs" />
<Compile Include="Parameters\Commands\SetHasRecycleBin\SetHasRecycleBinCommand.cs" />
<Compile Include="Parameters\Commands\SetKeyDerivation\SetKeyDerivationCommand.cs" />
<Compile Include="Parameters\Commands\SetRecycleBin\SetRecycleBinCommand.cs" />
<Compile Include="Parameters\Models\CipherVm.cs" />
<Compile Include="Parameters\Models\KeyDerivationVm.cs" />
<Compile Include="Parameters\Queries\GetCiphers\GetCiphersQuery.cs" />
<Compile Include="Parameters\Queries\GetCompressions\GetCompressionsQuery.cs" />
<Compile Include="Parameters\Queries\GetKeyDerivations\GetKeyDerivationsQuery.cs" />
<Compile Include="Database\Commands\CloseDatabase\CloseDatabaseCommand.cs" />
<Compile Include="Database\Commands\CreateDatabase\CreateDatabaseCommand.cs" />
<Compile Include="Database\Commands\CreateDatabase\CreateDatabaseQueryValidator.cs" />
<Compile Include="Database\Commands\SaveDatabase\SaveDatabaseCommand.cs" />
<Compile Include="Database\Commands\UpdateCredentials\UpdateCredentialsCommand.cs" />
<Compile Include="Database\Models\DatabaseVm.cs" />
<Compile Include="Database\Queries\GetDatabase\GetDatabaseQuery.cs" />
<Compile Include="Database\Queries\OpenDatabase\OpenDatabaseQuery.cs" />
<Compile Include="Database\Queries\OpenDatabase\OpenDatabaseQueryValidator.cs" />
<Compile Include="Database\Queries\ReOpenDatabase\ReOpenDatabaseQuery.cs" />
<Compile Include="DependencyInjection.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommand.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommandValidator.cs" />
<Compile Include="Entry\Models\EntryVm.cs" />
<Compile Include="Group\Commands\AddEntry\AddEntryCommand.cs" />
<Compile Include="Group\Commands\AddGroup\AddGroupCommand.cs" />
<Compile Include="Group\Commands\CreateEntry\CreateEntryCommand.cs" />
<Compile Include="Group\Commands\CreateGroup\CreateGroupCommand.cs" />
<Compile Include="Group\Commands\MoveEntry\MoveEntryCommand.cs" />
<Compile Include="Group\Commands\RemoveEntry\RemoveEntryCommand.cs" />
<Compile Include="Group\Commands\RemoveGroup\RemoveGroupCommand.cs" />
<Compile Include="Group\Commands\SortEntries\SortEntriesCommand.cs" />
<Compile Include="Group\Commands\SortGroups\SortGroupsCommand.cs" />
<Compile Include="Group\Models\GroupVm.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Security\Commands\GenerateKeyFile\GenerateKeyFileCommand.cs" />
<Compile Include="Security\Commands\GeneratePassword\GeneratePasswordCommand.cs" />
<Compile Include="Security\Queries\EstimatePasswordComplexity\EstimatePasswordComplexityQuery.cs" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<ProjectReference Include="..\ModernKeePass.Domain\Domain.csproj">
<Project>{9a0759f1-9069-4841-99e3-3bec44e17356}</Project>
<Name>Domain</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.CloseDatabase;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
namespace ModernKeePass.Application.Common.Behaviors
{
public class DirtyStatusBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly List<string> _excludedCommands = new List<string>
{nameof(SaveDatabaseCommand), nameof(CloseDatabaseCommand)};
private readonly IDatabaseProxy _database;
public DirtyStatusBehavior(IDatabaseProxy database)
{
_database = database;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
var queryName = typeof(TRequest).Name;
if (queryName.Contains("Command"))
{
_database.IsDirty = !_excludedCommands.Contains(queryName);
}
return response;
}
}
}

View File

@@ -0,0 +1,11 @@
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface ICredentialsProxy
{
string GeneratePassword(PasswordGenerationOptions options);
int EstimatePasswordComplexity(string password);
byte[] GenerateKeyFile(byte[] additionalEntropy);
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using ModernKeePass.Domain.Entities;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface ICryptographyClient
{
IEnumerable<BaseEntity> Ciphers { get; }
IEnumerable<BaseEntity> KeyDerivations { get; }
IEnumerable<string> CompressionAlgorithms { get; }
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IDatabaseProxy
{
// PW Database properties
bool IsOpen { get; }
string Name { get; }
string RootGroupId { get; }
string RecycleBinId { get; set; }
string CipherId { get; set; }
string KeyDerivationId { get; set; }
string Compression { get; set; }
bool IsRecycleBinEnabled { get; set; }
// Custom properties
string FileAccessToken { get; set; }
int Size { get; set; }
bool IsDirty { get; set; }
Task Open(byte[] file, Credentials credentials);
Task ReOpen(byte[] file);
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V4);
Task<byte[]> SaveDatabase();
void UpdateCredentials(Credentials credentials);
void CloseDatabase();
EntryEntity GetEntry(string id);
GroupEntity GetGroup(string id);
Task AddEntry(string parentGroupId, string entryId);
Task MoveEntry(string parentGroupId, string entryId, int index);
Task AddGroup(string parentGroupId, string groupId);
void UpdateEntry(string entryId, string fieldName, object fieldValue);
void UpdateGroup(GroupEntity group);
Task RemoveEntry(string parentGroupId, string entryId);
Task RemoveGroup(string parentGroupId, string groupId);
void DeleteEntity(string entityId);
EntryEntity CreateEntry(string parentGroupId);
GroupEntity CreateGroup(string parentGroupId, string name, bool isRecycleBin = false);
void SortEntries(string groupId);
void SortSubGroups(string groupId);
EntryEntity AddHistory(string entryId);
EntryEntity RestoreFromHistory(string entryId, int historyIndex);
void DeleteHistory(string entryId, int historyIndex);
IEnumerable<EntryEntity> Search(string groupId, string text);
IEnumerable<BaseEntity> GetAllGroups(string groupId);
}
}

View File

@@ -0,0 +1,13 @@
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IEntityVm
{
string Id { get; set; }
string Title { get; set; }
Icon Icon { get; set; }
string ParentGroupId { get; set; }
string ParentGroupName { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IFileProxy
{
Task<FileInfo> OpenFile(string name, string extension, bool addToRecent);
Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent);
Task<byte[]> ReadBinaryFile(string path);
Task<IList<string>> ReadTextFile(string path);
Task WriteBinaryContentsToFile(string path, byte[] contents);
void ReleaseFile(string path);
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IImportFormat
{
List<Dictionary<string, string>> Import(IList<string> fileContents);
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Application.Common.Interfaces
{
public interface INotificationService
{
void Show(string title, string text);
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IRecentProxy
{
int EntryCount { get; }
IEnumerable<FileInfo> GetAll();
void ClearAll();
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IResourceProxy
{
string GetResourceValue(string key);
}
}

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Application.Common.Interfaces
{
public interface ISettingsProxy
{
T GetSetting<T>(string property, T defaultValue = default(T));
void PutSetting<T>(string property, T value);
}
}

View File

@@ -0,0 +1,10 @@
using AutoMapper;
namespace ModernKeePass.Application.Common.Mappings
{
public interface IMapFrom<T>
{
void Mapping(Profile profile);
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Linq;
using System.Reflection;
using AutoMapper;
namespace ModernKeePass.Application.Common.Mappings
{
public class MappingProfile : Profile
{
public MappingProfile()
{
ApplyMappingsFromAssembly(typeof(MappingProfile).GetTypeInfo().Assembly);
}
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.ExportedTypes
.Where(t => t.GetTypeInfo().ImplementedInterfaces.Any(i =>
i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetTypeInfo().GetDeclaredMethod("Mapping");
methodInfo?.Invoke(instance, new object[] { this });
}
}
}
}

View File

@@ -0,0 +1,24 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Database.Commands.CloseDatabase
{
public class CloseDatabaseCommand: IRequest
{
public class CloseDatabaseCommandHandler : IRequestHandler<CloseDatabaseCommand>
{
private readonly IDatabaseProxy _database;
public CloseDatabaseCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(CloseDatabaseCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.CloseDatabase();
}
}
}
}

View File

@@ -0,0 +1,82 @@
using MediatR;
using System.Threading.Tasks;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Database.Commands.CreateDatabase
{
public class CreateDatabaseCommand : IRequest
{
public string FilePath { get; set; }
public string Password { get; set; }
public string KeyFilePath { get; set; }
public string Name { get; set; }
public string Version { get; set; }
public bool CreateSampleData { get; set; }
public class CreateDatabaseCommandHandler : IAsyncRequestHandler<CreateDatabaseCommand>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public CreateDatabaseCommandHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(CreateDatabaseCommand message)
{
if (_database.IsDirty) throw new DatabaseOpenException();
var version = DatabaseVersion.V2;
switch (message.Version)
{
case "4":
version = DatabaseVersion.V4;
break;
case "3":
version = DatabaseVersion.V3;
break;
}
await _database.Create(new Credentials
{
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
Password = message.Password
}, message.Name, version);
_database.FileAccessToken = message.FilePath;
if (message.CreateSampleData)
{
var bankingGroup = _database.CreateGroup(_database.RootGroupId, "Banking");
bankingGroup.Icon = Icon.Shop;
_database.UpdateGroup(bankingGroup);
var emailGroup = _database.CreateGroup(_database.RootGroupId, "Email");
emailGroup.Icon = Icon.Mail;
_database.UpdateGroup(emailGroup);
var internetGroup = _database.CreateGroup(_database.RootGroupId, "Internet");
internetGroup.Icon = Icon.World;
_database.UpdateGroup(internetGroup);
var sample1 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry" );
_database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample" );
var sample2 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2" );
_database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321" );
_database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345" );
_database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html" );
}
}
}
}
}

View File

@@ -0,0 +1,22 @@
using FluentValidation;
namespace ModernKeePass.Application.Database.Commands.CreateDatabase
{
public class CreateDatabaseQueryValidator : AbstractValidator<CreateDatabaseCommand>
{
public CreateDatabaseQueryValidator()
{
RuleFor(v => v.FilePath)
.NotNull()
.NotEmpty();
RuleFor(v => v.Password)
.NotNull()
.NotEmpty()
.When(v => string.IsNullOrEmpty(v.KeyFilePath));
RuleFor(v => v.KeyFilePath)
.NotNull()
.NotEmpty()
.When(v => string.IsNullOrEmpty(v.Password));
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using MediatR;
using System.Threading.Tasks;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Database.Commands.SaveDatabase
{
public class SaveDatabaseCommand : IRequest
{
public string FilePath { get; set; }
public class SaveDatabaseCommandHandler : IAsyncRequestHandler<SaveDatabaseCommand>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public SaveDatabaseCommandHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(SaveDatabaseCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
try
{
if (!string.IsNullOrEmpty(message.FilePath))
{
_database.FileAccessToken = message.FilePath;
}
var contents = await _database.SaveDatabase();
// Test DB integrity
_database.CloseDatabase();
await _database.ReOpen(contents);
// Transactional write to file
await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents);
}
catch (Exception exception)
{
throw new SaveException(exception);
}
}
}
}
}

View File

@@ -0,0 +1,36 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Database.Commands.UpdateCredentials
{
public class UpdateCredentialsCommand: IRequest
{
public string Password { get; set; }
public string KeyFilePath { get; set; }
public class UpdateCredentialsCommandHandler : IAsyncRequestHandler<UpdateCredentialsCommand>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public UpdateCredentialsCommandHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(UpdateCredentialsCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateCredentials(new Credentials
{
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
Password = message.Password
});
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace ModernKeePass.Application.Database.Models
{
public class DatabaseVm
{
public bool IsOpen { get; set; }
public string Name { get; set; }
public string RootGroupId { get; set; }
public string RecycleBinId { get; set; }
public bool IsRecycleBinEnabled { get; set; }
public string Compression { get; set; }
public string CipherId { get; set; }
public string KeyDerivationId { get; set; }
public int Size { get; internal set; }
public bool IsDirty { get; internal set; }
}
}

View File

@@ -0,0 +1,41 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Domain.Common;
namespace ModernKeePass.Application.Database.Queries.GetDatabase
{
public class GetDatabaseQuery: IRequest<DatabaseVm>
{
public class GetDatabaseQueryHandler : IRequestHandler<GetDatabaseQuery, DatabaseVm>
{
private readonly IDatabaseProxy _databaseProxy;
public GetDatabaseQueryHandler(IDatabaseProxy databaseProxy)
{
_databaseProxy = databaseProxy;
}
public DatabaseVm Handle(GetDatabaseQuery message)
{
var database = new DatabaseVm
{
IsOpen = _databaseProxy.IsOpen
};
if (database.IsOpen)
{
database.Name = _databaseProxy.Name;
database.RootGroupId = _databaseProxy.RootGroupId;
database.IsRecycleBinEnabled = _databaseProxy.IsRecycleBinEnabled;
database.RecycleBinId = _databaseProxy.RecycleBinId == Constants.EmptyId ? null : _databaseProxy.RecycleBinId;
database.Compression = _databaseProxy.Compression;
database.CipherId = _databaseProxy.CipherId;
database.KeyDerivationId = _databaseProxy.KeyDerivationId;
database.Size = _databaseProxy.Size;
database.IsDirty = _databaseProxy.IsDirty;
}
return database;
}
}
}
}

View File

@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Database.Queries.OpenDatabase
{
public class OpenDatabaseQuery: IRequest
{
public string FilePath { get; set; }
public string Password { get; set; }
public string KeyFilePath { get; set; }
public class OpenDatabaseQueryHandler : IAsyncRequestHandler<OpenDatabaseQuery>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public OpenDatabaseQueryHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(OpenDatabaseQuery message)
{
if (_database.IsDirty) throw new DatabaseOpenException();
var file = await _file.ReadBinaryFile(message.FilePath);
var hasKeyFile = !string.IsNullOrEmpty(message.KeyFilePath);
await _database.Open(file, new Credentials
{
KeyFileContents = hasKeyFile ? await _file.ReadBinaryFile(message.KeyFilePath): null,
Password = message.Password
});
if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath);
_database.Size = file.Length;
_database.FileAccessToken = message.FilePath;
}
}
}
}

View File

@@ -0,0 +1,22 @@
using FluentValidation;
namespace ModernKeePass.Application.Database.Queries.OpenDatabase
{
public class OpenDatabaseQueryValidator : AbstractValidator<OpenDatabaseQuery>
{
public OpenDatabaseQueryValidator()
{
RuleFor(v => v.FilePath)
.NotNull()
.NotEmpty();
RuleFor(v => v.Password)
.NotNull()
.NotEmpty()
.When(v => string.IsNullOrEmpty(v.KeyFilePath));
RuleFor(v => v.KeyFilePath)
.NotNull()
.NotEmpty()
.When(v => string.IsNullOrEmpty(v.Password));
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Database.Queries.ReOpenDatabase
{
public class ReOpenDatabaseQuery: IRequest
{
public class ReOpenDatabaseQueryHandler : IAsyncRequestHandler<ReOpenDatabaseQuery>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public ReOpenDatabaseQueryHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(ReOpenDatabaseQuery message)
{
if (_database.IsOpen) throw new DatabaseOpenException();
if (string.IsNullOrEmpty(_database.FileAccessToken)) throw new DatabaseClosedException();
var file = await _file.ReadBinaryFile(_database.FileAccessToken);
await _database.ReOpen(file);
}
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Reflection;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Behaviors;
namespace ModernKeePass.Application
{
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly;
services.AddMediatR(assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DirtyStatusBehavior<,>));
return services;
}
}
}

View File

@@ -0,0 +1,33 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.AddHistory
{
public class AddHistoryCommand : IRequest
{
public EntryVm Entry { get; set; }
public class AddHistoryCommandHandler : IRequestHandler<AddHistoryCommand>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public AddHistoryCommandHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public void Handle(AddHistoryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var history = _database.AddHistory(message.Entry.Id);
message.Entry.History.Add(_mapper.Map<EntryVm>(history));
}
}
}
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.DeleteHistory
{
public class DeleteHistoryCommand : IRequest
{
public EntryVm Entry { get; set; }
public int HistoryIndex { get; set; }
public class DeleteHistoryCommandHandler : IRequestHandler<DeleteHistoryCommand>
{
private readonly IDatabaseProxy _database;
public DeleteHistoryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(DeleteHistoryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.DeleteHistory(message.Entry.Id, message.HistoryIndex);
message.Entry.History.RemoveAt(message.HistoryIndex);
}
}
}
}

View File

@@ -0,0 +1,34 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.RestoreHistory
{
public class RestoreHistoryCommand : IRequest
{
public EntryVm Entry { get; set; }
public int HistoryIndex { get; set; }
public class RestoreHistoryCommandHandler : IRequestHandler<RestoreHistoryCommand>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public RestoreHistoryCommandHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public void Handle(RestoreHistoryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var entry = _database.RestoreFromHistory(message.Entry.Id, message.HistoryIndex);
message.Entry = _mapper.Map<EntryVm>(entry);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
{
public class SetFieldValueCommand : IRequest
{
public string EntryId { get; set; }
public string FieldName { get; set; }
public object FieldValue { get; set; }
public class SetFieldValueCommandHandler : IRequestHandler<SetFieldValueCommand>
{
private readonly IDatabaseProxy _database;
public SetFieldValueCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetFieldValueCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue);
}
}
}
}

View File

@@ -0,0 +1,17 @@
using FluentValidation;
namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
{
public class SetFieldValueCommandValidator: AbstractValidator<SetFieldValueCommand>
{
public SetFieldValueCommandValidator()
{
RuleFor(v => v.EntryId)
.NotNull()
.NotEmpty();
RuleFor(v => v.FieldName)
.NotNull()
.NotEmpty();
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Common.Mappings;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Entry.Models
{
public class EntryVm: IEntityVm, IMapFrom<EntryEntity>
{
public string ParentGroupId { get; set; }
public string ParentGroupName { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Url { get; set; }
public bool HasUrl => !string.IsNullOrEmpty(Url);
public Dictionary<string, string> AdditionalFields { get; set; }
public List<EntryVm> History { get; set; }
public Icon Icon { get; set; }
public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; }
public bool HasExpirationDate { get; set; }
public DateTimeOffset ExpirationDate { get; set; }
public DateTimeOffset ModificationDate { get; set; }
public override string ToString()
{
return ModificationDate.ToString("g");
}
public void Mapping(Profile profile)
{
profile.CreateMap<EntryEntity, EntryVm>()
.ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentId))
.ForMember(d => d.ParentGroupName, opts => opts.MapFrom(s => s.ParentName))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id))
.ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Username, opts => opts.MapFrom(s => s.UserName))
.ForMember(d => d.Password, opts => opts.MapFrom(s => s.Password))
.ForMember(d => d.Url, opts => opts.MapFrom(s => s.Url))
.ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Notes))
.ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.AdditionalFields))
.ForMember(d => d.History, opts => opts.MapFrom(s => s.History.Reverse()))
.ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate))
.ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate))
.ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon))
.ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor))
.ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor));
}
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Queries.GetEntry
{
public class GetEntryQuery: IRequest<EntryVm>
{
public string Id { get; set; }
public class GetEntryQueryHandler: IRequestHandler<GetEntryQuery, EntryVm>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public GetEntryQueryHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public EntryVm Handle(GetEntryQuery message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
return _mapper.Map<EntryVm>(_database.GetEntry(message.Id));
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.AddEntry
{
public class AddEntryCommand : IRequest
{
public string ParentGroupId { get; set; }
public string EntryId { get; set; }
public class AddEntryCommandHandler : IAsyncRequestHandler<AddEntryCommand>
{
private readonly IDatabaseProxy _database;
public AddEntryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(AddEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.AddEntry(message.ParentGroupId, message.EntryId);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.AddGroup
{
public class AddGroupCommand : IRequest
{
public string ParentGroupId { get; set; }
public string GroupId { get; set; }
public class AddGroupCommandHandler : IAsyncRequestHandler<AddGroupCommand>
{
private readonly IDatabaseProxy _database;
public AddGroupCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(AddGroupCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.AddGroup(message.ParentGroupId, message.GroupId);
}
}
}
}

View File

@@ -0,0 +1,36 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.CreateEntry
{
public class CreateEntryCommand : IRequest<EntryVm>
{
public GroupVm ParentGroup { get; set; }
public class CreateEntryCommandHandler : IRequestHandler<CreateEntryCommand, EntryVm>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public CreateEntryCommandHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public EntryVm Handle(CreateEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var entry = _database.CreateEntry(message.ParentGroup.Id);
var entryVm = _mapper.Map<EntryVm>(entry);
message.ParentGroup.Entries.Add(entryVm);
return entryVm;
}
}
}
}

View File

@@ -0,0 +1,37 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.CreateGroup
{
public class CreateGroupCommand : IRequest<GroupVm>
{
public GroupVm ParentGroup { get; set; }
public string Name { get; set; }
public bool IsRecycleBin { get; set; }
public class CreateGroupCommandHandler : IRequestHandler<CreateGroupCommand, GroupVm>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public CreateGroupCommandHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public GroupVm Handle(CreateGroupCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin);
var groupVm = _mapper.Map<GroupVm>(group);
message.ParentGroup.SubGroups.Add(groupVm);
return groupVm;
}
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.DeleteEntry
{
public class DeleteEntryCommand : IRequest
{
public string ParentGroupId { get; set; }
public string EntryId { get; set; }
public string RecycleBinName { get; set; }
public class DeleteEntryCommandHandler : IAsyncRequestHandler<DeleteEntryCommand>
{
private readonly IDatabaseProxy _database;
public DeleteEntryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(DeleteEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(Constants.EmptyId)))
{
_database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true);
}
if (!_database.IsRecycleBinEnabled || message.ParentGroupId.Equals(_database.RecycleBinId))
{
_database.DeleteEntity(message.EntryId);
}
else
{
await _database.AddEntry(_database.RecycleBinId, message.EntryId);
}
await _database.RemoveEntry(message.ParentGroupId, message.EntryId);
}
}
}
}

View File

@@ -0,0 +1,46 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.DeleteGroup
{
public class DeleteGroupCommand : IRequest
{
public string ParentGroupId { get; set; }
public string GroupId { get; set; }
public string RecycleBinName { get; set; }
public class DeleteGroupCommandHandler : IAsyncRequestHandler<DeleteGroupCommand>
{
private readonly IDatabaseProxy _database;
public DeleteGroupCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(DeleteGroupCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(Constants.EmptyId)))
{
_database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true);
}
if (!_database.IsRecycleBinEnabled || message.ParentGroupId.Equals(_database.RecycleBinId))
{
_database.DeleteEntity(message.GroupId);
}
else
{
await _database.AddGroup(_database.RecycleBinId, message.GroupId);
}
await _database.RemoveGroup(message.ParentGroupId, message.GroupId);
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.MoveEntry
{
public class MoveEntryCommand : IRequest
{
public GroupVm ParentGroup { get; set; }
public EntryVm Entry { get; set; }
public int Index { get; set; }
public class MoveEntryCommandHandler : IAsyncRequestHandler<MoveEntryCommand>
{
private readonly IDatabaseProxy _database;
public MoveEntryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(MoveEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.MoveEntry(message.ParentGroup.Id, message.Entry.Id, message.Index);
message.ParentGroup.Entries.Insert(message.Index, message.Entry);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.RemoveEntry
{
public class RemoveEntryCommand : IRequest
{
public string ParentGroupId { get; set; }
public string EntryId { get; set; }
public class RemoveEntryCommandHandler : IAsyncRequestHandler<RemoveEntryCommand>
{
private readonly IDatabaseProxy _database;
public RemoveEntryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(RemoveEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.RemoveEntry(message.ParentGroupId, message.EntryId);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.RemoveGroup
{
public class RemoveGroupCommand : IRequest
{
public string ParentGroupId { get; set; }
public string GroupId { get; set; }
public class RemoveGroupCommandHandler : IAsyncRequestHandler<RemoveGroupCommand>
{
private readonly IDatabaseProxy _database;
public RemoveGroupCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(RemoveGroupCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.RemoveGroup(message.ParentGroupId, message.GroupId);
}
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Linq;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.SortEntries
{
public class SortEntriesCommand : IRequest
{
public GroupVm Group { get; set; }
public class SortEntriesCommandHandler : IRequestHandler<SortEntriesCommand>
{
private readonly IDatabaseProxy _database;
public SortEntriesCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SortEntriesCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.SortEntries(message.Group.Id);
message.Group.Entries = message.Group.Entries.OrderBy(e => e.Title).ToList();
}
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Linq;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.SortGroups
{
public class SortGroupsCommand : IRequest
{
public GroupVm Group { get; set; }
public class SortGroupsCommandHandler : IRequestHandler<SortGroupsCommand>
{
private readonly IDatabaseProxy _database;
public SortGroupsCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SortGroupsCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.SortSubGroups(message.Group.Id);
message.Group.SubGroups = message.Group.SubGroups.OrderBy(g => g.Title).ToList();
}
}
}
}

View File

@@ -0,0 +1,41 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.UpdateGroup
{
public class UpdateGroupCommand : IRequest
{
public GroupVm Group { get; set; }
public string Title { get; set; }
public Icon Icon { get; set; }
public class UpdateGroupCommandHandler : IRequestHandler<UpdateGroupCommand>
{
private readonly IDatabaseProxy _database;
public UpdateGroupCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(UpdateGroupCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var group = new GroupEntity
{
Id = message.Group.Id,
Name = message.Title,
Icon = message.Icon
};
_database.UpdateGroup(group);
message.Group.Title = message.Title;
message.Group.Icon = message.Icon;
}
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Collections.Generic;
using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Common.Mappings;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Group.Models
{
public class GroupVm: IEntityVm, IMapFrom<GroupEntity>
{
public string ParentGroupId { get; set; }
public string ParentGroupName { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public Icon Icon { get; set; }
public List<GroupVm> SubGroups { get; set; }
public List<EntryVm> Entries { get; set; }
public override string ToString()
{
return Title;
}
public void Mapping(Profile profile)
{
profile.CreateMap<GroupEntity, GroupVm>()
.ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentId))
.ForMember(d => d.ParentGroupName, opts => opts.MapFrom(s => s.ParentName))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id))
.ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon))
.ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries))
.ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.SubGroups))
.MaxDepth(2);
}
}
}

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Queries.GetAllGroups
{
public class GetAllGroupsQuery : IRequest<IEnumerable<GroupVm>>
{
public string GroupId { get; set; }
public class GetAllGroupsQueryHandler : IRequestHandler<GetAllGroupsQuery, IEnumerable<GroupVm>>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public GetAllGroupsQueryHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public IEnumerable<GroupVm> Handle(GetAllGroupsQuery message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var groups = new List<GroupVm> {_mapper.Map<GroupVm>(_database.GetGroup(message.GroupId))};
groups.AddRange(_database.GetAllGroups(message.GroupId).Select(g => _mapper.Map<GroupVm>(g)));
return groups;
}
}
}
}

View File

@@ -0,0 +1,31 @@
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Queries.GetGroup
{
public class GetGroupQuery : IRequest<GroupVm>
{
public string Id { get; set; }
public class GetGroupQueryHandler : IRequestHandler<GetGroupQuery, GroupVm>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public GetGroupQueryHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public GroupVm Handle(GetGroupQuery message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
return _mapper.Map<GroupVm>(_database.GetGroup(message.Id));
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Queries.SearchEntries
{
public class SearchEntriesQuery : IRequest<IEnumerable<EntryVm>>
{
public string GroupId { get; set; }
public string SearchText { get; set; }
public class SearchEntriesQueryHandler : IRequestHandler<SearchEntriesQuery, IEnumerable<EntryVm>>
{
private readonly IDatabaseProxy _database;
private readonly IMapper _mapper;
public SearchEntriesQueryHandler(IDatabaseProxy database, IMapper mapper)
{
_database = database;
_mapper = mapper;
}
public IEnumerable<EntryVm> Handle(SearchEntriesQuery message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
return _database.Search(message.GroupId, message.SearchText).Select(e => _mapper.Map<EntryVm>(e));
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetCipher
{
public class SetCipherCommand : IRequest
{
public string CipherId { get; set; }
public class SetCipherCommandHandler : IRequestHandler<SetCipherCommand>
{
private readonly IDatabaseProxy _database;
public SetCipherCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetCipherCommand message)
{
if (_database.IsOpen) _database.CipherId = message.CipherId;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetCompression
{
public class SetCompressionCommand : IRequest
{
public string Compression { get; set; }
public class SetCompressionCommandHandler : IRequestHandler<SetCompressionCommand>
{
private readonly IDatabaseProxy _database;
public SetCompressionCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetCompressionCommand message)
{
if (_database.IsOpen) _database.Compression = message.Compression;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetHasRecycleBin
{
public class SetHasRecycleBinCommand : IRequest
{
public bool HasRecycleBin { get; set; }
public class SetHasRecycleBinCommandHandler : IRequestHandler<SetHasRecycleBinCommand>
{
private readonly IDatabaseProxy _database;
public SetHasRecycleBinCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetHasRecycleBinCommand message)
{
if (_database.IsOpen) _database.IsRecycleBinEnabled = message.HasRecycleBin;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetKeyDerivation
{
public class SetKeyDerivationCommand : IRequest
{
public string KeyDerivationId { get; set; }
public class SetKeyDerivationCommandHandler : IRequestHandler<SetKeyDerivationCommand>
{
private readonly IDatabaseProxy _database;
public SetKeyDerivationCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetKeyDerivationCommand message)
{
if (_database.IsOpen) _database.KeyDerivationId = message.KeyDerivationId;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,28 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetRecycleBin
{
public class SetRecycleBinCommand : IRequest
{
public string RecycleBinId { get; set; }
public class SetRecycleBinCommandHandler : IRequestHandler<SetRecycleBinCommand>
{
private readonly IDatabaseProxy _database;
public SetRecycleBinCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetRecycleBinCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.RecycleBinId = message.RecycleBinId ?? Constants.EmptyId;
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Application.Parameters.Models
{
public class CipherVm
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Application.Parameters.Models
{
public class KeyDerivationVm
{
public string Id { get; set; }
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Parameters.Models;
namespace ModernKeePass.Application.Parameters.Queries.GetCiphers
{
public class GetCiphersQuery: IRequest<IEnumerable<CipherVm>>
{
public class GetCiphersQueryHandler: IRequestHandler<GetCiphersQuery, IEnumerable<CipherVm>>
{
private readonly ICryptographyClient _cryptography;
public GetCiphersQueryHandler(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public IEnumerable<CipherVm> Handle(GetCiphersQuery message)
{
return _cryptography.Ciphers.Select(c => new CipherVm
{
Id = c.Id,
Name = c.Name
});
}
}
}
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
using System.Linq;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Application.Parameters.Queries.GetCompressions
{
public class GetCompressionsQuery : IRequest<IEnumerable<string>>
{
public class GetCompressionsQueryHandler : IRequestHandler<GetCompressionsQuery, IEnumerable<string>>
{
private readonly ICryptographyClient _cryptography;
public GetCompressionsQueryHandler(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public IEnumerable<string> Handle(GetCompressionsQuery message)
{
return _cryptography.CompressionAlgorithms.OrderBy(c => c);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Parameters.Models;
namespace ModernKeePass.Application.Parameters.Queries.GetKeyDerivations
{
public class GetKeyDerivationsQuery : IRequest<IEnumerable<KeyDerivationVm>>
{
public class GetKeyDerivationsQueryHandler : IRequestHandler<GetKeyDerivationsQuery, IEnumerable<KeyDerivationVm>>
{
private readonly ICryptographyClient _cryptography;
public GetKeyDerivationsQueryHandler(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public IEnumerable<KeyDerivationVm> Handle(GetKeyDerivationsQuery message)
{
return _cryptography.KeyDerivations.Select(c => new KeyDerivationVm
{
Id = c.Id,
Name = c.Name
});
}
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Resources;
using System.Reflection;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModernKeePass.Application")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModernKeePass.Application")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,38 @@
using System;
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile
{
public class GenerateKeyFileCommand : IRequest
{
public string KeyFilePath { get; set; }
public bool AddAdditionalEntropy { get; set; }
public class GenerateKeyFileCommandHandler : IAsyncRequestHandler<GenerateKeyFileCommand>
{
private readonly ICredentialsProxy _security;
private readonly IFileProxy _file;
public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file)
{
_security = security;
_file = file;
}
public async Task Handle(GenerateKeyFileCommand message)
{
byte[] entropy = null;
if (message.AddAdditionalEntropy)
{
entropy = new byte[10];
var random = new Random();
random.NextBytes(entropy);
}
var keyFile = _security.GenerateKeyFile(entropy);
await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile);
}
}
}
}

View File

@@ -0,0 +1,48 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Application.Security.Commands.GeneratePassword
{
public class GeneratePasswordCommand: IRequest<string>
{
public int PasswordLength { get; set; }
public bool UpperCasePatternSelected { get; set; }
public bool LowerCasePatternSelected { get; set; }
public bool DigitsPatternSelected { get; set; }
public bool SpecialPatternSelected { get; set; }
public bool MinusPatternSelected { get; set; }
public bool UnderscorePatternSelected { get; set; }
public bool SpacePatternSelected { get; set; }
public bool BracketsPatternSelected { get; set; }
public string CustomChars { get; set; }
public class GeneratePasswordCommandHandler: IRequestHandler<GeneratePasswordCommand, string>
{
private readonly ICredentialsProxy _security;
public GeneratePasswordCommandHandler(ICredentialsProxy security)
{
_security = security;
}
public string Handle(GeneratePasswordCommand message)
{
var options = new PasswordGenerationOptions
{
PasswordLength = message.PasswordLength,
BracketsPatternSelected = message.BracketsPatternSelected,
CustomChars = message.CustomChars,
DigitsPatternSelected = message.DigitsPatternSelected,
LowerCasePatternSelected = message.LowerCasePatternSelected,
MinusPatternSelected = message.MinusPatternSelected,
SpacePatternSelected = message.SpacePatternSelected,
SpecialPatternSelected = message.SpecialPatternSelected,
UnderscorePatternSelected = message.UnderscorePatternSelected,
UpperCasePatternSelected = message.UpperCasePatternSelected
};
return _security.GeneratePassword(options);
}
}
}
}

View File

@@ -0,0 +1,25 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity
{
public class EstimatePasswordComplexityQuery : IRequest<int>
{
public string Password { get; set; }
public class EstimatePasswordComplexityQueryHandler : IRequestHandler<EstimatePasswordComplexityQuery, int>
{
private readonly ICredentialsProxy _security;
public EstimatePasswordComplexityQueryHandler(ICredentialsProxy security)
{
_security = security;
}
public int Handle(EstimatePasswordComplexityQuery message)
{
return _security.EstimatePasswordComplexity(message.Password);
}
}
}
}

View File

@@ -0,0 +1,16 @@
{
"supports": {},
"dependencies": {
"AutoMapper": "5.2.0",
"FluentValidation": "8.6.2",
"MediatR": "3.0.1",
"MediatR.Extensions.Microsoft.DependencyInjection": "2.0.0",
"Microsoft.Extensions.DependencyInjection": "1.1.1",
"Microsoft.NETCore.Portable.Compatibility": "1.0.1",
"NETStandard.Library": "2.0.3",
"Splat": "3.0.0"
},
"frameworks": {
"netstandard1.2": {}
}
}

View File

@@ -0,0 +1,14 @@
namespace ModernKeePass.Domain.Common
{
public static class Constants
{
public static string EmptyId => "00000000000000000000000000000000";
public static class Extensions
{
public static string Any => "*";
public static string Kdbx => ".kdbx";
public static string Key => ".key";
}
}
}

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9A0759F1-9069-4841-99E3-3BEC44E17356}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernKeePass.Domain</RootNamespace>
<AssemblyName>ModernKeePass.Domain</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Constants.cs" />
<Compile Include="Dtos\Credentials.cs" />
<Compile Include="Dtos\FileInfo.cs" />
<Compile Include="Dtos\PasswordGenerationOptions.cs" />
<Compile Include="Entities\BaseEntity.cs" />
<Compile Include="Entities\EntryEntity.cs" />
<Compile Include="Entities\GroupEntity.cs" />
<Compile Include="Enums\CredentialStatusTypes.cs" />
<Compile Include="Enums\DatabaseVersion.cs" />
<Compile Include="Enums\EntryFieldName.cs" />
<Compile Include="Enums\Icon.cs" />
<Compile Include="Enums\ImportFormat.cs" />
<Compile Include="Exceptions\DatabaseClosedException.cs" />
<Compile Include="Exceptions\DatabaseOpenException.cs" />
<Compile Include="Exceptions\DatabaseTooBigException.cs" />
<Compile Include="Exceptions\NavigationException.cs" />
<Compile Include="Exceptions\SaveException.cs" />
<Compile Include="Interfaces\IDateTime.cs" />
<Compile Include="Interfaces\IHasSelectableObject.cs" />
<Compile Include="Interfaces\IImportFormat.cs" />
<Compile Include="Interfaces\IIsEnabled.cs" />
<Compile Include="Interfaces\ISelectableModel.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,9 @@
namespace ModernKeePass.Domain.Dtos
{
public class Credentials
{
public string Password { get; set; }
public byte[] KeyFileContents { get; set; }
// TODO: add Windows Hello
}
}

View File

@@ -0,0 +1,9 @@
namespace ModernKeePass.Domain.Dtos
{
public class FileInfo
{
public string Id { get; set; }
public string Name { get; set; }
public string Path { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
namespace ModernKeePass.Domain.Dtos
{
public class PasswordGenerationOptions
{
public int PasswordLength { get; set; }
public bool UpperCasePatternSelected { get; set; }
public bool LowerCasePatternSelected { get; set; }
public bool DigitsPatternSelected { get; set; }
public bool SpecialPatternSelected { get; set; }
public bool MinusPatternSelected { get; set; }
public bool UnderscorePatternSelected { get; set; }
public bool SpacePatternSelected { get; set; }
public bool BracketsPatternSelected { get; set; }
public string CustomChars { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace ModernKeePass.Domain.Entities
{
public class BaseEntity
{
public string Id { get; set; }
public string Name { get; set; }
public string ParentId { get; set; }
public string ParentName { get; set; }
public DateTimeOffset LastModificationDate { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Domain.Entities
{
public class EntryEntity: BaseEntity
{
public string UserName { get; set; }
public string Password { get; set; }
public string Url { get; set; }
public string Notes { get; set; }
public DateTimeOffset ExpirationDate { get; set; }
public Dictionary<string, string> AdditionalFields { get; set; } = new Dictionary<string, string>();
public IEnumerable<EntryEntity> History { get; set; } = new List<EntryEntity>();
public Icon Icon { get; set; }
public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; }
public bool HasExpirationDate { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Domain.Entities
{
public class GroupEntity : BaseEntity
{
public List<GroupEntity> SubGroups { get; set; } = new List<GroupEntity>();
public List<EntryEntity> Entries { get; set; } = new List<EntryEntity>();
public Icon Icon { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace ModernKeePass.Domain.Enums
{
public enum CredentialStatusTypes
{
Normal = 0,
Error = 1,
Warning = 3,
Success = 5
}
}

View File

@@ -0,0 +1,9 @@
namespace ModernKeePass.Domain.Enums
{
public enum DatabaseVersion
{
V2,
V3,
V4
}
}

View File

@@ -0,0 +1,16 @@
namespace ModernKeePass.Domain.Enums
{
public static class EntryFieldName
{
public const string Title = nameof(Title);
public const string UserName = nameof(UserName);
public const string Password = nameof(Password);
public const string Url = nameof(Url);
public const string Notes = nameof(Notes);
public const string Icon = nameof(Icon);
public const string ExpirationDate = nameof(ExpirationDate);
public const string HasExpirationDate = nameof(HasExpirationDate);
public const string BackgroundColor = nameof(BackgroundColor);
public const string ForegroundColor = nameof(ForegroundColor);
}
}

View File

@@ -0,0 +1,54 @@
namespace ModernKeePass.Domain.Enums
{
public enum Icon
{
Delete,
Edit,
Save,
Cancel,
Accept,
Home,
Camera,
Setting,
Mail,
Find,
Help,
Clock,
Crop,
World,
Flag,
PreviewLink,
Document,
ProtectedDocument,
ContactInfo,
ViewAll,
Rotate,
List,
Shop,
BrowsePhotos,
Caption,
Repair,
Page,
Paste,
Important,
SlideShow,
MapDrive,
ContactPresence,
Contact,
Folder,
View,
Permissions,
Map,
CellPhone,
OutlineStar,
Calculator,
Library,
SyncFolder,
GoToStart,
ZeroBars,
FourBars,
Scan,
ReportHacked,
Stop
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Domain.Enums
{
public enum ImportFormat
{
CSV
}
}

View File

@@ -0,0 +1,7 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class DatabaseClosedException: Exception
{ }
}

View File

@@ -0,0 +1,7 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class DatabaseOpenException: Exception
{ }
}

View File

@@ -0,0 +1,6 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class DatabaseTooBigException: Exception { }
}

View File

@@ -0,0 +1,11 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class NavigationException: Exception
{
public NavigationException(Type pageType) : base($"Failed to load Page {pageType.FullName}")
{
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class SaveException : Exception
{
public new string Message { get; }
public new string Source { get; }
public SaveException(Exception exception)
{
Message = exception.Message;
Source = exception.Source;
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace ModernKeePass.Domain.Interfaces
{
public interface IDateTime
{
DateTime Now { get; }
}
}

View File

@@ -1,4 +1,4 @@
namespace ModernKeePass.Interfaces namespace ModernKeePass.Domain.Interfaces
{ {
public interface IHasSelectableObject public interface IHasSelectableObject
{ {

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ModernKeePass.Domain.Interfaces
{
public interface IImportFormat
{
Task<List<Dictionary<string, string>>> Import(string path);
}
}

View File

@@ -1,4 +1,4 @@
namespace ModernKeePass.Interfaces namespace ModernKeePass.Domain.Interfaces
{ {
public interface IIsEnabled public interface IIsEnabled
{ {

View File

@@ -1,4 +1,4 @@
namespace ModernKeePass.Interfaces namespace ModernKeePass.Domain.Interfaces
{ {
public interface ISelectableModel public interface ISelectableModel
{ {

View File

@@ -0,0 +1,28 @@
using System.Resources;
using System.Reflection;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ModernKeePass.Domain")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ModernKeePass.Domain")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,11 @@
{
"supports": {},
"dependencies": {
"Microsoft.NETCore.Portable.Compatibility": "1.0.1",
"NETStandard.Library": "2.0.3",
"Splat": "3.0.0"
},
"frameworks": {
"netstandard1.2": {}
}
}

View File

@@ -0,0 +1,10 @@
using System;
using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.Infrastructure.Common
{
public class MachineDateTime: IDateTime
{
public DateTime Now => DateTime.Now;
}
}

View File

@@ -0,0 +1,36 @@
using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.Infrastructure.Common;
using ModernKeePass.Infrastructure.KeePass;
using ModernKeePass.Infrastructure.UWP;
namespace ModernKeePass.Infrastructure
{
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructureCommon(this IServiceCollection services)
{
services.AddTransient(typeof(IDateTime), typeof(MachineDateTime));
return services;
}
public static IServiceCollection AddInfrastructureKeePass(this IServiceCollection services)
{
services.AddSingleton(typeof(IDatabaseProxy), typeof(KeePassDatabaseClient));
services.AddTransient(typeof(ICryptographyClient), typeof(KeePassCryptographyClient));
services.AddTransient(typeof(ICredentialsProxy), typeof(KeePassCredentialsClient));
return services;
}
public static IServiceCollection AddInfrastructureUwp(this IServiceCollection services)
{
services.AddScoped(typeof(IFileProxy), typeof(StorageFileClient));
services.AddTransient(typeof(ISettingsProxy), typeof(UwpSettingsClient));
services.AddTransient(typeof(IRecentProxy), typeof(UwpRecentFilesClient));
services.AddTransient(typeof(IResourceProxy), typeof(UwpResourceClient));
services.AddTransient(typeof(INotificationService), typeof(ToastNotificationService));
return services;
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Infrastructure.File
{
public class CsvImportFormat: IImportFormat
{
private const bool HasHeaderRow = true;
private const char Delimiter = ';';
private const char LineDelimiter = '\n';
public List<Dictionary<string, string>> Import(IList<string> fileContents)
{
var parsedResult = new List<Dictionary<string, string>>();
foreach (var line in fileContents)
{
var fields = line.Split(Delimiter);
var recordItem = new Dictionary<string, string>();
var i = 0;
foreach (var field in fields)
{
recordItem.Add(i.ToString(), field);
i++;
}
parsedResult.Add(recordItem);
}
return parsedResult;
}
}
}

View File

@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{09577E4C-4899-45B9-BF80-1803D617CCAE}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ModernKeePass.Infrastructure</RootNamespace>
<AssemblyName>ModernKeePass.Infrastructure</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<TargetFrameworkVersion>v5.0</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|ARM' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<None Include="Libs\Windows.winmd" />
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\MachineDateTime.cs" />
<Compile Include="DependencyInjection.cs" />
<Compile Include="File\CsvImportFormat.cs" />
<Compile Include="KeePass\EntryFieldMapper.cs" />
<Compile Include="KeePass\EntryMappingProfile.cs" />
<Compile Include="KeePass\GroupMappingProfile.cs" />
<Compile Include="KeePass\IconMapper.cs" />
<Compile Include="KeePass\KeePassCryptographyClient.cs" />
<Compile Include="KeePass\KeePassDatabaseClient.cs" />
<Compile Include="KeePass\KeePassCredentialsClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UWP\StorageFileClient.cs" />
<Compile Include="UWP\ToastNotificationService.cs" />
<Compile Include="UWP\UwpRecentFilesClient.cs" />
<Compile Include="UWP\UwpResourceClient.cs" />
<Compile Include="UWP\UwpSettingsClient.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModernKeePass.Application\Application.csproj">
<Project>{42353562-5e43-459c-8e3e-2f21e575261d}</Project>
<Name>Application</Name>
</ProjectReference>
<ProjectReference Include="..\ModernKeePass.Domain\Domain.csproj">
<Project>{9a0759f1-9069-4841-99e3-3bec44e17356}</Project>
<Name>Domain</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Windows, Version=255.255.255.255, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>Libs\Windows.winmd</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,33 @@
using ModernKeePass.Domain.Enums;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public static class EntryFieldMapper
{
public static string MapPwDefsToField(string value)
{
switch (value)
{
case PwDefs.TitleField: return EntryFieldName.Title;
case PwDefs.UserNameField: return EntryFieldName.UserName;
case PwDefs.PasswordField: return EntryFieldName.Password;
case PwDefs.NotesField: return EntryFieldName.Notes;
case PwDefs.UrlField: return EntryFieldName.Url;
default: return value;
}
}
public static string MapFieldToPwDef(string value)
{
switch (value)
{
case EntryFieldName.Title: return PwDefs.TitleField;
case EntryFieldName.UserName: return PwDefs.UserNameField;
case EntryFieldName.Password: return PwDefs.PasswordField;
case EntryFieldName.Notes: return PwDefs.NotesField;
case EntryFieldName.Url: return PwDefs.UrlField;
default: return value;
}
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Linq;
using AutoMapper;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public class EntryMappingProfile: Profile
{
public EntryMappingProfile()
{
CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString()))
.ForMember(dest => dest.ParentName, opt => opt.MapFrom(src => src.ParentGroup.Name))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString()))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.TitleField)))
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UserNameField)))
.ForMember(dest => dest.Password, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.PasswordField)))
.ForMember(dest => dest.Url, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UrlField)))
.ForMember(dest => dest.Notes, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.NotesField)))
.ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor))
.ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor))
.ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime)))
.ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId)))
.ForMember(dest => dest.AdditionalFields, opt => opt.MapFrom(src =>
src.Strings.Where(s => !PwDefs.GetStandardFields().Contains(s.Key))
.ToDictionary(s => s.Key, s => GetEntryValue(src, s.Key))))
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime)));
}
private string GetEntryValue(PwEntry entry, string key) => entry.Strings.GetSafe(key).ReadString();
}
}

View File

@@ -0,0 +1,23 @@
using AutoMapper;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public class GroupMappingProfile : Profile
{
public GroupMappingProfile()
{
CreateMap<PwGroup, GroupEntity>()
.ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString()))
.ForMember(d => d.ParentName, opts => opts.MapFrom(s => s.ParentGroup.Name))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Uuid.ToHexString()))
.ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime))
.ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries))
.ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.Groups))
.MaxDepth(2);
}
}
}

View File

@@ -0,0 +1,126 @@
using ModernKeePass.Domain.Enums;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public static class IconMapper
{
public static Icon MapPwIconToIcon(PwIcon value)
{
switch (value)
{
case PwIcon.Key: return Icon.Permissions;
case PwIcon.WorldSocket:
case PwIcon.World: return Icon.World;
case PwIcon.Warning: return Icon.Important;
case PwIcon.WorldComputer:
case PwIcon.Drive:
case PwIcon.DriveWindows:
case PwIcon.NetworkServer: return Icon.MapDrive;
case PwIcon.MarkedDirectory: return Icon.Map;
case PwIcon.UserCommunication: return Icon.ContactInfo;
case PwIcon.Parts: return Icon.ViewAll;
case PwIcon.Notepad: return Icon.Document;
case PwIcon.Identity: return Icon.Contact;
case PwIcon.PaperReady: return Icon.SyncFolder;
case PwIcon.Digicam: return Icon.Camera;
case PwIcon.IRCommunication: return Icon.View;
case PwIcon.Energy: return Icon.ZeroBars;
case PwIcon.Scanner: return Icon.Scan;
case PwIcon.CDRom: return Icon.Rotate;
case PwIcon.Monitor: return Icon.Caption;
case PwIcon.EMailBox:
case PwIcon.EMail: return Icon.Mail;
case PwIcon.Configuration: return Icon.Setting;
case PwIcon.ClipboardReady: return Icon.Paste;
case PwIcon.PaperNew: return Icon.Page;
case PwIcon.Screen: return Icon.GoToStart;
case PwIcon.EnergyCareful: return Icon.FourBars;
case PwIcon.Disk: return Icon.Save;
case PwIcon.Console: return Icon.SlideShow;
case PwIcon.Printer: return Icon.Scan;
case PwIcon.ProgramIcons: return Icon.GoToStart;
case PwIcon.Settings:
case PwIcon.Tool: return Icon.Repair;
case PwIcon.Archive: return Icon.Crop;
case PwIcon.Count: return Icon.Calculator;
case PwIcon.Clock: return Icon.Clock;
case PwIcon.EMailSearch: return Icon.Find;
case PwIcon.PaperFlag: return Icon.Flag;
case PwIcon.TrashBin: return Icon.Delete;
case PwIcon.Expired: return Icon.ReportHacked;
case PwIcon.Info: return Icon.Help;
case PwIcon.Folder:
case PwIcon.FolderOpen:
case PwIcon.FolderPackage: return Icon.Folder;
case PwIcon.PaperLocked: return Icon.ProtectedDocument;
case PwIcon.Checked: return Icon.Accept;
case PwIcon.Pen: return Icon.Edit;
case PwIcon.Thumbnail: return Icon.BrowsePhotos;
case PwIcon.Book: return Icon.Library;
case PwIcon.List: return Icon.List;
case PwIcon.UserKey: return Icon.ContactPresence;
case PwIcon.Home: return Icon.Home;
case PwIcon.Star: return Icon.OutlineStar;
case PwIcon.Money: return Icon.Shop;
case PwIcon.Certificate: return Icon.PreviewLink;
case PwIcon.BlackBerry: return Icon.CellPhone;
default: return Icon.Stop;
}
}
public static PwIcon MapIconToPwIcon(Icon value)
{
switch (value)
{
case Icon.Delete: return PwIcon.TrashBin;
case Icon.Edit: return PwIcon.Pen;
case Icon.Save: return PwIcon.Disk;
case Icon.Cancel: return PwIcon.Expired;
case Icon.Accept: return PwIcon.Checked;
case Icon.Home: return PwIcon.Home;
case Icon.Camera: return PwIcon.Digicam;
case Icon.Setting: return PwIcon.Configuration;
case Icon.Mail: return PwIcon.EMail;
case Icon.Find: return PwIcon.EMailSearch;
case Icon.Help: return PwIcon.Info;
case Icon.Clock: return PwIcon.Clock;
case Icon.Crop: return PwIcon.Archive;
case Icon.World: return PwIcon.World;
case Icon.Flag: return PwIcon.PaperFlag;
case Icon.PreviewLink: return PwIcon.Certificate;
case Icon.Document: return PwIcon.Notepad;
case Icon.ProtectedDocument: return PwIcon.PaperLocked;
case Icon.ContactInfo: return PwIcon.UserCommunication;
case Icon.ViewAll: return PwIcon.Parts;
case Icon.Rotate: return PwIcon.CDRom;
case Icon.List: return PwIcon.List;
case Icon.Shop: return PwIcon.Money;
case Icon.BrowsePhotos: return PwIcon.Thumbnail;
case Icon.Caption: return PwIcon.Monitor;
case Icon.Repair: return PwIcon.Tool;
case Icon.Page: return PwIcon.PaperNew;
case Icon.Paste: return PwIcon.ClipboardReady;
case Icon.Important: return PwIcon.Warning;
case Icon.SlideShow: return PwIcon.Console;
case Icon.MapDrive: return PwIcon.NetworkServer;
case Icon.ContactPresence: return PwIcon.UserKey;
case Icon.Contact: return PwIcon.Identity;
case Icon.Folder: return PwIcon.Folder;
case Icon.View: return PwIcon.IRCommunication;
case Icon.Permissions: return PwIcon.Key;
case Icon.Map: return PwIcon.MarkedDirectory;
case Icon.CellPhone: return PwIcon.BlackBerry;
case Icon.OutlineStar: return PwIcon.Star;
case Icon.Calculator: return PwIcon.Count;
case Icon.Library: return PwIcon.Book;
case Icon.SyncFolder: return PwIcon.PaperReady;
case Icon.GoToStart: return PwIcon.Screen;
case Icon.ZeroBars: return PwIcon.Energy;
case Icon.FourBars: return PwIcon.EnergyCareful;
case Icon.Scan: return PwIcon.Scanner;
default: return PwIcon.Key;
}
}
}
}

View File

@@ -0,0 +1,48 @@
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePassLib.Cryptography;
using ModernKeePassLib.Cryptography.PasswordGenerator;
using ModernKeePassLib.Keys;
using ModernKeePassLib.Security;
namespace ModernKeePass.Infrastructure.KeePass
{
public class KeePassCredentialsClient: ICredentialsProxy
{
public string GeneratePassword(PasswordGenerationOptions options)
{
var pwProfile = new PwProfile
{
GeneratorType = PasswordGeneratorType.CharSet,
Length = (uint)options.PasswordLength,
CharSet = new PwCharSet()
};
if (options.UpperCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.UpperCase);
if (options.LowerCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.LowerCase);
if (options.DigitsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Digits);
if (options.SpecialPatternSelected) pwProfile.CharSet.Add(PwCharSet.Special);
if (options.MinusPatternSelected) pwProfile.CharSet.Add('-');
if (options.UnderscorePatternSelected) pwProfile.CharSet.Add('_');
if (options.SpacePatternSelected) pwProfile.CharSet.Add(' ');
if (options.BracketsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Brackets);
pwProfile.CharSet.Add(options.CustomChars);
ProtectedString password;
PwGenerator.Generate(out password, pwProfile, null, new CustomPwGeneratorPool());
return password.ReadString();
}
public int EstimatePasswordComplexity(string password)
{
return (int)QualityEstimation.EstimatePasswordBits(password?.ToCharArray());
}
public byte[] GenerateKeyFile(byte[] additionalEntropy)
{
return KcpKeyFile.Create(additionalEntropy);
}
}
}

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
using ModernKeePassLib.Cryptography.Cipher;
using ModernKeePassLib.Cryptography.KeyDerivation;
namespace ModernKeePass.Infrastructure.KeePass
{
public class KeePassCryptographyClient: ICryptographyClient
{
public IEnumerable<BaseEntity> Ciphers
{
get
{
for (var inx = 0; inx < CipherPool.GlobalPool.EngineCount; inx++)
{
var cipher = CipherPool.GlobalPool[inx];
yield return new BaseEntity
{
Id = cipher.CipherUuid.ToHexString(),
Name = cipher.DisplayName
};
}
}
}
public IEnumerable<BaseEntity> KeyDerivations => KdfPool.Engines.Select(e => new BaseEntity
{
Id = e.Uuid.ToHexString(),
Name = e.Name
});
public IEnumerable<string> CompressionAlgorithms => Enum.GetNames(typeof(PwCompressionAlgorithm)).Take((int) PwCompressionAlgorithm.Count);
}
}

View File

@@ -0,0 +1,391 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Interfaces;
using ModernKeePassLib;
using ModernKeePassLib.Collections;
using ModernKeePassLib.Cryptography.Cipher;
using ModernKeePassLib.Cryptography.KeyDerivation;
using ModernKeePassLib.Interfaces;
using ModernKeePassLib.Keys;
using ModernKeePassLib.Security;
using ModernKeePassLib.Serialization;
using ModernKeePassLib.Utility;
namespace ModernKeePass.Infrastructure.KeePass
{
public class KeePassDatabaseClient: IDatabaseProxy, IDisposable
{
private readonly IMapper _mapper;
private readonly IDateTime _dateTime;
private readonly PwDatabase _pwDatabase = new PwDatabase();
private Credentials _credentials;
// Flag: Has Dispose already been called?
private bool _disposed;
// Main information
public bool IsOpen => (_pwDatabase?.IsOpen).GetValueOrDefault();
public string Name => _pwDatabase?.Name;
public string RootGroupId => _pwDatabase?.RootGroup.Uuid.ToHexString();
// TODO: find a correct place for this
public string FileAccessToken { get; set; }
public int Size { get; set; }
public bool IsDirty { get; set; }
// Settings
public bool IsRecycleBinEnabled
{
get { return _pwDatabase.RecycleBinEnabled; }
set { _pwDatabase.RecycleBinEnabled = value; }
}
public string RecycleBinId
{
get
{
return _pwDatabase.RecycleBinUuid.ToHexString();
}
set
{
_pwDatabase.RecycleBinUuid = BuildIdFromString(value);
_pwDatabase.RecycleBinChanged = _dateTime.Now;
}
}
public string CipherId
{
get { return _pwDatabase.DataCipherUuid.ToHexString(); }
set { _pwDatabase.DataCipherUuid = BuildIdFromString(value); }
}
public string KeyDerivationId
{
get { return _pwDatabase.KdfParameters.KdfUuid.ToHexString(); }
set
{
_pwDatabase.KdfParameters = KdfPool.Engines
.FirstOrDefault(e => e.Uuid.Equals(BuildIdFromString(value)))?.GetDefaultParameters();
}
}
public string Compression
{
get { return _pwDatabase.Compression.ToString("G"); }
set { _pwDatabase.Compression = (PwCompressionAlgorithm) Enum.Parse(typeof(PwCompressionAlgorithm), value); }
}
public KeePassDatabaseClient(IMapper mapper, IDateTime dateTime)
{
_mapper = mapper;
_dateTime = dateTime;
}
public async Task Open(byte[] file, Credentials credentials)
{
try
{
await Task.Run(() =>
{
var compositeKey = CreateCompositeKey(credentials);
var ioConnection = IOConnectionInfo.FromByteArray(file);
_pwDatabase.Open(ioConnection, compositeKey, new NullStatusLogger());
_credentials = credentials;
});
}
catch (InvalidCompositeKeyException ex)
{
throw new ArgumentException(ex.Message, ex);
}
}
public async Task ReOpen(byte[] file)
{
await Open(file, _credentials);
}
public async Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V4)
{
try
{
await Task.Run(() =>
{
var compositeKey = CreateCompositeKey(credentials);
var ioConnection = IOConnectionInfo.FromByteArray(new byte[] {});
_pwDatabase.New(ioConnection, compositeKey);
_pwDatabase.Name = name;
_pwDatabase.RootGroup.Name = name;
_credentials = credentials;
switch (version)
{
case DatabaseVersion.V4:
_pwDatabase.KdfParameters = KdfPool.Get("Argon2").GetDefaultParameters();
_pwDatabase.DataCipherUuid = CipherPool.GlobalPool[1].CipherUuid;
break;
}
});
}
catch (Exception ex)
{
throw new ArgumentException(ex.Message, ex);
}
}
public async Task<byte[]> SaveDatabase()
{
return await Task.Run(() =>
{
_pwDatabase.Save(new NullStatusLogger());
return _pwDatabase.IOConnectionInfo.Bytes;
});
}
public void CloseDatabase()
{
_pwDatabase?.Close();
}
public async Task AddEntry(string parentGroupId, string entryId)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
parentPwGroup.AddEntry(pwEntry, true);
});
}
public async Task MoveEntry(string parentGroupId, string entryId, int index)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
var currentIndex = (uint)parentPwGroup.Entries.IndexOf(pwEntry);
parentPwGroup.Entries.RemoveAt(currentIndex);
parentPwGroup.Entries.Insert((uint)index, pwEntry);
});
}
public async Task AddGroup(string parentGroupId, string groupId)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
parentPwGroup.AddGroup(pwGroup, true);
});
}
public async Task RemoveEntry(string parentGroupId, string entryId)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwEntry = parentPwGroup.FindEntry(BuildIdFromString(entryId), false);
parentPwGroup.Entries.Remove(pwEntry);
});
}
public async Task RemoveGroup(string parentGroupId, string groupId)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwGroup = parentPwGroup.FindGroup(BuildIdFromString(groupId), false);
parentPwGroup.Groups.Remove(pwGroup);
});
}
public void DeleteEntity(string entityId)
{
_pwDatabase.DeletedObjects.Add(new PwDeletedObject(BuildIdFromString(entityId), _dateTime.Now));
}
public void UpdateEntry(string entryId, string fieldName, object fieldValue)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
switch (fieldName)
{
case EntryFieldName.Title:
case EntryFieldName.UserName:
case EntryFieldName.Password:
case EntryFieldName.Notes:
case EntryFieldName.Url:
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue.ToString()));
break;
case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue;
break;
case EntryFieldName.ExpirationDate:
pwEntry.ExpiryTime = (DateTime)fieldValue;
break;
case EntryFieldName.Icon:
pwEntry.IconId = IconMapper.MapIconToPwIcon((Icon)fieldValue);
break;
case EntryFieldName.BackgroundColor:
pwEntry.BackgroundColor = (Color)fieldValue;
break;
case EntryFieldName.ForegroundColor:
pwEntry.ForegroundColor = (Color)fieldValue;
break;
}
}
public EntryEntity AddHistory(string entryId)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.Touch(true);
pwEntry.CreateBackup(null);
return _mapper.Map<EntryEntity>(pwEntry.History.Last());
}
public EntryEntity RestoreFromHistory(string entryId, int historyIndex)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.RestoreFromBackup((uint)historyIndex, _pwDatabase);
pwEntry.Touch(true);
return _mapper.Map<EntryEntity>(pwEntry);
}
public void DeleteHistory(string entryId, int historyIndex)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.History.RemoveAt((uint)historyIndex);
}
public void UpdateGroup(GroupEntity group)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(group.Id), true);
pwGroup.Name = group.Name;
pwGroup.IconId = IconMapper.MapIconToPwIcon(group.Icon);
pwGroup.Touch(true);
}
public EntryEntity CreateEntry(string parentGroupId)
{
var pwEntry = new PwEntry(true, true);
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
parentPwGroup.AddEntry(pwEntry, true);
return _mapper.Map<EntryEntity>(pwEntry);
}
public GroupEntity CreateGroup(string parentGroupId, string name, bool isRecycleBin = false)
{
var pwGroup = new PwGroup(true, true, name, isRecycleBin? PwIcon.TrashBin : PwIcon.Folder);
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
parentPwGroup.AddGroup(pwGroup, true);
if (isRecycleBin) _pwDatabase.RecycleBinUuid = pwGroup.Uuid;
return _mapper.Map<GroupEntity>(pwGroup);
}
public void SortEntries(string groupId)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
var comparer = new PwEntryComparer(PwDefs.TitleField, true, false);
pwGroup.Entries.Sort(comparer);
}
public void SortSubGroups(string groupId)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
pwGroup.SortSubGroups(false);
}
public EntryEntity GetEntry(string id)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(id), true);
return _mapper.Map<EntryEntity>(pwEntry);
}
public GroupEntity GetGroup(string id)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(id), true);
return _mapper.Map<GroupEntity>(pwGroup);
}
public void UpdateCredentials(Credentials credentials)
{
_pwDatabase.MasterKey = CreateCompositeKey(credentials);
_credentials = credentials;
}
public IEnumerable<EntryEntity> Search(string groupId, string text)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
var searchResults = new PwObjectList<PwEntry>();
pwGroup.SearchEntries(new SearchParameters
{
ComparisonMode = StringComparison.OrdinalIgnoreCase,
SearchInTitles = true,
//SearchInUserNames = true,
SearchString = text
}, searchResults);
return searchResults.Select(e => _mapper.Map<EntryEntity>(e));
}
public IEnumerable<BaseEntity> GetAllGroups(string groupId)
{
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
var groups = pwGroup.GetGroups(true).Select(g => new GroupEntity
{
Id = g.Uuid.ToHexString(),
Name = g.Name,
ParentName = g.ParentGroup?.Name
});
return groups;
}
private CompositeKey CreateCompositeKey(Credentials credentials)
{
var compositeKey = new CompositeKey();
if (credentials.Password != null) compositeKey.AddUserKey(new KcpPassword(credentials.Password));
if (credentials.KeyFileContents != null)
{
compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromByteArray(credentials.KeyFileContents)));
}
return compositeKey;
}
private PwUuid BuildIdFromString(string id)
{
return new PwUuid(MemUtil.HexStringToByteArray(id));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
_pwDatabase?.Close();
}
_disposed = true;
}
}
}

Some files were not shown because too many files have changed in this diff Show More