1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

...

122 Commits

Author SHA1 Message Date
Kyle Spearrin
7490ba3179 copy update for itunes store, remove "free" 2017-07-26 22:05:26 -04:00
Kyle Spearrin
45da12ad55 catch exception when cannot create temp cam file 2017-07-26 16:19:58 -04:00
Kyle Spearrin
75f99bf899 Merge branch 'master' of github.com:bitwarden/mobile 2017-07-26 14:46:06 -04:00
Kyle Spearrin
bd2b1cc166 New Crowdin translations (#106)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Russian)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations AppResources.resx (Swedish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations AppResources.resx (Japanese)

* New translations copy.resx (Italian)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Croatian)

* New translations copy.resx (Croatian)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (Czech)

* New translations copy.resx (Croatian)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Finnish)

* New translations copy.resx (Finnish)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations copy.resx (French)

* New translations AppResources.resx (French)

* New translations copy.resx (Finnish)

* New translations copy.resx (French)

* New translations copy.resx (French)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Chinese Simplified)
2017-07-26 14:44:46 -04:00
Kyle Spearrin
5fa8d64994 fix preview identity url 2017-07-26 10:48:04 -04:00
Kyle Spearrin
034957b556 version bump 2017-07-25 22:25:13 -04:00
Kyle Spearrin
a9d7c73b04 Revert "remove --nodevcodeshare"
This reverts commit b68a94c0e5.
2017-07-25 16:33:11 -04:00
Kyle Spearrin
b09fe05fe6 Revert "added forms reference to extension"
This reverts commit f239868299.
2017-07-25 16:32:52 -04:00
Kyle Spearrin
ed146549ef Revert "dialogs package installed to extension"
This reverts commit 83047558d5.
2017-07-25 16:32:40 -04:00
Kyle Spearrin
86c11db1a1 Revert "added push package to extension"
This reverts commit 5969e2d7ed.
2017-07-25 16:32:31 -04:00
Kyle Spearrin
580cd57433 Revert "added ffimage lib to extension"
This reverts commit de46c9ee36.
2017-07-25 16:32:23 -04:00
Kyle Spearrin
9854ce82bd Revert "zxing lib to extension"
This reverts commit f8bd7c2e64.
2017-07-25 16:32:15 -04:00
Kyle Spearrin
af11df97f5 Revert "update build package"
This reverts commit ef7fa5363a.
2017-07-25 16:32:07 -04:00
Kyle Spearrin
9b4e664908 Revert "--nodevcodeshare"
This reverts commit 567e1ee116.
2017-07-25 16:31:58 -04:00
Kyle Spearrin
567e1ee116 --nodevcodeshare 2017-07-25 12:30:20 -04:00
Kyle Spearrin
ef7fa5363a update build package 2017-07-25 11:16:47 -04:00
Kyle Spearrin
f8bd7c2e64 zxing lib to extension 2017-07-25 11:08:31 -04:00
Kyle Spearrin
de46c9ee36 added ffimage lib to extension 2017-07-25 11:01:19 -04:00
Kyle Spearrin
5969e2d7ed added push package to extension 2017-07-25 10:55:26 -04:00
Kyle Spearrin
83047558d5 dialogs package installed to extension 2017-07-25 10:48:07 -04:00
Kyle Spearrin
f239868299 added forms reference to extension 2017-07-25 10:40:15 -04:00
Kyle Spearrin
e4b962a3a6 update launch screen 2017-07-25 10:37:48 -04:00
Kyle Spearrin
b68a94c0e5 remove --nodevcodeshare 2017-07-25 10:37:39 -04:00
Kyle Spearrin
ec53ca8423 missing ioc registration 2017-07-25 09:52:07 -04:00
Kyle Spearrin
1ba0729e34 cleanup 2017-07-25 08:51:55 -04:00
Kyle Spearrin
73425c0052 debug check on screenshot protection 2017-07-24 15:04:31 -04:00
Kyle Spearrin
679859fb37 stop timer when page disappears. autofocus camera. 2017-07-24 12:33:07 -04:00
Kyle Spearrin
dbdc660464 properly init events when provider changes 2017-07-24 12:33:07 -04:00
Kyle Spearrin
aa22e7e952 New Crowdin translations (#105)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Russian)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations AppResources.resx (Swedish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations AppResources.resx (Japanese)

* New translations copy.resx (Italian)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Croatian)

* New translations copy.resx (Croatian)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (Czech)

* New translations copy.resx (Croatian)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Finnish)

* New translations copy.resx (Finnish)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations copy.resx (French)

* New translations AppResources.resx (French)

* New translations copy.resx (Finnish)

* New translations copy.resx (French)

* New translations copy.resx (French)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Chinese Simplified)
2017-07-24 10:55:52 -04:00
Kyle Spearrin
b920e7e95c attachment updates 2017-07-24 10:34:22 -04:00
Kyle Spearrin
d14b23ca82 null check for mac 2017-07-23 00:10:32 -04:00
Kyle Spearrin
4e8f69f692 paperclip icon for attachments in listing 2017-07-23 00:09:24 -04:00
Kyle Spearrin
c96cf2b0e5 update samsung browser url view id 2017-07-22 23:31:38 -04:00
Kyle Spearrin
4921cfb593 mac is optional 2017-07-22 23:22:21 -04:00
Kyle Spearrin
395545f7b1 Add support for camera for android choose file 2017-07-22 21:06:53 -04:00
Kyle Spearrin
f9d336a3a6 attachments page with upload/delete 2017-07-22 15:38:08 -04:00
Kyle Spearrin
b32603b472 premium required for attachment download 2017-07-21 17:21:04 -04:00
Kyle Spearrin
1124c48c8d copy totp code on autofill 2017-07-21 11:39:22 -04:00
Kyle Spearrin
98e429505c get file data from document picker 2017-07-15 10:08:19 -04:00
Kyle Spearrin
d0b616ba24 select file for document picker 2017-07-15 01:09:30 -04:00
Kyle Spearrin
dac4ffcb98 new languages 2017-07-15 01:09:30 -04:00
Kyle Spearrin
680310cf70 New Crowdin translations (#103)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Russian)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations AppResources.resx (Swedish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations AppResources.resx (Japanese)

* New translations copy.resx (Italian)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Croatian)

* New translations copy.resx (Croatian)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (Czech)

* New translations copy.resx (Croatian)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Finnish)

* New translations copy.resx (Finnish)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations copy.resx (French)

* New translations AppResources.resx (French)

* New translations copy.resx (Finnish)

* New translations copy.resx (French)

* New translations copy.resx (French)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Chinese Simplified)
2017-07-14 16:08:26 -04:00
Kyle Spearrin
67ff82810f min width on file size 2017-07-13 18:08:16 -04:00
Kyle Spearrin
87e71ea860 QR code scanning for authenticator keys 2017-07-13 17:23:18 -04:00
Kyle Spearrin
26c110291e totp code generation on view login 2017-07-13 14:44:02 -04:00
Kyle Spearrin
9879f074b4 decrypt with org id 2017-07-13 12:08:48 -04:00
Kyle Spearrin
65168c71c0 add/edit login totp key 2017-07-13 11:52:24 -04:00
Kyle Spearrin
4c4996ee2a dont add empty note section first 2017-07-13 11:16:00 -04:00
Kyle Spearrin
e0c67f87b0 only clear cache if it hasnt been done in a while 2017-07-13 11:11:04 -04:00
Kyle Spearrin
352c8ee867 clear cache and open file on iOS 2017-07-13 10:51:45 -04:00
Kyle Spearrin
fe5cc1f8f3 conditions around opening file 2017-07-13 09:01:00 -04:00
Kyle Spearrin
eec4be1845 label right detail cell for attachments 2017-07-13 00:02:37 -04:00
Kyle Spearrin
2f86b5c7b0 show indicator when downloading attachment 2017-07-12 23:45:05 -04:00
Kyle Spearrin
0d672c4f99 sync attachment removals 2017-07-12 23:36:27 -04:00
Kyle Spearrin
ac3fdbc2cd download, decrypt and open attachment files 2017-07-12 23:09:44 -04:00
Kyle Spearrin
0a7ad44d23 sync and display attachments on view login 2017-07-12 16:23:24 -04:00
Kyle Spearrin
18a86d3f12 model adjustments 2017-07-12 15:16:36 -04:00
Kyle Spearrin
665e66a9a6 prod url for duo connector 2017-06-29 15:03:48 -04:00
Kyle Spearrin
06dc4117c7 ios webview fix and language updates 2017-06-29 14:55:13 -04:00
Kyle Spearrin
2651afcef0 2fa corrections 2017-06-29 12:42:59 -04:00
Kyle Spearrin
ce4d828380 l10n for 2fa and dismiss keyboard message 2017-06-29 12:11:07 -04:00
Kyle Spearrin
74fba486bd two-factor other methods switching and send email 2017-06-29 11:22:06 -04:00
Kyle Spearrin
56075cb7d9 read yubikey and log in 2017-06-28 22:24:04 -04:00
Kyle Spearrin
d71bc775d5 hybrid web view and duo html/js 2017-06-28 13:10:47 -04:00
Kyle Spearrin
45c5801538 detect nfc enabled 2017-06-28 08:27:06 -04:00
Kyle Spearrin
cf41b524b0 read yubikey otp via nfc 2017-06-27 23:33:13 -04:00
Kyle Spearrin
e2a3e55a17 setup 2fa methods page 2017-06-27 17:10:40 -04:00
Kyle Spearrin
ae35bd2047 encode email for token service key 2017-06-27 16:51:16 -04:00
Kyle Spearrin
2f0ca6f7c0 user specific remember two factor 2017-06-27 16:45:12 -04:00
Kyle Spearrin
37428c01dd remeber two factor token 2017-06-27 16:35:29 -04:00
Kyle Spearrin
4116d95a3e refactors for new 2fa flows 2017-06-27 16:18:32 -04:00
Kyle Spearrin
35ae2b783f undo debugging work 2017-06-27 15:00:21 -04:00
Kyle Spearrin
8a24a6d192 Revert "generate facet id"
This reverts commit 19374a5df4.
2017-06-27 13:48:57 -04:00
Kyle Spearrin
19374a5df4 generate facet id 2017-06-27 12:53:20 -04:00
Kyle Spearrin
12da6fbd18 launch for main activity and catch exceptions 2017-06-23 23:21:39 -04:00
Kyle Spearrin
573ff15925 remove mail sends for crash reports 2017-06-22 21:54:57 -04:00
Kyle Spearrin
1b2abbe321 no param needed 2017-06-22 21:53:44 -04:00
Kyle Spearrin
4a03da6b96 fallback to old KeyStoreStorageService 2017-06-22 21:53:32 -04:00
Kyle Spearrin
cf3998942f save crash file to external storage instead 2017-06-22 15:33:37 -04:00
Kyle Spearrin
0c71f783fc make exceptiond available 2017-06-22 09:42:32 -04:00
Kyle Spearrin
d30b30b24f turn crash emails back on for testing 2017-06-22 09:39:02 -04:00
Kyle Spearrin
7823ec3fc8 hmac check on rsa decrypt 2017-06-19 11:57:37 -04:00
Kyle Spearrin
1e5883f028 clear settings via format as prefix 2017-06-12 13:06:46 -04:00
Kyle Spearrin
33c3cf4c4f just use SettingsFormat 2017-06-12 12:59:17 -04:00
Kyle Spearrin
f41ace4d7c clear settings for prefix when key is generated 2017-06-12 12:56:18 -04:00
Kyle Spearrin
65d2d45a82 manually set validity. no more crash emails 2017-06-12 11:51:43 -04:00
Kyle Spearrin
47ca483459 catch decrypt migrate exceptions 2017-06-12 10:45:57 -04:00
Kyle Spearrin
ee759af078 version bump 2017-06-10 22:44:35 -04:00
Kyle Spearrin
872037cf4d New Crowdin translations (#99)
* New translations copy.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Thai)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations AppResources.resx (Swedish)

* New translations copy.resx (Russian)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Slovak)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (French)

* New translations copy.resx (Finnish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Swedish)

* New translations copy.resx (French)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Finnish)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Italian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Finnish)

* New translations copy.resx (French)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations AppResources.resx (Chinese Simplified)
2017-06-10 22:42:53 -04:00
Kyle Spearrin
6aaa083157 use aes key in keystore on "new" android. migrate. 2017-06-10 22:18:34 -04:00
Kyle Spearrin
6a88524f8e rename to AndroidKeyStoreStorageService 2017-06-10 10:52:13 -04:00
Kyle Spearrin
82d93d2602 move variables in scope 2017-06-09 22:19:04 -04:00
Kyle Spearrin
d62037ef6a apparently manifest merge doesn't work in Xamarin 2017-06-09 21:49:20 -04:00
Kyle Spearrin
7314b5a339 manually remove contacts and account permissions 2017-06-09 16:18:21 -04:00
Kyle Spearrin
62bc230521 New Crowdin translations (#98)
* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Japanese)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Japanese)

* New translations copy.resx (Japanese)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (Japanese)

* New translations copy.resx (Hindi)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations copy.resx (Russian)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Czech)

* New translations copy.resx (Finnish)

* New translations copy.resx (Swedish)

* New translations copy.resx (French)

* New translations AppResources.resx (Italian)

* New translations copy.resx (French)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Finnish)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Finnish)

* New translations copy.resx (Italian)

* New translations copy.resx (French)

* New translations AppResources.resx (Chinese Simplified)
2017-06-09 10:02:55 -04:00
Kyle Spearrin
3e0d34d148 version bump. deprecate KeyStoreStorageService 2017-06-08 21:20:56 -04:00
Kyle Spearrin
aff1cc1cc3 fallback to "old" KeyStoreStorageService 2017-06-08 20:37:44 -04:00
Kyle Spearrin
6ddc7fa4cc version bump 2017-06-08 16:22:52 -04:00
Kyle Spearrin
957db1ec11 launch android app packages 2017-06-08 16:22:11 -04:00
Kyle Spearrin
ae806da3f1 ubdo debugging items 2017-06-08 15:57:07 -04:00
Kyle Spearrin
6a03c3e77d Do not show launch unless starts with HTTP 2017-06-08 15:43:01 -04:00
Kyle Spearrin
72b18eadf3 do not implement UnhandledExceptionRaiser 2017-06-08 15:13:58 -04:00
Kyle Spearrin
67aa583709 disable screenshot blocking 2017-06-08 14:33:52 -04:00
Kyle Spearrin
21f3755e44 version bump. all unhandled crash report email 2017-06-08 12:44:16 -04:00
Kyle Spearrin
c9b6df846e version bump 2017-06-08 11:52:54 -04:00
Kyle Spearrin
7e23a8169f make crash email util 2017-06-08 11:52:29 -04:00
Kyle Spearrin
b139eadf0b KeyStoreBackedStorageService email crash reports 2017-06-08 11:43:26 -04:00
Kyle Spearrin
71ad648331 version bump 2017-06-07 22:07:25 -04:00
Kyle Spearrin
b8c7752356 oaep spec only for "new android" 2017-06-07 21:44:53 -04:00
Kyle Spearrin
b157f2085f android:allowBackup false 2017-06-07 15:52:40 -04:00
Kyle Spearrin
2fda7b8011 safety checks for popping modals 2017-06-07 10:19:56 -04:00
Kyle Spearrin
5b24d19630 remove unnecessary prop setting from gen spec 2017-06-07 00:43:46 -04:00
Kyle Spearrin
76652f6c6b KeyGenParameterSpec options added back. cleanup. 2017-06-07 00:10:31 -04:00
Kyle Spearrin
724ae51110 RSA/ECB/OAEPWithSHA-1AndMGF1Padding 2017-06-06 23:52:52 -04:00
Kyle Spearrin
1503124108 OAEPParameterSpec and provider specified 2017-06-06 23:27:57 -04:00
Kyle Spearrin
007125a071 include crypto providers with crash reprot email 2017-06-06 23:09:19 -04:00
Kyle Spearrin
b5f5b0b4aa sha1 digest 2017-06-06 22:53:14 -04:00
Kyle Spearrin
cbda59e547 switch to default oaep padding 2017-06-06 22:50:20 -04:00
Kyle Spearrin
a885e16049 email crash report for key store service 2017-06-06 22:04:54 -04:00
Kyle Spearrin
07eabad18d throw exceptions for testing 2017-06-06 08:10:07 -04:00
Kyle Spearrin
cf079a159f cleanup rsa encryption 2017-06-05 22:25:59 -04:00
Kyle Spearrin
93176989fd centralized crypto utils. keystore with rsa. 2017-06-05 21:04:19 -04:00
196 changed files with 11698 additions and 1722 deletions

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
# Visual Studio 15
VisualStudioVersion = 15.0.26430.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
EndProject

View File

@@ -116,6 +116,7 @@
<Private>True</Private>
</Reference>
<Reference Include="Mono.Android" />
<Reference Include="Mono.Android.Export" />
<Reference Include="mscorlib" />
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.9.0.1\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
@@ -296,12 +297,28 @@
<Reference Include="XLabs.Ioc.SimpleInjector, Version=2.0.5782.12229, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\XLabs.IoC.SimpleInjector.2.0.5782\lib\portable-net45+netcore45+wp8+MonoAndroid1+MonoTouch1\XLabs.Ioc.SimpleInjector.dll</HintPath>
</Reference>
<Reference Include="ZXing.Net.Mobile.Core, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Core.dll</HintPath>
</Reference>
<Reference Include="ZXing.Net.Mobile.Forms, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.dll</HintPath>
</Reference>
<Reference Include="ZXing.Net.Mobile.Forms.Android, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\MonoAndroid403\ZXing.Net.Mobile.Forms.Android.dll</HintPath>
</Reference>
<Reference Include="zxing.portable, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\zxing.portable.dll</HintPath>
</Reference>
<Reference Include="ZXingNetMobile, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\MonoAndroid403\ZXingNetMobile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AutofillActivity.cs" />
<Compile Include="AutofillCredentials.cs" />
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
<Compile Include="Controls\CustomButtonRenderer.cs" />
<Compile Include="Controls\HybridWebViewRenderer.cs" />
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
@@ -314,14 +331,14 @@
<Compile Include="Controls\ExtendedPickerRenderer.cs" />
<Compile Include="Controls\ExtendedEntryRenderer.cs" />
<Compile Include="Services\HttpService.cs" />
<Compile Include="Services\KeyStoreBackedStorageService.cs" />
<Compile Include="Services\AndroidKeyStoreStorageService.cs" />
<Compile Include="Services\LocalizeService.cs" />
<Compile Include="MainApplication.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Services\DeviceInfoService.cs" />
<Compile Include="Services\GoogleAnalyticsService.cs" />
<Compile Include="Services\AppInfoService.cs" />
<Compile Include="Services\ClipboardService.cs" />
<Compile Include="Services\DeviceActionService.cs" />
<Compile Include="Services\BouncyCastleKeyDerivationService.cs" />
<Compile Include="Services\KeyStoreStorageService.cs" />
<Compile Include="MainActivity.cs" />
@@ -331,6 +348,7 @@
<Compile Include="Services\ReflectionService.cs" />
<Compile Include="Services\SqlService.cs" />
<Compile Include="SplashActivity.cs" />
<Compile Include="Utilities.cs" />
</ItemGroup>
<ItemGroup>
<None Include="8bit.keystore.enc" />
@@ -857,6 +875,81 @@
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\share_tools.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\filepaths.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\download.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\download.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\download.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\download.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\download.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\camera.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\camera.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\camera.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\camera.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\camera.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\paperclip.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\paperclip.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\paperclip.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\paperclip.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\paperclip.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\trash.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\trash.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\trash.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\trash.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\trash.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@@ -36,7 +36,7 @@ namespace Bit.Android
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
new Browser("org.codeaurora.swe.browser", "url_bar"),
new Browser("org.iron.srware", "url_bar"),
new Browser("com.sec.android.app.sbrowser", "sbrowser_url_bar"),
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
(s) => s.Split(' ').FirstOrDefault()),
@@ -75,11 +75,9 @@ namespace Bit.Android
return;
}
/*
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
testNodes.Dispose();
*/
//var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
//var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
//testNodes.Dispose();
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
var cancelNotification = true;

View File

@@ -0,0 +1,72 @@
using System;
using Bit.Android.Controls;
using Bit.App.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Android.Webkit;
using AWebkit = Android.Webkit;
using Java.Interop;
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
namespace Bit.Android.Controls
{
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
{
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if(Control == null)
{
var webView = new AWebkit.WebView(Forms.Context);
webView.Settings.JavaScriptEnabled = true;
SetNativeControl(webView);
}
if(e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if(e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
InjectJS(JSFunction);
}
}
private void InjectJS(string script)
{
if(Control != null)
{
Control.LoadUrl(string.Format("javascript: {0}", script));
}
}
public class JSBridge : Java.Lang.Object
{
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
public JSBridge(HybridWebViewRenderer hybridRenderer)
{
_hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
}
[JavascriptInterface]
[Export("invokeAction")]
public void InvokeAction(string data)
{
HybridWebViewRenderer hybridRenderer;
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
{
hybridRenderer.Element.InvokeAction(data);
}
}
}
}
}

View File

@@ -14,6 +14,11 @@ using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using System.Threading.Tasks;
using Bit.App.Models.Page;
using Bit.App;
using Android.Nfc;
using Android.Views.InputMethods;
using System.IO;
using System.Linq;
namespace Bit.Android
{
@@ -24,6 +29,10 @@ namespace Bit.Android
public class MainActivity : FormsAppCompatActivity
{
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
private DateTime? _lastAction;
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
private IDeviceActionService _deviceActionService;
private ISettings _settings;
protected override void OnCreate(Bundle bundle)
{
@@ -47,7 +56,10 @@ namespace Bit.Android
Console.WriteLine("A OnCreate");
Window.SetSoftInputMode(SoftInput.StateHidden);
Window.AddFlags(WindowManagerFlags.Secure);
if(!App.Utilities.Helpers.InDebugMode())
{
Window.AddFlags(WindowManagerFlags.Secure);
}
var appIdService = Resolver.Resolve<IAppIdService>();
var authService = Resolver.Resolve<IAuthService>();
@@ -60,6 +72,8 @@ namespace Bit.Android
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
.SetValue(null, Color.FromHex("d2d6de"));
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
_settings = Resolver.Resolve<ISettings>();
LoadApplication(new App.App(
uri,
Resolver.Resolve<IAuthService>(),
@@ -67,12 +81,19 @@ namespace Bit.Android
Resolver.Resolve<IUserDialogs>(),
Resolver.Resolve<IDatabaseService>(),
Resolver.Resolve<ISyncService>(),
Resolver.Resolve<ISettings>(),
_settings,
Resolver.Resolve<ILockService>(),
Resolver.Resolve<IGoogleAnalyticsService>(),
Resolver.Resolve<ILocalizeService>(),
Resolver.Resolve<IAppInfoService>(),
Resolver.Resolve<IAppSettingsService>()));
Resolver.Resolve<IAppSettingsService>(),
_deviceActionService));
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
Xamarin.Forms.Application.Current, "DismissKeyboard", (sender) =>
{
DismissKeyboard();
});
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
{
@@ -94,6 +115,18 @@ namespace Bit.Android
{
MoveTaskToBack(true);
});
MessagingCenter.Subscribe<Xamarin.Forms.Application, string>(
Xamarin.Forms.Application.Current, "LaunchApp", (sender, args) =>
{
LaunchApp(args);
});
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) =>
{
ListenYubiKey(listen);
});
}
private void ReturnCredentials(VaultListPageModel.Login login)
@@ -105,6 +138,13 @@ namespace Bit.Android
}
else
{
var isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false);
if(isPremium && autoCopyEnabled && _deviceActionService != null && login.Totp.Value != null)
{
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(login.Totp.Value));
}
data.PutExtra("uri", login.Uri.Value);
data.PutExtra("username", login.Username);
data.PutExtra("password", login.Password.Value);
@@ -118,7 +158,7 @@ namespace Bit.Android
{
Parent.SetResult(Result.Ok, data);
}
Finish();
}
@@ -126,6 +166,7 @@ namespace Bit.Android
{
Console.WriteLine("A OnPause");
base.OnPause();
ListenYubiKey(false);
}
protected override void OnDestroy()
@@ -160,6 +201,68 @@ namespace Bit.Android
// workaround for app compat bug
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
Task.Delay(10).Wait();
if(Utilities.NfcEnabled())
{
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
}
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
Console.WriteLine("A OnNewIntent");
ParseYubiKey(intent.DataString);
}
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
if(requestCode == Constants.SelectFilePermissionRequestCode)
{
if(grantResults.Any(r => r != Permission.Granted))
{
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
}
await _deviceActionService.SelectFileAsync();
return;
}
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
{
global::Android.Net.Uri uri = null;
string fileName = null;
if(data != null && data.Data != null)
{
uri = data.Data;
fileName = Utilities.GetFileName(ApplicationContext, uri);
}
else
{
// camera
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
uri = global::Android.Net.Uri.FromFile(file);
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
}
if(uri == null)
{
return;
}
using(var stream = ContentResolver.OpenInputStream(uri))
using(var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
}
}
}
public void RateApp()
@@ -199,5 +302,80 @@ namespace Bit.Android
var intent = new Intent(global::Android.Provider.Settings.ActionAccessibilitySettings);
StartActivity(intent);
}
private void LaunchApp(string packageName)
{
if(_lastAction.LastActionWasRecent())
{
return;
}
_lastAction = DateTime.UtcNow;
packageName = packageName.Replace("androidapp://", string.Empty);
var launchIntent = PackageManager.GetLaunchIntentForPackage(packageName);
if(launchIntent == null)
{
var dialog = Resolver.Resolve<IUserDialogs>();
dialog.Alert(string.Format(App.Resources.AppResources.CannotOpenApp, packageName));
}
else
{
StartActivity(launchIntent);
}
}
private void ListenYubiKey(bool listen)
{
if(!Utilities.NfcEnabled())
{
return;
}
var adapter = NfcAdapter.GetDefaultAdapter(this);
if(listen)
{
var intent = new Intent(this, Class);
intent.AddFlags(ActivityFlags.SingleTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
// register for all NDEF tags starting with http och https
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
ndef.AddDataScheme("http");
ndef.AddDataScheme("https");
var filters = new IntentFilter[] { ndef };
// register for foreground dispatch so we'll receive tags according to our intent filters
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
}
else
{
adapter.DisableForegroundDispatch(this);
}
}
private void ParseYubiKey(string data)
{
if(data == null)
{
return;
}
var otpMatch = _otpPattern.Matcher(data);
if(otpMatch.Matches())
{
var otp = otpMatch.Group(1);
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
}
}
private void DismissKeyboard()
{
try
{
var imm = (InputMethodManager)GetSystemService(InputMethodService);
imm.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0);
}
catch { }
}
}
}

View File

@@ -38,12 +38,21 @@ namespace Bit.Android
public MainApplication(IntPtr handle, JniHandleOwnership transer)
: base(handle, transer)
{
//AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
if(!Resolver.IsSet)
{
SetIoc(this);
}
}
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
{
var message = Utilities.AppendExceptionToMessage("", e.Exception);
//Utilities.SaveCrashFile(message, true);
Utilities.SendCrashEmail(message, false);
}
public override void OnCreate()
{
base.OnCreate();
@@ -84,7 +93,7 @@ namespace Bit.Android
}
// 3. In debug mode
if(InDebugMode())
if(App.Utilities.Helpers.InDebugMode())
{
reregister = true;
}
@@ -105,15 +114,6 @@ namespace Bit.Android
}
}
private bool InDebugMode()
{
#if DEBUG
return true;
#else
return false;
#endif
}
public override void OnTerminate()
{
base.OnTerminate();
@@ -179,6 +179,7 @@ namespace Bit.Android
{
UserDialogs.Init(application);
CachedImageRenderer.Init();
ZXing.Net.Mobile.Forms.Android.Platform.Init();
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
//var container = new UnityContainer();
@@ -191,14 +192,14 @@ namespace Bit.Android
// Services
container.RegisterSingleton<IDatabaseService, DatabaseService>();
container.RegisterSingleton<ISqlService, SqlService>();
container.RegisterSingleton<ISecureStorageService, KeyStoreBackedStorageService>();
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
container.RegisterSingleton<ICryptoService, CryptoService>();
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
container.RegisterSingleton<IAuthService, AuthService>();
container.RegisterSingleton<IFolderService, FolderService>();
container.RegisterSingleton<ILoginService, LoginService>();
container.RegisterSingleton<ISyncService, SyncService>();
container.RegisterSingleton<IClipboardService, ClipboardService>();
container.RegisterSingleton<IDeviceActionService, DeviceActionService>();
container.RegisterSingleton<IAppIdService, AppIdService>();
container.RegisterSingleton<IPasswordGenerationService, PasswordGenerationService>();
container.RegisterSingleton<IReflectionService, ReflectionService>();
@@ -218,6 +219,7 @@ namespace Bit.Android
container.RegisterSingleton<IFolderRepository, FolderRepository>();
container.RegisterSingleton<IFolderApiRepository, FolderApiRepository>();
container.RegisterSingleton<ILoginRepository, LoginRepository>();
container.RegisterSingleton<IAttachmentRepository, AttachmentRepository>();
container.RegisterSingleton<ILoginApiRepository, LoginApiRepository>();
container.RegisterSingleton<IConnectApiRepository, ConnectApiRepository>();
container.RegisterSingleton<IDeviceApiRepository, DeviceApiRepository>();
@@ -225,6 +227,7 @@ namespace Bit.Android
container.RegisterSingleton<ICipherApiRepository, CipherApiRepository>();
container.RegisterSingleton<ISettingsRepository, SettingsRepository>();
container.RegisterSingleton<ISettingsApiRepository, SettingsApiRepository>();
container.RegisterSingleton<ITwoFactorApiRepository, TwoFactorApiRepository>();
// Other
container.RegisterSingleton(CrossSettings.Current);

View File

@@ -1,12 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.6.0" android:installLocation="auto" android:versionCode="502">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
<application android:label="bitwarden" android:theme="@style/BitwardenTheme"></application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.8.0" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
<application android:label="bitwarden" android:theme="@style/BitwardenTheme" android:allowBackup="false">
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.x8bit.bitwarden.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>

View File

@@ -28,7 +28,3 @@ using Android.App;
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// Add some common permissions, these can be removed if not needed
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

View File

@@ -193,6 +193,12 @@ namespace Bit.Android
global::Plugin.Fingerprint.Resource.Layout.FingerprintDialog = global::Bit.Android.Resource.Layout.FingerprintDialog;
global::Splat.Resource.String.library_name = global::Bit.Android.Resource.String.library_name;
global::Xamarin.Forms.Platform.Android.Resource.Attribute.actionBarSize = global::Bit.Android.Resource.Attribute.actionBarSize;
global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscanneractivitylayout = global::Bit.Android.Resource.Layout.zxingscanneractivitylayout;
global::ZXing.Net.Mobile.Forms.Android.Resource.Layout.zxingscannerfragmentlayout = global::Bit.Android.Resource.Layout.zxingscannerfragmentlayout;
global::ZXing.Net.Mobile.Forms.Android.Resource.String.library_name = global::Bit.Android.Resource.String.library_name;
global::ZXing.Mobile.Resource.Id.contentFrame = global::Bit.Android.Resource.Id.contentFrame;
global::ZXing.Mobile.Resource.Layout.zxingscanneractivitylayout = global::Bit.Android.Resource.Layout.zxingscanneractivitylayout;
global::ZXing.Mobile.Resource.Layout.zxingscannerfragmentlayout = global::Bit.Android.Resource.Layout.zxingscannerfragmentlayout;
}
public partial class Animation
@@ -2295,499 +2301,514 @@ namespace Bit.Android
public const int accessibility_step2 = 2130837582;
// aapt resource value: 0x7f02004f
public const int cloudup = 2130837583;
public const int camera = 2130837583;
// aapt resource value: 0x7f020050
public const int cogs = 2130837584;
public const int cloudup = 2130837584;
// aapt resource value: 0x7f020051
public const int cogs_selected = 2130837585;
public const int cogs = 2130837585;
// aapt resource value: 0x7f020052
public const int common_full_open_on_phone = 2130837586;
public const int cogs_selected = 2130837586;
// aapt resource value: 0x7f020053
public const int common_google_signin_btn_icon_dark = 2130837587;
public const int common_full_open_on_phone = 2130837587;
// aapt resource value: 0x7f020054
public const int common_google_signin_btn_icon_dark_disabled = 2130837588;
public const int common_google_signin_btn_icon_dark = 2130837588;
// aapt resource value: 0x7f020055
public const int common_google_signin_btn_icon_dark_focused = 2130837589;
public const int common_google_signin_btn_icon_dark_disabled = 2130837589;
// aapt resource value: 0x7f020056
public const int common_google_signin_btn_icon_dark_normal = 2130837590;
public const int common_google_signin_btn_icon_dark_focused = 2130837590;
// aapt resource value: 0x7f020057
public const int common_google_signin_btn_icon_dark_pressed = 2130837591;
public const int common_google_signin_btn_icon_dark_normal = 2130837591;
// aapt resource value: 0x7f020058
public const int common_google_signin_btn_icon_light = 2130837592;
public const int common_google_signin_btn_icon_dark_pressed = 2130837592;
// aapt resource value: 0x7f020059
public const int common_google_signin_btn_icon_light_disabled = 2130837593;
public const int common_google_signin_btn_icon_light = 2130837593;
// aapt resource value: 0x7f02005a
public const int common_google_signin_btn_icon_light_focused = 2130837594;
public const int common_google_signin_btn_icon_light_disabled = 2130837594;
// aapt resource value: 0x7f02005b
public const int common_google_signin_btn_icon_light_normal = 2130837595;
public const int common_google_signin_btn_icon_light_focused = 2130837595;
// aapt resource value: 0x7f02005c
public const int common_google_signin_btn_icon_light_pressed = 2130837596;
public const int common_google_signin_btn_icon_light_normal = 2130837596;
// aapt resource value: 0x7f02005d
public const int common_google_signin_btn_text_dark = 2130837597;
public const int common_google_signin_btn_icon_light_pressed = 2130837597;
// aapt resource value: 0x7f02005e
public const int common_google_signin_btn_text_dark_disabled = 2130837598;
public const int common_google_signin_btn_text_dark = 2130837598;
// aapt resource value: 0x7f02005f
public const int common_google_signin_btn_text_dark_focused = 2130837599;
public const int common_google_signin_btn_text_dark_disabled = 2130837599;
// aapt resource value: 0x7f020060
public const int common_google_signin_btn_text_dark_normal = 2130837600;
public const int common_google_signin_btn_text_dark_focused = 2130837600;
// aapt resource value: 0x7f020061
public const int common_google_signin_btn_text_dark_pressed = 2130837601;
public const int common_google_signin_btn_text_dark_normal = 2130837601;
// aapt resource value: 0x7f020062
public const int common_google_signin_btn_text_light = 2130837602;
public const int common_google_signin_btn_text_dark_pressed = 2130837602;
// aapt resource value: 0x7f020063
public const int common_google_signin_btn_text_light_disabled = 2130837603;
public const int common_google_signin_btn_text_light = 2130837603;
// aapt resource value: 0x7f020064
public const int common_google_signin_btn_text_light_focused = 2130837604;
public const int common_google_signin_btn_text_light_disabled = 2130837604;
// aapt resource value: 0x7f020065
public const int common_google_signin_btn_text_light_normal = 2130837605;
public const int common_google_signin_btn_text_light_focused = 2130837605;
// aapt resource value: 0x7f020066
public const int common_google_signin_btn_text_light_pressed = 2130837606;
public const int common_google_signin_btn_text_light_normal = 2130837606;
// aapt resource value: 0x7f020067
public const int common_ic_googleplayservices = 2130837607;
public const int common_google_signin_btn_text_light_pressed = 2130837607;
// aapt resource value: 0x7f020068
public const int common_plus_signin_btn_icon_dark = 2130837608;
public const int common_ic_googleplayservices = 2130837608;
// aapt resource value: 0x7f020069
public const int common_plus_signin_btn_icon_dark_disabled = 2130837609;
public const int common_plus_signin_btn_icon_dark = 2130837609;
// aapt resource value: 0x7f02006a
public const int common_plus_signin_btn_icon_dark_focused = 2130837610;
public const int common_plus_signin_btn_icon_dark_disabled = 2130837610;
// aapt resource value: 0x7f02006b
public const int common_plus_signin_btn_icon_dark_normal = 2130837611;
public const int common_plus_signin_btn_icon_dark_focused = 2130837611;
// aapt resource value: 0x7f02006c
public const int common_plus_signin_btn_icon_dark_pressed = 2130837612;
public const int common_plus_signin_btn_icon_dark_normal = 2130837612;
// aapt resource value: 0x7f02006d
public const int common_plus_signin_btn_icon_light = 2130837613;
public const int common_plus_signin_btn_icon_dark_pressed = 2130837613;
// aapt resource value: 0x7f02006e
public const int common_plus_signin_btn_icon_light_disabled = 2130837614;
public const int common_plus_signin_btn_icon_light = 2130837614;
// aapt resource value: 0x7f02006f
public const int common_plus_signin_btn_icon_light_focused = 2130837615;
public const int common_plus_signin_btn_icon_light_disabled = 2130837615;
// aapt resource value: 0x7f020070
public const int common_plus_signin_btn_icon_light_normal = 2130837616;
public const int common_plus_signin_btn_icon_light_focused = 2130837616;
// aapt resource value: 0x7f020071
public const int common_plus_signin_btn_icon_light_pressed = 2130837617;
public const int common_plus_signin_btn_icon_light_normal = 2130837617;
// aapt resource value: 0x7f020072
public const int common_plus_signin_btn_text_dark = 2130837618;
public const int common_plus_signin_btn_icon_light_pressed = 2130837618;
// aapt resource value: 0x7f020073
public const int common_plus_signin_btn_text_dark_disabled = 2130837619;
public const int common_plus_signin_btn_text_dark = 2130837619;
// aapt resource value: 0x7f020074
public const int common_plus_signin_btn_text_dark_focused = 2130837620;
public const int common_plus_signin_btn_text_dark_disabled = 2130837620;
// aapt resource value: 0x7f020075
public const int common_plus_signin_btn_text_dark_normal = 2130837621;
public const int common_plus_signin_btn_text_dark_focused = 2130837621;
// aapt resource value: 0x7f020076
public const int common_plus_signin_btn_text_dark_pressed = 2130837622;
public const int common_plus_signin_btn_text_dark_normal = 2130837622;
// aapt resource value: 0x7f020077
public const int common_plus_signin_btn_text_light = 2130837623;
public const int common_plus_signin_btn_text_dark_pressed = 2130837623;
// aapt resource value: 0x7f020078
public const int common_plus_signin_btn_text_light_disabled = 2130837624;
public const int common_plus_signin_btn_text_light = 2130837624;
// aapt resource value: 0x7f020079
public const int common_plus_signin_btn_text_light_focused = 2130837625;
public const int common_plus_signin_btn_text_light_disabled = 2130837625;
// aapt resource value: 0x7f02007a
public const int common_plus_signin_btn_text_light_normal = 2130837626;
public const int common_plus_signin_btn_text_light_focused = 2130837626;
// aapt resource value: 0x7f02007b
public const int common_plus_signin_btn_text_light_pressed = 2130837627;
public const int common_plus_signin_btn_text_light_normal = 2130837627;
// aapt resource value: 0x7f02007c
public const int design_fab_background = 2130837628;
public const int common_plus_signin_btn_text_light_pressed = 2130837628;
// aapt resource value: 0x7f02007d
public const int design_snackbar_background = 2130837629;
public const int design_fab_background = 2130837629;
// aapt resource value: 0x7f02007e
public const int envelope = 2130837630;
public const int design_snackbar_background = 2130837630;
// aapt resource value: 0x7f02007f
public const int eye = 2130837631;
public const int download = 2130837631;
// aapt resource value: 0x7f020080
public const int eye_slash = 2130837632;
public const int envelope = 2130837632;
// aapt resource value: 0x7f020081
public const int fa_lock = 2130837633;
public const int eye = 2130837633;
// aapt resource value: 0x7f020082
public const int fa_lock_selected = 2130837634;
public const int eye_slash = 2130837634;
// aapt resource value: 0x7f020083
public const int fingerprint = 2130837635;
public const int fa_lock = 2130837635;
// aapt resource value: 0x7f020084
public const int fingerprint_white = 2130837636;
public const int fa_lock_selected = 2130837636;
// aapt resource value: 0x7f020085
public const int folder = 2130837637;
public const int fingerprint = 2130837637;
// aapt resource value: 0x7f020086
public const int globe = 2130837638;
public const int fingerprint_white = 2130837638;
// aapt resource value: 0x7f020087
public const int hockeyapp_btn_background = 2130837639;
public const int folder = 2130837639;
// aapt resource value: 0x7f020088
public const int ic_audiotrack = 2130837640;
public const int globe = 2130837640;
// aapt resource value: 0x7f020089
public const int ic_audiotrack_light = 2130837641;
public const int hockeyapp_btn_background = 2130837641;
// aapt resource value: 0x7f02008a
public const int ic_bluetooth_grey = 2130837642;
public const int ic_audiotrack = 2130837642;
// aapt resource value: 0x7f02008b
public const int ic_bluetooth_white = 2130837643;
public const int ic_audiotrack_light = 2130837643;
// aapt resource value: 0x7f02008c
public const int ic_cast_dark = 2130837644;
public const int ic_bluetooth_grey = 2130837644;
// aapt resource value: 0x7f02008d
public const int ic_cast_disabled_light = 2130837645;
public const int ic_bluetooth_white = 2130837645;
// aapt resource value: 0x7f02008e
public const int ic_cast_grey = 2130837646;
public const int ic_cast_dark = 2130837646;
// aapt resource value: 0x7f02008f
public const int ic_cast_light = 2130837647;
public const int ic_cast_disabled_light = 2130837647;
// aapt resource value: 0x7f020090
public const int ic_cast_off_light = 2130837648;
public const int ic_cast_grey = 2130837648;
// aapt resource value: 0x7f020091
public const int ic_cast_on_0_light = 2130837649;
public const int ic_cast_light = 2130837649;
// aapt resource value: 0x7f020092
public const int ic_cast_on_1_light = 2130837650;
public const int ic_cast_off_light = 2130837650;
// aapt resource value: 0x7f020093
public const int ic_cast_on_2_light = 2130837651;
public const int ic_cast_on_0_light = 2130837651;
// aapt resource value: 0x7f020094
public const int ic_cast_on_light = 2130837652;
public const int ic_cast_on_1_light = 2130837652;
// aapt resource value: 0x7f020095
public const int ic_cast_white = 2130837653;
public const int ic_cast_on_2_light = 2130837653;
// aapt resource value: 0x7f020096
public const int ic_close_dark = 2130837654;
public const int ic_cast_on_light = 2130837654;
// aapt resource value: 0x7f020097
public const int ic_close_light = 2130837655;
public const int ic_cast_white = 2130837655;
// aapt resource value: 0x7f020098
public const int ic_collapse = 2130837656;
public const int ic_close_dark = 2130837656;
// aapt resource value: 0x7f020099
public const int ic_collapse_00000 = 2130837657;
public const int ic_close_light = 2130837657;
// aapt resource value: 0x7f02009a
public const int ic_collapse_00001 = 2130837658;
public const int ic_collapse = 2130837658;
// aapt resource value: 0x7f02009b
public const int ic_collapse_00002 = 2130837659;
public const int ic_collapse_00000 = 2130837659;
// aapt resource value: 0x7f02009c
public const int ic_collapse_00003 = 2130837660;
public const int ic_collapse_00001 = 2130837660;
// aapt resource value: 0x7f02009d
public const int ic_collapse_00004 = 2130837661;
public const int ic_collapse_00002 = 2130837661;
// aapt resource value: 0x7f02009e
public const int ic_collapse_00005 = 2130837662;
public const int ic_collapse_00003 = 2130837662;
// aapt resource value: 0x7f02009f
public const int ic_collapse_00006 = 2130837663;
public const int ic_collapse_00004 = 2130837663;
// aapt resource value: 0x7f0200a0
public const int ic_collapse_00007 = 2130837664;
public const int ic_collapse_00005 = 2130837664;
// aapt resource value: 0x7f0200a1
public const int ic_collapse_00008 = 2130837665;
public const int ic_collapse_00006 = 2130837665;
// aapt resource value: 0x7f0200a2
public const int ic_collapse_00009 = 2130837666;
public const int ic_collapse_00007 = 2130837666;
// aapt resource value: 0x7f0200a3
public const int ic_collapse_00010 = 2130837667;
public const int ic_collapse_00008 = 2130837667;
// aapt resource value: 0x7f0200a4
public const int ic_collapse_00011 = 2130837668;
public const int ic_collapse_00009 = 2130837668;
// aapt resource value: 0x7f0200a5
public const int ic_collapse_00012 = 2130837669;
public const int ic_collapse_00010 = 2130837669;
// aapt resource value: 0x7f0200a6
public const int ic_collapse_00013 = 2130837670;
public const int ic_collapse_00011 = 2130837670;
// aapt resource value: 0x7f0200a7
public const int ic_collapse_00014 = 2130837671;
public const int ic_collapse_00012 = 2130837671;
// aapt resource value: 0x7f0200a8
public const int ic_collapse_00015 = 2130837672;
public const int ic_collapse_00013 = 2130837672;
// aapt resource value: 0x7f0200a9
public const int ic_errorstatus = 2130837673;
public const int ic_collapse_00014 = 2130837673;
// aapt resource value: 0x7f0200aa
public const int ic_expand = 2130837674;
public const int ic_collapse_00015 = 2130837674;
// aapt resource value: 0x7f0200ab
public const int ic_expand_00000 = 2130837675;
public const int ic_errorstatus = 2130837675;
// aapt resource value: 0x7f0200ac
public const int ic_expand_00001 = 2130837676;
public const int ic_expand = 2130837676;
// aapt resource value: 0x7f0200ad
public const int ic_expand_00002 = 2130837677;
public const int ic_expand_00000 = 2130837677;
// aapt resource value: 0x7f0200ae
public const int ic_expand_00003 = 2130837678;
public const int ic_expand_00001 = 2130837678;
// aapt resource value: 0x7f0200af
public const int ic_expand_00004 = 2130837679;
public const int ic_expand_00002 = 2130837679;
// aapt resource value: 0x7f0200b0
public const int ic_expand_00005 = 2130837680;
public const int ic_expand_00003 = 2130837680;
// aapt resource value: 0x7f0200b1
public const int ic_expand_00006 = 2130837681;
public const int ic_expand_00004 = 2130837681;
// aapt resource value: 0x7f0200b2
public const int ic_expand_00007 = 2130837682;
public const int ic_expand_00005 = 2130837682;
// aapt resource value: 0x7f0200b3
public const int ic_expand_00008 = 2130837683;
public const int ic_expand_00006 = 2130837683;
// aapt resource value: 0x7f0200b4
public const int ic_expand_00009 = 2130837684;
public const int ic_expand_00007 = 2130837684;
// aapt resource value: 0x7f0200b5
public const int ic_expand_00010 = 2130837685;
public const int ic_expand_00008 = 2130837685;
// aapt resource value: 0x7f0200b6
public const int ic_expand_00011 = 2130837686;
public const int ic_expand_00009 = 2130837686;
// aapt resource value: 0x7f0200b7
public const int ic_expand_00012 = 2130837687;
public const int ic_expand_00010 = 2130837687;
// aapt resource value: 0x7f0200b8
public const int ic_expand_00013 = 2130837688;
public const int ic_expand_00011 = 2130837688;
// aapt resource value: 0x7f0200b9
public const int ic_expand_00014 = 2130837689;
public const int ic_expand_00012 = 2130837689;
// aapt resource value: 0x7f0200ba
public const int ic_expand_00015 = 2130837690;
public const int ic_expand_00013 = 2130837690;
// aapt resource value: 0x7f0200bb
public const int ic_media_pause = 2130837691;
public const int ic_expand_00014 = 2130837691;
// aapt resource value: 0x7f0200bc
public const int ic_media_play = 2130837692;
public const int ic_expand_00015 = 2130837692;
// aapt resource value: 0x7f0200bd
public const int ic_media_route_disabled_mono_dark = 2130837693;
public const int ic_media_pause = 2130837693;
// aapt resource value: 0x7f0200be
public const int ic_media_route_off_mono_dark = 2130837694;
public const int ic_media_play = 2130837694;
// aapt resource value: 0x7f0200bf
public const int ic_media_route_on_0_mono_dark = 2130837695;
public const int ic_media_route_disabled_mono_dark = 2130837695;
// aapt resource value: 0x7f0200c0
public const int ic_media_route_on_1_mono_dark = 2130837696;
public const int ic_media_route_off_mono_dark = 2130837696;
// aapt resource value: 0x7f0200c1
public const int ic_media_route_on_2_mono_dark = 2130837697;
public const int ic_media_route_on_0_mono_dark = 2130837697;
// aapt resource value: 0x7f0200c2
public const int ic_media_route_on_mono_dark = 2130837698;
public const int ic_media_route_on_1_mono_dark = 2130837698;
// aapt resource value: 0x7f0200c3
public const int ic_pause_dark = 2130837699;
public const int ic_media_route_on_2_mono_dark = 2130837699;
// aapt resource value: 0x7f0200c4
public const int ic_pause_light = 2130837700;
public const int ic_media_route_on_mono_dark = 2130837700;
// aapt resource value: 0x7f0200c5
public const int ic_play_dark = 2130837701;
public const int ic_pause_dark = 2130837701;
// aapt resource value: 0x7f0200c6
public const int ic_play_light = 2130837702;
public const int ic_pause_light = 2130837702;
// aapt resource value: 0x7f0200c7
public const int ic_speaker_dark = 2130837703;
public const int ic_play_dark = 2130837703;
// aapt resource value: 0x7f0200c8
public const int ic_speaker_group_dark = 2130837704;
public const int ic_play_light = 2130837704;
// aapt resource value: 0x7f0200c9
public const int ic_speaker_group_light = 2130837705;
public const int ic_speaker_dark = 2130837705;
// aapt resource value: 0x7f0200ca
public const int ic_speaker_light = 2130837706;
public const int ic_speaker_group_dark = 2130837706;
// aapt resource value: 0x7f0200cb
public const int ic_successstatus = 2130837707;
public const int ic_speaker_group_light = 2130837707;
// aapt resource value: 0x7f0200cc
public const int ic_tv_dark = 2130837708;
public const int ic_speaker_light = 2130837708;
// aapt resource value: 0x7f0200cd
public const int ic_tv_light = 2130837709;
public const int ic_successstatus = 2130837709;
// aapt resource value: 0x7f0200ce
public const int icon = 2130837710;
public const int ic_tv_dark = 2130837710;
// aapt resource value: 0x7f0200cf
public const int ion_chevron_right = 2130837711;
public const int ic_tv_light = 2130837711;
// aapt resource value: 0x7f0200d0
public const int lightbulb = 2130837712;
public const int icon = 2130837712;
// aapt resource value: 0x7f0200d1
public const int list_selector = 2130837713;
public const int ion_chevron_right = 2130837713;
// aapt resource value: 0x7f0200d2
public const int @lock = 2130837714;
public const int lightbulb = 2130837714;
// aapt resource value: 0x7f0200d3
public const int logo = 2130837715;
public const int list_selector = 2130837715;
// aapt resource value: 0x7f0200d4
public const int more = 2130837716;
public const int @lock = 2130837716;
// aapt resource value: 0x7f0200d5
public const int mr_dialog_material_background_dark = 2130837717;
public const int logo = 2130837717;
// aapt resource value: 0x7f0200d6
public const int mr_dialog_material_background_light = 2130837718;
public const int more = 2130837718;
// aapt resource value: 0x7f0200d7
public const int mr_ic_audiotrack_light = 2130837719;
public const int mr_dialog_material_background_dark = 2130837719;
// aapt resource value: 0x7f0200d8
public const int mr_ic_cast_dark = 2130837720;
public const int mr_dialog_material_background_light = 2130837720;
// aapt resource value: 0x7f0200d9
public const int mr_ic_cast_light = 2130837721;
public const int mr_ic_audiotrack_light = 2130837721;
// aapt resource value: 0x7f0200da
public const int mr_ic_close_dark = 2130837722;
public const int mr_ic_cast_dark = 2130837722;
// aapt resource value: 0x7f0200db
public const int mr_ic_close_light = 2130837723;
public const int mr_ic_cast_light = 2130837723;
// aapt resource value: 0x7f0200dc
public const int mr_ic_media_route_connecting_mono_dark = 2130837724;
public const int mr_ic_close_dark = 2130837724;
// aapt resource value: 0x7f0200dd
public const int mr_ic_media_route_connecting_mono_light = 2130837725;
public const int mr_ic_close_light = 2130837725;
// aapt resource value: 0x7f0200de
public const int mr_ic_media_route_mono_dark = 2130837726;
public const int mr_ic_media_route_connecting_mono_dark = 2130837726;
// aapt resource value: 0x7f0200df
public const int mr_ic_media_route_mono_light = 2130837727;
public const int mr_ic_media_route_connecting_mono_light = 2130837727;
// aapt resource value: 0x7f0200e0
public const int mr_ic_pause_dark = 2130837728;
public const int mr_ic_media_route_mono_dark = 2130837728;
// aapt resource value: 0x7f0200e1
public const int mr_ic_pause_light = 2130837729;
public const int mr_ic_media_route_mono_light = 2130837729;
// aapt resource value: 0x7f0200e2
public const int mr_ic_play_dark = 2130837730;
public const int mr_ic_pause_dark = 2130837730;
// aapt resource value: 0x7f0200e3
public const int mr_ic_play_light = 2130837731;
public const int mr_ic_pause_light = 2130837731;
// aapt resource value: 0x7f0200e4
public const int notification_sm = 2130837732;
// aapt resource value: 0x7f0200f3
public const int notification_template_icon_bg = 2130837747;
public const int mr_ic_play_dark = 2130837732;
// aapt resource value: 0x7f0200e5
public const int plus = 2130837733;
public const int mr_ic_play_light = 2130837733;
// aapt resource value: 0x7f0200e6
public const int refresh = 2130837734;
public const int notification_sm = 2130837734;
// aapt resource value: 0x7f0200f8
public const int notification_template_icon_bg = 2130837752;
// aapt resource value: 0x7f0200e7
public const int roundedbg = 2130837735;
public const int paperclip = 2130837735;
// aapt resource value: 0x7f0200e8
public const int roundedbgdark = 2130837736;
public const int plus = 2130837736;
// aapt resource value: 0x7f0200e9
public const int search = 2130837737;
public const int refresh = 2130837737;
// aapt resource value: 0x7f0200ea
public const int share = 2130837738;
public const int roundedbg = 2130837738;
// aapt resource value: 0x7f0200eb
public const int share_tools = 2130837739;
public const int roundedbgdark = 2130837739;
// aapt resource value: 0x7f0200ec
public const int splash_screen = 2130837740;
public const int search = 2130837740;
// aapt resource value: 0x7f0200ed
public const int star = 2130837741;
public const int share = 2130837741;
// aapt resource value: 0x7f0200ee
public const int star_selected = 2130837742;
public const int share_tools = 2130837742;
// aapt resource value: 0x7f0200ef
public const int tools = 2130837743;
public const int splash_screen = 2130837743;
// aapt resource value: 0x7f0200f0
public const int tools_selected = 2130837744;
public const int star = 2130837744;
// aapt resource value: 0x7f0200f1
public const int upload = 2130837745;
public const int star_selected = 2130837745;
// aapt resource value: 0x7f0200f2
public const int user = 2130837746;
public const int tools = 2130837746;
// aapt resource value: 0x7f0200f3
public const int tools_selected = 2130837747;
// aapt resource value: 0x7f0200f4
public const int trash = 2130837748;
// aapt resource value: 0x7f0200f5
public const int upload = 2130837749;
// aapt resource value: 0x7f0200f6
public const int user = 2130837750;
// aapt resource value: 0x7f0200f7
public const int yubikey = 2130837751;
static Drawable()
{
@@ -2919,6 +2940,9 @@ namespace Bit.Android
// aapt resource value: 0x7f0c0027
public const int collapseActionView = 2131492903;
// aapt resource value: 0x7f0c00c6
public const int contentFrame = 2131493062;
// aapt resource value: 0x7f0c0052
public const int contentPanel = 2131492946;
@@ -3675,6 +3699,12 @@ namespace Bit.Android
// aapt resource value: 0x7f030042
public const int toolbar = 2130903106;
// aapt resource value: 0x7f030043
public const int zxingscanneractivitylayout = 2130903107;
// aapt resource value: 0x7f030044
public const int zxingscannerfragmentlayout = 2130903108;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
@@ -5293,6 +5323,9 @@ namespace Bit.Android
// aapt resource value: 0x7f060000
public const int accessibilityservice = 2131099648;
// aapt resource value: 0x7f060001
public const int filepaths = 2131099649;
static Xml()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="cache" path="." />
</paths>

View File

@@ -0,0 +1,370 @@
using Java.Security;
using Javax.Crypto;
using Android.OS;
using Bit.App.Abstractions;
using System;
using Android.Security;
using Javax.Security.Auth.X500;
using Java.Math;
using Android.Security.Keystore;
using Android.App;
using Plugin.Settings.Abstractions;
using Java.Util;
using Javax.Crypto.Spec;
using Android.Preferences;
namespace Bit.Android.Services
{
public class AndroidKeyStoreStorageService : ISecureStorageService
{
private const string AndroidKeyStore = "AndroidKeyStore";
private const string AesMode = "AES/GCM/NoPadding";
private const string KeyAlias = "bitwardenKey2";
private const string KeyAliasV1 = "bitwardenKey";
private const string SettingsFormat = "ksSecured2:{0}";
private const string SettingsFormatV1 = "ksSecured:{0}";
private const string AesKey = "ksSecured2:aesKeyForService";
private const string AesKeyV1 = "ksSecured:aesKeyForService";
private readonly string _rsaMode;
private readonly bool _oldAndroid;
private readonly ISettings _settings;
private readonly KeyStore _keyStore;
private readonly ISecureStorageService _oldKeyStorageService;
public AndroidKeyStoreStorageService(ISettings settings)
{
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
_settings = settings;
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null);
GenerateStoreKey();
GenerateAesKey();
}
public bool Contains(string key)
{
return _settings.Contains(string.Format(SettingsFormat, key)) ||
_settings.Contains(string.Format(SettingsFormatV1, key)) ||
_oldKeyStorageService.Contains(key);
}
public void Delete(string key)
{
CleanupOld(key);
var formattedKey = string.Format(SettingsFormat, key);
if(_settings.Contains(formattedKey))
{
_settings.Remove(formattedKey);
}
}
public byte[] Retrieve(string key)
{
var formattedKey = string.Format(SettingsFormat, key);
if(!_settings.Contains(formattedKey))
{
return TryGetAndMigrate(key);
}
var cs = _settings.GetValueOrDefault<string>(formattedKey);
if(string.IsNullOrWhiteSpace(cs))
{
return null;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return null;
}
try
{
return App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKey);
}
catch(Exception e)
{
Console.WriteLine("Failed to decrypt from secure storage.");
_settings.Remove(formattedKey);
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
return null;
}
}
public void Store(string key, byte[] dataBytes)
{
var formattedKey = string.Format(SettingsFormat, key);
CleanupOld(key);
if(dataBytes == null)
{
_settings.Remove(formattedKey);
return;
}
var aesKey = GetAesKey();
if(aesKey == null)
{
return;
}
try
{
var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
}
catch(Exception e)
{
Console.WriteLine("Failed to encrypt to secure storage.");
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
}
private void GenerateStoreKey()
{
if(_keyStore.ContainsAlias(KeyAlias))
{
return;
}
ClearSettings();
var end = Calendar.Instance;
end.Add(CalendarField.Year, 99);
if(_oldAndroid)
{
var subject = new X500Principal($"CN={KeyAlias}");
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias)
.SetSubject(subject)
.SetSerialNumber(BigInteger.Ten)
.SetStartDate(new Date(0))
.SetEndDate(end.Time)
.Build();
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
gen.Initialize(spec);
gen.GenerateKeyPair();
}
else
{
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
.SetKeyValidityStart(new Date(0))
.SetKeyValidityEnd(end.Time)
.Build();
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
gen.Init(spec);
gen.GenerateKey();
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry(string alias)
{
return _keyStore.GetEntry(alias, null) as KeyStore.PrivateKeyEntry;
}
private void GenerateAesKey()
{
if(_settings.Contains(AesKey))
{
return;
}
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
_settings.AddOrUpdateValue(AesKey, encKey);
}
private App.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
{
try
{
var aesKey = v1 ? AesKeyV1 : AesKey;
if(!_settings.Contains(aesKey))
{
return null;
}
var encKey = _settings.GetValueOrDefault<string>(aesKey);
if(string.IsNullOrWhiteSpace(encKey))
{
return null;
}
if(_oldAndroid || v1)
{
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes, v1);
return new App.Models.SymmetricCryptoKey(key);
}
else
{
var parts = encKey.Split('|');
if(parts.Length < 2)
{
return null;
}
var ivBytes = Convert.FromBase64String(parts[0]);
var encKeyBytes = Convert.FromBase64String(parts[1]);
var key = AesDecrypt(ivBytes, encKeyBytes);
return new App.Models.SymmetricCryptoKey(key);
}
}
catch(Exception e)
{
Console.WriteLine("Cannot get AesKey.");
_keyStore.DeleteEntry(KeyAlias);
_settings.Remove(AesKey);
if(!v1)
{
//Utilities.SendCrashEmail(e);
//Utilities.SaveCrashFile(e);
}
return null;
}
}
private string AesEncrypt(byte[] input)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
cipher.Init(CipherMode.EncryptMode, entry);
var encBytes = cipher.DoFinal(input);
var ivBytes = cipher.GetIV();
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
}
}
private byte[] AesDecrypt(byte[] iv, byte[] encData)
{
using(var entry = _keyStore.GetKey(KeyAlias, null))
using(var cipher = Cipher.GetInstance(AesMode))
{
var spec = new GCMParameterSpec(128, iv);
cipher.Init(CipherMode.DecryptMode, entry, spec);
var decBytes = cipher.DoFinal(encData);
return decBytes;
}
}
private string RsaEncrypt(byte[] data)
{
using(var entry = GetRsaKeyEntry(KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
cipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
var cipherText = cipher.DoFinal(data);
return Convert.ToBase64String(cipherText);
}
}
private byte[] RsaDecrypt(byte[] encData, bool v1)
{
using(var entry = GetRsaKeyEntry(v1 ? KeyAliasV1 : KeyAlias))
using(var cipher = Cipher.GetInstance(_rsaMode))
{
if(_oldAndroid)
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
}
else
{
cipher.Init(CipherMode.DecryptMode, entry.PrivateKey, OAEPParameterSpec.Default);
}
var plainText = cipher.DoFinal(encData);
return plainText;
}
}
private byte[] TryGetAndMigrate(string key)
{
if(_oldKeyStorageService.Contains(key))
{
var value = _oldKeyStorageService.Retrieve(key);
Store(key, value);
return value;
}
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
var aesKeyV1 = GetAesKey(true);
if(aesKeyV1 != null)
{
try
{
var cs = _settings.GetValueOrDefault<string>(formattedKeyV1);
var value = App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKeyV1);
Store(key, value);
return value;
}
catch
{
Console.WriteLine("Failed to decrypt v1 from secure storage.");
}
}
_settings.Remove(formattedKeyV1);
}
return null;
}
private void CleanupOld(string key)
{
if(_oldKeyStorageService.Contains(key))
{
_oldKeyStorageService.Delete(key);
}
var formattedKeyV1 = string.Format(SettingsFormatV1, key);
if(_settings.Contains(formattedKeyV1))
{
_settings.Remove(formattedKeyV1);
}
}
private void ClearSettings(string format = SettingsFormat)
{
var prefix = string.Format(format, string.Empty);
using(var sharedPreferences = PreferenceManager.GetDefaultSharedPreferences(Application.Context))
using(var sharedPreferencesEditor = sharedPreferences.Edit())
{
var removed = false;
foreach(var pref in sharedPreferences.All)
{
if(pref.Key.StartsWith(prefix))
{
removed = true;
sharedPreferencesEditor.Remove(pref.Key);
}
}
if(removed)
{
sharedPreferencesEditor.Commit();
}
}
}
}
}

View File

@@ -1,15 +0,0 @@
using Android.Content;
using Bit.App.Abstractions;
using Xamarin.Forms;
namespace Bit.Android.Services
{
public class ClipboardService : IClipboardService
{
public void CopyToClipboard(string text)
{
var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
clipboardManager.Text = text;
}
}
}

View File

@@ -0,0 +1,229 @@
using System;
using Android.Content;
using Bit.App.Abstractions;
using Xamarin.Forms;
using Android.Webkit;
using Plugin.CurrentActivity;
using System.IO;
using Android.Support.V4.Content;
using Bit.App;
using Bit.App.Resources;
using Android.Provider;
using System.Threading.Tasks;
using Android.OS;
using System.Collections.Generic;
using Android;
using Android.Content.PM;
using Android.Support.V4.App;
namespace Bit.Android.Services
{
public class DeviceActionService : IDeviceActionService
{
private readonly IAppSettingsService _appSettingsService;
private bool _cameraPermissionsDenied;
public DeviceActionService(
IAppSettingsService appSettingsService)
{
_appSettingsService = appSettingsService;
}
public void CopyToClipboard(string text)
{
var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
clipboardManager.Text = text;
}
public bool OpenFile(byte[] fileData, string id, string fileName)
{
if(!CanOpenFile(fileName))
{
return false;
}
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
}
var cachePath = CrossCurrentActivity.Current.Activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if(!file.IsFile)
{
return false;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(CrossCurrentActivity.Current.Activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
CrossCurrentActivity.Current.Activity.StartActivity(intent);
return true;
}
catch { }
return false;
}
public bool CanOpenFile(string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if(extension == null)
{
return false;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if(mimeType == null)
{
return false;
}
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var intent = new Intent(Intent.ActionView);
intent.SetType(mimeType);
var activities = pm.QueryIntentActivities(intent, global::Android.Content.PM.PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
public void ClearCache()
{
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
_appSettingsService.LastCacheClear = DateTime.UtcNow;
}
catch(Exception) { }
}
private bool DeleteDir(Java.IO.File dir)
{
if(dir != null && dir.IsDirectory)
{
var children = dir.List();
for(int i = 0; i < children.Length; i++)
{
var success = DeleteDir(new Java.IO.File(dir, children[i]));
if(!success)
{
return false;
}
}
return dir.Delete();
}
else if(dir != null && dir.IsFile)
{
return dir.Delete();
}
else
{
return false;
}
}
public Task SelectFileAsync()
{
MessagingCenter.Unsubscribe<Application>(Application.Current, "SelectFileCameraPermissionDenied");
var hasStorageWritePermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.WriteExternalStorage);
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
if(!_cameraPermissionsDenied && !hasStorageWritePermission)
{
AskCameraPermission(Manifest.Permission.WriteExternalStorage);
return Task.FromResult(0);
}
if(!_cameraPermissionsDenied && !hasCameraPermission)
{
AskCameraPermission(Manifest.Permission.Camera);
return Task.FromResult(0);
}
var additionalIntents = new List<IParcelable>();
var docIntent = new Intent(Intent.ActionOpenDocument);
docIntent.AddCategory(Intent.CategoryOpenable);
docIntent.SetType("*/*");
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
if(!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
{
try
{
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
if(!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = global::Android.Net.Uri.FromFile(file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch(Java.IO.IOException) { }
}
if(additionalIntents.Count > 0)
{
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
}
CrossCurrentActivity.Current.Activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
return Task.FromResult(0);
}
private List<IParcelable> GetCameraIntents(global::Android.Net.Uri outputUri)
{
var intents = new List<IParcelable>();
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var captureIntent = new Intent(MediaStore.ActionImageCapture);
var listCam = pm.QueryIntentActivities(captureIntent, 0);
foreach(var res in listCam)
{
var packageName = res.ActivityInfo.PackageName;
var intent = new Intent(captureIntent);
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
intent.SetPackage(packageName);
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
intents.Add(intent);
}
return intents;
}
private bool HasPermission(string permission)
{
return ContextCompat.CheckSelfPermission(CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
}
private void AskCameraPermission(string permission)
{
MessagingCenter.Subscribe<Application>(Application.Current, "SelectFileCameraPermissionDenied", (sender) =>
{
_cameraPermissionsDenied = true;
});
AskPermission(permission);
}
private void AskPermission(string permission)
{
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
Constants.SelectFilePermissionRequestCode);
}
}
}

View File

@@ -1,6 +1,5 @@
using Android.App;
using Android.OS;
using Android.Util;
using Bit.App.Abstractions;
namespace Bit.Android.Services
@@ -42,5 +41,6 @@ namespace Bit.Android.Services
return 1f;
}
}
public bool NfcEnabled => Utilities.NfcEnabled();
}
}

View File

@@ -1,253 +0,0 @@
using System.IO;
using Java.Security;
using Javax.Crypto;
using Android.OS;
using Bit.App.Abstractions;
using System;
using Android.Security;
using Javax.Security.Auth.X500;
using Java.Math;
using Android.Security.Keystore;
using Android.App;
using Plugin.Settings.Abstractions;
using Javax.Crypto.Spec;
using System.Collections.Generic;
using Java.Util;
namespace Bit.Android.Services
{
public class KeyStoreBackedStorageService : ISecureStorageService
{
private const string AndroidKeyStore = "AndroidKeyStore";
private const string AndroidOpenSSL = "AndroidOpenSSL";
private const string KeyAlias = "bitwardenKey";
private const string SettingsFormat = "ksSecured:{0}";
private const string RsaMode = "RSA/ECB/PKCS1Padding";
private const string AesMode = "AES/GCM/NoPadding";
private const string AesKey = "ksSecured:aesKeyForService";
private readonly ISettings _settings;
private readonly KeyStore _keyStore;
private readonly bool _oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
private readonly KeyStoreStorageService _oldKeyStorageService;
public KeyStoreBackedStorageService(ISettings settings)
{
_oldKeyStorageService = new KeyStoreStorageService(new char[] { });
_settings = settings;
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
_keyStore.Load(null);
GenerateKeys();
GenerateAesKey();
}
public bool Contains(string key)
{
return _settings.Contains(string.Format(SettingsFormat, key)) || _oldKeyStorageService.Contains(key);
}
public void Delete(string key)
{
CleanupOldKeyStore(key);
_settings.Remove(string.Format(SettingsFormat, key));
}
public byte[] Retrieve(string key)
{
var formattedKey = string.Format(SettingsFormat, key);
if(!_settings.Contains(formattedKey))
{
return TryGetAndMigrateFromOldKeyStore(key);
}
var cipherString = _settings.GetValueOrDefault<string>(formattedKey);
return AesDecrypt(cipherString);
}
public void Store(string key, byte[] dataBytes)
{
var formattedKey = string.Format(SettingsFormat, key);
CleanupOldKeyStore(key);
if(dataBytes == null)
{
_settings.Remove(formattedKey);
return;
}
var cipherString = AesEncrypt(dataBytes);
_settings.AddOrUpdateValue(formattedKey, cipherString);
}
private byte[] RandomBytes(int length)
{
var key = new byte[length];
var secureRandom = new SecureRandom();
secureRandom.NextBytes(key);
return key;
}
private void GenerateKeys()
{
if(_keyStore.ContainsAlias(KeyAlias))
{
return;
}
if(_oldAndroid)
{
var start = Calendar.Instance;
var end = Calendar.Instance;
end.Add(CalendarField.Year, 30);
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
.SetAlias(KeyAlias)
.SetSubject(new X500Principal($"CN={KeyAlias}"))
.SetSerialNumber(BigInteger.Ten)
.SetStartDate(start.Time)
.SetEndDate(end.Time)
.Build();
gen.Initialize(spec);
gen.GenerateKeyPair();
}
else
{
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
.SetBlockModes(KeyProperties.BlockModeGcm)
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
.Build();
gen.Init(spec);
gen.GenerateKey();
}
}
private void GenerateAesKey()
{
if(!_oldAndroid)
{
return;
}
if(_settings.Contains(AesKey))
{
return;
}
var key = RandomBytes(16);
var encKey = RsaEncrypt(key);
_settings.AddOrUpdateValue(AesKey, Convert.ToBase64String(encKey));
}
private IKey GetAesKey()
{
if(_oldAndroid)
{
var encKey = _settings.GetValueOrDefault<string>(AesKey);
var encKeyBytes = Convert.FromBase64String(encKey);
var key = RsaDecrypt(encKeyBytes);
return new SecretKeySpec(key, "AES");
}
else
{
return _keyStore.GetKey(KeyAlias, null);
}
}
private KeyStore.PrivateKeyEntry GetRsaKeyEntry()
{
return _keyStore.GetEntry(KeyAlias, null) as KeyStore.PrivateKeyEntry;
}
private string AesEncrypt(byte[] input)
{
var cipher = Cipher.GetInstance(AesMode);
cipher.Init(CipherMode.EncryptMode, GetAesKey());
var encBytes = cipher.DoFinal(input);
var ivBytes = cipher.GetIV();
return $"{Convert.ToBase64String(ivBytes)}|{Convert.ToBase64String(encBytes)}";
}
private byte[] AesDecrypt(string cipherString)
{
var parts = cipherString.Split('|');
var ivBytes = Convert.FromBase64String(parts[0]);
var encBytes = Convert.FromBase64String(parts[1]);
var cipher = Cipher.GetInstance(AesMode);
var spec = new GCMParameterSpec(128, ivBytes);
cipher.Init(CipherMode.DecryptMode, GetAesKey(), spec);
var decBytes = cipher.DoFinal(encBytes);
return decBytes;
}
private byte[] RsaEncrypt(byte[] input)
{
var entry = GetRsaKeyEntry();
var inputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL);
inputCipher.Init(CipherMode.EncryptMode, entry.Certificate.PublicKey);
var outputStream = new MemoryStream();
var cipherStream = new CipherOutputStream(outputStream, inputCipher);
cipherStream.Write(input);
cipherStream.Close();
var vals = outputStream.ToArray();
outputStream.Close();
return vals;
}
private byte[] RsaDecrypt(byte[] encInput)
{
var entry = GetRsaKeyEntry();
var outputCipher = Cipher.GetInstance(RsaMode, AndroidOpenSSL);
outputCipher.Init(CipherMode.DecryptMode, entry.PrivateKey);
var inputStream = new MemoryStream(encInput);
var cipherStream = new CipherInputStream(inputStream, outputCipher);
var values = new List<byte>();
int nextByte;
while((nextByte = cipherStream.Read()) != -1)
{
values.Add((byte)nextByte);
}
inputStream.Close();
cipherStream.Close();
var bytes = new byte[values.Count];
for(var i = 0; i < bytes.Length; i++)
{
bytes[i] = values[i];
}
return bytes;
}
private byte[] TryGetAndMigrateFromOldKeyStore(string key)
{
if(_oldKeyStorageService.Contains(key))
{
var value = _oldKeyStorageService.Retrieve(key);
Store(key, value);
_oldKeyStorageService.Delete(key);
return value;
}
return null;
}
private void CleanupOldKeyStore(string key)
{
if(_oldKeyStorageService.Contains(key))
{
_oldKeyStorageService.Delete(key);
}
}
}
}

View File

@@ -8,6 +8,7 @@ using Bit.App.Abstractions;
namespace Bit.Android.Services
{
[System.Obsolete]
public class KeyStoreStorageService : ISecureStorageService
{
private const string StorageFile = "Bit.Android.KeyStoreStorageService";

129
src/Android/Utilities.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using Android.App;
using Android.Content;
using Java.Security;
using System.IO;
using Android.Nfc;
using Android.Provider;
namespace Bit.Android
{
public static class Utilities
{
public static bool NfcEnabled()
{
var manager = (NfcManager)Application.Context.GetSystemService("nfc");
var adapter = manager.DefaultAdapter;
return adapter != null && adapter.IsEnabled;
}
public static void SendCrashEmail(Exception e, bool includeSecurityProviders = true)
{
SendCrashEmail(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
}
public static void SendCrashEmail(Activity act, Exception e, bool includeSecurityProviders = true)
{
SendCrashEmail(act, e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
}
public static void SaveCrashFile(Exception e, bool includeSecurityProviders = true)
{
SaveCrashFile(e.Message + "\n\n" + e.StackTrace, includeSecurityProviders);
}
public static void SendCrashEmail(string text, bool includeSecurityProviders = true)
{
var emailIntent = new Intent(Intent.ActionSend);
emailIntent.SetType("plain/text");
emailIntent.PutExtra(Intent.ExtraEmail, new String[] { "hello@bitwarden.com" });
emailIntent.PutExtra(Intent.ExtraSubject, "bitwarden Crash Report");
emailIntent.PutExtra(Intent.ExtraText, FormatText(text, includeSecurityProviders));
Application.Context.StartActivity(Intent.CreateChooser(emailIntent, "Send mail..."));
}
public static void SendCrashEmail(Activity act, string text, bool includeSecurityProviders = true)
{
var emailIntent = new Intent(Intent.ActionSend);
emailIntent.SetType("plain/text");
emailIntent.PutExtra(Intent.ExtraEmail, new String[] { "hello@bitwarden.com" });
emailIntent.PutExtra(Intent.ExtraSubject, "bitwarden Crash Report");
emailIntent.PutExtra(Intent.ExtraText, FormatText(text, includeSecurityProviders));
act.StartActivity(Intent.CreateChooser(emailIntent, "Send mail..."));
}
public static void SaveCrashFile(string text, bool includeSecurityProviders = true)
{
var path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var filename = Path.Combine(path, $"crash-{Java.Lang.JavaSystem.CurrentTimeMillis()}.txt");
using(var streamWriter = new StreamWriter(filename, true))
{
streamWriter.WriteLine(FormatText(text, includeSecurityProviders));
}
}
private static string FormatText(string text, bool includeSecurityProviders = true)
{
var crashMessage = "bitwarden has crashed. Please send this email to our support team so that we can help " +
"resolve the problem for you. Thank you.";
text = crashMessage + "\n\n =============================================== \n\n" + text;
if(includeSecurityProviders)
{
text += "\n\n";
var providers = Security.GetProviders();
foreach(var provider in providers)
{
text += ("provider: " + provider.Name + "\n");
var services = provider.Services;
foreach(var service in provider.Services)
{
text += ("- alg: " + service.Algorithm + "\n");
}
}
}
text += "\n\n ==================================================== \n\n" + crashMessage;
return text;
}
public static string AppendExceptionToMessage(string message, Exception ex)
{
message += ("\n\n" + ex.Message + "\n\n" + ex.StackTrace);
if(ex.InnerException != null)
{
return AppendExceptionToMessage(message, ex.InnerException);
}
return message;
}
public static string GetFileName(Context context, global::Android.Net.Uri uri)
{
string name = null;
string[] projection = { MediaStore.MediaColumns.DisplayName };
var metaCursor = context.ContentResolver.Query(uri, projection, null, null, null);
if(metaCursor != null)
{
try
{
if(metaCursor.MoveToFirst())
{
name = metaCursor.GetString(0);
}
}
finally
{
metaCursor.Close();
}
}
return name;
}
}
}

View File

@@ -92,4 +92,6 @@
<package id="Xamarin.GooglePlayServices.Measurement" version="29.0.0.2" targetFramework="monoandroid60" />
<package id="XLabs.IoC" version="2.0.5782" targetFramework="monoandroid60" />
<package id="XLabs.IoC.SimpleInjector" version="2.0.5782" targetFramework="monoandroid71" />
<package id="ZXing.Net.Mobile" version="2.1.47" targetFramework="monoandroid71" />
<package id="ZXing.Net.Mobile.Forms" version="2.1.47" targetFramework="monoandroid71" />
</packages>

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models.Data;
namespace Bit.App.Abstractions
{
public interface IAttachmentRepository : IRepository<AttachmentData, string>
{
Task<IEnumerable<AttachmentData>> GetAllByLoginIdAsync(string loginId);
Task<IEnumerable<AttachmentData>> GetAllByUserIdAsync(string userId);
}
}

View File

@@ -8,5 +8,7 @@ namespace Bit.App.Abstractions
{
Task<ApiResult<CipherResponse>> GetByIdAsync(string id);
Task<ApiResult<ListResponse<CipherResponse>>> GetAsync();
Task<ApiResult<CipherResponse>> PostAttachmentAsync(string cipherId, byte[] data, string fileName);
Task<ApiResult> DeleteAttachmentAsync(string cipherId, string attachmentId);
}
}

View File

@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface ITwoFactorApiRepository
{
Task<ApiResult> PostSendEmailLoginAsync(TwoFactorEmailRequest requestObj);
}
}

View File

@@ -6,6 +6,7 @@ namespace Bit.App.Abstractions
{
bool Locked { get; set; }
DateTime LastActivity { get; set; }
DateTime LastCacheClear { get; set; }
bool AutofillPersistNotification { get; set; }
bool AutofillPasswordField { get; set; }
string SecurityStamp { get; set; }

View File

@@ -1,4 +1,5 @@
using Bit.App.Models;
using Bit.App.Enums;
using Bit.App.Models;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
@@ -15,6 +16,7 @@ namespace Bit.App.Abstractions
bool BelongsToOrganization(string orgId);
void LogOut();
Task<FullLoginResult> TokenPostAsync(string email, string masterPassword);
Task<LoginResult> TokenPostTwoFactorAsync(string token, string email, string masterPasswordHash, SymmetricCryptoKey key);
Task<LoginResult> TokenPostTwoFactorAsync(TwoFactorProviderType type, string token, bool remember, string email,
string masterPasswordHash, SymmetricCryptoKey key);
}
}

View File

@@ -1,7 +0,0 @@
namespace Bit.App.Abstractions
{
public interface IClipboardService
{
void CopyToClipboard(string text);
}
}

View File

@@ -19,8 +19,10 @@ namespace Bit.App.Abstractions
void ClearKeys();
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null);
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
byte[] EncryptToBytes(byte[] plainBytes, SymmetricCryptoKey key = null);
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);
string MakeKeyFromPasswordBase64(string password, string salt);
byte[] HashPassword(SymmetricCryptoKey key, string password);

View File

@@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
public interface IDeviceActionService
{
void CopyToClipboard(string text);
bool OpenFile(byte[] fileData, string id, string fileName);
bool CanOpenFile(string fileName);
Task SelectFileAsync();
void ClearCache();
}
}

View File

@@ -5,5 +5,6 @@
string Model { get; }
int Version { get; }
float Scale { get; }
bool NfcEnabled { get; }
}
}

View File

@@ -14,5 +14,8 @@ namespace Bit.App.Abstractions
Task<Tuple<IEnumerable<Login>, IEnumerable<Login>>> GetAllAsync(string uriString);
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
Task<ApiResult> DeleteAsync(string id);
Task<byte[]> DownloadAndDecryptAttachmentAsync(string url, string orgId = null);
Task<ApiResult<CipherResponse>> EncryptAndSaveAttachmentAsync(Login login, byte[] data, string fileName);
Task<ApiResult> DeleteAttachmentAsync(Login login, string attachmentId);
}
}

View File

@@ -8,6 +8,8 @@ namespace Bit.App.Abstractions
string RefreshToken { get; set; }
[Obsolete("Old auth scheme")]
string AuthBearer { get; set; }
string GetTwoFactorToken(string email);
void SetTwoFactorToken(string email, string token);
DateTime TokenExpiration { get; }
string TokenIssuer { get; }
bool TokenExpired { get; }
@@ -16,5 +18,6 @@ namespace Bit.App.Abstractions
string TokenUserId { get; }
string TokenEmail { get; }
string TokenName { get; }
bool TokenPremium { get; }
}
}

View File

@@ -32,6 +32,7 @@ namespace Bit.App
private readonly ILocalizeService _localizeService;
private readonly IAppInfoService _appInfoService;
private readonly IAppSettingsService _appSettingsService;
private readonly IDeviceActionService _deviceActionService;
public App(
string uri,
@@ -45,7 +46,8 @@ namespace Bit.App
IGoogleAnalyticsService googleAnalyticsService,
ILocalizeService localizeService,
IAppInfoService appInfoService,
IAppSettingsService appSettingsService)
IAppSettingsService appSettingsService,
IDeviceActionService deviceActionService)
{
_uri = uri;
_databaseService = databaseService;
@@ -59,6 +61,7 @@ namespace Bit.App
_localizeService = localizeService;
_appInfoService = appInfoService;
_appSettingsService = appSettingsService;
_deviceActionService = deviceActionService;
SetCulture();
SetStyles();
@@ -101,7 +104,7 @@ namespace Bit.App
if(string.IsNullOrWhiteSpace(_uri))
{
var lastBuild = _settings.GetValueOrDefault<string>(LastBuildKey);
if(InDebugMode() || lastBuild == null || lastBuild != _appInfoService.Build)
if(Utilities.Helpers.InDebugMode() || lastBuild == null || lastBuild != _appInfoService.Build)
{
_settings.AddOrUpdateValue(LastBuildKey, _appInfoService.Build);
_databaseService.CreateTables();
@@ -110,6 +113,11 @@ namespace Bit.App
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}
if((DateTime.UtcNow - _appSettingsService.LastCacheClear).TotalDays >= 1)
{
await Task.Run(() => _deviceActionService.ClearCache()).ConfigureAwait(false);
}
Debug.WriteLine("OnStart");
}
@@ -152,6 +160,13 @@ namespace Bit.App
{
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}
var now = DateTime.UtcNow;
if((now - _appSettingsService.LastCacheClear).TotalDays >= 1
&& (now - _appSettingsService.LastActivity).TotalHours >= 1)
{
await Task.Run(() => _deviceActionService.ClearCache()).ConfigureAwait(false);
}
}
private void SetMainPageFromAutofill()
@@ -169,15 +184,6 @@ namespace Bit.App
}
}
private bool InDebugMode()
{
#if DEBUG
return true;
#else
return false;
#endif
}
private async Task FullSyncAsync()
{
if(_connectivity.IsConnected)

View File

@@ -35,6 +35,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Abstractions\Repositories\IAttachmentRepository.cs" />
<Compile Include="Abstractions\Repositories\ITwoFactorApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ISettingsApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
@@ -49,7 +51,7 @@
<Compile Include="Abstractions\Services\IAppInfoService.cs" />
<Compile Include="Abstractions\Services\IAppIdService.cs" />
<Compile Include="Abstractions\Services\IAuthService.cs" />
<Compile Include="Abstractions\Services\IClipboardService.cs" />
<Compile Include="Abstractions\Services\IDeviceActionService.cs" />
<Compile Include="Abstractions\Services\IKeyDerivationService.cs" />
<Compile Include="Abstractions\Services\ILogService.cs" />
<Compile Include="Abstractions\Services\IReflectionService.cs" />
@@ -59,12 +61,14 @@
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
<Compile Include="Abstractions\Services\ISqlService.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Controls\HybridWebView.cs" />
<Compile Include="Controls\ExtendedToolbarItem.cs" />
<Compile Include="Controls\DismissModalToolBarItem.cs" />
<Compile Include="Controls\ExtendedEditor.cs" />
<Compile Include="Controls\ExtendedButton.cs" />
<Compile Include="Controls\ExtendedNavigationPage.cs" />
<Compile Include="Controls\ExtendedContentPage.cs" />
<Compile Include="Controls\LabeledRightDetailCell.cs" />
<Compile Include="Controls\MemoryContentView.cs" />
<Compile Include="Controls\StepperCell.cs" />
<Compile Include="Controls\ExtendedTableView.cs" />
@@ -80,7 +84,9 @@
<Compile Include="Controls\FormPickerCell.cs" />
<Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultAttachmentsViewCell.cs" />
<Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\TwoFactorProviderType.cs" />
<Compile Include="Enums\EncryptionType.cs" />
<Compile Include="Enums\OrganizationUserType.cs" />
<Compile Include="Enums\LockType.cs" />
@@ -95,11 +101,12 @@
<Compile Include="Models\Api\Request\DeviceTokenRequest.cs" />
<Compile Include="Models\Api\Request\FolderRequest.cs" />
<Compile Include="Models\Api\Request\DeviceRequest.cs" />
<Compile Include="Models\Api\Request\TwoFactorEmailRequest.cs" />
<Compile Include="Models\Api\Request\RegisterRequest.cs" />
<Compile Include="Models\Api\Request\LoginRequest.cs" />
<Compile Include="Models\Api\Request\PasswordHintRequest.cs" />
<Compile Include="Models\Api\Request\TokenRequest.cs" />
<Compile Include="Models\Api\Response\CipherHistoryResponse.cs" />
<Compile Include="Models\Api\Response\AttachmentResponse.cs" />
<Compile Include="Models\Api\Response\CipherResponse.cs" />
<Compile Include="Models\Api\Response\DomainsResponse.cs" />
<Compile Include="Models\Api\Response\ErrorResponse.cs" />
@@ -112,8 +119,10 @@
<Compile Include="Models\Api\Response\TokenResponse.cs" />
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
<Compile Include="Models\Api\LoginDataModel.cs" />
<Compile Include="Models\Cipher.cs" />
<Compile Include="Models\CipherString.cs" />
<Compile Include="Models\Data\AttachmentData.cs" />
<Compile Include="Models\Attachment.cs" />
<Compile Include="Models\Page\VaultAttachmentsPageModel.cs" />
<Compile Include="Models\SymmetricCryptoKey.cs" />
<Compile Include="Models\Data\SettingsData.cs" />
<Compile Include="Models\Data\FolderData.cs" />
@@ -136,6 +145,7 @@
<Compile Include="Pages\LoginTwoFactorPage.cs" />
<Compile Include="Pages\PasswordHintPage.cs" />
<Compile Include="Pages\RegisterPage.cs" />
<Compile Include="Pages\ScanPage.cs" />
<Compile Include="Pages\Settings\SettingsCreditsPage.cs" />
<Compile Include="Pages\Settings\SettingsHelpPage.cs" />
<Compile Include="Pages\Settings\SettingsFeaturesPage.cs" />
@@ -154,8 +164,11 @@
<Compile Include="Pages\Settings\SettingsPage.cs" />
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
<Compile Include="Pages\Vault\VaultAttachmentsPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Abstractions\Repositories\ILoginRepository.cs" />
<Compile Include="Repositories\AttachmentRepository.cs" />
<Compile Include="Repositories\TwoFactorApiRepository.cs" />
<Compile Include="Repositories\SettingsApiRepository.cs" />
<Compile Include="Repositories\ApiRepository.cs" />
<Compile Include="Repositories\AccountsApiRepository.cs" />
@@ -205,6 +218,11 @@
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.hi.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.hr.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.hr.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.id.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@@ -230,6 +248,11 @@
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.pt-PT.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.ro.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.ro.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.ru.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
@@ -285,6 +308,8 @@
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
<Compile Include="Services\PasswordGenerationService.cs" />
<Compile Include="Utilities\Base32.cs" />
<Compile Include="Utilities\Crypto.cs" />
<Compile Include="Utilities\Helpers.cs" />
<Compile Include="Utilities\IdentityHttpClient.cs" />
<Compile Include="Utilities\Extentions.cs" />
@@ -317,6 +342,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.hi.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.hr.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.hr.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.id.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.id.Designer.cs</LastGenOutput>
@@ -337,6 +366,10 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.pt-PT.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.ro.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.ro.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.ru.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.ru.Designer.cs</LastGenOutput>
@@ -484,6 +517,18 @@
<HintPath>..\..\packages\XLabs.IoC.2.0.5782\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1+Xamarin.iOS10\XLabs.Ioc.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ZXing.Net.Mobile.Core, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Core.dll</HintPath>
</Reference>
<Reference Include="ZXing.Net.Mobile.Forms, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.Forms.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXing.Net.Mobile.Forms.dll</HintPath>
</Reference>
<Reference Include="zxing.portable, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\zxing.portable.dll</HintPath>
</Reference>
<Reference Include="ZXingNetMobile, Version=2.1.47.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\ZXing.Net.Mobile.2.1.47\lib\portable-net45+netcore45+wpa81+wp8\ZXingNetMobile.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\AppResources.resx">

View File

@@ -8,6 +8,7 @@
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "setting:lockSeconds";
public const string SettingGaOptOut = "setting:googleAnalyticsOptOut";
public const string SettingDisableTotpCopy = "setting:disableAutoCopyTotp";
public const string AutofillPersistNotification = "setting:persistNotification";
public const string AutofillPasswordField = "setting:autofillPasswordField";
@@ -28,8 +29,12 @@
public const string SecurityStamp = "other:securityStamp";
public const string LastActivityDate = "other:lastActivityDate";
public const string LastCacheClearDate = "other:cacheClearDate";
public const string Locked = "other:locked";
public const string LastLoginEmail = "other:lastLoginEmail";
public const string LastSync = "other:lastSync";
public const int SelectFileRequestCode = 42;
public const int SelectFilePermissionRequestCode = 43;
}
}

View File

@@ -0,0 +1,34 @@
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class HybridWebView : View
{
private Action<string> _func;
public static readonly BindableProperty UriProperty = BindableProperty.Create(propertyName: nameof(Uri),
returnType: typeof(string), declaringType: typeof(HybridWebView), defaultValue: default(string));
public string Uri
{
get { return (string)GetValue(UriProperty); }
set { SetValue(UriProperty, value); }
}
public void RegisterAction(Action<string> callback)
{
_func = callback;
}
public void Cleanup()
{
_func = null;
}
public void InvokeAction(string data)
{
_func?.Invoke(data);
}
}
}

View File

@@ -28,6 +28,14 @@ namespace Bit.App.Controls
Margin = new Thickness(5, 0, 0, 0)
};
LabelIcon2 = new CachedImage
{
WidthRequest = 16,
HeightRequest = 16,
HorizontalOptions = LayoutOptions.Start,
Margin = new Thickness(5, 0, 0, 0)
};
Button = new ExtendedButton
{
WidthRequest = 60
@@ -42,13 +50,15 @@ namespace Bit.App.Controls
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(60, GridUnitType.Absolute) });
grid.Children.Add(Label, 0, 0);
grid.Children.Add(Detail, 0, 1);
grid.Children.Add(LabelIcon, 1, 0);
grid.Children.Add(Button, 2, 0);
Grid.SetColumnSpan(Detail, 2);
grid.Children.Add(LabelIcon2, 2, 0);
grid.Children.Add(Button, 3, 0);
Grid.SetColumnSpan(Detail, 3);
Grid.SetRowSpan(Button, 2);
if(Device.RuntimePlatform == Device.Android)
@@ -62,6 +72,7 @@ namespace Bit.App.Controls
public Label Label { get; private set; }
public Label Detail { get; private set; }
public CachedImage LabelIcon { get; private set; }
public CachedImage LabelIcon2 { get; private set; }
public Button Button { get; private set; }
}
}

View File

@@ -0,0 +1,59 @@
using FFImageLoading.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class LabeledRightDetailCell : ExtendedViewCell
{
public LabeledRightDetailCell(bool showIcon = true)
{
Label = new Label
{
LineBreakMode = LineBreakMode.TailTruncation,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.StartAndExpand,
};
Detail = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"],
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
};
StackLayout = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Padding = new Thickness(15, 10),
Children = { Label, Detail }
};
if(showIcon)
{
Icon = new CachedImage
{
WidthRequest = 16,
HeightRequest = 16,
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center,
Margin = new Thickness(5, 0, 0, 0)
};
StackLayout.Children.Add(Icon);
}
if(Device.RuntimePlatform == Device.Android)
{
Label.TextColor = Color.Black;
}
View = StackLayout;
}
public Label Label { get; private set; }
public Label Detail { get; private set; }
public CachedImage Icon { get; private set; }
public StackLayout StackLayout { get; private set; }
}
}

View File

@@ -8,7 +8,8 @@ namespace Bit.App.Controls
string labelText = null,
string valueText = null,
string button1Text = null,
string button2Text = null)
string button2Text = null,
string subText = null)
{
var containerStackLayout = new StackLayout
{
@@ -56,6 +57,18 @@ namespace Bit.App.Controls
VerticalOptions = LayoutOptions.CenterAndExpand
};
if(subText != null)
{
Sub = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
HorizontalOptions = LayoutOptions.End,
VerticalOptions = LayoutOptions.Center
};
buttonStackLayout.Children.Add(Sub);
}
if(button1Text != null)
{
Button1 = new ExtendedButton
@@ -100,12 +113,18 @@ namespace Bit.App.Controls
containerStackLayout.AdjustPaddingForDevice();
}
if(Sub != null && Button1 != null)
{
Sub.Margin = new Thickness(0, 0, 10, 0);
}
containerStackLayout.Children.Add(buttonStackLayout);
View = containerStackLayout;
}
public Label Label { get; private set; }
public Label Value { get; private set; }
public Label Sub { get; private set; }
public ExtendedButton Button1 { get; private set; }
public ExtendedButton Button2 { get; private set; }
}

View File

@@ -0,0 +1,22 @@
using Bit.App.Models.Page;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class VaultAttachmentsViewCell : LabeledRightDetailCell
{
public VaultAttachmentsViewCell()
{
Label.SetBinding(Label.TextProperty, nameof(VaultAttachmentsPageModel.Attachment.Name));
Detail.SetBinding(Label.TextProperty, nameof(VaultAttachmentsPageModel.Attachment.SizeName));
Icon.Source = "trash";
Detail.MinimumWidthRequest = 100;
BackgroundColor = Color.White;
if(Device.RuntimePlatform == Device.iOS)
{
StackLayout.BackgroundColor = Color.White;
}
}
}
}

View File

@@ -15,12 +15,14 @@ namespace Bit.App.Controls
Label.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Name));
Detail.SetBinding(Label.TextProperty, nameof(VaultListPageModel.Login.Username));
LabelIcon.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.Shared));
LabelIcon2.SetBinding(VisualElement.IsVisibleProperty, nameof(VaultListPageModel.Login.HasAttachments));
Button.Image = "more";
Button.Command = new Command(() => moreClickedAction?.Invoke(LoginParameter));
Button.BackgroundColor = Color.Transparent;
LabelIcon.Source = "share";
LabelIcon2.Source = "paperclip";
BackgroundColor = Color.White;
}

View File

@@ -6,6 +6,8 @@
AesCbc128_HmacSha256_B64 = 1,
AesCbc256_HmacSha256_B64 = 2,
Rsa2048_OaepSha256_B64 = 3,
Rsa2048_OaepSha1_B64 = 4
Rsa2048_OaepSha1_B64 = 4,
Rsa2048_OaepSha256_HmacSha256_B64 = 5,
Rsa2048_OaepSha1_HmacSha256_B64 = 6
}
}

View File

@@ -0,0 +1,12 @@
namespace Bit.App.Enums
{
public enum TwoFactorProviderType : byte
{
Authenticator = 0,
Email = 1,
Duo = 2,
YubiKey = 3,
U2f = 4,
Remember = 5
}
}

View File

@@ -7,5 +7,6 @@
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; }
}
}

View File

@@ -11,6 +11,7 @@
Username = login.Username?.EncryptedString;
Password = login.Password?.EncryptedString;
Notes = login.Notes?.EncryptedString;
Totp = login.Totp?.EncryptedString;
Favorite = login.Favorite;
}
@@ -21,6 +22,7 @@
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; }
public bool Favorite { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Bit.App.Enums;
using System;
using System.Collections.Generic;
namespace Bit.App.Models.Api
@@ -8,10 +9,11 @@ namespace Bit.App.Models.Api
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string Token { get; set; }
public int? Provider { get; set; }
public TwoFactorProviderType? Provider { get; set; }
[Obsolete]
public string OldAuthBearer { get; set; }
public DeviceRequest Device { get; set; }
public bool Remember { get; set; }
public IDictionary<string, string> ToIdentityTokenRequest()
{
@@ -40,7 +42,8 @@ namespace Bit.App.Models.Api
if(Token != null && Provider.HasValue)
{
dict.Add("TwoFactorToken", Token);
dict.Add("TwoFactorProvider", Provider.Value.ToString());
dict.Add("TwoFactorProvider", ((byte)(Provider.Value)).ToString());
dict.Add("TwoFactorRemember", Remember ? "1" : "0");
}
return dict;

View File

@@ -0,0 +1,8 @@
namespace Bit.App.Models.Api
{
public class TwoFactorEmailRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
namespace Bit.App.Models.Api
{
public class AttachmentResponse
{
public string Id { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public string Size { get; set; }
public string SizeName { get; set; }
}
}

View File

@@ -1,10 +0,0 @@
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
public class CipherHistoryResponse
{
public IEnumerable<CipherResponse> Revised { get; set; }
public IEnumerable<string> Deleted { get; set; }
}
}

View File

@@ -1,6 +1,7 @@
using Bit.App.Enums;
using System;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
@@ -12,7 +13,10 @@ namespace Bit.App.Models.Api
public string OrganizationId { get; set; }
public CipherType Type { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
public JObject Data { get; set; }
public IEnumerable<AttachmentResponse> Attachments { get; set; }
public DateTime RevisionDate { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
@@ -13,7 +14,11 @@ namespace Bit.App.Models.Api
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
public IEnumerable<AttachmentResponse> Attachments { get; set; }
public DateTime RevisionDate { get; set; }
}
}

View File

@@ -6,6 +6,12 @@ namespace Bit.App.Models.Api
{
public string Id { get; set; }
public string Name { get; set; }
public bool UseGroups { get; set; }
public bool UseDirectory { get; set; }
public bool UseTotp { get; set; }
public int Seats { get; set; }
public int MaxCollections { get; set; }
public short? MaxStorageGb { get; set; }
public string Key { get; set; }
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType Type { get; set; }

View File

@@ -7,6 +7,8 @@ namespace Bit.App.Models.Api
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public bool EmailVerified { get; set; }
public bool Premium { get; set; }
public string MasterPasswordHint { get; set; }
public string Culture { get; set; }
public bool TwoFactorEnabled { get; set; }

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using Bit.App.Enums;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Bit.App.Models.Api
@@ -13,8 +14,9 @@ namespace Bit.App.Models.Api
public string RefreshToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
public List<int> TwoFactorProviders { get; set; }
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders2 { get; set; }
public string PrivateKey { get; set; }
public string TwoFactorToken { get; set; }
public string Key { get; set; }
}
}

View File

@@ -0,0 +1,51 @@
using Bit.App.Models.Api;
using Bit.App.Models.Data;
namespace Bit.App.Models
{
public class Attachment
{
public Attachment()
{ }
public Attachment(AttachmentData data)
{
Id = data.Id;
Url = data.Url;
FileName = data.FileName != null ? new CipherString(data.FileName) : null;
SetSize(data.Size);
SizeName = data.SizeName;
}
public Attachment(AttachmentResponse response)
{
Id = response.Id;
Url = response.Url;
FileName = response.FileName != null ? new CipherString(response.FileName) : null;
SetSize(response.Size);
SizeName = response.SizeName;
}
public string Id { get; set; }
public string Url { get; set; }
public CipherString FileName { get; set; }
public long Size { get; set; }
public string SizeName { get; set; }
public AttachmentData ToAttachmentData(string loginId)
{
return new AttachmentData(this, loginId);
}
private void SetSize(string sizeString)
{
long size;
if(!long.TryParse(sizeString, out size))
{
size = 0;
}
Size = size;
}
}
}

View File

@@ -1,10 +0,0 @@
using System;
namespace Bit.App.Models
{
public abstract class Cipher
{
public string Id { get; set; }
public CipherString Name { get; set; }
}
}

View File

@@ -63,6 +63,15 @@ namespace Bit.App.Models
}
CipherText = encPieces[0];
break;
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
if(encPieces.Length != 2)
{
throw new ArgumentException("Malformed encPieces.");
}
CipherText = encPieces[0];
Mac = encPieces[1];
break;
default:
throw new ArgumentException("Unknown encType.");
}

View File

@@ -0,0 +1,47 @@
using SQLite;
using Bit.App.Abstractions;
using Bit.App.Models.Api;
namespace Bit.App.Models.Data
{
[Table("Attachment")]
public class AttachmentData : IDataObject<string>
{
public AttachmentData()
{ }
public AttachmentData(Attachment attachment, string loginId)
{
Id = attachment.Id;
LoginId = loginId;
Url = attachment.Url;
FileName = attachment.FileName?.EncryptedString;
Size = attachment.Size.ToString();
SizeName = attachment.SizeName;
}
public AttachmentData(AttachmentResponse response, string loginId)
{
Id = response.Id;
LoginId = loginId;
Url = response.Url;
FileName = response.FileName;
Size = response.Size;
SizeName = response.SizeName;
}
[PrimaryKey]
public string Id { get; set; }
[Indexed]
public string LoginId { get; set; }
public string Url { get; set; }
public string FileName { get; set; }
public string Size { get; set; }
public string SizeName { get; set; }
public Attachment ToAttachment()
{
return new Attachment(this);
}
}
}

View File

@@ -22,7 +22,10 @@ namespace Bit.App.Models.Data
Username = login.Username?.EncryptedString;
Password = login.Password?.EncryptedString;
Notes = login.Notes?.EncryptedString;
Totp = login?.Notes?.EncryptedString;
Favorite = login.Favorite;
Edit = login.Edit;
OrganizationUseTotp = login.OrganizationUseTotp;
}
public LoginData(LoginResponse login, string userId)
@@ -36,8 +39,11 @@ namespace Bit.App.Models.Data
Username = login.Username;
Password = login.Password;
Notes = login.Notes;
Totp = login.Totp;
Favorite = login.Favorite;
RevisionDateTime = login.RevisionDate;
Edit = login.Edit;
OrganizationUseTotp = login.OrganizationUseTotp;
}
public LoginData(CipherResponse cipher, string userId)
@@ -58,7 +64,10 @@ namespace Bit.App.Models.Data
Username = data.Username;
Password = data.Password;
Notes = data.Notes;
Totp = data.Totp;
Favorite = cipher.Favorite;
Edit = cipher.Edit;
OrganizationUseTotp = cipher.OrganizationUseTotp;
RevisionDateTime = cipher.RevisionDate;
}
@@ -73,7 +82,10 @@ namespace Bit.App.Models.Data
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Totp { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
public DateTime RevisionDateTime { get; set; } = DateTime.UtcNow;
public Login ToLogin()

View File

@@ -3,7 +3,7 @@ using Bit.App.Models.Api;
namespace Bit.App.Models
{
public class Folder : Cipher
public class Folder
{
public Folder()
{ }
@@ -20,6 +20,9 @@ namespace Bit.App.Models
Name = response.Name != null ? new CipherString(response.Name) : null;
}
public string Id { get; set; }
public CipherString Name { get; set; }
public FolderRequest ToFolderRequest()
{
return new FolderRequest(this);

View File

@@ -1,14 +1,16 @@
using Bit.App.Models.Api;
using Bit.App.Models.Data;
using System.Collections.Generic;
using System.Linq;
namespace Bit.App.Models
{
public class Login : Cipher
public class Login
{
public Login()
{ }
public Login(LoginData data)
public Login(LoginData data, IEnumerable<AttachmentData> attachments = null)
{
Id = data.Id;
UserId = data.UserId;
@@ -19,7 +21,11 @@ namespace Bit.App.Models
Username = data.Username != null ? new CipherString(data.Username) : null;
Password = data.Password != null ? new CipherString(data.Password) : null;
Notes = data.Notes != null ? new CipherString(data.Notes) : null;
Totp = data.Totp != null ? new CipherString(data.Totp) : null;
Favorite = data.Favorite;
Edit = data.Edit;
OrganizationUseTotp = data.OrganizationUseTotp;
Attachments = attachments?.Select(a => new Attachment(a));
}
public Login(LoginResponse response)
@@ -33,17 +39,27 @@ namespace Bit.App.Models
Username = response.Username != null ? new CipherString(response.Username) : null;
Password = response.Password != null ? new CipherString(response.Password) : null;
Notes = response.Notes != null ? new CipherString(response.Notes) : null;
Totp = response.Totp != null ? new CipherString(response.Totp) : null;
Favorite = response.Favorite;
Edit = response.Edit;
OrganizationUseTotp = response.OrganizationUseTotp;
Attachments = response.Attachments?.Select(a => new Attachment(a));
}
public string Id { get; set; }
public string UserId { get; set; }
public string OrganizationId { get; set; }
public string FolderId { get; set; }
public CipherString Name { get; set; }
public CipherString Uri { get; set; }
public CipherString Username { get; set; }
public CipherString Password { get; set; }
public CipherString Notes { get; set; }
public CipherString Totp { get; set; }
public bool Favorite { get; set; }
public bool Edit { get; set; }
public bool OrganizationUseTotp { get; set; }
public IEnumerable<Attachment> Attachments { get; set; }
public LoginRequest ToLoginRequest()
{

View File

@@ -1,4 +1,7 @@
namespace Bit.App.Models
using Bit.App.Enums;
using System.Collections.Generic;
namespace Bit.App.Models
{
public class LoginResult
{
@@ -8,7 +11,8 @@
public class FullLoginResult : LoginResult
{
public bool TwoFactorRequired { get; set; }
public bool TwoFactorRequired => TwoFactorProviders != null && TwoFactorProviders.Count > 0;
public Dictionary<TwoFactorProviderType, Dictionary<string, object>> TwoFactorProviders { get; set; }
public SymmetricCryptoKey Key { get; set; }
public string MasterPasswordHash { get; set; }
}

View File

@@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Bit.App.Models.Page
{
public class VaultAttachmentsPageModel
{
public class Attachment : List<Attachment>
{
public string Id { get; set; }
public string Name { get; set; }
public string SizeName { get; set; }
public long Size { get; set; }
public string Url { get; set; }
public Attachment(Models.Attachment attachment)
{
Id = attachment.Id;
Name = attachment.FileName?.Decrypt();
SizeName = attachment.SizeName;
Size = attachment.Size;
Url = attachment.Url;
}
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Bit.App.Resources;
using System.Linq;
namespace Bit.App.Models.Page
{
@@ -12,20 +13,24 @@ namespace Bit.App.Models.Page
{
Id = login.Id;
Shared = !string.IsNullOrWhiteSpace(login.OrganizationId);
HasAttachments = login.Attachments?.Any() ?? false;
FolderId = login.FolderId;
Name = login.Name?.Decrypt(login.OrganizationId);
Username = login.Username?.Decrypt(login.OrganizationId) ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt(login.OrganizationId));
Uri = new Lazy<string>(() => login.Uri?.Decrypt(login.OrganizationId));
Totp = new Lazy<string>(() => login.Totp?.Decrypt(login.OrganizationId));
}
public string Id { get; set; }
public bool Shared { get; set; }
public bool HasAttachments { get; set; }
public string FolderId { get; set; }
public string Name { get; set; }
public string Username { get; set; }
public Lazy<string> Password { get; set; }
public Lazy<string> Uri { get; set; }
public Lazy<string> Totp { get; set; }
}
public class AutofillLogin : Login

View File

@@ -2,6 +2,7 @@
using System.ComponentModel;
using Bit.App.Resources;
using Xamarin.Forms;
using System.Collections.Generic;
namespace Bit.App.Models.Page
{
@@ -12,7 +13,10 @@ namespace Bit.App.Models.Page
private string _password;
private string _uri;
private string _notes;
private string _totpCode;
private int _totpSec = 30;
private bool _revealPassword;
private List<Attachment> _attachments;
public VaultViewLoginPageModel() { }
@@ -119,6 +123,17 @@ namespace Bit.App.Models.Page
return false;
}
if(Device.RuntimePlatform == Device.Android && !Uri.StartsWith("http") &&
!Uri.StartsWith("androidapp://"))
{
return false;
}
if(Device.RuntimePlatform != Device.Android && !Uri.StartsWith("http"))
{
return false;
}
Uri uri;
if(!System.Uri.TryCreate(Uri, UriKind.Absolute, out uri))
{
@@ -181,6 +196,43 @@ namespace Bit.App.Models.Page
public string ShowHideText => RevealPassword ? AppResources.Hide : AppResources.Show;
public ImageSource ShowHideImage => RevealPassword ? ImageSource.FromFile("eye_slash") : ImageSource.FromFile("eye");
public string TotpCode
{
get { return _totpCode; }
set
{
_totpCode = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpCode)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpCodeFormatted)));
}
}
public int TotpSecond
{
get { return _totpSec; }
set
{
_totpSec = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpSecond)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(TotpColor)));
}
}
public bool TotpLow => TotpSecond <= 7;
public Color TotpColor => !string.IsNullOrWhiteSpace(TotpCode) && TotpLow ? Color.Red : Color.Black;
public string TotpCodeFormatted => !string.IsNullOrWhiteSpace(TotpCode) ?
string.Format("{0} {1}", TotpCode.Substring(0, 3), TotpCode.Substring(3)) : null;
public List<Attachment> Attachments
{
get { return _attachments; }
set
{
_attachments = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Attachments)));
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowAttachments)));
}
}
public bool ShowAttachments => (Attachments?.Count ?? 0) > 0;
public void Update(Login login)
{
Name = login.Name?.Decrypt(login.OrganizationId);
@@ -188,6 +240,36 @@ namespace Bit.App.Models.Page
Password = login.Password?.Decrypt(login.OrganizationId);
Uri = login.Uri?.Decrypt(login.OrganizationId);
Notes = login.Notes?.Decrypt(login.OrganizationId);
if(login.Attachments != null)
{
var attachments = new List<Attachment>();
foreach(var attachment in login.Attachments)
{
attachments.Add(new Attachment
{
Id = attachment.Id,
Name = attachment.FileName?.Decrypt(login.OrganizationId),
SizeName = attachment.SizeName,
Size = attachment.Size,
Url = attachment.Url
});
}
Attachments = attachments;
}
else
{
login.Attachments = null;
}
}
public class Attachment
{
public string Id { get; set; }
public string Name { get; set; }
public string SizeName { get; set; }
public long Size { get; set; }
public string Url { get; set; }
}
}
}

View File

@@ -189,10 +189,12 @@ namespace Bit.App.Pages
return;
}
PasswordCell.Entry.Text = string.Empty;
if(result.TwoFactorRequired)
{
_googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step");
await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result.MasterPasswordHash, result.Key));
await Navigation.PushAsync(new LoginTwoFactorPage(EmailCell.Entry.Text, result));
return;
}

View File

@@ -9,155 +9,398 @@ using System.Threading.Tasks;
using PushNotification.Plugin.Abstractions;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.App.Enums;
using System.Collections.Generic;
using System.Net;
using FFImageLoading.Forms;
namespace Bit.App.Pages
{
public class LoginTwoFactorPage : ExtendedContentPage
{
private DateTime? _lastAction;
private IAuthService _authService;
private IUserDialogs _userDialogs;
private ISyncService _syncService;
private IDeviceInfoService _deviceInfoService;
private IGoogleAnalyticsService _googleAnalyticsService;
private ITwoFactorApiRepository _twoFactorApiRepository;
private IPushNotification _pushNotification;
private readonly string _email;
private readonly string _masterPasswordHash;
private readonly SymmetricCryptoKey _key;
private readonly Dictionary<TwoFactorProviderType, Dictionary<string, object>> _providers;
private TwoFactorProviderType? _providerType;
private readonly FullLoginResult _result;
public LoginTwoFactorPage(string email, string masterPasswordHash, SymmetricCryptoKey key)
public LoginTwoFactorPage(string email, FullLoginResult result, TwoFactorProviderType? type = null)
: base(updateActivity: false)
{
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_email = email;
_masterPasswordHash = masterPasswordHash;
_key = key;
_result = result;
_masterPasswordHash = result.MasterPasswordHash;
_key = result.Key;
_providers = result.TwoFactorProviders;
_providerType = type ?? GetDefaultProvider();
_authService = Resolver.Resolve<IAuthService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_twoFactorApiRepository = Resolver.Resolve<ITwoFactorApiRepository>();
_pushNotification = Resolver.Resolve<IPushNotification>();
Init();
}
public FormEntryCell CodeCell { get; set; }
public FormEntryCell TokenCell { get; set; }
public ExtendedSwitchCell RememberCell { get; set; }
private void Init()
{
var padding = Helpers.OnPlatform(
iOS: new Thickness(15, 20),
Android: new Thickness(15, 8),
WinPhone: new Thickness(15, 20));
CodeCell = new FormEntryCell(AppResources.VerificationCode, useLabelAsPlaceholder: true,
imageSource: "lock", containerPadding: padding);
CodeCell.Entry.Keyboard = Keyboard.Numeric;
CodeCell.Entry.ReturnType = Enums.ReturnType.Go;
var table = new ExtendedTableView
SubscribeYubiKey(true);
if(_providers.Count > 1)
{
Intent = TableIntent.Settings,
EnableScrolling = false,
HasUnevenRows = true,
EnableSelection = true,
NoFooter = true,
VerticalOptions = LayoutOptions.Start,
Root = new TableRoot
{
new TableSection(" ")
{
CodeCell
}
}
};
var codeLabel = new Label
{
Text = AppResources.EnterVerificationCode,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"],
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
};
var lostAppButton = new ExtendedButton
{
Text = AppResources.Lost2FAApp,
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
Margin = new Thickness(15, 0, 15, 25),
Command = new Command(() => Lost2FAApp()),
Uppercase = false,
BackgroundColor = Color.Transparent
};
var layout = new StackLayout
{
Children = { table, codeLabel, lostAppButton },
Spacing = 0
};
var scrollView = new ScrollView { Content = layout };
if(Device.RuntimePlatform == Device.iOS)
{
table.RowHeight = -1;
table.EstimatedRowHeight = 70;
var sendEmailTask = SendEmailAsync(false);
}
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
{
await LogInAsync();
}, ToolbarItemOrder.Default, 0);
ToolbarItems.Clear();
var scrollView = new ScrollView();
ToolbarItems.Add(continueToolbarItem);
Title = AppResources.VerificationCode;
Content = scrollView;
var anotherMethodButton = new ExtendedButton
{
Text = AppResources.UseAnotherTwoStepMethod,
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
Margin = new Thickness(15, 0, 15, 25),
Command = new Command(() => AnotherMethodAsync()),
Uppercase = false,
BackgroundColor = Color.Transparent,
VerticalOptions = LayoutOptions.Start
};
var instruction = new Label
{
LineBreakMode = LineBreakMode.WordWrap,
Margin = new Thickness(15),
HorizontalTextAlignment = TextAlignment.Center
};
RememberCell = new ExtendedSwitchCell
{
Text = AppResources.RememberMe,
On = false
};
if(!_providerType.HasValue)
{
instruction.Text = AppResources.NoTwoStepAvailable;
var layout = new StackLayout
{
Children = { instruction, anotherMethodButton },
Spacing = 0
};
scrollView.Content = layout;
Title = AppResources.LoginUnavailable;
Content = scrollView;
}
else if(_providerType.Value == TwoFactorProviderType.Authenticator ||
_providerType.Value == TwoFactorProviderType.Email)
{
var continueToolbarItem = new ToolbarItem(AppResources.Continue, null, async () =>
{
var token = TokenCell?.Entry.Text.Trim().Replace(" ", "");
await LogInAsync(token);
}, ToolbarItemOrder.Default, 0);
var padding = Helpers.OnPlatform(
iOS: new Thickness(15, 20),
Android: new Thickness(15, 8),
WinPhone: new Thickness(15, 20));
TokenCell = new FormEntryCell(AppResources.VerificationCode, useLabelAsPlaceholder: true,
imageSource: "lock", containerPadding: padding);
TokenCell.Entry.Keyboard = Keyboard.Numeric;
TokenCell.Entry.ReturnType = ReturnType.Go;
var table = new TwoFactorTable(
new TableSection(" ")
{
TokenCell,
RememberCell
});
var layout = new StackLayout
{
Children = { instruction, table },
Spacing = 0
};
scrollView.Content = layout;
switch(_providerType.Value)
{
case TwoFactorProviderType.Authenticator:
instruction.Text = AppResources.EnterVerificationCodeApp;
layout.Children.Add(anotherMethodButton);
break;
case TwoFactorProviderType.Email:
var emailParams = _providers[TwoFactorProviderType.Email];
var redactedEmail = emailParams["Email"].ToString();
instruction.Text = string.Format(AppResources.EnterVerificationCodeEmail, redactedEmail);
var resendEmailButton = new ExtendedButton
{
Text = AppResources.SendVerificationCodeAgain,
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
Margin = new Thickness(15, 0, 15, 0),
Command = new Command(async () => await SendEmailAsync(true)),
Uppercase = false,
BackgroundColor = Color.Transparent,
VerticalOptions = LayoutOptions.Start
};
layout.Children.Add(resendEmailButton);
layout.Children.Add(anotherMethodButton);
break;
default:
break;
}
ToolbarItems.Add(continueToolbarItem);
Title = AppResources.VerificationCode;
Content = scrollView;
TokenCell.Entry.FocusWithDelay();
}
else if(_providerType == TwoFactorProviderType.Duo)
{
var duoParams = _providers[TwoFactorProviderType.Duo];
var host = WebUtility.UrlEncode(duoParams["Host"].ToString());
var req = WebUtility.UrlEncode(duoParams["Signature"].ToString());
var webView = new HybridWebView
{
Uri = $"https://vault.bitwarden.com/duo-connector.html?host={host}&request={req}",
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
MinimumHeightRequest = 400
};
webView.RegisterAction(async (sig) =>
{
await LogInAsync(sig);
});
var table = new TwoFactorTable(
new TableSection(" ")
{
RememberCell
});
var layout = new StackLayout
{
Children = { webView, table, anotherMethodButton },
Spacing = 0
};
scrollView.Content = layout;
Title = "Duo";
Content = scrollView;
}
else if(_providerType == TwoFactorProviderType.YubiKey)
{
instruction.Text = AppResources.YubiKeyInstruction;
var image = new CachedImage
{
Source = "yubikey",
VerticalOptions = LayoutOptions.Start,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = 266,
HeightRequest = 160,
Margin = new Thickness(0, 0, 0, 25)
};
var table = new TwoFactorTable(
new TableSection(" ")
{
RememberCell
});
var layout = new StackLayout
{
Children = { instruction, image, table, anotherMethodButton },
Spacing = 0
};
scrollView.Content = layout;
Title = AppResources.YubiKeyTitle;
Content = scrollView;
}
}
protected override void OnAppearing()
{
base.OnAppearing();
CodeCell.InitEvents();
CodeCell.Entry.FocusWithDelay();
CodeCell.Entry.Completed += Entry_Completed;
ListenYubiKey(true);
InitEvents();
if(TokenCell == null && Device.RuntimePlatform == Device.Android)
{
MessagingCenter.Send(Application.Current, "DismissKeyboard");
}
}
private void InitEvents()
{
if(TokenCell != null)
{
TokenCell.InitEvents();
TokenCell.Entry.Completed += Entry_Completed;
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
CodeCell.Dispose();
CodeCell.Entry.Completed -= Entry_Completed;
ListenYubiKey(false);
if(TokenCell != null)
{
TokenCell.Dispose();
TokenCell.Entry.Completed -= Entry_Completed;
}
}
private void Lost2FAApp()
private async void AnotherMethodAsync()
{
Device.OpenUri(new Uri("https://help.bitwarden.com/article/lost-two-step-device/"));
var beforeProviderType = _providerType;
var options = new List<string>();
if(_providers.ContainsKey(TwoFactorProviderType.Authenticator))
{
options.Add(AppResources.AuthenticatorAppTitle);
}
if(_providers.ContainsKey(TwoFactorProviderType.Duo))
{
options.Add("Duo");
}
if(_providers.ContainsKey(TwoFactorProviderType.YubiKey))
{
var nfcKey = _providers[TwoFactorProviderType.YubiKey].ContainsKey("Nfc") &&
(bool)_providers[TwoFactorProviderType.YubiKey]["Nfc"];
if(_deviceInfoService.NfcEnabled && nfcKey)
{
options.Add(AppResources.YubiKeyTitle);
}
}
if(_providers.ContainsKey(TwoFactorProviderType.Email))
{
options.Add(AppResources.Email);
}
options.Add(AppResources.RecoveryCodeTitle);
var selection = await DisplayActionSheet(AppResources.TwoStepLoginOptions, AppResources.Cancel, null,
options.ToArray());
if(selection == AppResources.AuthenticatorAppTitle)
{
_providerType = TwoFactorProviderType.Authenticator;
}
else if(selection == "Duo")
{
_providerType = TwoFactorProviderType.Duo;
}
else if(selection == AppResources.YubiKeyTitle)
{
_providerType = TwoFactorProviderType.YubiKey;
}
else if(selection == AppResources.Email)
{
_providerType = TwoFactorProviderType.Email;
}
else if(selection == AppResources.RecoveryCodeTitle)
{
Device.OpenUri(new Uri("https://help.bitwarden.com/article/lost-two-step-device/"));
return;
}
if(beforeProviderType != _providerType)
{
Init();
ListenYubiKey(false, beforeProviderType == TwoFactorProviderType.YubiKey);
ListenYubiKey(true);
InitEvents();
}
}
private async Task SendEmailAsync(bool doToast)
{
if(_providerType != TwoFactorProviderType.Email)
{
return;
}
var response = await _twoFactorApiRepository.PostSendEmailLoginAsync(new Models.Api.TwoFactorEmailRequest
{
Email = _email,
MasterPasswordHash = _masterPasswordHash
});
if(response.Succeeded && doToast)
{
_userDialogs.Toast(AppResources.VerificationEmailSent);
}
else if(!response.Succeeded)
{
_userDialogs.Alert(AppResources.VerificationEmailNotSent);
}
}
private async void Entry_Completed(object sender, EventArgs e)
{
await LogInAsync();
var token = TokenCell.Entry.Text.Trim().Replace(" ", "");
await LogInAsync(token);
}
private async Task LogInAsync()
private async Task LogInAsync(string token)
{
if(string.IsNullOrWhiteSpace(CodeCell.Entry.Text))
if(!_providerType.HasValue || _lastAction.LastActionWasRecent())
{
return;
}
_lastAction = DateTime.UtcNow;
if(string.IsNullOrWhiteSpace(token))
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.VerificationCode), AppResources.Ok);
return;
}
_userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black);
var response = await _authService.TokenPostTwoFactorAsync(CodeCell.Entry.Text, _email, _masterPasswordHash, _key);
_userDialogs.ShowLoading(string.Concat(AppResources.Validating, "..."), MaskType.Black);
var response = await _authService.TokenPostTwoFactorAsync(_providerType.Value, token, RememberCell.On,
_email, _masterPasswordHash, _key);
_userDialogs.HideLoading();
if(!response.Success)
{
ListenYubiKey(true);
await DisplayAlert(AppResources.AnErrorHasOccurred, response.ErrorMessage, AppResources.Ok);
return;
}
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step");
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step", _providerType.Value.ToString());
if(Device.RuntimePlatform == Device.Android)
{
@@ -165,7 +408,128 @@ namespace Bit.App.Pages
}
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage();
Device.BeginInvokeOnMainThread(() =>
{
Application.Current.MainPage = new MainPage();
});
}
private TwoFactorProviderType? GetDefaultProvider()
{
TwoFactorProviderType? provider = null;
if(_providers != null)
{
foreach(var p in _providers)
{
switch(p.Key)
{
case TwoFactorProviderType.Authenticator:
if(provider == TwoFactorProviderType.Duo || provider == TwoFactorProviderType.YubiKey)
{
continue;
}
break;
case TwoFactorProviderType.Email:
if(provider.HasValue)
{
continue;
}
break;
case TwoFactorProviderType.Duo:
if(provider == TwoFactorProviderType.YubiKey)
{
continue;
}
break;
case TwoFactorProviderType.YubiKey:
var nfcKey = p.Value.ContainsKey("Nfc") && (bool)p.Value["Nfc"];
if(!_deviceInfoService.NfcEnabled || !nfcKey)
{
continue;
}
break;
default:
continue;
}
provider = p.Key;
}
}
return provider;
}
private void ListenYubiKey(bool listen, bool overrideCheck = false)
{
if(_providerType == TwoFactorProviderType.YubiKey || overrideCheck)
{
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", listen);
}
}
private void SubscribeYubiKey(bool subscribe)
{
if(_providerType != TwoFactorProviderType.YubiKey)
{
return;
}
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
if(!subscribe)
{
return;
}
MessagingCenter.Subscribe<Application, string>(Application.Current, "GotYubiKeyOTP", async (sender, otp) =>
{
MessagingCenter.Unsubscribe<Application, string>(Application.Current, "GotYubiKeyOTP");
if(_providerType == TwoFactorProviderType.YubiKey)
{
await LogInAsync(otp);
}
});
SubscribeYubiKeyResume();
}
private void SubscribeYubiKeyResume()
{
MessagingCenter.Subscribe<Application>(Application.Current, "ResumeYubiKey", (sender) =>
{
MessagingCenter.Unsubscribe<Application>(Application.Current, "ResumeYubiKey");
if(_providerType == TwoFactorProviderType.YubiKey)
{
MessagingCenter.Send(Application.Current, "ListenYubiKeyOTP", true);
SubscribeYubiKeyResume();
}
});
}
public class TwoFactorTable : ExtendedTableView
{
public TwoFactorTable(TableSection section)
{
Intent = TableIntent.Settings;
EnableScrolling = false;
HasUnevenRows = true;
EnableSelection = true;
NoFooter = true;
NoHeader = true;
VerticalOptions = LayoutOptions.Start;
Root = Root = new TableRoot
{
section
};
if(Device.RuntimePlatform == Device.iOS)
{
RowHeight = -1;
EstimatedRowHeight = 70;
}
}
}
}
}

161
src/App/Pages/ScanPage.cs Normal file
View File

@@ -0,0 +1,161 @@
using Bit.App.Controls;
using Bit.App.Resources;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using ZXing.Net.Mobile.Forms;
namespace Bit.App.Pages
{
public class ScanPage : ExtendedContentPage
{
private readonly ZXingScannerView _zxing;
private readonly OverlayGrid _overlay;
private bool _pageDisappeared = true;
public ScanPage(Action<string> callback)
: base(updateActivity: false)
{
_zxing = new ZXingScannerView
{
HorizontalOptions = LayoutOptions.FillAndExpand,
VerticalOptions = LayoutOptions.FillAndExpand,
AutomationId = "zxingScannerView",
Options = new ZXing.Mobile.MobileBarcodeScanningOptions
{
UseNativeScanning = true,
PossibleFormats = new List<ZXing.BarcodeFormat> { ZXing.BarcodeFormat.QR_CODE },
AutoRotate = false
}
};
_zxing.OnScanResult += (result) =>
{
// Stop analysis until we navigate away so we don't keep reading barcodes
_zxing.IsAnalyzing = false;
_zxing.IsScanning = false;
Uri uri;
if(!string.IsNullOrWhiteSpace(result.Text) && Uri.TryCreate(result.Text, UriKind.Absolute, out uri) &&
!string.IsNullOrWhiteSpace(uri.Query))
{
var queryParts = uri.Query.Substring(1).ToLowerInvariant().Split('&');
foreach(var part in queryParts)
{
if(part.StartsWith("secret="))
{
callback(part.Substring(7)?.ToUpperInvariant());
return;
}
}
}
callback(null);
};
_overlay = new OverlayGrid
{
AutomationId = "zxingDefaultOverlay"
};
_overlay.TopLabel.Text = AppResources.CameraInstructionTop;
_overlay.BottomLabel.Text = AppResources.CameraInstructionBottom;
var grid = new Grid
{
VerticalOptions = LayoutOptions.FillAndExpand,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { _zxing, _overlay }
};
if(Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
}
Title = AppResources.ScanQrTitle;
Content = grid;
}
protected override void OnAppearing()
{
_pageDisappeared = false;
base.OnAppearing();
_zxing.IsScanning = true;
Device.StartTimer(new TimeSpan(0, 0, 2), () =>
{
if(_pageDisappeared)
{
return false;
}
_zxing.AutoFocus();
return true;
});
}
protected override void OnDisappearing()
{
_pageDisappeared = true;
_zxing.IsScanning = false;
base.OnDisappearing();
}
public class OverlayGrid : Grid
{
public OverlayGrid()
{
VerticalOptions = LayoutOptions.FillAndExpand;
HorizontalOptions = LayoutOptions.FillAndExpand;
RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
RowDefinitions.Add(new RowDefinition { Height = new GridLength(2, GridUnitType.Star) });
RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
Children.Add(new BoxView
{
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.FillAndExpand,
BackgroundColor = Color.Black,
Opacity = 0.7,
}, 0, 0);
Children.Add(new BoxView
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.FillAndExpand,
BackgroundColor = Color.Transparent
}, 0, 1);
Children.Add(new BoxView
{
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.FillAndExpand,
BackgroundColor = Color.Black,
Opacity = 0.7,
}, 0, 2);
TopLabel = new Label
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.White,
AutomationId = "zxingDefaultOverlay_TopTextLabel",
};
Children.Add(TopLabel, 0, 0);
BottomLabel = new Label
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
TextColor = Color.White,
AutomationId = "zxingDefaultOverlay_BottomTextLabel",
};
Children.Add(BottomLabel, 0, 2);
}
public Label TopLabel { get; set; }
public Label BottomLabel { get; set; }
}
}
}

View File

@@ -88,9 +88,9 @@ namespace Bit.App.Pages
if(saveResult.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.FolderCreated);
_googleAnalyticsService.TrackAppEvent("CreatedFolder");
await Navigation.PopForDeviceAsync();
}
else if(saveResult.Errors.Count() > 0)
{

View File

@@ -102,9 +102,9 @@ namespace Bit.App.Pages
if(saveResult.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.FolderUpdated);
_googleAnalyticsService.TrackAppEvent("EditedFolder");
await Navigation.PopForDeviceAsync();
}
else if(saveResult.Errors.Count() > 0)
{
@@ -165,8 +165,8 @@ namespace Bit.App.Pages
if(deleteTask.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.FolderDeleted);
await Navigation.PopForDeviceAsync();
}
else if(deleteTask.Errors.Count() > 0)
{

View File

@@ -27,6 +27,8 @@ namespace Bit.App.Pages
}
private StackLayout StackLayout { get; set; }
private ExtendedSwitchCell CopyTotpCell { get; set; }
private Label CopyTotpLabel { get; set; }
private ExtendedSwitchCell AnalyticsCell { get; set; }
private Label AnalyticsLabel { get; set; }
private ExtendedSwitchCell AutofillPersistNotificationCell { get; set; }
@@ -38,6 +40,23 @@ namespace Bit.App.Pages
private void Init()
{
CopyTotpCell = new ExtendedSwitchCell
{
Text = AppResources.DisableAutoTotpCopy,
On = _settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false)
};
var totpTable = new FormTableView(true)
{
Root = new TableRoot
{
new TableSection(" ")
{
CopyTotpCell
}
}
};
AnalyticsCell = new ExtendedSwitchCell
{
Text = AppResources.DisableGA,
@@ -55,18 +74,19 @@ namespace Bit.App.Pages
}
};
AnalyticsLabel = new Label
CopyTotpLabel = new FormTableLabel(this)
{
Text = AppResources.DisbaleGADescription,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"],
Margin = new Thickness(15, (this.IsLandscape() ? 5 : 0), 15, 25)
Text = AppResources.DisableAutoTotpCopyDescription
};
AnalyticsLabel = new FormTableLabel(this)
{
Text = AppResources.DisableGADescription
};
StackLayout = new StackLayout
{
Children = { analyticsTable, AnalyticsLabel },
Children = { totpTable, CopyTotpLabel, analyticsTable, AnalyticsLabel },
Spacing = 0
};
@@ -78,7 +98,7 @@ namespace Bit.App.Pages
On = !_appSettings.AutofillPersistNotification && !_appSettings.AutofillPasswordField
};
var autofillAlwaysTable = new FormTableView
var autofillAlwaysTable = new FormTableView(true)
{
Root = new TableRoot
{
@@ -102,7 +122,6 @@ namespace Bit.App.Pages
var autofillPersistNotificationTable = new FormTableView
{
NoHeader = true,
Root = new TableRoot
{
new TableSection(" ")
@@ -125,7 +144,6 @@ namespace Bit.App.Pages
var autofillPasswordFieldTable = new FormTableView
{
NoHeader = true,
Root = new TableRoot
{
new TableSection(" ")
@@ -169,6 +187,7 @@ namespace Bit.App.Pages
base.OnAppearing();
AnalyticsCell.OnChanged += AnalyticsCell_Changed;
CopyTotpCell.OnChanged += CopyTotpCell_OnChanged;
StackLayout.LayoutChanged += Layout_LayoutChanged;
if(Device.RuntimePlatform == Device.Android)
@@ -184,6 +203,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
AnalyticsCell.OnChanged -= AnalyticsCell_Changed;
CopyTotpCell.OnChanged -= CopyTotpCell_OnChanged;
StackLayout.LayoutChanged -= Layout_LayoutChanged;
if(Device.RuntimePlatform == Device.Android)
@@ -197,6 +217,23 @@ namespace Bit.App.Pages
private void Layout_LayoutChanged(object sender, EventArgs e)
{
AnalyticsLabel.WidthRequest = StackLayout.Bounds.Width - AnalyticsLabel.Bounds.Left * 2;
CopyTotpLabel.WidthRequest = StackLayout.Bounds.Width - CopyTotpLabel.Bounds.Left * 2;
if(AutofillAlwaysLabel != null)
{
AutofillAlwaysLabel.WidthRequest = StackLayout.Bounds.Width - AutofillAlwaysLabel.Bounds.Left * 2;
}
if(AutofillPasswordFieldLabel != null)
{
AutofillPasswordFieldLabel.WidthRequest = StackLayout.Bounds.Width - AutofillPasswordFieldLabel.Bounds.Left * 2;
}
if(AutofillPersistNotificationLabel != null)
{
AutofillPersistNotificationLabel.WidthRequest =
StackLayout.Bounds.Width - AutofillPersistNotificationLabel.Bounds.Left * 2;
}
}
private void AnalyticsCell_Changed(object sender, ToggledEventArgs e)
@@ -211,6 +248,17 @@ namespace Bit.App.Pages
_googleAnalyticsService.SetAppOptOut(cell.On);
}
private void CopyTotpCell_OnChanged(object sender, ToggledEventArgs e)
{
var cell = sender as ExtendedSwitchCell;
if(cell == null)
{
return;
}
_settings.AddOrUpdateValue(Constants.SettingDisableTotpCopy, cell.On);
}
private void AutofillAlwaysCell_OnChanged(object sender, ToggledEventArgs e)
{
var cell = sender as ExtendedSwitchCell;
@@ -262,7 +310,7 @@ namespace Bit.App.Pages
private class FormTableView : ExtendedTableView
{
public FormTableView()
public FormTableView(bool header = false)
{
Intent = TableIntent.Settings;
EnableScrolling = false;
@@ -270,6 +318,7 @@ namespace Bit.App.Pages
EnableSelection = true;
VerticalOptions = LayoutOptions.Start;
NoFooter = true;
NoHeader = !header;
}
}

View File

@@ -73,10 +73,16 @@ namespace Bit.App.Pages
ShowDisclousure = true
};
LockCell = new ExtendedTextCell
{
Text = AppResources.Lock
};
var securitySecion = new TableSection(AppResources.Security)
{
LockOptionsCell,
PinCell,
LockCell,
TwoStepCell
};
@@ -117,11 +123,6 @@ namespace Bit.App.Pages
ShowDisclousure = true
};
LockCell = new ExtendedTextCell
{
Text = AppResources.Lock
};
LogOutCell = new ExtendedTextCell
{
Text = AppResources.LogOut
@@ -177,18 +178,14 @@ namespace Bit.App.Pages
new TableSection(AppResources.Account)
{
ChangeMasterPasswordCell,
ChangeEmailCell
ChangeEmailCell,
LogOutCell
},
new TableSection(AppResources.Manage)
{
FoldersCell,
SyncCell
},
new TableSection(AppResources.CurrentSession)
{
LockCell,
LogOutCell
},
otherSection
}
};

View File

@@ -12,6 +12,7 @@ namespace Bit.App.Pages
{
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly IAppInfoService _appInfoService;
private bool _pageDisappeared = false;
public ToolsAutofillServicePage()
{
@@ -170,6 +171,11 @@ namespace Bit.App.Pages
UpdateEnabled();
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
{
if(_pageDisappeared)
{
return false;
}
UpdateEnabled();
return true;
});
@@ -178,6 +184,18 @@ namespace Bit.App.Pages
Content = ScrollView;
}
protected override void OnAppearing()
{
_pageDisappeared = false;
base.OnAppearing();
}
protected override void OnDisappearing()
{
_pageDisappeared = true;
base.OnDisappearing();
}
private void UpdateEnabled()
{
ScrollView.Content = _appInfoService.AutofillServiceEnabled ? EnabledStackLayout : DisabledStackLayout;

View File

@@ -16,7 +16,7 @@ namespace Bit.App.Pages
private readonly IUserDialogs _userDialogs;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly Action<string> _passwordValueAction;
private readonly bool _fromAutofill;
@@ -26,7 +26,7 @@ namespace Bit.App.Pages
_userDialogs = Resolver.Resolve<IUserDialogs>();
_passwordGenerationService = Resolver.Resolve<IPasswordGenerationService>();
_settings = Resolver.Resolve<ISettings>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_passwordValueAction = passwordValueAction;
_fromAutofill = fromAutofill;

View File

@@ -51,6 +51,7 @@ namespace Bit.App.Pages
public FormEntryCell UsernameCell { get; private set; }
public FormEntryCell UriCell { get; private set; }
public FormEntryCell NameCell { get; private set; }
public FormEntryCell TotpCell { get; private set; }
public FormEditorCell NotesCell { get; private set; }
public FormPickerCell FolderCell { get; private set; }
public ExtendedTextCell GenerateCell { get; private set; }
@@ -58,7 +59,15 @@ namespace Bit.App.Pages
private void Init()
{
NotesCell = new FormEditorCell(height: 180);
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
TotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
useButton: true);
TotpCell.Button.Image = "camera";
TotpCell.Entry.DisableAutocapitalize = true;
TotpCell.Entry.Autocorrect = false;
TotpCell.Entry.FontFamily = Helpers.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: TotpCell.Entry,
useButton: true);
PasswordCell.Button.Image = "eye";
PasswordCell.Entry.DisableAutocapitalize = true;
@@ -115,6 +124,7 @@ namespace Bit.App.Pages
},
new TableSection(" ")
{
TotpCell,
FolderCell,
favoriteCell
},
@@ -132,7 +142,7 @@ namespace Bit.App.Pages
}
else if(Device.RuntimePlatform == Device.Android)
{
PasswordCell.Button.WidthRequest = 40;
PasswordCell.Button.WidthRequest = TotpCell.Button.WidthRequest = 40;
}
var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () =>
@@ -158,11 +168,12 @@ namespace Bit.App.Pages
var login = new Login
{
Uri = UriCell.Entry.Text?.Encrypt(),
Name = NameCell.Entry.Text?.Encrypt(),
Username = UsernameCell.Entry.Text?.Encrypt(),
Password = PasswordCell.Entry.Text?.Encrypt(),
Notes = NotesCell.Editor.Text?.Encrypt(),
Name = NameCell.Entry.Text.Encrypt(),
Uri = string.IsNullOrWhiteSpace(UriCell.Entry.Text) ? null : UriCell.Entry.Text.Encrypt(),
Username = string.IsNullOrWhiteSpace(UsernameCell.Entry.Text) ? null : UsernameCell.Entry.Text.Encrypt(),
Password = string.IsNullOrWhiteSpace(PasswordCell.Entry.Text) ? null : PasswordCell.Entry.Text.Encrypt(),
Notes = string.IsNullOrWhiteSpace(NotesCell.Editor.Text) ? null : NotesCell.Editor.Text.Encrypt(),
Totp = string.IsNullOrWhiteSpace(TotpCell.Entry.Text) ? null : TotpCell.Entry.Text.Encrypt(),
Favorite = favoriteCell.On
};
@@ -173,11 +184,10 @@ namespace Bit.App.Pages
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
var saveTask = await _loginService.SaveAsync(login);
_userDialogs.HideLoading();
if(saveTask.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.NewLoginCreated);
if(_fromAutofill)
{
@@ -187,6 +197,7 @@ namespace Bit.App.Pages
{
_googleAnalyticsService.TrackAppEvent("CreatedLogin");
}
await Navigation.PopForDeviceAsync();
}
else if(saveTask.Errors.Count() > 0)
{
@@ -220,8 +231,10 @@ namespace Bit.App.Pages
UriCell.InitEvents();
NameCell.InitEvents();
NotesCell.InitEvents();
TotpCell.InitEvents();
FolderCell.InitEvents();
PasswordCell.Button.Clicked += PasswordButton_Clicked;
TotpCell.Button.Clicked += TotpButton_Clicked;
GenerateCell.Tapped += GenerateCell_Tapped;
if(!_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
@@ -250,8 +263,10 @@ namespace Bit.App.Pages
UriCell.Dispose();
NameCell.Dispose();
NotesCell.Dispose();
TotpCell.Dispose();
FolderCell.Dispose();
PasswordCell.Button.Clicked -= PasswordButton_Clicked;
TotpCell.Button.Clicked -= TotpButton_Clicked;
GenerateCell.Tapped -= GenerateCell_Tapped;
}
@@ -261,6 +276,28 @@ namespace Bit.App.Pages
PasswordCell.Button.Image = "eye" + (!PasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty);
}
private async void TotpButton_Clicked(object sender, EventArgs e)
{
var scanPage = new ScanPage((key) =>
{
Device.BeginInvokeOnMainThread(async () =>
{
await Navigation.PopModalAsync();
if(!string.IsNullOrWhiteSpace(key))
{
TotpCell.Entry.Text = key;
_userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
}
else
{
_userDialogs.Alert(AppResources.AuthenticatorKeyReadError);
}
});
});
await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
}
private async void GenerateCell_Tapped(object sender, EventArgs e)
{
var page = new ToolsPasswordGeneratorPage((password) =>

View File

@@ -0,0 +1,323 @@
using System;
using System.Linq;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models.Page;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
using Bit.App.Utilities;
using Plugin.Connectivity.Abstractions;
using System.Collections.Generic;
using Bit.App.Models;
using System.Threading.Tasks;
namespace Bit.App.Pages
{
public class VaultAttachmentsPage : ExtendedContentPage
{
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IDeviceActionService _deviceActiveService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly ITokenService _tokenService;
private readonly ICryptoService _cryptoService;
private readonly string _loginId;
private Login _login;
private byte[] _fileBytes;
private DateTime? _lastAction;
private bool _canUseAttachments = true;
public VaultAttachmentsPage(string loginId)
: base(true)
{
_loginId = loginId;
_loginService = Resolver.Resolve<ILoginService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_deviceActiveService = Resolver.Resolve<IDeviceActionService>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_tokenService = Resolver.Resolve<ITokenService>();
_cryptoService = Resolver.Resolve<ICryptoService>();
Init();
}
public ExtendedObservableCollection<VaultAttachmentsPageModel.Attachment> PresentationAttchments { get; private set; }
= new ExtendedObservableCollection<VaultAttachmentsPageModel.Attachment>();
public ListView ListView { get; set; }
public StackLayout NoDataStackLayout { get; set; }
public StackLayout AddNewStackLayout { get; set; }
public Label FileLabel { get; set; }
public ExtendedTableView NewTable { get; set; }
public Label NoDataLabel { get; set; }
private void Init()
{
_canUseAttachments = _cryptoService.EncKey != null;
SubscribeFileResult(true);
var selectButton = new ExtendedButton
{
Text = AppResources.ChooseFile,
Command = new Command(async () => await _deviceActiveService.SelectFileAsync()),
Style = (Style)Application.Current.Resources["btn-primaryAccent"],
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
};
FileLabel = new Label
{
Text = AppResources.NoFileChosen,
Style = (Style)Application.Current.Resources["text-muted"],
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
HorizontalTextAlignment = TextAlignment.Center
};
AddNewStackLayout = new StackLayout
{
Children = { selectButton, FileLabel },
Orientation = StackOrientation.Vertical,
Padding = new Thickness(20, Helpers.OnPlatform(iOS: 10, Android: 20), 20, 20),
VerticalOptions = LayoutOptions.Start
};
NewTable = new ExtendedTableView
{
Intent = TableIntent.Settings,
HasUnevenRows = true,
NoFooter = true,
EnableScrolling = false,
EnableSelection = false,
VerticalOptions = LayoutOptions.Start,
Margin = new Thickness(0, Helpers.OnPlatform(iOS: 10, Android: 30), 0, 0),
Root = new TableRoot
{
new TableSection(AppResources.AddNewAttachment)
{
new ExtendedViewCell
{
View = AddNewStackLayout,
BackgroundColor = Color.White
}
}
}
};
ListView = new ListView(ListViewCachingStrategy.RecycleElement)
{
ItemsSource = PresentationAttchments,
HasUnevenRows = true,
ItemTemplate = new DataTemplate(() => new VaultAttachmentsViewCell()),
VerticalOptions = LayoutOptions.FillAndExpand
};
if(_tokenService.TokenPremium)
{
ListView.Footer = NewTable;
}
NoDataLabel = new Label
{
Text = AppResources.NoAttachments,
HorizontalTextAlignment = TextAlignment.Center,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
Style = (Style)Application.Current.Resources["text-muted"]
};
NoDataStackLayout = new StackLayout
{
VerticalOptions = LayoutOptions.Start,
Spacing = 0,
Margin = new Thickness(0, 40, 0, 0)
};
var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () =>
{
if(_lastAction.LastActionWasRecent() || _login == null)
{
return;
}
_lastAction = DateTime.UtcNow;
if(!_canUseAttachments)
{
await ShowUpdateKeyAsync();
return;
}
if(!_connectivity.IsConnected)
{
AlertNoConnection();
return;
}
if(_fileBytes == null)
{
await DisplayAlert(AppResources.AnErrorHasOccurred, string.Format(AppResources.ValidationFieldRequired,
AppResources.File), AppResources.Ok);
return;
}
_userDialogs.ShowLoading(AppResources.Saving, MaskType.Black);
var saveTask = await _loginService.EncryptAndSaveAttachmentAsync(_login, _fileBytes, FileLabel.Text);
_userDialogs.HideLoading();
if(saveTask.Succeeded)
{
_fileBytes = null;
FileLabel.Text = AppResources.NoFileChosen;
_userDialogs.Toast(AppResources.AttachementAdded);
_googleAnalyticsService.TrackAppEvent("AddedAttachment");
await LoadAttachmentsAsync();
}
else if(saveTask.Errors.Count() > 0)
{
await _userDialogs.AlertAsync(saveTask.Errors.First().Message, AppResources.AnErrorHasOccurred);
}
else
{
await _userDialogs.AlertAsync(AppResources.AnErrorHasOccurred);
}
}, ToolbarItemOrder.Default, 0);
Title = AppResources.Attachments;
Content = ListView;
if(_tokenService.TokenPremium)
{
ToolbarItems.Add(saveToolBarItem);
}
if(Device.RuntimePlatform == Device.iOS)
{
ListView.RowHeight = -1;
NewTable.RowHeight = -1;
NewTable.EstimatedRowHeight = 44;
NewTable.HeightRequest = 180;
ListView.BackgroundColor = Color.Transparent;
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
}
}
protected async override void OnAppearing()
{
base.OnAppearing();
ListView.ItemSelected += AttachmentSelected;
await LoadAttachmentsAsync();
if(_tokenService.TokenPremium && !_canUseAttachments)
{
await ShowUpdateKeyAsync();
}
}
protected override void OnDisappearing()
{
base.OnDisappearing();
ListView.ItemSelected -= AttachmentSelected;
}
private async Task LoadAttachmentsAsync()
{
_login = await _loginService.GetByIdAsync(_loginId);
if(_login == null)
{
await Navigation.PopForDeviceAsync();
return;
}
var attachmentsToAdd = _login.Attachments
.Select(a => new VaultAttachmentsPageModel.Attachment(a))
.OrderBy(s => s.Name);
PresentationAttchments.ResetWithRange(attachmentsToAdd);
AdjustContent();
}
private void AdjustContent()
{
if(PresentationAttchments.Count == 0)
{
NoDataStackLayout.Children.Clear();
NoDataStackLayout.Children.Add(NoDataLabel);
NoDataStackLayout.Children.Add(NewTable);
Content = NoDataStackLayout;
}
else
{
Content = ListView;
}
}
private async void AttachmentSelected(object sender, SelectedItemChangedEventArgs e)
{
var attachment = e.SelectedItem as VaultAttachmentsPageModel.Attachment;
if(attachment == null)
{
return;
}
((ListView)sender).SelectedItem = null;
if(!await _userDialogs.ConfirmAsync(AppResources.DoYouReallyWantToDelete, null, AppResources.Yes, AppResources.No))
{
return;
}
_userDialogs.ShowLoading(AppResources.Deleting, MaskType.Black);
var saveTask = await _loginService.DeleteAttachmentAsync(_login, attachment.Id);
_userDialogs.HideLoading();
if(saveTask.Succeeded)
{
_userDialogs.Toast(AppResources.AttachmentDeleted);
_googleAnalyticsService.TrackAppEvent("DeletedAttachment");
await LoadAttachmentsAsync();
}
else if(saveTask.Errors.Count() > 0)
{
await _userDialogs.AlertAsync(saveTask.Errors.First().Message, AppResources.AnErrorHasOccurred);
}
else
{
await _userDialogs.AlertAsync(AppResources.AnErrorHasOccurred);
}
}
private void AlertNoConnection()
{
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
AppResources.Ok);
}
private void SubscribeFileResult(bool subscribe)
{
MessagingCenter.Unsubscribe<Application, Tuple<byte[], string>>(Application.Current, "SelectFileResult");
if(!subscribe)
{
return;
}
MessagingCenter.Subscribe<Application, Tuple<byte[], string>>(
Application.Current, "SelectFileResult", (sender, result) =>
{
FileLabel.Text = result.Item2;
_fileBytes = result.Item1;
SubscribeFileResult(true);
});
}
private async Task ShowUpdateKeyAsync()
{
var confirmed = await _userDialogs.ConfirmAsync(AppResources.UpdateKey, AppResources.FeatureUnavailable,
AppResources.LearnMore, AppResources.Cancel);
if(confirmed)
{
Device.OpenUri(new Uri("https://help.bitwarden.com/article/update-encryption-key/"));
}
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Bit.App.Pages
{
private readonly ILoginService _loginService;
private readonly IDeviceInfoService _deviceInfoService;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly ISettingsService _settingsService;
private CancellationTokenSource _filterResultsCancellationTokenSource;
private readonly string _name;
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve<ILoginService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_settingsService = Resolver.Resolve<ISettingsService>();
UserDialogs = Resolver.Resolve<IUserDialogs>();
GoogleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();

View File

@@ -38,9 +38,11 @@ namespace Bit.App.Pages
public FormEntryCell UsernameCell { get; private set; }
public FormEntryCell UriCell { get; private set; }
public FormEntryCell NameCell { get; private set; }
public FormEntryCell TotpCell { get; private set; }
public FormEditorCell NotesCell { get; private set; }
public FormPickerCell FolderCell { get; private set; }
public ExtendedTextCell GenerateCell { get; private set; }
public ExtendedTextCell AttachmentsCell { get; private set; }
public ExtendedTextCell DeleteCell { get; private set; }
private void Init()
@@ -55,7 +57,15 @@ namespace Bit.App.Pages
NotesCell = new FormEditorCell(height: 180);
NotesCell.Editor.Text = login.Notes?.Decrypt(login.OrganizationId);
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: NotesCell.Editor,
TotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
useButton: true);
TotpCell.Entry.Text = login.Totp?.Decrypt(login.OrganizationId);
TotpCell.Button.Image = "camera";
TotpCell.Entry.DisableAutocapitalize = true;
TotpCell.Entry.Autocorrect = false;
TotpCell.Entry.FontFamily = Helpers.OnPlatform(iOS: "Courier", Android: "monospace", WinPhone: "Courier");
PasswordCell = new FormEntryCell(AppResources.Password, isPassword: true, nextElement: TotpCell.Entry,
useButton: true);
PasswordCell.Entry.Text = login.Password?.Decrypt(login.OrganizationId);
PasswordCell.Button.Image = "eye";
@@ -103,6 +113,12 @@ namespace Bit.App.Pages
On = login.Favorite
};
AttachmentsCell = new ExtendedTextCell
{
Text = AppResources.Attachments,
ShowDisclousure = true
};
DeleteCell = new ExtendedTextCell { Text = AppResources.Delete, TextColor = Color.Red };
var table = new ExtendedTableView
@@ -122,8 +138,10 @@ namespace Bit.App.Pages
},
new TableSection(" ")
{
TotpCell,
FolderCell,
favoriteCell
favoriteCell,
AttachmentsCell
},
new TableSection(AppResources.Notes)
{
@@ -143,7 +161,7 @@ namespace Bit.App.Pages
}
else if(Device.RuntimePlatform == Device.Android)
{
PasswordCell.Button.WidthRequest = 40;
PasswordCell.Button.WidthRequest = TotpCell.Button.WidthRequest = 40;
}
var saveToolBarItem = new ToolbarItem(AppResources.Save, null, async () =>
@@ -167,11 +185,17 @@ namespace Bit.App.Pages
return;
}
login.Uri = UriCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Name = NameCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Username = UsernameCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Password = PasswordCell.Entry.Text?.Encrypt(login.OrganizationId);
login.Notes = NotesCell.Editor.Text?.Encrypt(login.OrganizationId);
login.Name = NameCell.Entry.Text.Encrypt(login.OrganizationId);
login.Uri = string.IsNullOrWhiteSpace(UriCell.Entry.Text) ? null :
UriCell.Entry.Text.Encrypt(login.OrganizationId);
login.Username = string.IsNullOrWhiteSpace(UsernameCell.Entry.Text) ? null :
UsernameCell.Entry.Text.Encrypt(login.OrganizationId);
login.Password = string.IsNullOrWhiteSpace(PasswordCell.Entry.Text) ? null :
PasswordCell.Entry.Text.Encrypt(login.OrganizationId);
login.Notes = string.IsNullOrWhiteSpace(NotesCell.Editor.Text) ? null :
NotesCell.Editor.Text.Encrypt(login.OrganizationId);
login.Totp = string.IsNullOrWhiteSpace(TotpCell.Entry.Text) ? null :
TotpCell.Entry.Text.Encrypt(login.OrganizationId);
login.Favorite = favoriteCell.On;
if(FolderCell.Picker.SelectedIndex > 0)
@@ -190,9 +214,9 @@ namespace Bit.App.Pages
if(saveTask.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.LoginUpdated);
_googleAnalyticsService.TrackAppEvent("EditeLogin");
await Navigation.PopForDeviceAsync();
}
else if(saveTask.Errors.Count() > 0)
{
@@ -226,16 +250,25 @@ namespace Bit.App.Pages
UriCell?.InitEvents();
NameCell?.InitEvents();
NotesCell?.InitEvents();
TotpCell?.InitEvents();
FolderCell?.InitEvents();
if(PasswordCell?.Button != null)
{
PasswordCell.Button.Clicked += PasswordButton_Clicked;
}
if(TotpCell?.Button != null)
{
TotpCell.Button.Clicked += TotpButton_Clicked;
}
if(GenerateCell != null)
{
GenerateCell.Tapped += GenerateCell_Tapped;
}
if(AttachmentsCell != null)
{
AttachmentsCell.Tapped += AttachmentsCell_Tapped;
}
if(DeleteCell != null)
{
DeleteCell.Tapped += DeleteCell_Tapped;
@@ -246,6 +279,7 @@ namespace Bit.App.Pages
{
base.OnDisappearing();
PasswordCell?.Dispose();
TotpCell?.Dispose();
UsernameCell?.Dispose();
UriCell?.Dispose();
NameCell?.Dispose();
@@ -256,10 +290,18 @@ namespace Bit.App.Pages
{
PasswordCell.Button.Clicked -= PasswordButton_Clicked;
}
if(TotpCell?.Button != null)
{
TotpCell.Button.Clicked -= TotpButton_Clicked;
}
if(GenerateCell != null)
{
GenerateCell.Tapped -= GenerateCell_Tapped;
}
if(AttachmentsCell != null)
{
AttachmentsCell.Tapped -= AttachmentsCell_Tapped;
}
if(DeleteCell != null)
{
DeleteCell.Tapped -= DeleteCell_Tapped;
@@ -272,6 +314,28 @@ namespace Bit.App.Pages
PasswordCell.Button.Image = "eye" + (!PasswordCell.Entry.IsPasswordFromToggled ? "_slash" : string.Empty);
}
private async void TotpButton_Clicked(object sender, EventArgs e)
{
var scanPage = new ScanPage((key) =>
{
Device.BeginInvokeOnMainThread(async () =>
{
await Navigation.PopModalAsync();
if(!string.IsNullOrWhiteSpace(key))
{
TotpCell.Entry.Text = key;
_userDialogs.Toast(AppResources.AuthenticatorKeyAdded);
}
else
{
_userDialogs.Alert(AppResources.AuthenticatorKeyReadError);
}
});
});
await Navigation.PushModalAsync(new ExtendedNavigationPage(scanPage));
}
private async void GenerateCell_Tapped(object sender, EventArgs e)
{
if(!string.IsNullOrWhiteSpace(PasswordCell.Entry.Text)
@@ -288,6 +352,12 @@ namespace Bit.App.Pages
await Navigation.PushForDeviceAsync(page);
}
private async void AttachmentsCell_Tapped(object sender, EventArgs e)
{
var page = new ExtendedNavigationPage(new VaultAttachmentsPage(_loginId));
await Navigation.PushModalAsync(page);
}
private async void DeleteCell_Tapped(object sender, EventArgs e)
{
if(!_connectivity.IsConnected)
@@ -307,9 +377,9 @@ namespace Bit.App.Pages
if(deleteTask.Succeeded)
{
await Navigation.PopForDeviceAsync();
_userDialogs.Toast(AppResources.LoginDeleted);
_googleAnalyticsService.TrackAppEvent("DeletedLogin");
await Navigation.PopForDeviceAsync();
}
else if(deleteTask.Errors.Count() > 0)
{

View File

@@ -24,7 +24,7 @@ namespace Bit.App.Pages
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
private readonly IClipboardService _clipboardService;
private readonly IDeviceActionService _clipboardService;
private readonly ISyncService _syncService;
private readonly IPushNotification _pushNotification;
private readonly IDeviceInfoService _deviceInfoService;
@@ -41,7 +41,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve<ILoginService>();
_connectivity = Resolver.Resolve<IConnectivity>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_clipboardService = Resolver.Resolve<IClipboardService>();
_clipboardService = Resolver.Resolve<IDeviceActionService>();
_syncService = Resolver.Resolve<ISyncService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();

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