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

Compare commits

...

196 Commits

Author SHA1 Message Date
André Bispo
fdc05d3cdf Merge branch 'feature/pm-1208-f3-options' into auth/pm-2293-tde-auth-requests
# Conflicts:
#	src/App/Pages/Accounts/LoginPasswordlessRequestViewModel.cs
2023-07-28 00:04:38 +01:00
André Bispo
c25906206e [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642) 2023-07-27 21:18:36 +01:00
André Bispo
148b2ad048 [PM-2293] PR fixes 2023-07-27 18:22:07 +01:00
André Bispo
6edac4f296 [PM-2408] Pending admin approval requests (#2643)
* [PM-2408] Save PendingAdminAuthRequest to state

* [PM-2408] Add logic to check pending auth requests before creating a new one.

* [PM-2408] Remove active user params

* [PM-2408] Fix bug where TDE MP unlock wasn't syncing and vault appeared empty

* [PM-2408] Add reconciledOptions

* [PM-2408] Set user key when login using master password

* [PM-2408] Generate Fingerprint phrase and PubKey locally to avoid MITM attacks

* [PM-2408] inheritdoc

* [PM-1280] TDE 2FA - approval options navigation (#2646)

* [PM-1208] Add navigation to device approval options after 2FA auth when TDE is enabled

* [PM-1208] Add navigations to iOS extensions

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2023-07-27 18:17:04 +01:00
André Bispo
5fda15bbc7 [PM-2293] PR fixes 2023-07-27 17:13:37 +01:00
André Bispo
d3023c101b Merge branch 'feature/pm-1208-f3-options' into auth/pm-2293-tde-auth-requests
# Conflicts:
#	src/Core/Services/AuthService.cs
2023-07-27 17:08:46 +01:00
André Bispo
dfc7c55b77 [PM-2297] Login with trusted device (Flow 2) (#2623)
* [PM-2297] Add DecryptUserKeyWithDeviceKey method

* [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model

* [PM-2297] Update account decryption options model

* [PM-2297] Fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id

* [PM-2297] Update navigation to decryption options

* [PM-2297] Add missing action navigations to iOS extensions

* [PM-2297] Fix trust device bug/typo

* [PM-2297] Fix model bug

* [PM-2297] Fix state var crash

* [PM-2297] Add trust device login logic to auth service

* [PM-2297] Refactor auth service key connector code

* [PM-2297] Remove reconciledOptions for deviceKey in state service

* [PM-2297] Remove unnecessary user id params
2023-07-27 16:55:06 +01:00
André Bispo
7901931899 Merge branch 'feature/pm-2297-f2-trusted' into auth/pm-2293-tde-auth-requests 2023-07-27 12:27:40 +01:00
André Bispo
8ff79d0e86 [PM-2297] PR fixes 2023-07-27 12:27:08 +01:00
André Bispo
a938a3f0fc [PM-2293] PR fixes 2023-07-27 12:18:01 +01:00
André Bispo
0f776074c8 [PM-2293] code format 2023-07-27 12:14:26 +01:00
André Bispo
2208ae67fb [PM-2293] Check for user key only 2023-07-27 10:14:09 +01:00
André Bispo
9b7dc7210f [PM-2297] remove uncessary user id params 2023-07-25 21:46:37 +01:00
André Bispo
28addfeb43 [PM-2293] Remove userid params 2023-07-25 17:39:57 +01:00
André Bispo
13a46fae5a Merge branch 'feature/pm-2297-f2-trusted' into auth/pm-2293-tde-auth-requests 2023-07-25 09:44:02 +01:00
André Bispo
52aabe8237 [PM-2297] Remove reconciledOptions for deviceKey in state service 2023-07-24 22:52:25 +01:00
André Bispo
eceee581c9 [PM-2297] Refactor auth service key connector code 2023-07-24 22:23:17 +01:00
André Bispo
15be0f8c2e [PM-2293] Fix admin approval auth call 2023-07-24 13:37:43 +01:00
André Bispo
01ed99e4b1 [PM-2293] Fix login with device normal flow and SSO 2023-07-24 13:07:06 +01:00
André Bispo
e2841e8d89 [PM-2297] remove unnecessary using 2023-07-24 10:47:37 +01:00
André Bispo
9b02879782 [PM-2293] Change endpoint based on request type 2023-07-24 10:46:59 +01:00
André Bispo
dd2cd0d9df [PM-2297] code format 2023-07-24 09:39:42 +01:00
André Bispo
661ff3da5c [PM-2297] Add trust device login logic to auth service 2023-07-24 09:29:44 +01:00
André Bispo
cdacbafae4 [PM-2297] Fix state var crash 2023-07-24 09:29:06 +01:00
André Bispo
acf7d9d9c4 [PM-2297] Fix model bug 2023-07-24 09:28:41 +01:00
André Bispo
f12cc69b60 [PM-2297] Fix trust device bug/typo 2023-07-24 09:28:10 +01:00
André Bispo
2465a28667 Merge branch 'feature/pm-1208-f3-options' into feature/pm-2297-f2-trusted 2023-07-20 15:40:05 +01:00
André Bispo
080aabfe82 [PM-1208] Fix merge 2023-07-20 15:39:03 +01:00
André Bispo
b7a8e0ab07 Merge branch 'feature/pm-1208-f3-options' into feature/pm-2297-f2-trusted 2023-07-20 15:37:24 +01:00
André Bispo
c0688c584e [PM-1208] Fix merge 2023-07-20 15:37:08 +01:00
André Bispo
c09672ff88 [PM-1208] Fix app resource file 2023-07-20 15:30:29 +01:00
André Bispo
80eec8d81e Merge branch 'feature/pm-1208-f3-options' into feature/pm-2297-f2-trusted 2023-07-20 14:56:56 +01:00
André Bispo
635b6bc184 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Pages/Settings/SettingsPage/SettingsPageViewModel.cs
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IStateService.cs
2023-07-20 14:56:40 +01:00
Jacob Fink
da7a1964ef Update crypto service api call to fix build 2023-07-20 09:28:56 -04:00
Jacob Fink
73b8d8e6b8 fix bad merge 2023-07-20 09:20:31 -04:00
Jacob Fink
c61f9f0357 Merge branch 'auth/pm-2713/drop-master-key-dependency' into feature/pm-1029-tde-login 2023-07-20 09:19:48 -04:00
Jacob Fink
a3183857b9 [PM-2713] set decrypt and set user key in login helper 2023-07-19 21:23:21 -04:00
Jacob Fink
bedbca841d [PM-2713] remove unused cached values in crypto service 2023-07-19 21:23:21 -04:00
Jacob Fink
0ff314f076 [PM-2713] use new crypto service api in auth service 2023-07-19 21:23:21 -04:00
Jacob Fink
c9a7c29190 [PM-2713] More conversions to crypto api 2023-07-19 21:23:20 -04:00
Jacob Fink
546bf8dcb1 [PM-2713] convert cipher service and others to crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7fdc5597fc [PM-2713] more conversions to new crypto service api 2023-07-19 21:23:20 -04:00
Jacob Fink
7c664f58b3 [PM-2713] add migration for pin on lock screens 2023-07-19 21:23:20 -04:00
Jacob Fink
bdfe806846 [PM-2713] converting calls to new crypto service api 2023-07-19 21:23:19 -04:00
Jacob Fink
5ed567ab90 [PM-2713] add toggle method to crypto service for keys 2023-07-19 21:23:19 -04:00
Jacob Fink
cd4f44e6f6 [PM-2713] use new MakeMasterKey method 2023-07-19 21:23:19 -04:00
Jacob Fink
d58f0b281b [PM-2713] refresh pin key when setting user key 2023-07-19 21:23:19 -04:00
Jacob Fink
5ba3fac0c0 [PM-2713] add make user key method to crypto service 2023-07-19 21:23:19 -04:00
Jacob Fink
1e30524985 [PM-2713] fix signature of GetUserKeyPin 2023-07-19 21:23:18 -04:00
Jacob Fink
515decb4c9 [PM-2713] add new pin methods to state service 2023-07-19 21:23:18 -04:00
Jacob Fink
bf28d373e9 [PM-2713] more updates to crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
69d38d4d75 [PM-2713] continue organizing crypto service 2023-07-19 21:23:18 -04:00
Jacob Fink
c1619536aa [PM-2713] rename key hash to password hash & begin add methods to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
079e02e4e5 [PM-271] add UserKey and MasterKey support to crypto service 2023-07-19 21:23:17 -04:00
Jacob Fink
15d3da607b [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)
2023-07-19 21:23:17 -04:00
Jacob Fink
b5cf9fd79d [PM-2731] add user key and master key types 2023-07-19 21:23:17 -04:00
André Bispo
848e620647 [PM-2297] Add missing action navigations to iOS extensions 2023-07-19 11:28:47 +01:00
André Bispo
4e886de103 [PM-2297] update navigation to decryption options 2023-07-19 10:46:04 +01:00
André Bispo
0e450904c5 [PM-2297] fix TrustedDeviceOption and DeviceResponse model. Change StateService device key get set to have default user id 2023-07-18 18:01:42 +01:00
Federico Maccaroni
dd52ff0dcc [PM-2320] Improve Android block Auto-fill URIs (#2616)
* PM-2320 Added new view for block autofill URIs on Android

* PM-2320 Fix formatting

* PM-2320 Improved validations on block autofill uris

* PM-2320 Improved autofill block uris placeholder colors on different themes
2023-07-18 11:25:38 -03:00
github-actions[bot]
c678c17ebc Bumped version to 2023.7.1 (#2625)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-18 14:23:08 +00:00
André Bispo
5e1229f943 [PM-2297] Update account decryption options model 2023-07-18 11:34:39 +01:00
Federico Maccaroni
cd9e49b13b ac-1425 added main thread invocations when updating the vault properties to fix cases where the screen stays blank and doesn't update (#2604) 2023-07-17 16:53:30 -03:00
Federico Maccaroni
6d7970f767 [AC-762] Configure Crowdin to localize watch app (#2552)
* AC-762 Added localization files to watch project

* AC-762 Added crowdin config for watchOS localizable files
2023-07-17 13:35:54 -03:00
André Bispo
9b6b2c005e [PM-2297] Add methods to DeviceTrustCryptoService update decryption options model 2023-07-17 15:30:15 +01:00
mpbw2
9adc4d3080 Catch additional exception types when validating intents (#2618) 2023-07-17 08:40:35 -04:00
ifernandezdiaz
1f20f70d13 Fixing show value id button (#2620) 2023-07-16 20:13:55 -03:00
Vince Grassia
a25da68437 Fix syntax in Version Auto Bump workflow (#2615) 2023-07-13 11:55:16 -04:00
André Bispo
e9ac1e3b5b [PM-2297] Add DecryptUserKeyWithDeviceKey method 2023-07-13 15:41:08 +01:00
Vince Grassia
fdc0313d10 Fix Build Workflow (#2613) 2023-07-13 10:05:57 -04:00
André Bispo
b688b85d0f [PM-1201] Change timeout actions available based on hasMasterPassword (#2610)
* [PM-1201] Change timeout actions available based on hasMasterPassword
2023-07-12 20:42:21 +01:00
André Bispo
a5df6c0c65 [PM-2287][PM-2289][PM-2293] Approval Options (#2608)
* [PM-2293] Add AuthRequestType to PasswordlessLoginPage.

* [PM-2293] Add Actions to ApproveWithDevicePage

* [PM-2293] Change screen text based on AuthRequestType

* [PM-2293] Refactor AuthRequestType enum. Add label. Remove unnecessary actions.

* [PM-2293] Change boolean variable expression.

* [PM-2293] Trust device after admin request login.

* code format

* [PM-2287] Add trust device to master password unlock. Change trust device method. Remove email from SSO login page.

* [PM-2293] Fix state variable get set.

* [PM-2287][PM-2289][PM-2293] Rename method
2023-07-12 19:12:57 +01:00
github-actions[bot]
f31c87b52e Bumped version to 2023.7.0 (#2612)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-12 17:34:23 +00:00
André Bispo
c2d4fa4429 [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609) 2023-07-11 23:05:35 +01:00
Cesar Gonzalez
1e79e1182f [PM-1063] Re-prompt for Master Password Can be Bypassed When Using Gboard Inline Autofill (#2593) 2023-07-11 08:15:37 -05:00
André Bispo
548bd12a8e Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-10 12:37:13 +01:00
André Bispo
58542fd255 Merge branch 'master' into feature/pm-1029-tde-login 2023-07-10 12:36:59 +01:00
André Bispo
800b4c71de Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/Core/Models/Response/DeviceResponse.cs
#	src/Core/Services/ApiService.cs
2023-07-10 12:32:27 +01:00
github-actions[bot]
11947ce99a Autosync the updated translations (#2603)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-07 05:13:18 +00:00
Jake Fink
3053eaa036 [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535)
* [PM-1379] add DeviceCryptoService with establish trust logic

* PM-1379 update api location and other minor refactors

* pm-1379 fix encoding

* update trusted device keys api call to Put

* [PM-1379] rename DeviceCryptoService to DeviceTrustCryptoService
- refactors to prevent side effects

* [PM-1379] rearrange methods in DeviceTrustCryptoService

* [PM-1379] rearrange methods in abstraction

* [PM-1379] deconstruct tuples

* [PM-1379] remove extra tasks
2023-07-05 16:13:20 -04:00
mpbw2
4abb472998 Revert "reset lock delay when returning from activity result (#2539)" (#2597)
This reverts commit 0288a6659c.
2023-07-03 09:56:10 -04:00
André Bispo
6268f0776b Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options 2023-07-03 10:34:53 +01:00
André Bispo
cbbc41be67 [PM-1208] Add continue button and not you option 2023-07-03 10:34:02 +01:00
github-actions[bot]
1d541e5b8e Autosync the updated translations (#2595)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-30 07:06:12 +00:00
ifernandezdiaz
175b9936b6 [PM-2798] Fixing Toolbar locators in FolderAddEditPage (#2592)
* Fixing Toolbar locators

* Adding Matt's suggestions
2023-06-29 15:50:01 -03:00
ifernandezdiaz
72e67bd6f2 [PM-2691] Adding AutomationIDs for Vault Page sections (#2580)
* Adding IDs for Vault Page sections

* Removing extra spaces

* Adding Matt's comments

* Fixing Filters Id bug

* Adding Fede's suggestions

* Fixing Settings Ids issues

* Fixing AutomationIds issues with RecyclerViews + implementing AutomationId helper class

* Adding Fede's suggestion

* Adding latest Fede's suggestions
2023-06-29 15:37:08 -03:00
André Bispo
e164fb9823 Merge branch 'feature/pm-1029-tde-login' into feature/pm-1208-f3-options
# Conflicts:
#	src/App/Resources/AppResources.resx
#	src/Core/Abstractions/IApiService.cs
#	src/Core/Services/StateService.cs
2023-06-29 14:36:46 +01:00
André Bispo
87866304a6 [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account. 2023-06-28 22:37:08 +01:00
ifernandezdiaz
216c6abcf6 [PM-2737] Adding AutomationIDs for Send page elements (#2583)
* Adding AutomationIDs for Send page elements

* Fixing some spaces

* Adding Matt's suggestion

* Adding Fede's suggestion

* Removing unnecesarry breaks
2023-06-28 14:07:03 -04:00
Federico Maccaroni
1014563c75 [PM-192] Refactor forwarded email providers (#2579)
* PM-192 Refactor Forwarded email providers to use better patterns and code reuse.

* PM-192 fix format
2023-06-27 18:49:38 -03:00
ifernandezdiaz
3506269811 [PM-2688] Adding IDs for Options and Folders pages (#2585)
* Adding IDs for Options and Folders pages

* Fixing extra spaces
2023-06-26 10:31:57 -03:00
ifernandezdiaz
31487a31bb [PM-2748] Refactoring locator strategy for Cipher Details page (#2586)
* Refactoring locator strategy for Cipher Details page

* Fixing extra spaces
2023-06-26 10:30:13 -03:00
ifernandezdiaz
1407aa5655 [PM-2678] Adding IDs for Settings Page elements (#2584)
* Adding IDS for Settings elements

* Adding IDS for Settings elements
2023-06-23 13:31:24 -03:00
github-actions[bot]
16f59e2698 Autosync the updated translations (#2582)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-23 05:40:37 +00:00
ifernandezdiaz
d876b54f45 Adding IDs for AttachmentPage elements (#2577) 2023-06-20 17:49:26 -03:00
ifernandezdiaz
6644e3b449 [PM-2545] Adding Automation IDs for CipherDetailsPage elements (#2576)
* Adding IDs to CipherDetailsPage

* Fixing extra spaces

* Fixing extra space
2023-06-20 16:26:43 -03:00
ifernandezdiaz
8d98d1d5bd [PM-2612] Adding AutomationIDs for LoginPasswordlessPage elements (#2574)
* Adding AutomationIDs for LoginPasswordlessPag elements

* Adding AutomationIDs for LoginPasswordlessRequest page elements

* Fixing missing space
2023-06-20 15:12:15 -03:00
ifernandezdiaz
3e9711f8f2 [PM-2611] Adding IDs for Cipher/Send search results (#2575)
* Adding IDs for Cipher/Send search results

* Adding missing spaces
2023-06-20 11:28:54 -03:00
github-actions[bot]
3af37f01d3 Autosync the updated translations (#2570)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-19 09:05:12 +00:00
ifernandezdiaz
43d2d386b1 [PM-2645] Adding IDs for Account Switching elements (#2572)
* Adding IDs for Account Switching elements

* Fixing Active/Inactive vault icon IDs
2023-06-16 16:20:09 -03:00
ifernandezdiaz
bc5c11b47f Adding AutomationIDs on Generator page elements (#2569)
* Adding AutomationIDs on Generator pages

* Adding missing spaces
2023-06-15 16:11:55 -03:00
ifernandezdiaz
52843b4181 [PM-2544] Adding AutomationIDs for CipherAddEditViewPage elements (#2564)
* Adding AutomationIDs for Add/Edit Items page

* Adding IDs to CustomFields

* Adding Matt's suggestions

* Adding newest suggestions
2023-06-14 09:34:38 -03:00
Federico Maccaroni
98705e443f PM-2575 Fixed extension freeze when using the return button on the keyboard when unlocking the extension. Also added way to prevent multiple executions of checking the password and logging exceptions. (#2568) 2023-06-13 22:38:08 +02:00
mpbw2
1332ef7b43 Enhancement to login field detection for Android autofill (#2561) 2023-06-13 13:54:28 -04:00
mpbw2
04e30c2146 Update F-Droid listing author name (#2501)
Update F-Droid listing author name to `Bitwarden Inc`
2023-06-13 18:46:41 +02:00
Opeyemi
f604da13a1 add more comment to missing actions (#2567) 2023-06-13 15:57:02 +01:00
github-actions[bot]
dcf9acb51c Autosync the updated translations (#2562)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-09 09:19:34 +02:00
Federico Maccaroni
3b087c50ae PM-1076 added warning on unlocking iOS extensions when the kdf type is argon2id and the memory is higher than 48MB, to let the user know that unlocking might crash the extension (#2560) 2023-06-07 16:21:51 +02:00
ifernandezdiaz
1c13ed9895 [PS-2558] Mobile Automation - Starting automationIDs additions to our codebase (#2558)
* Adding locators for Environment, Hope, Login and Register pages

* Adding Locators on LockPage

* Adding Álison's suggestions
2023-06-06 21:00:01 -03:00
Federico Maccaroni
eeb634e698 PM-1798 Added accessibility names on entries on cipher add (#2550) 2023-06-05 18:58:38 +02:00
github-actions[bot]
8bc2df6c8a Autosync the updated translations (#2555)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-04 16:23:34 +02:00
github-actions[bot]
7cd40d4d89 Bumped version to 2023.5.1 (#2554)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-06-01 12:18:12 -04:00
Federico Maccaroni
bebf23785d PM-2232 Fix api response not being read as string because the content was not being considered json when it was indeed. Now Netacea messages are shown on the UI. (#2541) 2023-06-01 10:35:35 +03:00
github-actions[bot]
e78833cbcb Bumped version to 2023.5.0 (#2553)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-31 09:33:47 -04:00
github-actions[bot]
b7ff636862 Autosync the updated translations (#2540)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-26 08:12:16 +02:00
mpbw2
0288a6659c reset lock delay when returning from activity result (#2539) 2023-05-25 11:43:45 -04:00
André Bispo
c7fd113f26 [PM-2347] Refresh feature flags when environment urls change (#2538) 2023-05-25 14:37:53 +01:00
Michał Chęciński
79241731e7 Add github actions to renovate (#2536)
* Add github actions to renovate

* Add gh actions manager

* Apply whole renovate config

* Add newline
2023-05-24 16:04:39 +02:00
github-actions[bot]
74e9914f5b Autosync the updated translations (#2531)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-22 11:35:15 +02:00
André Bispo
65307f6eab [PM-1351][PM-190] Add a mobile service to retrieve feature flags from API (#2431) 2023-05-19 12:42:41 +01:00
github-actions[bot]
e9f83aee90 Autosync the updated translations (#2524)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-19 08:28:02 +02:00
André Bispo
84a82f0876 [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added. 2023-05-17 17:46:45 +01:00
Federico Maccaroni
fdaf743868 PM-2249 Fix vault timeout action policy check (#2521) 2023-05-15 15:28:18 +02:00
github-actions[bot]
9d6b938ba9 Autosync the updated translations (#2519)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-11 19:42:27 +02:00
Federico Maccaroni
1c8328f62d [PM-1402] Refactor PasswordGenerationService alongside PolicyService (#2443)
* PM-1402 Refactor pass generation service alongside policyservice

* PM-1402 Refactor PasswordGenerationService and PolicyService to have a simpler code and more specific to each class

* PM-1402 Fix format

* PM-1402 Moved policy consts from PolicyService to Policy

* PM-1402 fix crash due to lack of null checking

* PM-1402 fix format

* PM-1402 removed GetValueOrDefault() given that it was not needed and was changing the behavior
2023-05-11 18:41:32 +02:00
mp-bw
f24b82f345 Dependency Updates (#2517) 2023-05-11 11:13:30 -04:00
Federico Maccaroni
37f1a7087e [PM-1748] Fix Watch TOTP details on Always On Display (#2515)
* PM-1748 Fix watchOS issue where the TOTP code wasn't being regenerated after always on display. Also, blurred totp code and timer value when entering in Always On Display

* Fixed PR labeler for WatchOS changes

* PM-1748 watchOS made username privacy sensitive for always on display

* Revert "Fixed PR labeler for WatchOS changes"

This reverts commit 3c55f38069.

---------

Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
2023-05-10 16:59:49 +02:00
Opeyemi
6bb654e630 update all actions for version pin (#2512) 2023-05-06 01:22:58 +01:00
Álison Fernandes
fc260f8159 PR Labeler: fix for paths with dots (#2511)
* PR labeler: Added '' to paths with dots in
2023-05-05 21:39:29 +02:00
Federico Maccaroni
bf463926a3 PM-1798 Fix voice over on buttons when adding new item from iOS extension (#2510) 2023-05-05 20:18:01 +02:00
Federico Maccaroni
c1673a1bbf PM-1352 Fix avatar toolbar item not loading on OTP cipher selection (#2507) 2023-05-05 18:18:07 +02:00
github-actions[bot]
7b44395e1a Autosync the updated translations (#2506)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-05 10:10:37 +02:00
Álison Fernandes
0f3529aab8 Added src/App to PR Labeler (#2504) 2023-05-04 15:23:19 -07:00
Álison Fernandes
a72779997c Adding a Pull Request Labeler workflow (#2503)
* Added a Pull Request Labeler workflow

This workflow will add labels based on the PR file path changes

* Update .github/workflows/pr-labeler.yml

Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>

---------

Co-authored-by: Joseph Flinn <58369717+joseph-flinn@users.noreply.github.com>
2023-05-04 22:48:38 +01:00
Rico Acosta
49da536c7a Update config.yml (#2373)
Moving Customer Support to top as requested by Aaron Marshall
2023-05-04 21:22:31 +01:00
github-actions[bot]
c985c0a62b Autosync the updated translations (#2502)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-05-03 21:02:39 +02:00
mp-bw
0f417b8434 [PM-1817] Expand biometric integrity checks to the account level (#2498)
* Change bio integrity validation to work at account-level

* biometric state migration

* fix account bio valid key storage location during migration

* comment clarification

* fix for iOS extensions not using custom avatar color
2023-05-01 09:47:00 -04:00
github-actions[bot]
4f0238122b Autosync the updated translations (#2499)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-28 07:53:49 +02:00
github-actions[bot]
52ff634f00 Bumped version to 2023.4.1 (#2497)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-27 15:13:50 +02:00
github-actions[bot]
e820537fce Bumped version to 2023.4.0 (#2496)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-26 13:47:53 +02:00
André Bispo
7130d8a18c [PM-1946] remove ApprovePasswordlessLogins value on logout (#2494) 2023-04-25 09:41:35 -04:00
Jake Fink
659d34dfc2 [PM-1906] check value of KeyValuePair for null instead of object (#2489) 2023-04-21 11:24:33 -04:00
github-actions[bot]
6a5c999628 Autosync the updated translations (#2486)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-21 11:59:46 +02:00
Opeyemi
3bcb44ea71 changing CI-only SP KV job names (#2484) 2023-04-18 11:48:05 +01:00
Shane Melton
b108b4e71d [AC-1070] Enforce master password policy on login/unlock (#2410)
* [AC-1070] Add EnforceOnLogin property to MasterPasswordPolicyOptions

* [AC-1070] Add MasterPasswordPolicy property to Identity responses

* [AC-1070] Add policy service dependency to auth service

* [AC-1070] Introduce logic to evaluate master password after successful login

* [AC-1070] Add optional ForcePasswordResetReason to profile / state service

* [AC-1070] Save ForcePasswordResetReason to state when a weak master password is found during login

- Additionally, save the AdminForcePasswordReset reason if the identity result indicates an admin password reset is in effect.

* [AC-1070] Check for a saved ForcePasswordReset reason on TabsPage load force show the update password page

* [AC-1070] Make InitAsync virtual

Allow the UpdateTempPasswordPage to override the InitAsync method to check for a reset password reason in the state service

* [AC-1070] Modify UpdateTempPassword page appearance

- Load the force password reset reason from the state service
- Make warning text dynamic based on force password reason
- Conditionally show the Current master password field if updating a weak master password

* [AC-1070] Add update password method to Api service

* [AC-1070] Introduce logic to update both temp and regular passwords

- Check the Reason to use the appropriate request/endpoint when submitting.
- Verify the users current password locally using the user verification service.

* [AC-1070] Introduce VerifyMasterPasswordResponse

* [AC-1070] Add logic to evaluate master password on unlock

* [AC-1070] Add support 2FA login flow

Keep track of the reset password reason after a password login requires 2FA. During 2FA submission, check if there is a saved reason, and if so, force the user to update their password.

* [AC-1070] Formatting

* [AC-1070] Remove string key from service resolution

* [AC-1070] Change master password options to method variable to avoid class field

Add null check for password strength result and log an error as this is an unexpected flow

* [AC-1070] Remove usage of i18nService

* [AC-1070] Use AsyncCommand for SubmitCommand

* [AC-1070] Remove type from ShowToast call

* [AC-1070] Simplify UpdatePassword methods to accept string for the new encryption key

* [AC-1070] Use full text for key for the CurrentMasterPassword resource

* [AC-1070] Convert Reason to a private class field

* [AC-1070] Formatting changes

* [AC-1070] Simplify if statements in master password options policy service method

* [AC-1070] Use the saved force password reset reason after 2FA login

* [AC-1070] Use constant for ForceUpdatePassword message command

* [AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService

* Revert "[AC-1070] Move shared RequirePasswordChangeOnLogin method into PolicyService"

This reverts commit e4feac130f.

* [AC-1070] Add check for null password strength response

* [AC-1070] Fix broken show password icon

* [AC-1070] Add show password icon for current master password
2023-04-17 07:35:50 -07:00
Jake Fink
a72f267558 [AC-1045] vault timeout action policy (#2415)
* [EC-1045] lock action if policy and show message

* [EC-1045] add text for policy message

* [EC-1045] add consts to policy service

* [EC-1045] missed a const

* [AC-1045] fix build

* [AC-1045] fix bug where UI wasn't updating after sync

* [AC-1045] change FirstOrDefault to First to avoid nulls

* [AC-1045] refactor get vault timeout functions

* [AC-1045] don't filter action options unecessarily

* [AC-1045] refactor build alert logic for readability

* [AC-1045] use policy to filter timeout options instead of current timeout

* [AC-1045] update timeout during sync instead of getter
- remove encrypted from state since it's not encrypted
- if policies return a timeout policy, check and update vault timeout

* [AC-1045] default to custom if we can't find vault timeout option

* [AC-1045] revert Encrypted Policies rename
2023-04-14 15:39:57 -04:00
github-actions[bot]
cc75cebdb8 Autosync the updated translations (#2476)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-14 20:35:29 +02:00
Daniel James Smith
3a0510d6b4 [PS-2507] Enable firefox relay address on creation (#2474)
* Enable firefox relay address on creation

Adding a body (json) to the request and setting enabled to true.
Additionally the description is set to "Generated by Bitwarden." to mimick the behaviour of the other clients

* Add missing encoding and mediaType

* Replace JObject with anonymous type
2023-04-14 19:20:35 +02:00
aj-rosado
0c4b88e562 PM-1731 - Changed UIDocumentInteractionController with UIDocumentPickerViewController (#2472) 2023-04-13 19:51:56 +01:00
Michał Chęciński
ac3b0c2bad [DEVOPS-1261] Update workflows to use new CI only keyvault (#2462)
* Fixed warning in version-bump

* Use new CI Azure Key Vault

* Fix name
2023-04-11 17:18:59 +02:00
Federico Maccaroni
1823efa0e5 [PM-1576] Fix Race condition AccountsManager registration (#2434)
* PM-1576 Moved registration of AccountsManager to avoid race conditions with the app start. To do so, added ConditionedAwaiterManager so that it handles a task to be awaited or completed depending on the callers.

* PM-1576 Fix format

* PM-1576 Fix throw to preserve StackTrace
2023-04-07 13:24:54 -04:00
github-actions[bot]
e5ce1760a6 Autosync the updated translations (#2465)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-07 07:52:59 +02:00
Michał Chęciński
e77a971519 Fix Mobile Release workflow to properly upload SHA256 hashes (#2459)
* List files

* Cat files

* Fix
2023-04-04 09:19:48 +02:00
github-actions[bot]
d7715c90f0 Autosync the updated translations (#2457)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-04-03 20:25:08 +02:00
Federico Maccaroni
8fe9bd7347 PM-1615 Fix cipher options not working in lists due to binding failing due to type mismatch. Updated GroupingsPageViewModel to new way of calling as well (#2460) 2023-04-03 20:02:16 +02:00
Michał Chęciński
11d3d71c32 Fix version auto bump workflow (#2439)
* Fix verion autobump workflow

* Fix

* Update .github/workflows/version-auto-bump.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

* Update .github/workflows/version-auto-bump.yml

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>

---------

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2023-04-03 10:13:52 +02:00
Federico Maccaroni
0462f4db63 Revert "PM-1615 Fix cipher options not working in lists due to binding failing due to type mismatch. Updated GroupingsPageViewModel to new way of calling as well (#2456)" (#2458)
This reverts commit 120f1d6859.
2023-03-31 18:09:32 +02:00
Federico Maccaroni
120f1d6859 PM-1615 Fix cipher options not working in lists due to binding failing due to type mismatch. Updated GroupingsPageViewModel to new way of calling as well (#2456) 2023-03-30 22:47:25 +02:00
mp-bw
99ceb8dbc1 [PM-1646] Add thread safety to migration process (#2453)
* Make migration process thread safe

* tweaks
2023-03-28 17:22:09 -04:00
github-actions[bot]
d7d044f717 Bumped version to 2023.3.3 (#2451)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-27 20:39:10 -04:00
Ilya Nikitenkov
53d892a0ba [PS-2328] Added RuPay card brand (#2314)
* Added Mir and RuPay card brands

* Remove Mir because of https://github.com/bitwarden/clients/pull/5011

---------

Co-authored-by: Daniel James Smith <djsmith@web.de>
2023-03-27 11:26:36 +02:00
mp-bw
80e38f8669 [PM-1567] Fix for vault timeout 'never' not persisting (#2440) 2023-03-24 20:34:48 +00:00
github-actions[bot]
3e76f6b054 Autosync the updated translations (#2438)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-24 01:31:59 +01:00
github-actions[bot]
55a3b76f45 Bumped version to 2023.3.2 (#2436)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-23 18:01:39 -04:00
github-actions[bot]
bd9b767339 Bumped version to 2023.3.1 (#2432)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-23 18:07:08 +01:00
mp-bw
276a93c497 Fix migration crash (#2430) 2023-03-23 11:35:08 -04:00
github-actions[bot]
c6bdb67981 Bumped version to 2023.3.0 (#2423)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-22 10:45:02 -04:00
André Bispo
a6bb089633 [PM-1497] Known device api error (#2418)
* [PM-1497] Ignore know device api error.
2023-03-17 15:07:41 +00:00
github-actions[bot]
606b00142f Autosync the updated translations (#2419)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-17 01:24:09 +01:00
André Bispo
151ecf83e7 [PM-1431] [Defect] [Android] New accounts are not able to log in (#2417)
* [PM-1431] Do not clear Master password  if login is ongoing.
2023-03-16 12:48:40 +00:00
André Bispo
ccd71202de [PM-1078] Login with Device - Change mobile to not get fingerprint from API (#2390)
* [PM-1078] Fingerprint phrase gets calculated from pub key on AuthService instead of coming as a property from the api.
2023-03-13 15:39:55 +00:00
Jake Fink
839aa9134c Revert "[EC-1045] add vault timeout action to policy (#2372)" (#2414)
This reverts commit dcb5854557.
2023-03-10 13:02:41 -05:00
Jake Fink
dcb5854557 [EC-1045] add vault timeout action to policy (#2372)
* [EC-1045] lock action if policy and show message

* [EC-1045] add text for policy message

* [EC-1045] add consts to policy service

* [EC-1045] missed a const

* [AC-1045] fix build
2023-03-10 12:55:48 -05:00
larena1
ad9ca125a0 [PS-2486] Finally stop filling password into username field (#2367)
* Finally stop filling password into username field

The logic in #2331 is unfortunately not very reliable as it'll only detect fields that have one of "email", "phone" or "username" in their id as username fields.
This commit ensures that additonally fields that have TextVariationWebEmailAddress are also detected as username fields.

* Add TextVariationEmailAddress

* Remove

---------

Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>
2023-03-10 10:39:20 +00:00
github-actions[bot]
fe12b0e908 Autosync the updated translations (#2412)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-10 01:31:13 +01:00
Federico Maccaroni
f733d22d55 PM-1386 Fix otp data issuer and account name being set correctly (#2411) 2023-03-09 13:58:16 -05:00
Federico Maccaroni
9f8307a4ff [EC-770] Implement MessagePack on Watch sync (#2264)
* EC-770 Started implementing MessagePack for the iPhone -> Watch communication

* EC-770 Removed Pods and installed MessagePack through SPM

* EC-770 Implemented MessagePack + Lzfse compression when syncing iPhone -> Watch

* EC-770 Added MessagePack as submodule and updated the build to checkout the submodule as well. Also added MessagePack files as reference in the watch project

* EC-770 Updated build

Updated build.yml to checkout submodules on iOS
2023-03-09 15:45:51 -03:00
Federico Maccaroni
a18f74a72a [PM-1129] iOS 16 Third-Party 2FA OTP handling (#2409)
* [EC-980] Added iOS otpauth handler (#2370)

* EC-980 added Bitwarden as otpauth scheme handler

* EC-980 Fix format

* [EC-981] OTP handling - Set to selected cipher (#2404)

* EC-981 Started adding OTP to existing cipher. Reused AutofillCiphersPage for the cipher selection and refactored it so that we have more code reuse

* EC-981 Fix navigation on otp handling

* EC-981 Fix formatting

* EC-981 Added otp cipher selection callout and add close toolbar item when needed

* PM-1131 implemented cipher creation from otp handling flow with otp key filled (#2407)

* PM-1133 Updated empty states for search and cipher selection on otp flow (#2408)
2023-03-09 11:16:48 -03:00
Matt Gibson
4d2b53c809 Use encoded query parameters over path (#2354)
* Use encoded query parameters over path

* Prefer POST for requests with sensitive information

* Send private information in headers over query

* B64 encode email
2023-03-07 16:16:28 -06:00
mp-bw
c02cd1f15b [PM-1249] Clear/reset password/PIN fields on login/lock screen when app is backgrounded (#2395)
* [PM-1249] Clear/reset password/PIN fields on login/lock screen when app is backgrounded

* fixes
2023-03-07 13:40:22 -03:00
github-actions[bot]
74139627e2 Autosync the updated translations (#2399)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-03-06 11:38:27 +01:00
Federico Maccaroni
3f86bb0cd7 PM-1271 Removed StorageMediatorOptions and go to a plain parameters based approach (#2397) 2023-03-02 19:06:38 -03:00
Joseph Flinn
a81dfc271c Patch/update ios provision profiles (#2398)
* Update iOS Distribution cert and provision profiles

* Rename the provision profiles

* Update the App Store provision profile names in plist

* Update Watch provision profile

* Remove testing code in pipeline

* Remove more test code
2023-03-02 15:18:22 -05:00
Federico Maccaroni
470e08f165 [EC-1002] [BEEEP] Add ability to change language in app (#2299)
* EC-1002 BEEEP Added ability to change language in app

* EC-1002 fix format

* EC-1002 Renamed IPreferencesStorageService to ISynchronousStorageService

* EC-1002 Moved get/set Locale to the StateService and added the StorageMediatorService to a new way to interact with the storage. Later the StateService will only interact with this mediator instead of directly with the storage services, with this we have more control inside the mediator and we can have both sync and async methods to interact with storages handled by the mediator
2023-03-01 13:28:28 -03:00
github-actions[bot]
5164762f2e Autosync the updated translations (#2391)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-24 14:39:43 +01:00
mp-bw
6da1875ab6 [PS-2520] Restore copy confirmation toast on Android 13+ (#2388)
* Restore copy notification toast on Android 13+

* fixed space
2023-02-21 10:49:24 -05:00
André Bispo
3f72d35145 [SG-744] Add claimed domain logic to mobile (#2333) 2023-02-20 14:49:20 +00:00
Federico Maccaroni
b26b9ea41b [EC-763] Cake build watchOS (#2278)
* EC-763  Implemented watchOS parts on cake build

* EC-763 Improved setting the version name of the watch by caching the version on when updating the ios info plist file

* EC-763 Fix cake build criteria to use lambda version so that it takes into consideration that the field might change on executing the script

* EC-763 Added iOS and watchOS icons update to the cake build script
2023-02-20 10:05:21 -03:00
github-actions[bot]
0539eda57e Autosync the updated translations (#2381)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-17 01:45:23 +01:00
mp-bw
c5d72ad7cb State migration storage key refinement (#2379) 2023-02-16 14:44:45 -05:00
Brandon Maharaj
bf7d9b5646 [SG-1022] Update min password requirements to 12 char (#2368)
* work: adjusted numbers to 12

* work: made string dynamic

* fix: spaces
2023-02-16 13:15:40 -05:00
Oscar Hinton
8ec6c7f0f7 Remove dotnet format note (#2378) 2023-02-16 16:17:09 +01:00
github-actions[bot]
2321122e81 Bumped version to 2023.2.1 (#2377)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-16 15:15:28 +00:00
github-actions[bot]
bc439b45c9 Bump version to 2023.2.0 (#2374)
* [SG-1057] Make check breaches true by default (#2351)

(cherry picked from commit 6875389948)

* [SG-1055] Init strength bar color (#2352)

(cherry picked from commit 3780587991)

* [SG-1052] Fix copy (#2353)

(cherry picked from commit 0bd1b3f45f)

* [SG-1056] Fix login prompt loop for added accounts (#2357)

* SG-1056 Fix login prompt loop for added accounts

* SG-1056 Fix PR comment

(cherry picked from commit f42c677d5a)

* Bumped version to 2023.2.0

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Carlos Gonçalves <cgoncalves@bitwarden.com>
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
Co-authored-by: Opeyemi Alao <54288773+Eeebru@users.noreply.github.com>
2023-02-15 23:23:20 +00:00
mp-bw
e7d6783156 [PS-2280] Retain app settings on logout (#2366)
* [PS-2280] Retain app settings on logout

* adjustments
2023-02-15 12:50:02 -05:00
github-actions[bot]
44e5682b1d Autosync the updated translations (#2371)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-15 11:56:23 +01:00
428 changed files with 26971 additions and 7984 deletions

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.
- name: Report mobile autofill failure
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users!
@@ -9,9 +12,6 @@ contact_links:
- name: Bitwarden Community Forums
url: https://community.bitwarden.com
about: Please visit the community forums for general community discussion, support and the development roadmap.
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.
- name: Security Issues
url: https://hackerone.com/bitwarden
about: We use HackerOne to manage security disclosures.

19
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
android:
- src/App/*
- src/Core/*
- src/Android/*
iOS:
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
watchOS:
- src/watchOS/*

57
.github/renovate.json vendored
View File

@@ -1,22 +1,37 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"schedule:monthly",
":maintainLockFilesMonthly",
":preserveSemverRanges",
":rebaseStalePrs",
":disableDependencyDashboard"
],
"enabledManagers": [
"nuget"
],
"packageRules": [
{
"matchManagers": ["nuget"],
"groupName": "Nuget updates",
"groupSlug": "nuget",
"separateMajorMinor": false
}
]
}
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":combinePatchMinorReleases",
":dependencyDashboard",
":maintainLockFilesWeekly",
":pinAllExceptPeerDependencies",
":prConcurrentLimit10",
":rebaseStalePrs",
"schedule:weekends",
":separateMajorReleases"
],
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
"packageRules": [
{
"groupName": "cargo minor",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "gh minor",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "npm minor",
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "nuget minor",
"matchManagers": ["nuget"],
"matchUpdateTypes": ["minor", "patch"]
},
]
}

View File

@@ -7,13 +7,13 @@
<key>provisioningProfiles</key>
<dict>
<key>com.8bit.bitwarden</key>
<string>Dist: Bitwarden 2021</string>
<string>Dist: Bitwarden</string>
<key>com.8bit.bitwarden.autofill</key>
<string>Dist: Autofill 2021</string>
<string>Dist: Autofill</string>
<key>com.8bit.bitwarden.find-login-action-extension</key>
<string>Dist: Extension 2021</string>
<string>Dist: Extension</string>
<key>com.8bit.bitwarden.share-extension</key>
<string>Dist: Share Extension 2021</string>
<string>Dist: Share Extension</string>
<key>com.8bit.bitwarden.watchkitapp</key>
<string>Dist: Bitwarden Watch App</string>
<key>com.8bit.bitwarden.watchkitapp.watchkitextension</key>

Binary file not shown.

Binary file not shown.

View File

@@ -14,7 +14,7 @@ jobs:
# Feature request
- if: github.event.label.name == 'feature-request'
name: Feature request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
@@ -25,7 +25,7 @@ jobs:
# Intended behavior
- if: github.event.label.name == 'intended-behavior'
name: Intended behaviour
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
with:
comment: |
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
@@ -38,7 +38,7 @@ jobs:
# Customer support request
- if: github.event.label.name == 'customer-support'
name: Customer Support request
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
with:
comment: |
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
@@ -49,14 +49,14 @@ jobs:
# Resolved
- if: github.event.label.name == 'resolved'
name: Resolved
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
with:
comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale
- if: github.event.label.name == 'stale'
name: Stale
uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
with:
comment: |
As we havent heard from you about this problem in some time, this issue will now be closed.

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Set up CLOC
run: |
@@ -36,7 +36,9 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
submodules: 'true'
- name: Check if special branches exist
id: branch-check
@@ -65,12 +67,17 @@ jobs:
variant: ["prod", "qa"]
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 5.9.0
- name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '3.1.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
- name: Setup Windows builder
run: choco install checksum --no-progress
@@ -103,7 +110,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
fetch-depth: 0
- name: Decrypt secrets
@@ -155,7 +162,7 @@ jobs:
shell: pwsh
- name: Report test results
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
if: always()
with:
name: Test Results
@@ -189,7 +196,7 @@ jobs:
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
@@ -230,7 +237,7 @@ jobs:
shell: pwsh
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
@@ -238,7 +245,7 @@ jobs:
- name: Upload Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
@@ -246,7 +253,7 @@ jobs:
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
@@ -266,7 +273,7 @@ jobs:
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
@@ -274,7 +281,7 @@ jobs:
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
@@ -301,12 +308,12 @@ jobs:
runs-on: windows-2022
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 5.9.0
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
- name: Setup Windows builder
run: choco install checksum --no-progress
@@ -340,7 +347,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Decrypt secrets
env:
@@ -475,7 +482,7 @@ jobs:
shell: pwsh
- name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
@@ -487,7 +494,7 @@ jobs:
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
@@ -500,7 +507,7 @@ jobs:
needs: setup
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 5.9.0
@@ -513,17 +520,19 @@ jobs:
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
KEYVAULT: bitwarden-ci
SECRETS: |
appcenter-ios-token
run: |
@@ -694,7 +703,7 @@ jobs:
shell: bash
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: Bitwarden iOS
path: |
@@ -767,17 +776,17 @@ jobs:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
KEYVAULT: bitwarden-ci
SECRETS: |
crowdin-api-token
run: |
@@ -789,7 +798,7 @@ jobs:
done
- name: Upload Sources
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -835,17 +844,17 @@ jobs:
exit 1
fi
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
if: failure()
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
if: failure()
env:
KEYVAULT: bitwarden-prod-kv
KEYVAULT: bitwarden-ci
SECRETS: |
devops-alerts-slack-webhook-url
run: |
@@ -857,7 +866,7 @@ jobs:
done
- name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -15,22 +15,22 @@ jobs:
_CROWDIN_PROJECT_ID: "269690"
steps:
- name: Checkout repo
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
with:
keyvault: "bitwarden-prod-kv"
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -48,4 +48,4 @@ jobs:
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Enforce Label
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
with:
BANNED_LABELS: "hold,needs-qa"
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"

17
.github/workflows/pr-labeler.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
---
name: "Pull Request Labeler"
on:
pull_request_target: {}
jobs:
labeler:
name: "Pull Request Labeler"
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-20.04
steps:
- uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b # v4.0.3
with:
sync-labels: true

View File

@@ -38,11 +38,11 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
uses: bitwarden/gh-actions/release-version-check@34ecb67b2a357795dc893549df0795e7383ff50f
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
@@ -56,7 +56,7 @@ jobs:
- name: Create GitHub deployment
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
id: deployment
with:
token: '${{ secrets.GITHUB_TOKEN }}'
@@ -68,7 +68,7 @@ jobs:
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -76,7 +76,7 @@ jobs:
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -93,8 +93,8 @@ jobs:
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip,
./bw-android-apk-sha256.txt,
./bw-fdroid-apk-sha256.txt"
./bw-android-apk-sha256.txt/bw-android-apk-sha256.txt,
./bw-fdroid-apk-sha256.txt/bw-fdroid-apk-sha256.txt"
commit: ${{ github.sha }}
tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.version.outputs.version }}
@@ -104,7 +104,7 @@ jobs:
- name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success'
@@ -112,7 +112,7 @@ jobs:
- name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
with:
token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure'
@@ -126,11 +126,11 @@ jobs:
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success
@@ -139,7 +139,7 @@ jobs:
- name: Dry Run - Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
with:
workflow: build.yml
workflow_conclusion: success

View File

@@ -14,7 +14,7 @@ jobs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Calculate bumped version
id: version
@@ -32,32 +32,10 @@ jobs:
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: "Trigger version bump workflow"
runs-on: ubuntu-22.04
needs:
- setup
steps:
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
keyvault: "bitwarden-prod-kv"
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Call GitHub API to trigger workflow bump
env:
TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
VERSION: ${{ needs.setup.outputs.version_number}}
run: |
JSON_STRING=$(printf '{"ref":"master", "inputs": { "version_number":"%s"}}' "$VERSION")
curl \
-X POST \
-i -u bitwarden-devops-bot:$TOKEN \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/bitwarden/mobile/actions/workflows/version-bump.yml/dispatches \
-d $JSON_STRING
name: Bump version to ${{ needs.setup.outputs.version_number }}
needs: setup
uses: ./.github/workflows/version-bump.yml
secrets:
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
with:
version_number: ${{ needs.setup.outputs.version_number }}

View File

@@ -7,6 +7,14 @@ on:
version_number:
description: "New Version"
required: true
workflow_call:
inputs:
version_number:
required: true
type: string
secrets:
AZURE_PROD_KV_CREDENTIALS:
required: true
jobs:
bump_version:
@@ -14,22 +22,22 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
with:
keyvault: "bitwarden-prod-kv"
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@c8bb57c57e8df1be8c73ff3d59deab1dbc00e0d1
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
@@ -40,31 +48,31 @@ jobs:
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/Android/Properties/AndroidManifest.xml"
- name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Autofill/Info.plist"
- name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Extension/Info.plist"
- name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.ShareExtension/Info.plist"
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS/Info.plist"

View File

@@ -8,4 +8,4 @@ on:
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@34ecb67b2a357795dc893549df0795e7383ff50f

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "lib/MessagePack"]
path = lib/MessagePack
url = https://github.com/bitwarden/MessagePack.git

View File

@@ -23,15 +23,3 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
### Dotnet-format
We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35`
3. Resolve any merge conflicts, commit.
4. Run `dotnet tool run dotnet-format`
5. Commit
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
7. Push

View File

@@ -0,0 +1,11 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_29)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.0669V51.9453H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.211 57.5H70.1118V59H67.177C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.1118V51.9219H67.211V53.8027H69.895V55.291H67.211V57.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM59.7817 50.4336H57.1157V59H60.3208C60.9692 59 61.5278 58.9023 61.9966 58.707C62.4692 58.5078 62.8325 58.2227 63.0864 57.8516C63.3403 57.4805 63.4673 57.0352 63.4673 56.5156C63.4673 56.0664 63.397 55.707 63.2563 55.4375C63.1196 55.1641 62.936 54.957 62.7056 54.8164C62.4751 54.6719 62.2173 54.5703 61.9321 54.5117V54.4531C62.2134 54.4023 62.4517 54.293 62.647 54.125C62.8423 53.957 62.9907 53.7422 63.0923 53.4805C63.1978 53.2188 63.2505 52.9258 63.2505 52.6016C63.2505 51.7969 62.9575 51.2344 62.3716 50.9141C61.7856 50.5938 60.9224 50.4336 59.7817 50.4336ZM59.9868 53.8262H58.9321V51.9219H59.8872C60.4067 51.9219 60.7856 51.9941 61.0239 52.1387C61.2661 52.2793 61.3872 52.5137 61.3872 52.8418C61.3872 53.166 61.2856 53.4121 61.0825 53.5801C60.8794 53.7441 60.5142 53.8262 59.9868 53.8262ZM58.9321 57.5V55.2676H60.0571C60.4438 55.2676 60.7466 55.3125 60.9653 55.4023C61.188 55.4922 61.3462 55.6172 61.4399 55.7773C61.5337 55.9375 61.5806 56.123 61.5806 56.334C61.5806 56.6895 61.4731 56.9727 61.2583 57.1836C61.0435 57.3945 60.6626 57.5 60.1157 57.5H58.9321ZM65.1782 59H70.1118V57.5H66.9946V55.291H69.895V53.8027H66.9946V51.9219H70.1118V50.4336H65.1782V59ZM73.3931 59H75.2095V51.9453H77.5356V50.4336H71.0669V51.9453H73.3931V59ZM83.4771 56.9609L84.0981 59H86.0552L83.02 50.3984H80.7993L77.7759 59H79.7329L80.354 56.9609H83.4771ZM82.4224 53.4453L83.0435 55.4375H80.811L81.4263 53.4453C81.4536 53.3555 81.4985 53.2051 81.561 52.9941C81.6235 52.7832 81.688 52.5605 81.7544 52.3262C81.8247 52.0879 81.8794 51.8887 81.9185 51.7285C81.9575 51.8887 82.0083 52.0781 82.0708 52.2969C82.1372 52.5117 82.2017 52.7246 82.2642 52.9355C82.3306 53.1426 82.3833 53.3125 82.4224 53.4453Z" fill="#6795E8"/>
</g>
<defs>
<clipPath id="clip0_41_29">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,17 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_36_10)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_10)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#6795E8"/>
<path d="M57.116 50.4336H59.782C60.9226 50.4336 61.7859 50.5938 62.3718 50.9141C62.9578 51.2344 63.2507 51.7969 63.2507 52.6016C63.2507 52.9258 63.198 53.2188 63.0925 53.4805C62.991 53.7422 62.8425 53.957 62.6472 54.125C62.4519 54.293 62.2136 54.4023 61.9324 54.4531V54.5117C62.2175 54.5703 62.4753 54.6719 62.7058 54.8164C62.9363 54.957 63.1199 55.1641 63.2566 55.4375C63.3972 55.707 63.4675 56.0664 63.4675 56.5156C63.4675 57.0352 63.3406 57.4805 63.0867 57.8516C62.8328 58.2227 62.4695 58.5078 61.9968 58.707C61.5281 58.9023 60.9695 59 60.321 59H57.116V50.4336ZM58.9324 53.8262H59.9871C60.5144 53.8262 60.8796 53.7441 61.0828 53.5801C61.2859 53.4121 61.3875 53.166 61.3875 52.8418C61.3875 52.5137 61.2664 52.2793 61.0242 52.1387C60.7859 51.9941 60.407 51.9219 59.8875 51.9219H58.9324V53.8262ZM58.9324 55.2676V57.5H60.116C60.6628 57.5 61.0437 57.3945 61.2585 57.1836C61.4734 56.9727 61.5808 56.6895 61.5808 56.334C61.5808 56.123 61.5339 55.9375 61.4402 55.7773C61.3464 55.6172 61.1882 55.4922 60.9656 55.4023C60.7468 55.3125 60.4441 55.2676 60.0574 55.2676H58.9324ZM70.1121 59H65.1785V50.4336H70.1121V51.9219H66.9949V53.8027H69.8953V55.291H66.9949V57.5H70.1121V59ZM75.2097 59H73.3933V51.9453H71.0671V50.4336H77.5359V51.9453H75.2097V59ZM84.0984 59L83.4773 56.9609H80.3542L79.7332 59H77.7761L80.7996 50.3984H83.0203L86.0554 59H84.0984ZM83.0437 55.4375L82.4226 53.4453C82.3835 53.3125 82.3308 53.1426 82.2644 52.9355C82.2019 52.7246 82.1375 52.5117 82.071 52.2969C82.0085 52.0781 81.9578 51.8887 81.9187 51.7285C81.8796 51.8887 81.825 52.0879 81.7546 52.3262C81.6882 52.5605 81.6238 52.7832 81.5613 52.9941C81.4988 53.2051 81.4539 53.3555 81.4265 53.4453L80.8113 55.4375H83.0437Z" fill="#212529"/>
</g>
<defs>
<linearGradient id="paint0_linear_36_10" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_36_10">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,11 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_21)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.647L72.0971 51.7604V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.177 59C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.9321V51.9219H67.8149V53.8027H70.7153V55.291H67.8149V57.5H70.9321V59H67.177Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM63.6665 57.0547C64.0376 56.4062 64.2231 55.5996 64.2231 54.6348C64.2231 53.7168 64.0415 52.9473 63.6782 52.3262C63.3149 51.7012 62.8032 51.2305 62.1431 50.9141C61.4829 50.5938 60.7036 50.4336 59.8052 50.4336H57.1157V59H59.5415C60.5259 59 61.3677 58.8379 62.0669 58.5137C62.7661 58.1855 63.2993 57.6992 63.6665 57.0547ZM62.0552 53.123C62.2427 53.5293 62.3364 54.0488 62.3364 54.6816C62.3364 55.6152 62.1196 56.3184 61.686 56.791C61.2563 57.2637 60.5981 57.5 59.7114 57.5H58.9321V51.9219H59.8989C60.4302 51.9219 60.8755 52.0195 61.2349 52.2148C61.5981 52.4102 61.8716 52.7129 62.0552 53.123ZM65.9985 59H70.9321V57.5H67.8149V55.291H70.7153V53.8027H67.8149V51.9219H70.9321V50.4336H65.9985V59ZM76.5337 59L79.4458 50.4336H77.6118L75.9888 55.5312C75.9614 55.6211 75.9165 55.7852 75.854 56.0234C75.7954 56.2578 75.7349 56.5059 75.6724 56.7676C75.6138 57.0293 75.5728 57.2461 75.5493 57.418C75.5259 57.2461 75.481 57.0293 75.4146 56.7676C75.3521 56.502 75.2896 56.252 75.2271 56.0176C75.1646 55.7793 75.1196 55.6172 75.0923 55.5312L73.481 50.4336H71.647L74.5532 59H76.5337Z" fill="#2DA49D"/>
</g>
<defs>
<clipPath id="clip0_41_21">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,17 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_36_2)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_2)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#2DA49D"/>
<path d="M64.2234 54.6348C64.2234 55.5996 64.0378 56.4062 63.6667 57.0547C63.2996 57.6992 62.7664 58.1855 62.0671 58.5137C61.3679 58.8379 60.5261 59 59.5417 59H57.116V50.4336H59.8054C60.7039 50.4336 61.4832 50.5938 62.1433 50.9141C62.8035 51.2305 63.3152 51.7012 63.6785 52.3262C64.0417 52.9473 64.2234 53.7168 64.2234 54.6348ZM62.3367 54.6816C62.3367 54.0488 62.2429 53.5293 62.0554 53.123C61.8718 52.7129 61.5984 52.4102 61.2351 52.2148C60.8757 52.0195 60.4304 51.9219 59.8992 51.9219H58.9324V57.5H59.7117C60.5984 57.5 61.2566 57.2637 61.6863 56.791C62.1199 56.3184 62.3367 55.6152 62.3367 54.6816ZM70.9324 59H65.9988V50.4336H70.9324V51.9219H67.8152V53.8027H70.7156V55.291H67.8152V57.5H70.9324V59ZM79.446 50.4336L76.5339 59H74.5535L71.6472 50.4336H73.4812L75.0925 55.5312C75.1199 55.6172 75.1648 55.7793 75.2273 56.0176C75.2898 56.252 75.3523 56.502 75.4148 56.7676C75.4812 57.0293 75.5261 57.2461 75.5496 57.418C75.573 57.2461 75.614 57.0293 75.6726 56.7676C75.7351 56.5059 75.7957 56.2578 75.8542 56.0234C75.9167 55.7852 75.9617 55.6211 75.989 55.5312L77.6121 50.4336H79.446Z" fill="#212529"/>
</g>
<defs>
<linearGradient id="paint0_linear_36_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_36_2">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,11 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_13)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V52.9823L70.8325 49.3984H68.6118L67.211 53.3838V38.7573H53.5337V73.2875C53.5337 73.2875 67.211 66.3497 67.211 58.3019V58H67.5454L68.1665 55.9609H71.2896L71.9106 58H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM64.6626 55.457C64.8149 54.9258 64.8911 54.3418 64.8911 53.7051C64.8911 52.8145 64.7446 52.0391 64.4517 51.3789C64.1626 50.7188 63.7173 50.207 63.1157 49.8438C62.5181 49.4805 61.7544 49.2988 60.8247 49.2988C59.8911 49.2988 59.1216 49.4805 58.5161 49.8438C57.9106 50.207 57.4614 50.7188 57.1685 51.3789C56.8794 52.0352 56.7349 52.8066 56.7349 53.6934C56.7349 54.3574 56.8169 54.9609 56.981 55.5039C57.145 56.0469 57.3931 56.5137 57.7251 56.9043C58.061 57.2949 58.4849 57.5957 58.9966 57.8066C59.5083 58.0137 60.1138 58.1172 60.813 58.1172H60.8774H60.9478L62.5181 60.0391H64.8442L62.7817 57.7363C63.2622 57.5176 63.6587 57.2148 63.9712 56.8281C64.2837 56.4414 64.5142 55.9844 64.6626 55.457ZM58.8618 55.252C58.7134 54.8184 58.6392 54.3027 58.6392 53.7051C58.6392 53.1035 58.7134 52.5879 58.8618 52.1582C59.0142 51.7246 59.2505 51.3926 59.5708 51.1621C59.895 50.9277 60.313 50.8105 60.8247 50.8105C61.5942 50.8105 62.147 51.0684 62.4829 51.584C62.8188 52.0996 62.9868 52.8066 62.9868 53.7051C62.9868 54.3027 62.9126 54.8184 62.7642 55.252C62.6196 55.6816 62.3872 56.0137 62.0669 56.248C61.7466 56.4785 61.3286 56.5938 60.813 56.5938C60.3052 56.5938 59.8911 56.4785 59.5708 56.248C59.2505 56.0137 59.0142 55.6816 58.8618 55.252ZM71.2896 55.9609L71.9106 58H73.8677L70.8325 49.3984H68.6118L65.5884 58H67.5454L68.1665 55.9609H71.2896ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="#C32998"/>
</g>
<defs>
<clipPath id="clip0_41_13">
<rect width="108" height="108" rx="34" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,17 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_34_2)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_34_2)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#C32998"/>
<path d="M64.8914 53.7051C64.8914 54.3418 64.8152 54.9258 64.6628 55.457C64.5144 55.9844 64.2839 56.4414 63.9714 56.8281C63.6589 57.2148 63.2625 57.5176 62.782 57.7363L64.8445 60.0391H62.5183L60.948 58.1172C60.9207 58.1172 60.8972 58.1172 60.8777 58.1172C60.8582 58.1172 60.8367 58.1172 60.8132 58.1172C60.114 58.1172 59.5085 58.0137 58.9968 57.8066C58.4851 57.5957 58.0613 57.2949 57.7253 56.9043C57.3933 56.5137 57.1453 56.0469 56.9812 55.5039C56.8171 54.9609 56.7351 54.3574 56.7351 53.6934C56.7351 52.8066 56.8796 52.0352 57.1687 51.3789C57.4617 50.7188 57.9109 50.207 58.5164 49.8438C59.1218 49.4805 59.8914 49.2988 60.825 49.2988C61.7546 49.2988 62.5183 49.4805 63.116 49.8438C63.7175 50.207 64.1628 50.7188 64.4519 51.3789C64.7449 52.0391 64.8914 52.8145 64.8914 53.7051ZM58.6394 53.7051C58.6394 54.3027 58.7136 54.8184 58.8621 55.252C59.0144 55.6816 59.2507 56.0137 59.571 56.248C59.8914 56.4785 60.3054 56.5938 60.8132 56.5938C61.3289 56.5938 61.7468 56.4785 62.0671 56.248C62.3875 56.0137 62.6199 55.6816 62.7644 55.252C62.9128 54.8184 62.9871 54.3027 62.9871 53.7051C62.9871 52.8066 62.8191 52.0996 62.4832 51.584C62.1472 51.0684 61.5945 50.8105 60.825 50.8105C60.3132 50.8105 59.8953 50.9277 59.571 51.1621C59.2507 51.3926 59.0144 51.7246 58.8621 52.1582C58.7136 52.5879 58.6394 53.1035 58.6394 53.7051ZM71.9109 58L71.2898 55.9609H68.1667L67.5457 58H65.5886L68.6121 49.3984H70.8328L73.8679 58H71.9109ZM70.8562 54.4375L70.2351 52.4453C70.196 52.3125 70.1433 52.1426 70.0769 51.9355C70.0144 51.7246 69.95 51.5117 69.8835 51.2969C69.821 51.0781 69.7703 50.8887 69.7312 50.7285C69.6921 50.8887 69.6375 51.0879 69.5671 51.3262C69.5007 51.5605 69.4363 51.7832 69.3738 51.9941C69.3113 52.2051 69.2664 52.3555 69.239 52.4453L68.6238 54.4375H70.8562Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_34_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_34_2">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
appIcons/iOS/beta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
appIcons/iOS/dev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
appIcons/iOS/prod.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
appIcons/iOS/qa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

136
appIcons/icongen.sh Executable file
View File

@@ -0,0 +1,136 @@
#! /bin/sh
function print_example() {
echo "Example"
echo " icons ios ~/AppIcon.pdf ~/Icons/"
}
function print_usage() {
echo "Usage"
echo " icons <ios|watch|complication|macos> in-file.pdf (out-dir)"
}
function command_exists() {
if type "$1" >/dev/null 2>&1; then
return 1
else
return 0
fi
}
if command_exists "sips" == 0 ; then
echo "sips tool not found"
exit 1
fi
if [ "$1" = "--help" ] || [ "$1" = "-h" ] ; then
print_usage
exit 0
fi
PLATFORM="$1"
FILE="$2"
if [ -z "$PLATFORM" ] || [ -z "$FILE" ] ; then
echo "Error: missing arguments"
echo ""
print_usage
echo ""
print_example
exit 1
fi
DIR="$3"
if [ -z "$DIR" ] ; then
DIR=$(dirname $FILE)
fi
# Create directory if needed
mkdir -p "$DIR"
if [[ "$PLATFORM" == *"ios"* ]] ; then # iOS
sips -s format png -Z '180' "${FILE}" --out "${DIR}"/Icon-180.png
sips -s format png -Z '29' "${FILE}" --out "${DIR}"/Icon-29.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/Icon-58.png
sips -s format png -Z '120' "${FILE}" --out "${DIR}"/Icon-120.png
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/Icon-87.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Icon-40.png
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Icon-80.png
sips -s format png -Z '76' "${FILE}" --out "${DIR}"/Icon-76.png
sips -s format png -Z '152' "${FILE}" --out "${DIR}"/Icon-152.png
sips -s format png -Z '167' "${FILE}" --out "${DIR}"/Icon-167.png
sips -s format png -Z '60' "${FILE}" --out "${DIR}"/Icon-60.png
sips -s format png -Z '20' "${FILE}" --out "${DIR}"/Icon-20.png
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/Icon-1024.png
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
contents_json='{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPad.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPad@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadPro@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppStoreMarketing.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
echo $contents_json > "${DIR}"/Contents.json
fi
if [[ "$PLATFORM" == *"watch"* ]] ; then # Apple Watch
sips -s format png -Z '48' "${FILE}" --out "${DIR}"/Watch38mmNotificationCenter.png
sips -s format png -Z '55' "${FILE}" --out "${DIR}"/Watch42mmNotificationCenter.png
sips -s format png -Z '66' "${FILE}" --out "${DIR}"/Watch66NotificationCenter.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/WatchCompanionSettings@2x.png
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/WatchCompanionSettings@3x.png
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Watch38MM42MMHomeScreen.png
sips -s format png -Z '88' "${FILE}" --out "${DIR}"/Watch40MMHomeScreen.png
sips -s format png -Z '92' "${FILE}" --out "${DIR}"/Watch41MMHomeScreen.png
sips -s format png -Z '100' "${FILE}" --out "${DIR}"/Watch44MMHomeScreen.png
sips -s format png -Z '102' "${FILE}" --out "${DIR}"/Watch45MMHomeScreen.png
sips -s format png -Z '108' "${FILE}" --out "${DIR}"/Watch49MMHomeScreen.png
sips -s format png -Z '172' "${FILE}" --out "${DIR}"/Watch38MMShortLook.png
sips -s format png -Z '196' "${FILE}" --out "${DIR}"/Watch40MM42MMShortLook.png
sips -s format png -Z '216' "${FILE}" --out "${DIR}"/Watch44MMShortLook.png
sips -s format png -Z '234' "${FILE}" --out "${DIR}"/Watch234ShortLook.png
sips -s format png -Z '258' "${FILE}" --out "${DIR}"/Watch258ShortLook.png
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/WatchAppStore.png
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
contents_json='{"images":[{"size":"24x24","idiom":"watch","scale":"2x","filename":"Watch38mmNotificationCenter.png","role":"notificationCenter","subtype":"38mm"},{"size":"27.5x27.5","idiom":"watch","scale":"2x","filename":"Watch42mmNotificationCenter.png","role":"notificationCenter","subtype":"42mm"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@2x.png","role":"companionSettings","scale":"2x"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@3x.png","role":"companionSettings","scale":"3x"},{"size":"40x40","idiom":"watch","filename":"Watch38MM42MMHomeScreen.png","scale":"2x","role":"appLauncher","subtype":"38mm"},{"size":"44x44","idiom":"watch","scale":"2x","filename":"Watch40MMHomeScreen.png","role":"appLauncher","subtype":"40mm"},{"size":"50x50","idiom":"watch","scale":"2x","filename":"Watch44MMHomeScreen.png","role":"appLauncher","subtype":"44mm"},{"size":"86x86","idiom":"watch","scale":"2x","filename":"Watch38MMShortLook.png","role":"quickLook","subtype":"38mm"},{"size":"98x98","idiom":"watch","scale":"2x","filename":"Watch40MM42MMShortLook.png","role":"quickLook","subtype":"42mm"},{"size":"108x108","idiom":"watch","scale":"2x","filename":"Watch44MMShortLook.png","role":"quickLook","subtype":"44mm"},{"idiom":"watch-marketing","filename":"WatchAppStore.png","size":"1024x1024","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
echo $contents_json > "${DIR}"/Contents.json
fi
if [[ "$PLATFORM" == *"complication"* ]] ; then # Apple Watch
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/Circular38mm2x.png
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular40mm2x.png
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular42mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Circular44mm2x.png
sips -s format png -Z '182' "${FILE}" --out "${DIR}"/ExtraLarge38mm2x.png
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge40mm2x.png
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge42mm2x.png
sips -s format png -Z '224' "${FILE}" --out "${DIR}"/ExtraLarge44mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel40mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel42mm2x.png
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicBezel44mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular40mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular42mm2x.png
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicCircular44mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner40mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner42mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicCorner44mm2x.png
sips -s format png -Z '52' "${FILE}" --out "${DIR}"/GraphicModular38mm2x.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular40mm2x.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular42mm2x.png
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/GraphicModular44mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicUtilitarian38mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian40mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian42mm2x.png
sips -s format png -Z '50' "${FILE}" --out "${DIR}"/GraphicUtilitarian44mm2x.png
sips -s format png -Z '206' "${FILE}" --out "${DIR}"/GraphicExtraLarge38mm2x.png
sips -s format png -Z '264' "${FILE}" --out "${DIR}"/GraphicExtraLarge44mm2x.png
echo "NOTE: Graphic Extra Large is not generated since that is not rectangular"
fi
if [[ "$PLATFORM" == *"macos"* ]] ; then # macOS
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/icon_512x512@2x.png
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_512x512.png
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_256x256@2x.png
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_256x256.png
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_128x128@2x.png
sips -s format png -Z '128' "${FILE}" --out "${DIR}"/icon_128x128.png
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/icon_32x32@2x.png
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_32x32.png
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_16x16@2x.png
sips -s format png -Z '16' "${FILE}" --out "${DIR}"/icon_16x16.png
fi

View File

@@ -4,6 +4,7 @@
#addin nuget:?package=Cake.Incubator&version=7.0.0
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
using Path = System.IO.Path;
using System.Text.RegularExpressions;
var debugScript = Argument<bool>("debugScript", false);
var target = Argument("target", "Default");
@@ -35,6 +36,7 @@ VariantConfig GetVariant() => variant.ToLower() switch{
GitVersion _gitVersion; //will be set by GetGitInfo task
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
string _iOSVersionName = string.Empty; //will be set by UpdateiOSPlist task
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
int CreateBuildNumber(int previousNumber) => ++previousNumber;
@@ -163,7 +165,8 @@ enum iOSProjectType
MainApp,
Autofill,
Extension,
ShareExtension
ShareExtension,
WatchApp
}
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
@@ -171,6 +174,7 @@ string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) =>
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
iOSProjectType.WatchApp => $"{buildVariant.iOSBundleId}.watchkitapp",
_ => buildVariant.iOSBundleId
};
@@ -205,6 +209,7 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
if(projectType == iOSProjectType.MainApp)
{
_iOSVersionName = newVersionName;
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
}
@@ -240,10 +245,79 @@ private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig b
Information($"{entitlementsPath} updated with success!");
}
Task("UpdateiOSIcon")
private void UpdateWatchKitAppInfoPlist(string plistPath, VariantConfig buildVariant)
{
var plistFile = File(plistPath);
dynamic plist = DeserializePlist(plistFile);
var prevBundleId = plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"];
var newBundleId = GetiOSBundleId(buildVariant, iOSProjectType.WatchApp);
plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = newBundleId;
SerializePlist(plistFile, plist);
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
Information($"{plistPath} updated with success!");
}
private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
{
var fileText = FileReadText(pbxprojPath);
if (string.IsNullOrEmpty(fileText))
{
throw new Exception($"Couldn't find {pbxprojPath}");
}
const string pattern = @"MARKETING_VERSION = [^;]*;";
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified successfully.");
}
/// <summary>
/// Updates the target icons on the given appiconset target
/// taking as source the icon in appIcons/iOS folder for the giving variant
/// </summary>
/// <param name="target">It can be <ios|watch|complication|macos></param>
/// <param name="appiconsetTarget">Folder to copy the generated icons to</param>
private void UpdateAppleIcons(string target, string appiconsetTarget)
{
Information($"Updating {target} App Icons");
var iconsTempDirPath = Path.Combine(_slnPath, "appIcons", "temp");
CreateDirectory(iconsTempDirPath);
var arguments = new ProcessArgumentBuilder();
arguments.Append(target);
arguments.Append(Path.Combine(_slnPath, "appIcons", "iOS", $"{variant}.png"));
arguments.Append(iconsTempDirPath);
using(var process = StartAndReturnProcess(Path.Combine(_slnPath, "appIcons", "icongen.sh"),
new ProcessSettings { Arguments = arguments }))
{
process.WaitForExit();
Information("Exit code: {0}", process.GetExitCode());
}
var generatedIconsPath = Path.Combine(iconsTempDirPath, "*.png");
CopyFiles(generatedIconsPath, appiconsetTarget);
DeleteDirectory(iconsTempDirPath, new DeleteDirectorySettings {
Recursive = true,
Force = true
});
Information($"{target} App Icons have been updated");
}
Task("UpdateiOSIcons")
.Does(()=>{
//TODO we'll implement variant icons later
Information($"Updating IOS App Icon");
UpdateAppleIcons("ios", Path.Combine(_slnPath, "src", "iOS", "Resources", "Assets.xcassets", "AppIcons.appiconset"));
UpdateAppleIcons("watch", Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit App", "Assets.xcassets", "AppIcon.appiconset"));
// TODO: Update complication icons when they start working
});
Task("UpdateiOSPlist")
@@ -296,8 +370,10 @@ Task("UpdateiOSCodeFiles")
var fileList = new string[] {
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Helpers", "KeychainHelper.swift"),
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
Path.Combine(".github", "resources", "export-options-app-store.plist"),
Path.Combine(".github", "resources", "export-options-app-store.plist")
};
foreach(string path in fileList)
@@ -305,6 +381,22 @@ Task("UpdateiOSCodeFiles")
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
}
});
Task("UpdateWatchProject")
.IsDependentOn("UpdateiOSPlist")
.WithCriteria(() => !string.IsNullOrEmpty(_iOSVersionName))
.Does(()=> {
var watchProjectPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj");
UpdateWatchPbxproj(watchProjectPath, _iOSVersionName);
});
Task("UpdateWatchKitAppInfoPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Info.plist");
UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
});
#endregion iOS
#region Main Tasks
@@ -318,12 +410,14 @@ Task("Android")
});
Task("iOS")
//.IsDependentOn("UpdateiOSIcon")
.IsDependentOn("UpdateiOSIcons")
.IsDependentOn("UpdateiOSPlist")
.IsDependentOn("UpdateiOSAutofillPlist")
.IsDependentOn("UpdateiOSExtensionPlist")
.IsDependentOn("UpdateiOSShareExtensionPlist")
.IsDependentOn("UpdateiOSCodeFiles")
.IsDependentOn("UpdateWatchProject")
.IsDependentOn("UpdateWatchKitAppInfoPlist")
.Does(()=>
{
Information("iOS app updated");

View File

@@ -38,3 +38,15 @@ files:
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
update_option: update_as_unapproved
languages_mapping:
two_letters_code:
zh-CN: zh-Hans
zh-TW: zh-Hant
pt-BR: pt-BR
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN

1
lib/MessagePack Submodule

Submodule lib/MessagePack added at 1ecb15e311

View File

@@ -78,21 +78,20 @@
<Version>1.9.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1.1" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.14" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.17" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0.1" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.15" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.16" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.19" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.0" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.3</Version>
<Version>1.7.5</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>123.0.8</Version>
<Version>123.1.1.1</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.6.1.1" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.41.0.2" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.8.0" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.44.2.1" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>118.0.1.2</Version>
<Version>118.0.1.3</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -160,6 +159,7 @@
<Compile Include="Constants.cs" />
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
<Compile Include="Services\WatchDeviceService.cs" />
<Compile Include="Renderers\CustomLabelRenderer.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" />
@@ -233,6 +233,18 @@
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />

View File

@@ -12,7 +12,7 @@ namespace Bit.Droid.Autofill
private List<Field> _passwordFields = null;
private List<Field> _usernameFields = null;
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username"};
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username" };
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
@@ -54,15 +54,14 @@ namespace Bit.Droid.Autofill
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
{
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
return _passwordFields;
}
}
else
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
if (!_passwordFields.Any())
{
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
if (!_passwordFields.Any())
{
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
}
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
}
return _passwordFields;
}
@@ -87,24 +86,26 @@ namespace Bit.Droid.Autofill
{
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
}
if (_usernameFields.Any())
{
return _usernameFields;
}
}
else
{
foreach (var passwordField in PasswordFields)
{
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
.LastOrDefault();
if (usernameField != null)
{
_usernameFields.Add(usernameField);
}
}
if (!_usernameFields.Any())
foreach (var passwordField in PasswordFields)
{
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
.LastOrDefault();
if (usernameField != null)
{
_usernameFields = Fields.Where(f => FieldHasUsernameTerms(f)).ToList();
_usernameFields.Add(usernameField);
}
}
if (!_usernameFields.Any())
{
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
}
return _usernameFields;
}
}
@@ -327,13 +328,18 @@ namespace Bit.Droid.Autofill
}
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms) && !FieldHasUsernameTerms(f);
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms) && !FieldIsUsername(f);
}
private bool FieldHasPasswordTerms(Field f)
{
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
}
private bool FieldIsUsername(Field f)
{
return f.InputType.HasFlag(InputTypes.TextVariationWebEmailAddress) || FieldHasUsernameTerms(f);
}
private bool FieldHasUsernameTerms(Field f)
{
@@ -350,4 +356,4 @@ namespace Bit.Droid.Autofill
return terms.Any(t => lowerValue.Contains(t));
}
}
}
}

View File

@@ -170,7 +170,7 @@ namespace Bit.Droid
{
if (intent?.GetStringExtra("uri") is string uri)
{
_messagingService.Send("popAllAndGoToAutofillCiphers");
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE);
if (_appOptions != null)
{
_appOptions.Uri = uri;
@@ -178,7 +178,7 @@ namespace Bit.Droid
}
else if (intent.GetBooleanExtra("generatorTile", false))
{
_messagingService.Send("popAllAndGoToTabGenerator");
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE);
if (_appOptions != null)
{
_appOptions.GeneratorTile = true;
@@ -186,7 +186,7 @@ namespace Bit.Droid
}
else if (intent.GetBooleanExtra("myVaultTile", false))
{
_messagingService.Send("popAllAndGoToTabMyVault");
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE);
if (_appOptions != null)
{
_appOptions.MyVaultTile = true;
@@ -198,7 +198,7 @@ namespace Bit.Droid
{
_appOptions.CreateSend = GetCreateSendRequest(intent);
}
_messagingService.Send("popAllAndGoToTabSend");
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE);
}
else
{

View File

@@ -21,6 +21,7 @@ using Bit.App.Utilities;
using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls;
using Bit.Core.Enums;
#if !FDROID
using Android.Gms.Security;
#endif
@@ -81,7 +82,8 @@ namespace Bit.Droid
ServiceContainer.Resolve<IAuthService>("authService"),
ServiceContainer.Resolve<ILogger>("logger"),
ServiceContainer.Resolve<IMessagingService>("messagingService"),
ServiceContainer.Resolve<IWatchDeviceService>());
ServiceContainer.Resolve<IWatchDeviceService>(),
ServiceContainer.Resolve<IConditionedAwaiterManager>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
}
#if !FDROID
@@ -143,9 +145,10 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var storageMediatorService = new StorageMediatorService(mobileStorageService, secureStorageService, preferencesStorage);
var stateService = new StateService(mobileStorageService, secureStorageService, storageMediatorService, messagingService);
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
new StateMigrationService(DeviceType.Android, liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(stateService, messagingService);
var fileService = new FileService(stateService, broadcasterService);
@@ -153,11 +156,12 @@ namespace Bit.Droid
messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService();
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
@@ -165,6 +169,7 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStorageMediatorService>(storageMediatorService);
ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
@@ -197,7 +202,9 @@ namespace Bit.Droid
private void Bootstrap()
{
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
var locale = ServiceContainer.Resolve<IStateService>().GetLocale();
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService)
.Init(locale != null ? new System.Globalization.CultureInfo(locale) : null);
ServiceContainer.Resolve<IAuthService>("authService").Init();
// Note: This is not awaited
var bootstrapTask = BootstrapAsync();

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.1.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2023.7.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
@@ -45,11 +45,11 @@
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http"/>
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https"/>
<data android:scheme="https" />
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,43 @@
using System.ComponentModel;
using Android.Content;
using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomLabelRenderer : LabelRenderer
{
public CustomLabelRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomLabel label)
{
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var label = sender as CustomLabel;
switch (e.PropertyName)
{
case nameof(CustomLabel.AutomationId):
Control.ContentDescription = label.AutomationId;
break;
}
base.OnElementPropertyChanged(sender, e);
}
}
}

View File

@@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="#F0F0F0"
android:strokeColor="#89929F"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#89929F" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#89929F" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#89929F" />
</vector>

View File

@@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="@android:color/transparent"
android:strokeColor="#A3A3A3"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#A3A3A3" />
</vector>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="30dp"
android:paddingRight="30dp">
<TextView
android:id="@+id/lblHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_header_text_size"
android:layout_marginTop="5dp"
android:layout_marginBottom="-3dp"
android:labelFor="@+id/txtValue"/>
<EditText
android:id="@id/txtValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_input_text_size"/>
<TextView
android:id="@+id/lblValueSubinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
</LinearLayout>

View File

@@ -2,4 +2,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
<dimen name="dialog_input_text_size">16sp</dimen>
<dimen name="dialog_header_text_size">12sp</dimen>
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
</resources>

View File

@@ -4,7 +4,6 @@ using Android.OS;
using Android.Security.Keystore;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security;
using Javax.Crypto;
@@ -12,6 +11,8 @@ namespace Bit.Droid.Services
{
public class BiometricService : IBiometricService
{
private readonly IStateService _stateService;
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
private const string KeyStoreName = "AndroidKeyStore";
@@ -23,28 +24,28 @@ namespace Bit.Droid.Services
private readonly KeyStore _keystore;
public BiometricService()
public BiometricService(IStateService stateService)
{
_stateService = stateService;
_keystore = KeyStore.GetInstance(KeyStoreName);
_keystore.Load(null);
}
public Task<bool> SetupBiometricAsync(string bioIntegrityKey = null)
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{
// bioIntegrityKey used in iOS only
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
CreateKey();
await CreateKeyAsync(bioIntegritySrcKey);
}
return Task.FromResult(true);
return true;
}
public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{
return Task.FromResult(true);
return true;
}
try
@@ -55,7 +56,7 @@ namespace Bit.Droid.Services
if (key == null || cipher == null)
{
return Task.FromResult(true);
return true;
}
cipher.Init(CipherMode.EncryptMode, key);
@@ -63,25 +64,32 @@ namespace Bit.Droid.Services
catch (KeyPermanentlyInvalidatedException e)
{
// Biometric has changed
return Task.FromResult(false);
await ClearStateAsync(bioIntegritySrcKey);
return false;
}
catch (UnrecoverableKeyException e)
{
// Biometric was disabled and re-enabled
return Task.FromResult(false);
await ClearStateAsync(bioIntegritySrcKey);
return false;
}
catch (InvalidKeyException e)
{
// Fallback for old bitwarden users without a key
LoggerHelper.LogEvenIfCantBeResolved(e);
CreateKey();
await CreateKeyAsync(bioIntegritySrcKey);
}
return Task.FromResult(true);
return true;
}
private void CreateKey()
private async Task CreateKeyAsync(string bioIntegritySrcKey = null)
{
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey,
await GetStateAsync(bioIntegritySrcKey));
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
try
{
var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
@@ -101,5 +109,16 @@ namespace Bit.Droid.Services
LoggerHelper.LogEvenIfCantBeResolved(e);
}
}
private async Task<string> GetStateAsync(string bioIntegritySrcKey)
{
return await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey) ??
Guid.NewGuid().ToString();
}
private async Task ClearStateAsync(string bioIntegritySrcKey)
{
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, null);
}
}
}

View File

@@ -49,12 +49,6 @@ namespace Bit.Droid.Services
}
}
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true)
{
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Media;
using Android.Nfc;
using Android.OS;
using Android.Provider;
@@ -13,11 +14,17 @@ using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Xamarin.Forms.Platform.Android;
using static Android.Icu.Text.CaseMap;
using static Android.Renderscripts.ScriptGroup;
using static Android.Util.EventLogTags;
using static Bit.App.Pages.SettingsPageViewModel;
namespace Bit.Droid.Services
@@ -209,10 +216,7 @@ namespace Bit.Droid.Services
}
if (numericKeyboard)
{
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
SetNumericKeyboardTo(input);
}
if (password)
{
@@ -248,6 +252,82 @@ namespace Bit.Droid.Services
return result.Task;
}
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
}
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetTitle(config.Title);
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
alertBuilder.SetView(view);
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
if (!string.IsNullOrEmpty(config.ThirdButtonText))
{
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
}
var alert = alertBuilder.Create();
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
lblHeader.Text = config.Subtitle;
lblValueSubinfo.Text = config.ValueSubInfo;
var defaultSubInfoColor = lblValueSubinfo.TextColors;
input.InputType = InputTypes.ClassText;
if (config.NumericKeyboard)
{
SetNumericKeyboardTo(input);
}
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
input.Text = config.Text ?? string.Empty;
input.SetSelection(config.Text?.Length ?? 0);
input.AfterTextChanged += (sender, args) =>
{
if (lblValueSubinfo.Text != config.ValueSubInfo)
{
lblValueSubinfo.Text = config.ValueSubInfo;
lblHeader.SetTextColor(defaultSubInfoColor);
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
}
};
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
alert.Show();
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
positiveButton.Click += (sender, args) =>
{
var error = config.ValidateText(input.Text);
if (error != null)
{
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.Text = error;
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
return;
}
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
alert.Dismiss();
};
return result.Task;
}
public void RateApp()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -502,11 +582,6 @@ namespace Bit.Droid.Services
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync())
{
@@ -530,5 +605,13 @@ namespace Bit.Droid.Services
// only used by iOS
throw new NotImplementedException();
}
private void SetNumericKeyboardTo(EditText editText)
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
}

View File

@@ -2,7 +2,6 @@
using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Models;
namespace Bit.Droid.Services
{
@@ -22,7 +21,7 @@ namespace Bit.Droid.Services
protected override bool CanSendData => false;
protected override Task SendDataToWatchAsync(WatchDTO watchDto) => throw new NotImplementedException();
protected override Task SendDataToWatchAsync(byte[] rawData) => throw new NotImplementedException();
protected override void ConnectToWatch() => throw new NotImplementedException();
}

View File

@@ -1,5 +1,6 @@
using Android.Content;
using Android.OS;
using Java.Lang;
namespace Bit.Droid.Utilities
{
@@ -13,7 +14,12 @@ namespace Bit.Droid.Utilities
// Note: getting the bundle like this will cause to call unparcel() internally
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
}
catch (BadParcelableException)
catch (Exception ex) when
(
ex is BadParcelableException ||
ex is ClassNotFoundException ||
ex is RuntimeException
)
{
intent.ReplaceExtras((Bundle)null);
}

View File

@@ -8,6 +8,7 @@ namespace Bit.App.Abstractions
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task StartDefaultNavigationFlowAsync(Action<AppOptions> appOptionsAction);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
Task PromptToSwitchToExistingAccountAsync(string userId);
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Bit.App.Abstractions
{
public interface IDeepLinkContext
{
bool OnNewUri(Uri uri);
}
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums;
using Bit.Core.Models;
@@ -18,6 +19,7 @@ namespace Bit.App.Abstractions
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true, bool password = false);
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config);
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);

View File

@@ -14,13 +14,14 @@
<ItemGroup>
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.2" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.5" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.3" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.6" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.5" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2578" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="MessagePack" Version="2.4.59" />
</ItemGroup>
<ItemGroup>
@@ -144,6 +145,8 @@
<Folder Include="Controls\DateTime\" />
<Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" />
<Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
</ItemGroup>
<ItemGroup>
@@ -436,6 +439,10 @@
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
<None Remove="MessagePack" />
<None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
<None Remove="Utilities\Automation\" />
<None Remove="Utilities\Prompts\" />
</ItemGroup>
</Project>

View File

@@ -23,6 +23,11 @@ namespace Bit.App
{
public partial class App : Application, IAccountsManagerHost
{
public const string POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE = "popAllAndGoToTabGenerator";
public const string POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE = "popAllAndGoToTabMyVault";
public const string POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE = "popAllAndGoToTabSend";
public const string POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE = "popAllAndGoToAutofillCiphers";
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IStateService _stateService;
@@ -33,6 +38,7 @@ namespace Bit.App
private readonly IFileService _fileService;
private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService;
private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests;
@@ -56,6 +62,7 @@ namespace Bit.App
_fileService = ServiceContainer.Resolve<IFileService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_configService = ServiceContainer.Resolve<IConfigService>();
_accountsManager.Init(() => Options, this);
@@ -103,12 +110,18 @@ namespace Bit.App
await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync();
}
else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == "popAllAndGoToTabMyVault" ||
message.Command == "popAllAndGoToTabSend" ||
message.Command == "popAllAndGoToAutofillCiphers")
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE ||
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
Device.BeginInvokeOnMainThread(async () =>
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
Options.OtpData = new OtpData((string)message.Data);
}
await Device.InvokeOnMainThreadAsync(async () =>
{
if (Current.MainPage is TabsPage tabsPage)
{
@@ -116,24 +129,29 @@ namespace Bit.App
{
await tabsPage.Navigation.PopModalAsync(false);
}
if (message.Command == "popAllAndGoToAutofillCiphers")
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE)
{
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
}
else if (message.Command == "popAllAndGoToTabMyVault")
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE)
{
Options.MyVaultTile = false;
tabsPage.ResetToVaultPage();
}
else if (message.Command == "popAllAndGoToTabGenerator")
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE)
{
Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage();
}
else if (message.Command == "popAllAndGoToTabSend")
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE)
{
tabsPage.ResetToSendPage();
}
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
}
});
}
@@ -145,6 +163,18 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage()));
});
}
else if (message.Command == Constants.ForceUpdatePassword)
{
Device.BeginInvokeOnMainThread(async () =>
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
@@ -201,7 +231,7 @@ namespace Bit.App
Id = loginRequestData.Id,
IpAddress = loginRequestData.RequestIpAddress,
Email = await _stateService.GetEmailAsync(),
FingerprintPhrase = loginRequestData.RequestFingerprint,
FingerprintPhrase = loginRequestData.FingerprintPhrase,
RequestDate = loginRequestData.CreationDate,
DeviceType = loginRequestData.RequestDeviceType,
Origin = loginRequestData.Origin
@@ -269,6 +299,8 @@ namespace Bit.App
// Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null;
}
await _configService.GetAsync();
_messagingService.Send("startEventTimer");
}
@@ -308,6 +340,7 @@ namespace Bit.App
private async Task SleptAsync()
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
await ClearSensitiveFieldsAsync();
_messagingService.Send("stopEventTimer");
}
@@ -315,6 +348,7 @@ namespace Bit.App
{
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync();
await ClearSensitiveFieldsAsync();
_messagingService.Send("startEventTimer");
await UpdateThemeAsync();
await ClearCacheIfNeededAsync();
@@ -335,6 +369,14 @@ namespace Bit.App
});
}
private async Task ClearSensitiveFieldsAsync()
{
await Device.InvokeOnMainThreadAsync(() =>
{
_messagingService.Send(Constants.ClearSensitiveFields);
});
}
private void SetCulture()
{
// Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
@@ -484,7 +526,8 @@ namespace Bit.App
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break;
case NavigationTarget.AutofillCiphers:
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
case NavigationTarget.OtpCipherSelection:
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
break;
case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));

View File

@@ -30,13 +30,15 @@
BackgroundColor="{DynamicResource BackgroundColor}"
VerticalOptions="Start"
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never"
AutomationId="AccountListView">
<ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell
Account="{Binding .}"
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
AutomationId="AccountViewCell"
/>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
@@ -60,20 +60,23 @@
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive}"
StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountEmailLabel" />
<Label
Grid.Row="0"
Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
StyleClass="accountlist-title, accountlist-title-platform"
TextColor="{DynamicResource MutedColor}"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountEmailLabel" />
<Label
Grid.Row="1"
IsVisible="{Binding ShowHostname}"
Text="{Binding AccountView.Hostname}"
StyleClass="accountlist-sub, accountlist-sub-platform"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountHostUrlLabel" />
<Label
Grid.Row="2"
Text="{u:I18n AccountUnlocked}"
@@ -81,7 +84,8 @@
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountStatusLabel" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLocked}"
@@ -89,7 +93,8 @@
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountStatusLabel" />
<Label
Grid.Row="2"
Text="{u:I18n AccountLoggedOut}"
@@ -97,7 +102,8 @@
StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic"
TextTransform="Lowercase"
LineBreakMode="TailTruncation" />
LineBreakMode="TailTruncation"
AutomationId="AccountStatusLabel" />
</Grid>
<controls:IconLabel
@@ -107,7 +113,8 @@
Margin="12,0"
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" />
StyleClass="list-icon, list-icon-platform"
AutomationId="InactiveVaultIcon" />
<controls:IconLabel
Grid.Column="2"
Text="{Binding AuthStatusIconActive}"
@@ -116,7 +123,8 @@
HorizontalOptions="Center"
VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform"
TextColor="{DynamicResource TextColor}"/>
TextColor="{DynamicResource TextColor}"
AutomationId="ActiveVaultIcon" />
</Grid>
<Grid
@@ -147,7 +155,8 @@
StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation"
VerticalOptions="Center"
Grid.Column="1" />
Grid.Column="1"
AutomationId="AddAccountButton" />
</Grid>
</Grid>
</ViewCell>

View File

@@ -9,7 +9,8 @@
StyleClass="list-row, list-row-platform"
RowSpacing="0"
ColumnSpacing="0"
x:DataType="controls:CipherViewCellViewModel">
x:DataType="controls:CipherViewCellViewModel"
AutomationId="CipherCell">
<Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/>
@@ -36,7 +37,8 @@
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" />
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherTypeIcon" />
<ff:CachedImage
x:Name="_iconImage"
@@ -52,7 +54,8 @@
Aspect="AspectFit"
IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" />
AutomationProperties.IsInAccessibleTree="False"
AutomationId="CipherWebsiteIcon" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions>
@@ -71,7 +74,8 @@
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" />
Text="{Binding Cipher.Name}"
AutomationId="CipherNameLabel" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
@@ -80,7 +84,8 @@
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}"
IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
Converter={StaticResource stringHasValueConverter}}"/>
Converter={StaticResource stringHasValueConverter}}"
AutomationId="CipherSubTitleLabel" />
<controls:IconLabel
Grid.Column="1"
Grid.Row="0"
@@ -91,7 +96,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" />
AutomationProperties.Name="{u:I18n Shared}"
AutomationId="CipherInCollectionIcon" />
<controls:IconLabel
Grid.Column="2"
Grid.Row="0"
@@ -102,7 +108,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Attachments}" />
AutomationProperties.Name="{u:I18n Attachments}"
AutomationId="CipherWithAttachmentsIcon" />
</Grid>
<controls:MiButton
@@ -114,6 +121,7 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Options}"
AutomationId="CipherOptionsButton" />
</controls:ExtendedGrid>

View File

@@ -1,4 +1,5 @@
using System;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
@@ -18,7 +19,7 @@ namespace Bit.App.Controls
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(Command<CipherView>), typeof(CipherViewCell));
nameof(ButtonCommand), typeof(ICommand), typeof(CipherViewCell));
public CipherViewCell()
{
@@ -42,9 +43,9 @@ namespace Bit.App.Controls
set => SetValue(CipherProperty, value);
}
public Command<CipherView> ButtonCommand
public ICommand ButtonCommand
{
get => GetValue(ButtonCommandProperty) as Command<CipherView>;
get => GetValue(ButtonCommandProperty) as ICommand;
set => SetValue(ButtonCommandProperty, value);
}

View File

@@ -0,0 +1,13 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class CustomLabel : Label
{
public CustomLabel()
{
}
public int? FontWeight { get; set; }
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<controls:ExtendedGrid xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Controls.SendViewCell"
@@ -54,14 +54,16 @@
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform"
Text="{Binding Send.Name}" />
Text="{Binding Send.Name}"
AutomationId="SendNameLabel" />
<Label
LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="6"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Send.DisplayDate}" />
Text="{Binding Send.DisplayDate}"
AutomationId="SendDateLabel" />
<controls:IconLabel
Grid.Column="1"
Grid.Row="0"
@@ -72,7 +74,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.ExclamationTriangle}}"
IsVisible="{Binding Send.Disabled, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Disabled}" />
AutomationProperties.Name="{u:I18n Disabled}"
AutomationId="DisabledSendLabel" />
<controls:IconLabel
Grid.Column="2"
Grid.Row="0"
@@ -83,7 +86,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Key}}"
IsVisible="{Binding Send.HasPassword, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Password}" />
AutomationProperties.Name="{u:I18n Password}"
AutomationId="PasswordProtectedSendLabel" />
<controls:IconLabel
Grid.Column="3"
Grid.Row="0"
@@ -94,7 +98,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Ban}}"
IsVisible="{Binding Send.MaxAccessCountReached, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n MaxAccessCountReached}" />
AutomationProperties.Name="{u:I18n MaxAccessCountReached}"
AutomationId="SendMaxAccessCountReachedLabel" />
<controls:IconLabel
Grid.Column="4"
Grid.Row="0"
@@ -105,7 +110,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Clock}}"
IsVisible="{Binding Send.Expired, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Expired}" />
AutomationProperties.Name="{u:I18n Expired}"
AutomationId="ExpiredSendLabel" />
<controls:IconLabel
Grid.Column="5"
Grid.Row="0"
@@ -116,7 +122,8 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
IsVisible="{Binding Send.PendingDelete, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n PendingDelete}" />
AutomationProperties.Name="{u:I18n PendingDelete}"
AutomationId="SendWithPendingDeletionLabel" />
</Grid>
<controls:MiButton
@@ -129,6 +136,7 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Options}"
AutomationId="SendOptionsButton" />
</controls:ExtendedGrid>

View File

@@ -33,7 +33,8 @@
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0"
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
AutomationId="BooleanCustomFieldNameLabel" />
<Label
Text="{Binding Field.Name, Mode=OneWay}"
IsVisible="{Binding IsEditing}"
@@ -49,13 +50,15 @@
Grid.Row="1"
Grid.Column="0"
Margin="0, 5, 0, 0"
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
AutomationId="BooleanCustomFieldValueLabel" />
<Switch
IsToggled="{Binding BooleanValue}"
IsVisible="{Binding IsEditing}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2" />
Grid.RowSpan="2"
AutomationId="BooleanCustomFieldValueToggle" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"

View File

@@ -31,7 +31,8 @@
Text="{Binding Field.Name, Mode=OneWay}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
Grid.Column="0"
AutomationId="HiddenCustomFieldNameLabel" />
<StackLayout
Grid.Row="1"
Grid.Column="0"
@@ -39,7 +40,8 @@
<controls:MonoLabel
Text="{Binding ValueText, Mode=OneWay}"
StyleClass="box-value"
IsVisible="{Binding ShowHiddenValue}" />
IsVisible="{Binding ShowHiddenValue}"
AutomationId="HiddenCustomFieldValueLabel" />
<controls:MonoLabel
Text="{Binding Field.MaskedValue, Mode=OneWay}"
StyleClass="box-value"
@@ -54,7 +56,10 @@
IsPassword="{Binding ShowHiddenValue, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding ShowViewHidden}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False">
IsTextPredictionEnabled="False"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding Field.Name}"
AutomationId="HiddenCustomFieldValueEntry">
<Entry.Keyboard>
<Keyboard x:FactoryMethod="Create">
<x:Arguments>
@@ -72,7 +77,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}" />
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationId="HiddenCustomFieldShowValueButton" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"

View File

@@ -29,13 +29,15 @@
Text="{Binding Field.Name, Mode=OneWay}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
Grid.Column="0"
AutomationId="LinkedCustomFieldNameLabel" />
<controls:IconLabel
Text="{Binding ValueText, Mode=OneWay}"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
AutomationId="LinkedCustomFieldValueLabel" />
<StackLayout
StyleClass="box-row, box-row-input"
IsVisible="{Binding IsEditing}">
@@ -44,7 +46,8 @@
ItemsSource="{Binding LinkedFieldOptions, Mode=OneTime}"
SelectedIndex="{Binding LinkedFieldOptionSelectedIndex}"
ItemDisplayBinding="{Binding Key}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="LinkedCustomFieldValuePicker" />
</StackLayout>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
@@ -55,7 +58,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Options}"
AutomationId="LinkedCustomFieldOptionsButton" />
</Grid>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
</StackLayout>

View File

@@ -29,19 +29,24 @@
Text="{Binding Field.Name, Mode=OneWay}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
Grid.Column="0"
AutomationId="TextCustomFieldNameLabel" />
<Label
Text="{Binding ValueText, Mode=OneWay}"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}"
AutomationId="TextCustomFieldValueLabel" />
<Entry
Text="{Binding Field.Value}"
StyleClass="box-value"
Grid.Row="1"
Grid.Column="0"
IsVisible="{Binding IsEditing}" />
IsVisible="{Binding IsEditing}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{Binding Field.Name}"
AutomationId="TextCustomFieldValueEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
@@ -51,7 +56,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Copy}" />
AutomationProperties.Name="{u:I18n Copy}"
AutomationId="TextCustomFieldCopyValue" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Cog}}"
@@ -61,7 +67,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Options}"
AutomationId="TextCustomFieldOptionsButton" />
</Grid>
<BoxView StyleClass="box-row-separator" IsVisible="{Binding IsEditing, Mode=OneWay, Converter={StaticResource inverseBool}}" />
</StackLayout>

View File

@@ -1,5 +1,6 @@
using System;
using Bit.Core.Enums;
using Bit.Core.Utilities;
namespace Bit.App.Models
{
@@ -23,6 +24,7 @@ namespace Bit.App.Models
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; }
public OtpData? OtpData { get; set; }
public void SetAllFrom(AppOptions o)
{
@@ -48,6 +50,7 @@ namespace Bit.App.Models
CreateSend = o.CreateSend;
CopyInsteadOfShareAfterSaving = o.CopyInsteadOfShareAfterSaving;
HideAccountSwitcher = o.HideAccountSwitcher;
OtpData = o.OtpData;
}
}
}

View File

@@ -1,10 +1,7 @@
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Text;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Domain;
@@ -73,13 +70,13 @@ namespace Bit.App.Pages
set => SetProperty(ref _policy, value);
}
public string ShowPasswordIcon => ShowPassword ? "" : "";
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string ConfirmMasterPassword { get; set; }
public string Hint { get; set; }
public async Task InitAsync(bool forceSync = false)
public virtual async Task InitAsync(bool forceSync = false)
{
if (forceSync)
{
@@ -162,7 +159,7 @@ namespace Bit.App.Pages
{
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.MasterPasswordLengthValMessageX, Constants.MasterPasswordMinimumChars),
AppResources.MasterPasswordPolicyValidationTitle, AppResources.Ok);
return false;
}

View File

@@ -34,7 +34,8 @@
Placeholder="ex. https://bitwarden.company.com"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="ServerUrlEntry"/>
</StackLayout>
<Label
Text="{u:I18n SelfHostedEnvironmentFooter}"
@@ -53,7 +54,8 @@
x:Name="_webVaultEntry"
Text="{Binding WebVaultUrl}"
Keyboard="Url"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="WebVaultUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
<Label
@@ -63,7 +65,8 @@
x:Name="_apiEntry"
Text="{Binding ApiUrl}"
Keyboard="Url"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="ApiUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
<Label
@@ -73,7 +76,8 @@
x:Name="_identityEntry"
Text="{Binding IdentityUrl}"
Keyboard="Url"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="IdentityUrlEntry"/>
</StackLayout>
<StackLayout StyleClass="box-row">
<Label
@@ -85,7 +89,8 @@
Keyboard="Url"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="IconsUrlEntry"/>
</StackLayout>
<Label
Text="{u:I18n CustomEnvironmentFooter}"

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -18,7 +19,8 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
PageTitle = AppResources.Settings;
BaseUrl = _environmentService.BaseUrl;
BaseUrl = _environmentService.BaseUrl == EnvironmentUrlData.DefaultEU.Base || EnvironmentUrlData.DefaultUS.Base == _environmentService.BaseUrl ?
string.Empty : _environmentService.BaseUrl;
WebVaultUrl = _environmentService.WebVaultUrl;
ApiUrl = _environmentService.ApiUrl;
IdentityUrl = _environmentService.IdentityUrl;

View File

@@ -23,12 +23,9 @@
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
AutomationProperties.Name="{u:I18n Account}"
AutomationId="AccountIconButton" />
<ToolbarItem x:Name="_closeButton" Text="{u:I18n Close}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"/>
<ToolbarItem
Icon="cog_environment.png" Clicked="Environment_Clicked" Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
@@ -53,7 +50,9 @@
Keyboard="Email"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding ContinueCommand}">
ReturnCommand="{Binding ContinueCommand}"
AutomationId="EmailAddressEntry"
>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
@@ -66,7 +65,28 @@
</Entry>
<StackLayout
Orientation="Horizontal"
Margin="0, 16, 0 ,0">
Margin="0, 6, 0 ,0">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding ShowEnvironmentPickerCommand}" />
</StackLayout.GestureRecognizers>
<Label
Text="{Binding RegionText}"
FontSize="13"
TextColor="{DynamicResource MutedColor}"
VerticalOptions="Center"
VerticalTextAlignment="Center"/>
<controls:IconLabel
Text="{Binding SelectedEnvironmentName}"
FontSize="13"
TextColor="{DynamicResource PrimaryColor}"
VerticalOptions="Center"
VerticalTextAlignment="Center"
AutomationId="RegionSelectorDropdown"/>
</StackLayout>
<StackLayout
Orientation="Horizontal"
Margin="0, 20, 0 ,0">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding RememberEmailCommand}" />
@@ -76,21 +96,27 @@
StyleClass="text-sm"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center"
VerticalTextAlignment="Center"/>
VerticalTextAlignment="Center"
/>
<Switch
Scale="0.8"
IsToggled="{Binding RememberEmail}"
VerticalOptions="Center"/>
VerticalOptions="Center"
AutomationId="RememberMeSwitch"
/>
</StackLayout>
</StackLayout>
<Button Text="{u:I18n Continue}"
StyleClass="btn-primary"
IsEnabled="{Binding CanContinue}"
Command="{Binding ContinueCommand}" />
Command="{Binding ContinueCommand}"
AutomationId="ContinueButton"
/>
<Label FormattedText="{Binding CreateAccountText}"
Margin="0, 10"
StyleClass="box-footer-label">
StyleClass="box-footer-label"
AutomationId="CreateAccountLabel">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CreateAccountCommand}" />
</Label.GestureRecognizers>
@@ -116,5 +142,4 @@
MainPage="{Binding Source={x:Reference _page}}"
BindingContext="{Binding AccountSwitchingOverlayViewModel}"/>
</AbsoluteLayout>
</pages:BaseContentPage>

View File

@@ -15,6 +15,8 @@ namespace Bit.App.Pages
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
@@ -70,6 +72,14 @@ namespace Bit.App.Pages
});
}
});
try
{
await _vm.UpdateEnvironment();
}
catch (Exception ex)
{
_logger.Value?.Exception(ex);
}
}
protected override bool OnBackButtonPressed()
@@ -128,14 +138,6 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page));
}
private void Environment_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.StartEnvironmentAction();
}
}
private async Task StartEnvironmentAsync()
{
await _accountListOverlay.HideAsync();

View File

@@ -4,7 +4,10 @@ using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -17,16 +20,19 @@ namespace Bit.App.Pages
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly ILogger _logger;
private readonly IEnvironmentService _environmentService;
private readonly IAccountsManager _accountManager;
private readonly IConfigService _configService;
private bool _showCancelButton;
private bool _rememberEmail;
private string _email;
private string _selectedEnvironmentName;
private bool _isEmailEnabled;
private bool _canLogin;
private IPlatformUtilsService _platformUtilsService;
private ILogger _logger;
private IEnvironmentService _environmentService;
private IAccountsManager _accountManager;
private bool _displayEuEnvironment;
public HomeViewModel()
{
@@ -36,6 +42,7 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
_configService = ServiceContainer.Resolve<IConfigService>();
PageTitle = AppResources.Bitwarden;
@@ -49,6 +56,8 @@ namespace Bit.App.Pages
onException: _logger.Exception, allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(async () => await Device.InvokeOnMainThreadAsync(CloseAction),
onException: _logger.Exception, allowsMultipleExecutions: false);
ShowEnvironmentPickerCommand = new AsyncCommand(ShowEnvironmentPickerAsync,
onException: _logger.Exception, allowsMultipleExecutions: false);
InitAsync().FireAndForget();
}
@@ -71,6 +80,13 @@ namespace Bit.App.Pages
additionalPropertyNames: new[] { nameof(CanContinue) });
}
public string SelectedEnvironmentName
{
get => $"{_selectedEnvironmentName} {BitwardenIcons.AngleDown}";
set => SetProperty(ref _selectedEnvironmentName, value);
}
public string RegionText => $"{AppResources.Region}:";
public bool CanContinue => !string.IsNullOrEmpty(Email);
public FormattedString CreateAccountText
@@ -101,11 +117,13 @@ namespace Bit.App.Pages
public AsyncCommand ContinueCommand { get; }
public AsyncCommand CloseCommand { get; }
public AsyncCommand CreateAccountCommand { get; }
public AsyncCommand ShowEnvironmentPickerCommand { get; }
public async Task InitAsync()
{
Email = await _stateService.GetRememberedEmailAsync();
RememberEmail = !string.IsNullOrEmpty(Email);
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag, forceRefresh: true);
}
public async Task ContinueToLoginStepAsync()
@@ -144,5 +162,59 @@ namespace Bit.App.Pages
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage, AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
public async Task ShowEnvironmentPickerAsync()
{
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
var options = _displayEuEnvironment
? new string[] { AppResources.US, AppResources.EU, AppResources.SelfHosted }
: new string[] { AppResources.US, AppResources.SelfHosted };
await Device.InvokeOnMainThreadAsync(async () =>
{
var result = await Page.DisplayActionSheet(AppResources.DataRegion, AppResources.Cancel, null, options);
if (result is null || result == AppResources.Cancel)
{
return;
}
if (result == AppResources.SelfHosted)
{
StartEnvironmentAction?.Invoke();
return;
}
await _environmentService.SetUrlsAsync(result == AppResources.EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
await _configService.GetAsync(true);
SelectedEnvironmentName = result;
});
}
public async Task UpdateEnvironment()
{
var environmentsSaved = await _stateService.GetPreAuthEnvironmentUrlsAsync();
if (environmentsSaved == null || environmentsSaved.IsEmpty)
{
await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS);
environmentsSaved = EnvironmentUrlData.DefaultUS;
SelectedEnvironmentName = AppResources.US;
return;
}
if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base)
{
SelectedEnvironmentName = AppResources.US;
}
else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base)
{
SelectedEnvironmentName = AppResources.EU;
}
else
{
await _configService.GetAsync(true);
SelectedEnvironmentName = AppResources.SelfHosted;
}
}
}
}

View File

@@ -24,7 +24,8 @@
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
AutomationProperties.Name="{u:I18n Account}"
AutomationId="AccountIconButton" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
@@ -45,7 +46,7 @@
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
IsVisible="{Binding PinEnabled}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -71,7 +72,8 @@
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="PinEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -81,12 +83,13 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="PinVisibilityToggle" />
</Grid>
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -111,7 +114,8 @@
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="MasterPasswordEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -121,7 +125,9 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="PasswordVisibilityToggle"
/>
</Grid>
<StackLayout
StyleClass="box-row"
@@ -137,7 +143,7 @@
</StackLayout>
<StackLayout Padding="10, 0">
<Label
Text="{u:I18n BiometricInvalidated}"
Text="{u:I18n AccountBiometricInvalidated}"
StyleClass="box-footer-label,text-danger,text-bold"
IsVisible="{Binding BiometricIntegrityValid, Converter={StaticResource inverseBool}}" />
<Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
@@ -147,7 +153,8 @@
x:Name="_unlockButton"
Text="{u:I18n Unlock}"
StyleClass="btn-primary"
Clicked="Unlock_Clicked" />
Clicked="Unlock_Clicked"
AutomationId="UnlockVaultButton" />
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -3,6 +3,8 @@ using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -10,6 +12,7 @@ namespace Bit.App.Pages
{
public partial class LockPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly AppOptions _appOptions;
private readonly bool _autoPromptBiometric;
private readonly LockPageViewModel _vm;
@@ -22,6 +25,7 @@ namespace Bit.App.Pages
_appOptions = appOptions;
_autoPromptBiometric = autoPromptBiometric;
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_vm = BindingContext as LockPageViewModel;
_vm.Page = this;
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
@@ -40,7 +44,7 @@ namespace Bit.App.Pages
{
get
{
if (_vm?.PinLock ?? false)
if (_vm?.PinEnabled ?? false)
{
return _pin;
}
@@ -50,7 +54,7 @@ namespace Bit.App.Pages
public async Task PromptBiometricAfterResumeAsync()
{
if (_vm.BiometricLock)
if (_vm.BiometricEnabled)
{
await Task.Delay(500);
if (!_promptedAfterResume)
@@ -64,6 +68,13 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(LockPage), message =>
{
if (message.Command == Constants.ClearSensitiveFields)
{
Device.BeginInvokeOnMainThread(_vm.ResetPinPasswordFields);
}
});
if (_appeared)
{
return;
@@ -80,13 +91,13 @@ namespace Bit.App.Pages
_vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock)
if (!_vm.BiometricEnabled)
{
RequestFocus(SecretEntry);
}
else
{
if (_vm.UsingKeyConnector && !_vm.PinLock)
if (_vm.UsingKeyConnector && !_vm.PinEnabled)
{
_passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false;
@@ -129,6 +140,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(LockPage));
}
private void Unlock_Clicked(object sender, EventArgs e)

View File

@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.Helpers;
using Xamarin.Forms;
@@ -30,19 +31,23 @@ namespace Bit.App.Pages
private readonly ILogger _logger;
private readonly IWatchDeviceService _watchDeviceService;
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ISyncService _syncService;
private string _email;
private string _masterPassword;
private string _pin;
private bool _showPassword;
private bool _pinLock;
private bool _biometricLock;
private PinLockEnum _pinStatus;
private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible;
private bool _usingKeyConnector;
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel()
{
@@ -58,6 +63,10 @@ namespace Bit.App.Pages
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_policyService = ServiceContainer.Resolve<IPolicyService>();
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
@@ -70,6 +79,18 @@ namespace Bit.App.Pages
};
}
public string MasterPassword
{
get => _masterPassword;
set => SetProperty(ref _masterPassword, value);
}
public string Pin
{
get => _pin;
set => SetProperty(ref _pin, value);
}
public bool ShowPassword
{
get => _showPassword;
@@ -81,10 +102,10 @@ namespace Bit.App.Pages
});
}
public bool PinLock
public bool PinEnabled
{
get => _pinLock;
set => SetProperty(ref _pinLock, value);
get => _pinEnabled;
set => SetProperty(ref _pinEnabled, value);
}
public bool UsingKeyConnector
@@ -92,10 +113,10 @@ namespace Bit.App.Pages
get => _usingKeyConnector;
}
public bool BiometricLock
public bool BiometricEnabled
{
get => _biometricLock;
set => SetProperty(ref _biometricLock, value);
get => _biometricEnabled;
set => SetProperty(ref _biometricEnabled, value);
}
public bool BiometricIntegrityValid
@@ -134,8 +155,6 @@ namespace Bit.App.Pages
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string MasterPassword { get; set; }
public string Pin { get; set; }
public Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry
{
@@ -145,14 +164,18 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
_pinStatus = await _vaultTimeoutService.IsPinLockSetAsync();
var ephemeralPinSet = await _stateService.GetUserKeyPinEphemeralAsync()
?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockEnum.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockEnum.Persistent;
BiometricEnabled = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasEncryptedUserKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricLock || PinLock))
if (_usingKeyConnector && !(BiometricEnabled || PinEnabled))
{
await _vaultTimeoutService.LogOutAsync();
return;
@@ -171,7 +194,7 @@ namespace Bit.App.Pages
}
var webVaultHostname = CoreHelpers.GetHostname(webVault);
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
if (PinLock)
if (PinEnabled)
{
PageTitle = AppResources.VerifyPIN;
LockedVerifyText = AppResources.VaultLockedPIN;
@@ -190,9 +213,9 @@ namespace Bit.App.Pages
}
}
if (BiometricLock)
if (BiometricEnabled)
{
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid)
{
BiometricButtonVisible = false;
@@ -212,14 +235,14 @@ namespace Bit.App.Pages
public async Task SubmitAsync()
{
if (PinLock && string.IsNullOrWhiteSpace(Pin))
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok);
return;
}
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
@@ -230,34 +253,54 @@ namespace Bit.App.Pages
ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock)
if (PinEnabled)
{
var failed = true;
try
{
if (_isPinProtected)
EncString userKeyPin = null;
EncString oldPinProtected = null;
if (_pinStatus == PinLockEnum.Persistent)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
userKeyPin = await _stateService.GetUserKeyPinAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
}
else if (_pinStatus == PinLockEnum.Transient)
{
userKeyPin = await _stateService.GetUserKeyPinEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockEnum.Transient,
Pin,
_email,
kdfConfig,
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
oldPinProtected
);
}
else
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
failed = false;
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
Pin,
_email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
await SetKeyAndContinueAsync(userKey);
}
}
catch
@@ -278,26 +321,31 @@ namespace Bit.App.Pages
}
else
{
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetPasswordHashAsync();
var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null)
{
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
// Offline unlock possible
passwordValid = await _cryptoService.CompareAndUpdatePasswordHashAsync(MasterPassword, masterKey);
}
else
{
// Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
await _apiService.PostAccountVerifyPasswordAsync(request);
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash);
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey, HashPurpose.LocalAuthorization);
await _cryptoService.SetPasswordHashAsync(localKeyHash);
}
catch (Exception e)
{
@@ -307,20 +355,22 @@ namespace Bit.App.Pages
}
if (passwordValid)
{
if (_isPinProtected)
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
// Save the ForcePasswordResetReason to force a password reset after unlock
await _stateService.SetForcePasswordResetReasonAsync(
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetKeyAndContinueAsync(userKey);
// Re-enable biometrics
if (BiometricLock & !BiometricIntegrityValid)
if (BiometricEnabled & !BiometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync();
}
@@ -339,6 +389,37 @@ namespace Bit.App.Pages
}
}
/// <summary>
/// Checks if the master password requires updating to meet the enforced policy requirements
/// </summary>
/// <param name="options"></param>
private async Task<bool> RequirePasswordChangeAsync(MasterPasswordPolicyOptions options = null)
{
// If no policy options are provided, attempt to load them from the policy service
var enforcedOptions = options ?? await _policyService.GetMasterPasswordPolicyOptions();
// No policy to enforce on login/unlock
if (!(enforcedOptions is { EnforceOnLogin: true }))
{
return false;
}
var strength = _passwordGenerationService.PasswordStrength(
MasterPassword, _passwordGenerationService.GetPasswordStrengthUserInput(_email))?.Score;
if (!strength.HasValue)
{
_logger.Error("Unable to evaluate master password strength during unlock");
return false;
}
return !await _policyService.EvaluateMasterPassword(
strength.Value,
MasterPassword,
enforcedOptions
);
}
public async Task LogOutAsync()
{
var confirmed = await _platformUtilsService.ShowDialogAsync(AppResources.LogoutConfirmation,
@@ -349,22 +430,37 @@ namespace Bit.App.Pages
}
}
public void ResetPinPasswordFields()
{
try
{
MasterPassword = string.Empty;
Pin = string.Empty;
ShowPassword = false;
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword;
var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
}
public async Task PromptBiometricAsync()
{
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
if (!BiometricLock || !BiometricIntegrityValid)
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricEnabled || !BiometricIntegrityValid)
{
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
await _stateService.SetBiometricLockedAsync(!success);
if (success)
@@ -373,18 +469,20 @@ namespace Bit.App.Pages
}
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
private async Task SetKeyAndContinueAsync(UserKey key)
{
var hasKey = await _cryptoService.HasKeyAsync();
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
{
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetUserKeyAsync(key);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync();
}
private async Task DoContinueAsync()
{
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_messagingService.Send("unlocked");

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" ?>
<pages:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Bit.App.Pages.LoginApproveDevicePage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginApproveDeviceViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LoginApproveDeviceViewModel />
</ContentPage.BindingContext>
<StackLayout Padding="10, 10">
<StackLayout Padding="5, 10" Orientation="Horizontal">
<StackLayout HorizontalOptions="FillAndExpand">
<Label
StyleClass="text-md"
Text="{u:I18n RememberThisDevice}"/>
<Label
StyleClass="box-sub-label"
Text="{u:I18n TurnOffUsingPublicDevice}"/>
</StackLayout>
<Switch
Scale="0.8"
IsToggled="{Binding RememberThisDevice}"
VerticalOptions="Center"/>
</StackLayout>
<StackLayout Margin="0, 20, 0, 0">
<Button
x:Name="_continue"
Text="{u:I18n Continue}"
StyleClass="btn-primary"
Command="{Binding ContinueCommand}"
IsVisible="{Binding ContinueEnabled}"/>
<Button
x:Name="_approveWithMyOtherDevice"
Text="{u:I18n ApproveWithMyOtherDevice}"
StyleClass="btn-primary"
Command="{Binding ApproveWithMyOtherDeviceCommand}"
IsVisible="{Binding ApproveWithMyOtherDeviceEnabled}"/>
<Button
x:Name="_requestAdminApproval"
Text="{u:I18n RequestAdminApproval}"
StyleClass="box-button-row"
Command="{Binding RequestAdminApprovalCommand}"
IsVisible="{Binding RequestAdminApprovalEnabled}"/>
<Button
x:Name="_approveWithMasterPassword"
Text="{u:I18n ApproveWithMasterPassword}"
StyleClass="box-button-row"
Command="{Binding ApproveWithMasterPasswordCommand}"
IsVisible="{Binding ApproveWithMasterPasswordEnabled}"/>
<Label
Text="{Binding LoggingInAsText}"
StyleClass="text-sm"
Margin="0,40,0,0"
AutomationId="LoggingInAsLabel"
/>
<Label
Text="{u:I18n NotYou}"
StyleClass="text-md"
HorizontalOptions="Start"
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="NotYouLabel">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Cancel_Clicked" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</StackLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginApproveDevicePage : BaseContentPage
{
private readonly LoginApproveDeviceViewModel _vm;
private readonly AppOptions _appOptions;
public LoginApproveDevicePage(AppOptions appOptions = null)
{
InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel;
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPassword().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.CloseAction = () => { Navigation.PopModalAsync(); };
_vm.Page = this;
_appOptions = appOptions;
}
protected override void OnAppearing()
{
_vm.InitAsync();
}
private void Cancel_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_vm.CloseAction();
}
}
private async Task StartLogInWithMasterPassword()
{
var page = new LockPage(_appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task RequestAdminApprovalAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -0,0 +1,132 @@
using System;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginApproveDeviceViewModel : BaseViewModel
{
private bool _rememberThisDevice;
private bool _approveWithMyOtherDeviceEnabled;
private bool _requestAdminApprovalEnabled;
private bool _approveWithMasterPasswordEnabled;
private bool _continueEnabled;
private string _email;
private readonly IStateService _stateService;
private readonly IApiService _apiService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; }
public ICommand ApproveWithMasterPasswordCommand { get; }
public ICommand ContinueCommand { get; }
public Action LogInWithMasterPasswordAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
public Action RequestAdminApprovalAction { get; set; }
public Action CloseAction { get; set; }
public LoginApproveDeviceViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.LoggedIn;
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
ContinueCommand = new AsyncCommand(InitAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public bool RememberThisDevice
{
get => _rememberThisDevice;
set => SetProperty(ref _rememberThisDevice, value);
}
public bool ApproveWithMyOtherDeviceEnabled
{
get => _approveWithMyOtherDeviceEnabled;
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
}
public bool RequestAdminApprovalEnabled
{
get => _requestAdminApprovalEnabled;
set => SetProperty(ref _requestAdminApprovalEnabled, value);
}
public bool ApproveWithMasterPasswordEnabled
{
get => _approveWithMasterPasswordEnabled;
set => SetProperty(ref _approveWithMasterPasswordEnabled, value);
}
public bool ContinueEnabled
{
get => _continueEnabled;
set => SetProperty(ref _continueEnabled, value);
}
public string Email
{
get => _email;
set => SetProperty(ref _email, value, additionalPropertyNames:
new string[] {
nameof(LoggingInAsText)
});
}
public async Task InitAsync()
{
try
{
Email = await _stateService.GetRememberedEmailAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
}
catch (Exception ex)
{
HandleException(ex);
}
// TODO: Change this expression to, Appear if the browser is trusted and shared the key with the app
ContinueEnabled = !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled && !ApproveWithMyOtherDeviceEnabled;
}
private async Task SetDeviceTrustAndInvokeAsync(Action action)
{
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
await Device.InvokeOnMainThreadAsync(action);
}
}
}

View File

@@ -9,23 +9,23 @@
xmlns:u="clr-namespace:Bit.App.Utilities"
x:DataType="pages:LoginPageViewModel"
x:Name="_page"
Title="{Binding PageTitle}">
Title="{Binding PageTitle}"
AutomationId="PageTitleLabel">
<ContentPage.BindingContext>
<pages:LoginPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
x:Key="accountAvatar"
IconImageSource="{Binding AvatarImageSource}"
Command="{Binding Source={x:Reference _accountListOverlay}, Path=ToggleVisibililtyCommand}"
Order="Primary"
Priority="-1"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
AutomationProperties.Name="{u:I18n Account}"
AutomationId="AccountIconButton" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
@@ -34,7 +34,8 @@
<ToolbarItem Icon="more_vert.png" Clicked="More_Clicked" Order="Primary"
x:Name="_moreItem" x:Key="moreItem"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" />
AutomationProperties.Name="{u:I18n Options}"
AutomationId="OptionsButton" />
<ToolbarItem Text="{u:I18n GetPasswordHint}"
x:Key="getPasswordHint"
x:Name="_getPasswordHint"
@@ -75,7 +76,9 @@
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding LogInCommand}" />
ReturnCommand="{Binding LogInCommand}"
AutomationId="MasterPasswordEntry"
/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -84,6 +87,7 @@
Grid.Column="1"
Grid.RowSpan="1"
AutomationProperties.IsInAccessibleTree="True"
AutomationId="PasswordVisibilityToggle"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
<Label
@@ -93,7 +97,9 @@
Padding="0,5,0,0"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2">
Grid.ColumnSpan="2"
AutomationId="GetMasterPasswordHintLabel"
>
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Hint_Clicked" />
</Label.GestureRecognizers>
@@ -104,19 +110,24 @@
<Button x:Name="_loginWithMasterPassword"
Text="{u:I18n LogInWithMasterPassword}"
StyleClass="btn-primary"
Clicked="LogIn_Clicked" />
Clicked="LogIn_Clicked"
AutomationId="LogInWithMasterPasswordButton"
/>
<controls:IconLabelButton
HorizontalOptions="Fill"
VerticalOptions="CenterAndExpand"
Icon="{Binding Source={x:Static core:BitwardenIcons.Device}}"
Label="{u:I18n LogInWithAnotherDevice}"
ButtonCommand="{Binding LogInWithDeviceCommand}"
IsVisible="{Binding IsKnownDevice}"/>
IsVisible="{Binding IsKnownDevice}"
AutomationId="LogInWithAnotherDeviceButton"
/>
<controls:IconLabelButton
HorizontalOptions="Fill"
VerticalOptions="CenterAndExpand"
Icon="{Binding Source={x:Static core:BitwardenIcons.Suitcase}}"
Label="{u:I18n LogInSso}">
Label="{u:I18n LogInSso}"
AutomationId="LogInWithSsoButton">
<controls:IconLabelButton.GestureRecognizers>
<TapGestureRecognizer Tapped="LogInSSO_Clicked" />
</controls:IconLabelButton.GestureRecognizers>
@@ -124,12 +135,15 @@
<Label
Text="{Binding LoggingInAsText}"
StyleClass="text-sm"
Margin="0,40,0,0"/>
Margin="0,40,0,0"
AutomationId="LoggingInAsLabel"
/>
<Label
Text="{u:I18n NotYou}"
StyleClass="text-md"
HorizontalOptions="Start"
TextColor="{DynamicResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="NotYouLabel">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="Cancel_Clicked" />
</Label.GestureRecognizers>

View File

@@ -2,7 +2,9 @@
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -12,6 +14,7 @@ namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
@@ -23,6 +26,7 @@ namespace Bit.App.Pages
{
_appOptions = appOptions;
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_vm = BindingContext as LoginPageViewModel;
_vm.Page = this;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
@@ -70,6 +74,13 @@ namespace Bit.App.Pages
protected override async void OnAppearing()
{
base.OnAppearing();
_broadcasterService.Subscribe(nameof(LoginPage), message =>
{
if (message.Command == Constants.ClearSensitiveFields)
{
Device.BeginInvokeOnMainThread(_vm.ResetPasswordField);
}
});
_mainContent.Content = _mainLayout;
_accountAvatar?.OnAppearing();
@@ -104,6 +115,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(LoginPage));
}
private async void LogIn_Clicked(object sender, EventArgs e)
@@ -124,7 +136,7 @@ namespace Bit.App.Pages
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -40,6 +40,8 @@ namespace Bit.App.Pages
private string _masterPassword;
private bool _isEmailEnabled;
private bool _isKnownDevice;
private bool _isExecutingLogin;
private string _environmentHostName;
public LoginPageViewModel()
{
@@ -114,6 +116,16 @@ namespace Bit.App.Pages
set => SetProperty(ref _isKnownDevice, value);
}
public string EnvironmentDomainName
{
get => _environmentHostName;
set => SetProperty(ref _environmentHostName, value,
additionalPropertyNames: new string[]
{
nameof(LoggingInAsText)
});
}
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command LogInCommand { get; }
public Command TogglePasswordCommand { get; }
@@ -121,7 +133,7 @@ namespace Bit.App.Pages
public ICommand LogInWithDeviceCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public string LoggingInAsText => string.Format(AppResources.LoggingInAsXOnY, Email, EnvironmentDomainName);
public bool IsIosExtension { get; set; }
public bool CanRemoveAccount { get; set; }
public Action StartTwoFactorAction { get; set; }
@@ -143,20 +155,28 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _stateService.SetPreLoginEmailAsync(Email);
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
if (string.IsNullOrWhiteSpace(Email))
{
Email = await _stateService.GetRememberedEmailAsync();
}
var deviceIdentifier = await _appIdService.GetAppIdAsync();
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
await _deviceActionService.HideLoadingAsync();
EnvironmentDomainName = CoreHelpers.GetDomain((await _stateService.GetPreAuthEnvironmentUrlsAsync())?.Base);
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, await _appIdService.GetAppIdAsync());
}
catch (ApiException apiEx) when (apiEx.Error.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
_logger.Exception(apiEx);
}
catch (Exception ex)
{
HandleException(ex);
}
finally
{
await _deviceActionService.HideLoadingAsync();
}
}
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
@@ -191,6 +211,7 @@ namespace Bit.App.Pages
ShowPassword = false;
try
{
_isExecutingLogin = true;
if (checkForExistingAccount)
{
var userId = await _stateService.GetUserIdAsync(Email);
@@ -252,6 +273,26 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred, AppResources.Ok);
}
}
finally
{
_isExecutingLogin = false;
}
}
public void ResetPasswordField()
{
try
{
if (!_isExecutingLogin)
{
MasterPassword = string.Empty;
ShowPassword = false;
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
}
private async Task MoreAsync()

View File

@@ -32,7 +32,8 @@
<Label
Text="{Binding LogInAttemptByLabel}"
FontSize="Small"
Margin="0,0,0,24"/>
Margin="0,0,0,24"
AutomationId="LogInAttemptByLabel" />
<Label
Text="{u:I18n FingerprintPhrase}"
FontSize="Small"
@@ -41,7 +42,8 @@
FormattedText="{Binding LoginRequest.FingerprintPhrase}"
FontSize="Medium"
TextColor="{DynamicResource FingerprintPhrase}"
Margin="0,0,0,27"/>
Margin="0,0,0,27"
AutomationId="FingerprintValueLabel" />
<Label
Text="{u:I18n DeviceType}"
FontSize="Small"
@@ -49,7 +51,8 @@
<Label
Text="{Binding LoginRequest.DeviceType}"
FontSize="Small"
Margin="0,0,0,21"/>
Margin="0,0,0,21"
AutomationId="DeviceTypeValueLabel" />
<Label
Text="{u:I18n IpAddress}"
IsVisible="{Binding ShowIpAddress}"
@@ -59,7 +62,8 @@
Text="{Binding LoginRequest.IpAddress}"
IsVisible="{Binding ShowIpAddress}"
FontSize="Small"
Margin="0,0,0,21"/>
Margin="0,0,0,21"
AutomationId="IpAddressValueLabel" />
<Label
Text="{u:I18n Time}"
FontSize="Small"
@@ -67,7 +71,8 @@
<Label
Text="{Binding TimeOfRequestText}"
FontSize="Small"
Margin="0,0,0,57"/>
Margin="0,0,0,57"
AutomationId="TimeOfRequestValueLabel" />
</StackLayout>
</ScrollView>
@@ -75,11 +80,13 @@
Text="{u:I18n ConfirmLogIn}"
Command="{Binding AcceptRequestCommand}"
Margin="0,0,0,17"
StyleClass="btn-primary"/>
StyleClass="btn-primary"
AutomationId="ConfirmLoginButton" />
<Button
Text="{u:I18n DenyLogIn}"
Command="{Binding RejectRequestCommand}"
StyleClass="btn-secundary"/>
StyleClass="btn-secundary"
AutomationId="DenyLoginButton" />
</StackLayout>
</pages:BaseContentPage>

View File

@@ -21,16 +21,17 @@
<StackLayout
Padding="7, 0, 7, 20">
<Label
Text="{u:I18n LogInInitiated}"
Text="{Binding Tittle}"
FontSize="Title"
FontAttributes="Bold"
Margin="0,14,0,21"/>
Margin="0,14,0,21"
AutomationId="LogInInitiatedLabel" />
<Label
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
Text="{Binding SubTittle}"
FontSize="Small"
Margin="0,0,0,10"/>
<Label
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
Text="{Binding Description}"
FontSize="Small"
Margin="0,0,0,24"/>
<Label
@@ -40,13 +41,16 @@
<controls:MonoLabel
FormattedText="{Binding FingerprintPhrase}"
FontSize="Medium"
TextColor="{DynamicResource FingerprintPhrase}"/>
TextColor="{DynamicResource FingerprintPhrase}"
AutomationId="FingerprintPhraseValue" />
<Label
Text="{u:I18n ResendNotification}"
IsVisible="{Binding ResendNotificationVisible}"
StyleClass="text-md"
HorizontalOptions="Start"
Margin="0,40,0,0"
TextColor="{DynamicResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="ResendNotificationButton">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
</Label.GestureRecognizers>
@@ -55,7 +59,7 @@
Orientation="Horizontal"
Margin="0,30,0,0">
<Label
Text="{u:I18n NeedAnotherOption}"
Text="{Binding OtherOptions}"
FontSize="Small"
VerticalTextAlignment="End"/>
<Label
@@ -64,7 +68,8 @@
VerticalTextAlignment="End"
VerticalOptions="CenterAndExpand"
Margin="5, 0"
TextColor="{DynamicResource HyperlinkColor}">
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="ViewAllLoginOptionsButton">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CloseCommand}" />
</Label.GestureRecognizers>

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -12,13 +13,14 @@ namespace Bit.App.Pages
private LoginPasswordlessRequestViewModel _vm;
private readonly AppOptions _appOptions;
public LoginPasswordlessRequestPage(string email, AppOptions appOptions = null)
public LoginPasswordlessRequestPage(string email, AuthRequestType authRequestType, AppOptions appOptions = null)
{
InitializeComponent();
_appOptions = appOptions;
_vm = BindingContext as LoginPasswordlessRequestViewModel;
_vm.Page = this;
_vm.Email = email;
_vm.AuthRequestType = authRequestType;
_vm.StartTwoFactorAction = () => Device.BeginInvokeOnMainThread(async () => await StartTwoFactorAsync());
_vm.LogInSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await LogInSuccessAsync());
_vm.UpdateTempPasswordAction = () => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -12,6 +13,7 @@ using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Response;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -32,6 +34,9 @@ namespace Bit.App.Pages
private IPlatformUtilsService _platformUtilsService;
private IEnvironmentService _environmentService;
private ILogger _logger;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoFunctionService _cryptoFunctionService;
private readonly ICryptoService _cryptoService;
protected override II18nService i18nService => _i18nService;
protected override IEnvironmentService environmentService => _environmentService;
@@ -44,6 +49,7 @@ namespace Bit.App.Pages
private string _email;
private string _requestId;
private string _requestAccessCode;
private AuthRequestType _authRequestType;
// Item1 publicKey, Item2 privateKey
private Tuple<byte[], byte[]> _requestKeyPair;
@@ -57,6 +63,9 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>();
_stateService = ServiceContainer.Resolve<IStateService>();
_logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoFunctionService = ServiceContainer.Resolve<ICryptoFunctionService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.LogInWithAnotherDevice;
@@ -77,6 +86,70 @@ namespace Bit.App.Pages
public ICommand CreatePasswordlessLoginCommand { get; }
public ICommand CloseCommand { get; }
public string Tittle
{
get
{
switch (_authRequestType)
{
case AuthRequestType.AuthenticateAndUnlock:
return AppResources.LogInInitiated;
case AuthRequestType.AdminApproval:
return AppResources.AdminApprovalRequested;
default:
return string.Empty;
};
}
}
public string SubTittle
{
get
{
switch (_authRequestType)
{
case AuthRequestType.AuthenticateAndUnlock:
return AppResources.ANotificationHasBeenSentToYourDevice;
case AuthRequestType.AdminApproval:
return AppResources.YourRequestHasBeenSentToYourAdmin;
default:
return string.Empty;
};
}
}
public string Description
{
get
{
switch (_authRequestType)
{
case AuthRequestType.AuthenticateAndUnlock:
return AppResources.PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice;
case AuthRequestType.AdminApproval:
return AppResources.YouWillBeNotifiedOnceApproved;
default:
return string.Empty;
};
}
}
public string OtherOptions
{
get
{
switch (_authRequestType)
{
case AuthRequestType.AuthenticateAndUnlock:
return AppResources.NeedAnotherOption;
case AuthRequestType.AdminApproval:
return AppResources.TroubleLoggingIn;
default:
return string.Empty;
};
}
}
public string FingerprintPhrase
{
get => _fingerprintPhrase;
@@ -89,6 +162,21 @@ namespace Bit.App.Pages
set => SetProperty(ref _email, value);
}
public AuthRequestType AuthRequestType
{
get => _authRequestType;
set => SetProperty(ref _authRequestType, value, additionalPropertyNames: new string[]
{
nameof(Tittle),
nameof(SubTittle),
nameof(Description),
nameof(OtherOptions),
nameof(ResendNotificationVisible)
});
}
public bool ResendNotificationVisible => AuthRequestType == AuthRequestType.AuthenticateAndUnlock;
public void StartCheckLoginRequestStatus()
{
try
@@ -119,14 +207,22 @@ namespace Bit.App.Pages
private async Task CheckLoginRequestStatus()
{
if (string.IsNullOrEmpty(_requestId) || string.IsNullOrEmpty(_requestAccessCode))
if (string.IsNullOrEmpty(_requestId))
{
return;
}
try
{
var response = await _authService.GetPasswordlessLoginResponseAsync(_requestId, _requestAccessCode);
PasswordlessLoginResponse response = null;
if (await _stateService.IsAuthenticatedAsync())
{
response = await _authService.GetPasswordlessLoginRequestByIdAsync(_requestId);
}
else
{
response = await _authService.GetPasswordlessLoginResquestAsync(_requestId, _requestAccessCode);
}
if (response.RequestApproved == null || !response.RequestApproved.Value)
{
@@ -138,6 +234,12 @@ namespace Bit.App.Pages
var authResult = await _authService.LogInPasswordlessAsync(Email, _requestAccessCode, _requestId, _requestKeyPair.Item2, response.Key, response.MasterPasswordHash);
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await HandleLoginCompleteAsync();
return;
}
if (await HandleCaptchaAsync(authResult.CaptchaSiteKey, authResult.CaptchaNeeded, CheckLoginRequestStatus))
{
return;
@@ -153,8 +255,7 @@ namespace Bit.App.Pages
}
else
{
_syncService.FullSyncAsync(true).FireAndForget();
LogInSuccessAction?.Invoke();
await HandleLoginCompleteAsync();
}
}
catch (Exception ex)
@@ -164,22 +265,66 @@ namespace Bit.App.Pages
}
}
private async Task HandleLoginCompleteAsync()
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
LogInSuccessAction?.Invoke();
}
private async Task CreatePasswordlessLoginAsync()
{
await Device.InvokeOnMainThreadAsync(() => _deviceActionService.ShowLoadingAsync(AppResources.Loading));
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
if (response != null)
PasswordlessLoginResponse response = null;
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null && _authRequestType == AuthRequestType.AdminApproval)
{
FingerprintPhrase = response.RequestFingerprint;
_requestId = response.Id;
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;
response = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (response == null || (response.IsAnswered && !response.RequestApproved.Value))
{
// handle pending auth request not valid remove it from state
await _stateService.SetPendingAdminAuthRequestAsync(null);
pendingRequest = null;
}
else
{
// Derive pubKey from privKey in state to avoid MITM attacks
// Also generate FingerprintPhrase locally for the same reason
var derivedPublicKey = await _cryptoFunctionService.RsaExtractPublicKeyAsync(pendingRequest.PrivateKey);
response.FingerprintPhrase = string.Join("-", await _cryptoService.GetFingerprintAsync(Email, derivedPublicKey));
response.RequestKeyPair = new Tuple<byte[], byte[]>(derivedPublicKey, pendingRequest.PrivateKey);
}
}
if (response == null)
{
response = await _authService.PasswordlessCreateLoginRequestAsync(_email, AuthRequestType);
}
await HandlePasswordlessLoginAsync(response, pendingRequest == null && _authRequestType == AuthRequestType.AdminApproval);
await _deviceActionService.HideLoadingAsync();
}
private async Task HandlePasswordlessLoginAsync(PasswordlessLoginResponse response, bool createPendingAdminRequest)
{
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
if (createPendingAdminRequest)
{
var pendingAuthRequest = new PendingAdminAuthRequest { Id = response.Id, PrivateKey = response.RequestKeyPair.Item2 };
await _stateService.SetPendingAdminAuthRequestAsync(pendingAuthRequest);
}
FingerprintPhrase = response.FingerprintPhrase;
_requestId = response.Id;
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;
}
private void HandleException(Exception ex)
{
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>

View File

@@ -29,6 +29,8 @@ namespace Bit.App.Pages
_vm.SsoAuthSuccessAction = () => Device.BeginInvokeOnMainThread(async () => await SsoAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () =>
{
await Navigation.PopModalAsync();
@@ -106,10 +108,17 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task SsoAuthSuccessAsync()
{
RestoreAppOptionsFromCopy();
await AppHelpers.ClearPreviousPage();
if (await _vaultTimeoutService.IsLockedAsync())
{
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));

View File

@@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
@@ -8,6 +9,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
@@ -27,6 +29,9 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly ILogger _logger;
private readonly IOrganizationService _organizationService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoService _cryptoService;
private string _orgIdentifier;
@@ -42,7 +47,9 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
@@ -58,14 +65,31 @@ namespace Bit.App.Pages
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
public async Task InitAsync()
{
if (string.IsNullOrWhiteSpace(OrgIdentifier))
try
{
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
if (await TryClaimedDomainLogin())
{
return;
}
if (string.IsNullOrWhiteSpace(OrgIdentifier))
{
OrgIdentifier = await _stateService.GetRememberedOrgIdentifierAsync();
}
}
catch (Exception ex)
{
_logger.Exception(ex);
}
finally
{
await _deviceActionService.HideLoadingAsync();
}
}
@@ -102,9 +126,8 @@ namespace Bit.App.Pages
var ssoToken = response.Token;
var passwordOptions = new PasswordGenerationOptions(true);
passwordOptions.Length = 64;
var passwordOptions = PasswordGenerationOptions.CreateDefault
.WithLength(64);
var codeVerifier = await _passwordGenerationService.GeneratePasswordAsync(passwordOptions);
var codeVerifierHash = await _cryptoFunctionService.HashAsync(codeVerifier, CryptoHashAlgorithm.Sha256);
@@ -179,6 +202,7 @@ namespace Bit.App.Pages
try
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync();
@@ -194,9 +218,31 @@ namespace Bit.App.Pages
{
UpdateTempPasswordAction?.Invoke();
}
else if (decryptOptions?.TrustedDeviceOption != null)
{
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
}
@@ -207,5 +253,37 @@ namespace Bit.App.Pages
AppResources.AnErrorHasOccurred);
}
}
private async Task<bool> TryClaimedDomainLogin()
{
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var userEmail = await _stateService.GetPreLoginEmailAsync();
var claimedDomainOrgDetails = await _organizationService.GetClaimedOrganizationDomainAsync(userEmail);
await _deviceActionService.HideLoadingAsync();
if (claimedDomainOrgDetails == null || !claimedDomainOrgDetails.SsoAvailable)
{
return false;
}
if (string.IsNullOrEmpty(claimedDomainOrgDetails.OrganizationIdentifier))
{
await _platformUtilsService.ShowDialogAsync(AppResources.OrganizationSsoIdentifierRequired, AppResources.AnErrorHasOccurred);
return false;
}
OrgIdentifier = claimedDomainOrgDetails.OrganizationIdentifier;
await LogInAsync();
return true;
}
catch (Exception ex)
{
HandleException(ex);
}
return false;
}
}
}

View File

@@ -35,7 +35,8 @@
x:Name="_email"
Text="{Binding Email}"
Keyboard="Email"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="EmailAddressEntry"/>
</StackLayout>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
@@ -59,7 +60,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="MasterPasswordEntry"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -69,7 +71,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"/>
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="PasswordVisibilityToggle"/>
</Grid>
<Label
StyleClass="box-sub-label"
@@ -109,7 +112,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="ConfirmMasterPasswordEntry"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -118,6 +122,7 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationId="ConfirmPasswordVisibilityToggle"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
@@ -130,7 +135,8 @@
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="MasterPasswordHintLabel" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"
@@ -142,7 +148,8 @@
IsToggled="{Binding CheckExposedMasterPassword}"
StyleClass="box-value"
HorizontalOptions="Start"
Margin="0, 0, 10, 0"/>
Margin="0, 0, 10, 0"
AutomationId="CheckExposedMasterPasswordToggle"/>
<Label
Text="{u:I18n CheckKnownDataBreachesForThisPassword}"
StyleClass="box-footer-label"
@@ -154,7 +161,8 @@
IsToggled="{Binding AcceptPolicies}"
StyleClass="box-value"
HorizontalOptions="Start"
Margin="0, 0, 10, 0"/>
Margin="0, 0, 10, 0"
AutomationId="AcceptPoliciesToggle"/>
<Label StyleClass="box-footer-label"
HorizontalOptions="Fill">
<Label.FormattedText>

View File

@@ -148,7 +148,7 @@ namespace Bit.App.Pages
}
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
{
await _platformUtilsService.ShowDialogAsync(AppResources.MasterPasswordLengthValMessage,
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.MasterPasswordLengthValMessageX, Constants.MasterPasswordMinimumChars),
AppResources.AnErrorHasOccurred, AppResources.Ok);
return;
}
@@ -177,25 +177,28 @@ namespace Bit.App.Pages
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower();
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdfConfig);
var encKey = await _cryptoService.MakeEncKeyAsync(key);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, key);
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, Email, kdfConfig);
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(
newMasterKey,
await _cryptoService.MakeUserKeyAsync()
);
var hashedPassword = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey);
var (newPublicKey, newProtectedPrivateKey) = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new RegisterRequest
{
Email = Email,
Name = Name,
MasterPasswordHash = hashedPassword,
MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString,
Key = newProtectedUserKey.EncryptedString,
Kdf = kdfConfig.Type,
KdfIterations = kdfConfig.Iterations,
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
Keys = new KeysRequest
{
PublicKey = keys.Item1,
EncryptedPrivateKey = keys.Item2.EncryptedString
PublicKey = newPublicKey,
EncryptedPrivateKey = newProtectedPrivateKey.EncryptedString
},
CaptchaResponse = _captchaToken,
};

View File

@@ -152,7 +152,7 @@ namespace Bit.App.Pages
if (MasterPassword.Length < Constants.MasterPasswordMinimumChars)
{
await Page.DisplayAlert(AppResources.MasterPasswordPolicyValidationTitle,
AppResources.MasterPasswordLengthValMessage, AppResources.Ok);
string.Format(AppResources.MasterPasswordLengthValMessageX, Constants.MasterPasswordMinimumChars), AppResources.Ok);
return;
}
}
@@ -165,26 +165,18 @@ namespace Bit.App.Pages
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
var newMasterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.ServerAuthorization);
var localMasterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, newMasterKey, HashPurpose.LocalAuthorization);
Tuple<SymmetricCryptoKey, EncString> encKey;
var existingEncKey = await _cryptoService.GetEncKeyAsync();
if (existingEncKey == null)
{
encKey = await _cryptoService.MakeEncKeyAsync(key);
}
else
{
encKey = await _cryptoService.RemakeEncKeyAsync(key);
}
var (newUserKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(newMasterKey,
await _cryptoService.GetUserKeyAsync() ?? await _cryptoService.MakeUserKeyAsync());
var keys = await _cryptoService.MakeKeyPairAsync(encKey.Item1);
var keys = await _cryptoService.MakeKeyPairAsync(newUserKey);
var request = new SetPasswordRequest
{
MasterPasswordHash = masterPasswordHash,
Key = encKey.Item2.EncryptedString,
Key = newProtectedUserKey.EncryptedString,
MasterPasswordHint = Hint,
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
@@ -204,19 +196,19 @@ namespace Bit.App.Pages
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);
await _cryptoService.SetEncPrivateKeyAsync(keys.Item2.EncryptedString);
await _cryptoService.SetMasterKeyAsync(newMasterKey);
await _cryptoService.SetPasswordHashAsync(localMasterPasswordHash);
await _cryptoService.SetMasterKeyEncryptedUserKeyAsync(newProtectedUserKey.EncryptedString);
await _cryptoService.SetPrivateKeyAsync(keys.Item2.EncryptedString);
if (ResetPasswordAutoEnroll)
{
// Grab Organization Keys
var response = await _apiService.GetOrganizationKeysAsync(OrgId);
var publicKey = CoreHelpers.Base64UrlDecode(response.PublicKey);
// Grab user's Encryption Key and encrypt with Org Public Key
var userEncKey = await _cryptoService.GetEncKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(userEncKey.Key, publicKey);
// Grab User Key and encrypt with Org Public Key
var userKey = await _cryptoService.GetUserKeyAsync();
var encryptedKey = await _cryptoService.RsaEncryptAsync(userKey.Key, publicKey);
// Request
var resetRequest = new OrganizationUserResetPasswordEnrollmentRequest
{

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android)
@@ -180,6 +182,12 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartDeviceApprovalOptionsAsync()
{
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync()
{
if (_authingWithSso)

View File

@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -33,7 +34,7 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private readonly ILogger _logger;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com";
@@ -55,6 +56,7 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -118,6 +120,7 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; }
public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
@@ -315,6 +318,7 @@ namespace Bit.App.Pages
var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
@@ -326,6 +330,27 @@ namespace Bit.App.Pages
{
UpdateTempPasswordAction?.Invoke();
}
else if (decryptOptions?.TrustedDeviceOption != null)
{
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
TwoFactorAuthSuccessAction?.Invoke();
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
TwoFactorAuthSuccessAction?.Invoke();

View File

@@ -46,7 +46,7 @@
BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}">
<Label
Text="{u:I18n UpdateMasterPasswordWarning}"
Text="{Binding UpdateMasterPasswordWarningText }"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
@@ -74,6 +74,40 @@
HorizontalTextAlignment="Start" />
</Frame>
</Grid>
<Grid StyleClass="box-row" IsVisible="{Binding RequireCurrentPassword }">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Text="{u:I18n CurrentMasterPassword}"
StyleClass="box-label"
Grid.Row="0"
Grid.Column="0" />
<controls:MonoEntry
x:Name="_currentMasterPassword"
Text="{Binding CurrentMasterPassword}"
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
Command="{Binding TogglePasswordCommand}"
Grid.Row="0"
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
</Grid>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@@ -1,27 +1,68 @@
using System;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class UpdateTempPasswordPageViewModel : BaseChangePasswordViewModel
{
private readonly IUserVerificationService _userVerificationService;
private ForcePasswordResetReason _reason = ForcePasswordResetReason.AdminForcePasswordReset;
public UpdateTempPasswordPageViewModel()
{
PageTitle = AppResources.UpdateMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
ToggleConfirmPasswordCommand = new Command(ToggleConfirmPassword);
SubmitCommand = new Command(async () => await SubmitAsync());
SubmitCommand = new AsyncCommand(SubmitAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
}
public Command SubmitCommand { get; }
public AsyncCommand SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public Command ToggleConfirmPasswordCommand { get; }
public Action UpdateTempPasswordSuccessAction { get; set; }
public Action LogOutAction { get; set; }
public string CurrentMasterPassword { get; set; }
public override async Task InitAsync(bool forceSync = false)
{
await base.InitAsync(forceSync);
var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
if (forcePasswordResetReason.HasValue)
{
_reason = forcePasswordResetReason.Value;
}
}
public bool RequireCurrentPassword
{
get => _reason == ForcePasswordResetReason.WeakMasterPasswordOnLogin;
}
public string UpdateMasterPasswordWarningText
{
get
{
return _reason == ForcePasswordResetReason.WeakMasterPasswordOnLogin
? AppResources.UpdateWeakMasterPasswordWarning
: AppResources.UpdateMasterPasswordWarning;
}
}
public void TogglePassword()
{
@@ -42,32 +83,46 @@ namespace Bit.App.Pages
return;
}
if (RequireCurrentPassword &&
!await _userVerificationService.VerifyUser(CurrentMasterPassword, VerificationType.MasterPassword))
{
return;
}
// Retrieve details for key generation
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new master key and hash new password
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, masterKey);
// Create new encKey for the User
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
// Create request
var request = new UpdateTempPasswordRequest
{
Key = newEncKey.Item2.EncryptedString,
NewMasterPasswordHash = masterPasswordHash,
MasterPasswordHint = Hint
};
// Encrypt user key with new master key
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
// Initiate API action
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword);
await _apiService.PutUpdateTempPasswordAsync(request);
switch (_reason)
{
case ForcePasswordResetReason.AdminForcePasswordReset:
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break;
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break;
default:
throw new ArgumentOutOfRangeException();
}
await _deviceActionService.HideLoadingAsync();
// Clear the force reset password reason
await _stateService.SetForcePasswordResetReasonAsync(null);
_platformUtilsService.ShowToast(null, null, AppResources.UpdatedMasterPassword);
UpdateTempPasswordSuccessAction?.Invoke();
}
catch (ApiException e)
@@ -85,5 +140,32 @@ namespace Bit.App.Pages
}
}
}
private async Task UpdateTempPasswordAsync(string newMasterPasswordHash, string newEncKey)
{
var request = new UpdateTempPasswordRequest
{
Key = newEncKey,
NewMasterPasswordHash = newMasterPasswordHash,
MasterPasswordHint = Hint
};
await _apiService.PutUpdateTempPasswordAsync(request);
}
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
{
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
var request = new PasswordRequest
{
MasterPasswordHash = currentPasswordHash,
Key = newEncKey,
NewMasterPasswordHash = newMasterPasswordHash,
MasterPasswordHint = Hint
};
await _apiService.PostPasswordAsync(request);
}
}
}

View File

@@ -27,7 +27,8 @@
Clicked="Clear_Clicked"
Order="Secondary"
x:Name="_clearItem"
x:Key="clearItem" />
x:Key="clearItem"
AutomationId="ClearPasswordList" />
<ToolbarItem Icon="more_vert.png"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}"
@@ -43,7 +44,8 @@
Margin="20, 0"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center"></Label>
HorizontalTextAlignment="Center"
AutomationId="NoPasswordsDisplayedLabel"></Label>
<controls:ExtendedCollectionView
IsVisible="{Binding ShowNoData, Converter={StaticResource inverseBool}}"
ItemsSource="{Binding History}"
@@ -56,7 +58,8 @@
StyleClass="list-row, list-row-platform"
Padding="10"
RowSpacing="0"
ColumnSpacing="10">
ColumnSpacing="10"
AutomationId="GeneratedPasswordRow">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -71,12 +74,14 @@
Grid.Column="0"
Grid.Row="0"
StyleClass="list-title, list-title-platform, text-html"
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}" />
Text="{Binding Password, Mode=OneWay, Converter={StaticResource coloredPassword}}"
AutomationId="GeneratedPasswordValue" />
<Label LineBreakMode="TailTruncation"
Grid.Column="0"
Grid.Row="1"
StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}" />
Text="{Binding Date, Mode=OneWay, Converter={StaticResource dateTime}}"
AutomationId="GeneratedPasswordDateLabel" />
<controls:IconButton
StyleClass="list-row-button, list-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Paste}}"
@@ -86,7 +91,8 @@
Grid.Column="1"
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
AutomationProperties.Name="{u:I18n CopyPassword}"
AutomationId="CopyPasswordValueButton" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>

View File

@@ -71,7 +71,8 @@
<Label
Text="{u:I18n PasswordGeneratorPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="PasswordGeneratorPolicyInEffectLabel" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsUsername, Converter={StaticResource inverseBool}}"
@@ -82,21 +83,24 @@
x:Name="lblPassword"
StyleClass="text-lg, text-html"
Text="{Binding ColoredPassword, Mode=OneWay}"
Margin="0, 20" />
Margin="0, 20"
AutomationId="GeneratedPasswordLabel" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
Grid.Column="1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyPassword}" />
AutomationProperties.Name="{u:I18n CopyPassword}"
AutomationId="CopyValueButton" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
Command="{Binding RegenerateCommand}"
Grid.Column="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n GeneratePassword}" />
AutomationProperties.Name="{u:I18n GeneratePassword}"
AutomationId="RegenerateValueButton" />
</Grid>
<Grid IsVisible="{Binding IsUsername}"
StyleClass="box-row"
@@ -107,21 +111,24 @@
StyleClass="text-lg, text-html"
Text="{Binding ColoredUsername, Mode=OneWay}"
Margin="0, 20"
HorizontalOptions="Start" />
HorizontalOptions="Start"
AutomationId="GeneratedPasswordLabel" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
Command="{Binding CopyCommand}"
Grid.Column="1"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n CopyUsername}" />
AutomationProperties.Name="{u:I18n CopyUsername}"
AutomationId="CopyValueButton" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Generate}}"
Command="{Binding RegenerateUsernameCommand}"
Grid.Column="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n GenerateUsername}" />
AutomationProperties.Name="{u:I18n GenerateUsername}"
AutomationId="RegenerateValueButton" />
</Grid>
<BoxView StyleClass="box-row-separator"/>
<StackLayout StyleClass="box"
@@ -135,7 +142,8 @@
ItemsSource="{Binding GeneratorTypeOptions, Mode=OneTime}"
SelectedItem="{Binding GeneratorTypeSelected}"
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="GeneratorTypePicker" />
</StackLayout>
<Label Text="{u:I18n Options, Header=True}"
StyleClass="box-header, box-header-platform"
@@ -161,7 +169,8 @@
ItemsSource="{Binding UsernameTypeOptions, Mode=OneTime}"
SelectedItem="{Binding UsernameTypeSelected}"
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="UsernameTypePicker" />
<Label
StyleClass="box-footer-label"
Text="{Binding UsernameTypeDescriptionLabel}" />
@@ -172,7 +181,8 @@
StyleClass="box-label" />
<Entry x:Name="_plusAddressedEmailEntry"
Text="{Binding PlusAddressedEmail}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="PlusAddressedEmailEntry" />
<Label IsVisible="{Binding ShowUsernameEmailType}"
Text="{u:I18n EmailType}"
StyleClass="box-label"
@@ -203,7 +213,8 @@
<Entry
x:Name="_catchAllEmailDomainNameEntry"
Text="{Binding CatchAllEmailDomain}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="CatchAllEmailDomainEntry" />
<Label IsVisible="{Binding ShowUsernameEmailType}"
Text="{u:I18n EmailType}"
StyleClass="box-label"
@@ -236,26 +247,27 @@
ItemsSource="{Binding ForwardedEmailServiceTypeOptions, Mode=OneTime}"
SelectedItem="{Binding ForwardedEmailServiceSelected}"
ItemDisplayBinding="{Binding ., Converter={StaticResource localizableEnum}}"
StyleClass="box-value" />
<!--ANONADDY OPTIONS-->
<Grid IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
StyleClass="box-value"
AutomationId="ServiceTypePicker" />
<Grid
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
<Label
Margin="0,10,0,0"
Text="{u:I18n APIAccessToken}"
Text="{Binding ForwardedEmailApiSecretLabel}"
StyleClass="box-label"/>
<Entry
x:Name="_anonAddyApiAccessTokenEntry"
Text="{Binding AnonAddyApiAccessToken}"
IsPassword="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool}}"
Grid.Row="1"/>
Text="{Binding ForwardedEmailApiSecret}"
IsPassword="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool}}"
Grid.Row="1"
AutomationId="ForwardedEmailApiSecretEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Text="{Binding ShowForwardedEmailApiSecret, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
Grid.Column="1"
AutomationId="ShowForwardedEmailApiSecretButton" />
</Grid>
<Label IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
Text="{u:I18n DomainNameRequiredParenthesis}"
@@ -264,91 +276,8 @@
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
x:Name="_anonAddyDomainNameEntry"
Text="{Binding AnonAddyDomainName}"
StyleClass="box-value"/>
<!--FIREFOX RELAY OPTIONS-->
<Grid StyleClass="box-row, box-row-input"
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.FirefoxRelay}}"
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
<Label
Text="{u:I18n APIAccessToken}"
StyleClass="box-label"/>
<Entry
x:Name="_firefoxRelayApiAccessTokenEntry"
Text="{Binding FirefoxRelayApiAccessToken}"
StyleClass="box-value"
Grid.Row="1"
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
<!--SIMPLELOGIN OPTIONS-->
<Grid StyleClass="box-row, box-row-input"
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.SimpleLogin}}"
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
<Label
Text="{u:I18n APIKeyRequiredParenthesis}"
StyleClass="box-label"/>
<Entry
x:Name="_simpleLoginApiKeyEntry"
Text="{Binding SimpleLoginApiKey}"
StyleClass="box-value"
Grid.Row="1"
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
<!--DUCKDUCKGO OPTIONS-->
<Grid StyleClass="box-row, box-row-input"
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.DuckDuckGo}}"
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
<Label
Text="{u:I18n APIKeyRequiredParenthesis}"
StyleClass="box-label"/>
<Entry
x:Name="_duckDuckGoApiAccessTokenEntry"
Text="{Binding DuckDuckGoApiKey}"
StyleClass="box-value"
Grid.Row="1"
IsPassword="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowDuckDuckGoApiKey, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
<!--FASTMAIL OPTIONS-->
<Grid StyleClass="box-row, box-row-input"
IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.Fastmail}}"
Grid.RowDefinitions="Auto,*"
Grid.ColumnDefinitions="*,Auto">
<Label
Text="{u:I18n APIKeyRequiredParenthesis}"
StyleClass="box-label"/>
<Entry
x:Name="_fastmailApiAccessTokenEntry"
Text="{Binding FastmailApiKey}"
StyleClass="box-value"
Grid.Row="1"
IsPassword="{Binding ShowFastmailApiKey, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowFastmailApiKey, Converter={StaticResource iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
</Grid>
StyleClass="box-value"
AutomationId="AnonAddyDomainNameEntry" />
</StackLayout>
<!--RANDOM WORD OPTIONS-->
<Grid IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}">
@@ -359,7 +288,8 @@
<Switch
IsToggled="{Binding CapitalizeRandomWordUsername}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationId="CapitalizeRandomWordUsernameToggle" />
</Grid>
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
StyleClass="box-row-separator" />
@@ -371,7 +301,8 @@
<Switch
IsToggled="{Binding IncludeNumberRandomWordUsername}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationId="IncludeNumberRandomWordUsernameToggle" />
</Grid>
<BoxView IsVisible="{Binding UsernameTypeSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:UsernameType.RandomWord}}"
StyleClass="box-row-separator" />
@@ -386,7 +317,8 @@
x:Name="_passwordTypePicker"
ItemsSource="{Binding PasswordTypeOptions, Mode=OneTime}"
SelectedIndex="{Binding PasswordTypeSelectedIndex}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="PasswordTypePicker" />
</StackLayout>
<StackLayout Spacing="0"
Padding="0"
@@ -403,12 +335,14 @@
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
VerticalTextAlignment="Center"
AutomationId="NumberOfWordsLabel" />
<controls:ExtendedStepper
Value="{Binding NumWords}"
Maximum="20"
Minimum="3"
Increment="1" />
Increment="1"
AutomationId="NumberOfWordsStepper" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-input">
@@ -419,7 +353,8 @@
Text="{Binding WordSeparator}"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
StyleClass="box-value">
StyleClass="box-value"
AutomationId="WordSeparatorEntry">
<Entry.Effects>
<effects:NoEmojiKeyboardEffect />
</Entry.Effects>
@@ -435,7 +370,8 @@
IsEnabled="{Binding EnforcedPolicyOptions.Capitalize,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationId="CapitalizePassphraseToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -448,7 +384,8 @@
IsEnabled="{Binding EnforcedPolicyOptions.IncludeNumber,
Converter={StaticResource inverseBool}}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationId="IncludeNumbersToggle" />
</StackLayout>
</StackLayout>
<StackLayout Spacing="0" Padding="0" IsVisible="{Binding IsPassword}">
@@ -462,7 +399,8 @@
StyleClass="box-sub-label"
VerticalOptions="CenterAndExpand"
HorizontalTextAlignment="End"
WidthRequest="50" />
WidthRequest="50"
AutomationId="PasswordLengthLabel" />
<controls:ExtendedSlider
DragCompleted="LengthSlider_DragCompleted"
Value="{Binding Length}"
@@ -471,7 +409,8 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="FillAndExpand"
Maximum="128"
Minimum="5" />
Minimum="5"
AutomationId="PasswordLengthSlider" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -488,7 +427,8 @@
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"/>
AutomationProperties.Name="{u:I18n UppercaseAtoZ}"
AutomationId="UppercaseAtoZToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -505,7 +445,8 @@
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"/>
AutomationProperties.Name="{u:I18n LowercaseAtoZ}"
AutomationId="LowercaseAtoZToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -522,7 +463,8 @@
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"/>
AutomationProperties.Name="{u:I18n NumbersZeroToNine}"
AutomationId="NumbersZeroToNineToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -539,7 +481,8 @@
StyleClass="box-value"
HorizontalOptions="End"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n SpecialCharacters}"/>
AutomationProperties.Name="{u:I18n SpecialCharacters}"
AutomationId="SpecialCharactersToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
@@ -554,12 +497,14 @@
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
VerticalTextAlignment="Center"
AutomationId="MinNumberValueLabel" />
<controls:ExtendedStepper
Value="{Binding MinNumber}"
Maximum="5"
Minimum="0"
Increment="1" />
Increment="1"
AutomationId="MinNumberStepper" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-stepper">
@@ -574,12 +519,14 @@
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="End"
VerticalOptions="FillAndExpand"
VerticalTextAlignment="Center" />
VerticalTextAlignment="Center"
AutomationId="MinSpecialValueLabel" />
<controls:ExtendedStepper
Value="{Binding MinSpecial}"
Maximum="5"
Minimum="0"
Increment="1" />
Increment="1"
AutomationId="MinSpecialStepper" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
<StackLayout StyleClass="box-row, box-row-switch">
@@ -590,7 +537,8 @@
<Switch
IsToggled="{Binding AvoidAmbiguousChars}"
StyleClass="box-value"
HorizontalOptions="End" />
HorizontalOptions="End"
AutomationId="AvoidAmbiguousCharsToggle" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>

View File

@@ -8,6 +8,7 @@ using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -23,7 +24,7 @@ namespace Bit.App.Pages
private readonly IUsernameGenerationService _usernameGenerationService;
private readonly ITokenService _tokenService;
private readonly IDeviceActionService _deviceActionService;
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
private PasswordGenerationOptions _options;
private UsernameGenerationOptions _usernameOptions;
@@ -49,11 +50,7 @@ namespace Bit.App.Pages
private bool _doneIniting;
private bool _showTypePicker;
private string _emailWebsite;
private bool _showFirefoxRelayApiAccessToken;
private bool _showAnonAddyApiAccessToken;
private bool _showSimpleLoginApiKey;
private bool _showDuckDuckGoApiKey;
private bool _showFastmailApiKey;
private bool _showForwardedEmailApiSecret;
private bool _editMode;
public GeneratorPageViewModel()
@@ -96,7 +93,7 @@ namespace Bit.App.Pages
UsernameTypePromptHelpCommand = new Command(UsernameTypePromptHelp);
RegenerateCommand = new AsyncCommand(RegenerateAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
RegenerateUsernameCommand = new AsyncCommand(RegenerateUsernameAsync, onException: ex => OnSubmitException(ex), allowsMultipleExecutions: false);
ToggleForwardedEmailHiddenValueCommand = new AsyncCommand(ToggleForwardedEmailHiddenValueAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
ToggleForwardedEmailHiddenValueCommand = new Command(() => ShowForwardedEmailApiSecret = !ShowForwardedEmailApiSecret);
CopyCommand = new AsyncCommand(CopyAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
CloseCommand = new AsyncCommand(CloseAsync, onException: ex => _logger.Value.Exception(ex), allowsMultipleExecutions: false);
}
@@ -415,7 +412,6 @@ namespace Bit.App.Pages
public string UsernameTypeDescriptionLabel => GetUsernameTypeLabelDescription(UsernameTypeSelected);
public ForwardedEmailServiceType ForwardedEmailServiceSelected
{
get => _usernameOptions.ServiceType;
@@ -425,7 +421,11 @@ namespace Bit.App.Pages
{
_usernameOptions.ServiceType = value;
Username = Constants.DefaultUsernameGenerated;
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected), new string[]
{
nameof(ForwardedEmailApiSecret),
nameof(ForwardedEmailApiSecretLabel)
});
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
@@ -445,27 +445,104 @@ namespace Bit.App.Pages
}
}
public string AnonAddyApiAccessToken
public string ForwardedEmailApiSecret
{
get => _usernameOptions.AnonAddyApiAccessToken;
get
{
switch (ForwardedEmailServiceSelected)
{
case ForwardedEmailServiceType.AnonAddy:
return _usernameOptions.AnonAddyApiAccessToken;
case ForwardedEmailServiceType.DuckDuckGo:
return _usernameOptions.DuckDuckGoApiKey;
case ForwardedEmailServiceType.Fastmail:
return _usernameOptions.FastMailApiKey;
case ForwardedEmailServiceType.FirefoxRelay:
return _usernameOptions.FirefoxRelayApiAccessToken;
case ForwardedEmailServiceType.SimpleLogin:
return _usernameOptions.SimpleLoginApiKey;
default:
return null;
}
}
set
{
if (_usernameOptions.AnonAddyApiAccessToken != value)
bool changed = false;
switch (ForwardedEmailServiceSelected)
{
_usernameOptions.AnonAddyApiAccessToken = value;
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
case ForwardedEmailServiceType.AnonAddy:
if (_usernameOptions.AnonAddyApiAccessToken != value)
{
_usernameOptions.AnonAddyApiAccessToken = value;
changed = true;
}
break;
case ForwardedEmailServiceType.DuckDuckGo:
if (_usernameOptions.DuckDuckGoApiKey != value)
{
_usernameOptions.DuckDuckGoApiKey = value;
changed = true;
}
break;
case ForwardedEmailServiceType.Fastmail:
if (_usernameOptions.FastMailApiKey != value)
{
_usernameOptions.FastMailApiKey = value;
changed = true;
}
break;
case ForwardedEmailServiceType.FirefoxRelay:
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
{
_usernameOptions.FirefoxRelayApiAccessToken = value;
changed = true;
}
break;
case ForwardedEmailServiceType.SimpleLogin:
if (_usernameOptions.SimpleLoginApiKey != value)
{
_usernameOptions.SimpleLoginApiKey = value;
changed = true;
}
break;
default:
break;
}
if (changed)
{
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
}
public bool ShowAnonAddyApiAccessToken
public string ForwardedEmailApiSecretLabel
{
get
{
return _showAnonAddyApiAccessToken;
switch (ForwardedEmailServiceSelected)
{
case ForwardedEmailServiceType.AnonAddy:
case ForwardedEmailServiceType.FirefoxRelay:
return AppResources.APIAccessToken;
case ForwardedEmailServiceType.DuckDuckGo:
case ForwardedEmailServiceType.Fastmail:
case ForwardedEmailServiceType.SimpleLogin:
return AppResources.APIKeyRequiredParenthesis;
default:
return null;
}
}
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
}
public bool ShowForwardedEmailApiSecret
{
get
{
return _showForwardedEmailApiSecret;
}
set => SetProperty(ref _showForwardedEmailApiSecret, value);
}
public string AnonAddyDomainName
@@ -482,99 +559,6 @@ namespace Bit.App.Pages
}
}
public string FirefoxRelayApiAccessToken
{
get => _usernameOptions.FirefoxRelayApiAccessToken;
set
{
if (_usernameOptions.FirefoxRelayApiAccessToken != value)
{
_usernameOptions.FirefoxRelayApiAccessToken = value;
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
}
public bool ShowFirefoxRelayApiAccessToken
{
get
{
return _showFirefoxRelayApiAccessToken;
}
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
}
public string SimpleLoginApiKey
{
get => _usernameOptions.SimpleLoginApiKey;
set
{
if (_usernameOptions.SimpleLoginApiKey != value)
{
_usernameOptions.SimpleLoginApiKey = value;
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
}
public bool ShowSimpleLoginApiKey
{
get
{
return _showSimpleLoginApiKey;
}
set => SetProperty(ref _showSimpleLoginApiKey, value);
}
public string DuckDuckGoApiKey
{
get => _usernameOptions.DuckDuckGoApiKey;
set
{
if (_usernameOptions.DuckDuckGoApiKey != value)
{
_usernameOptions.DuckDuckGoApiKey = value;
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
}
public bool ShowDuckDuckGoApiKey
{
get
{
return _showDuckDuckGoApiKey;
}
set => SetProperty(ref _showDuckDuckGoApiKey, value);
}
public string FastmailApiKey
{
get => _usernameOptions.FastMailApiKey;
set
{
if (_usernameOptions.FastMailApiKey != value)
{
_usernameOptions.FastMailApiKey = value;
TriggerPropertyChanged(nameof(FastmailApiKey));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
}
public bool ShowFastmailApiKey
{
get
{
return _showFastmailApiKey;
}
set => SetProperty(ref _showFastmailApiKey, value);
}
public bool CapitalizeRandomWordUsername
{
get => _usernameOptions.CapitalizeRandomWordUsername;
@@ -807,12 +791,9 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(PlusAddressedEmailTypeSelected));
TriggerPropertyChanged(nameof(IncludeNumberRandomWordUsername));
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
TriggerPropertyChanged(nameof(SimpleLoginApiKey));
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
TriggerPropertyChanged(nameof(ForwardedEmailApiSecretLabel));
TriggerPropertyChanged(nameof(AnonAddyDomainName));
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
TriggerPropertyChanged(nameof(FastmailApiKey));
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
TriggerPropertyChanged(nameof(UsernameTypeSelected));
@@ -827,7 +808,7 @@ namespace Bit.App.Pages
private void SetOptions()
{
_options.AllowAmbiguousChar = AllowAmbiguousChars;
_options.Type = PasswordTypeSelectedIndex == 1 ? "passphrase" : "password";
_options.Type = PasswordTypeSelectedIndex == 1 ? PasswordGenerationOptions.TYPE_PASSPHRASE : PasswordGenerationOptions.TYPE_PASSWORD;
_options.MinNumber = MinNumber;
_options.MinSpecial = MinSpecial;
_options.Special = Special;
@@ -845,15 +826,23 @@ namespace Bit.App.Pages
{
_logger.Value.Exception(ex);
string message = AppResources.GenericErrorMessage;
if (IsUsername && UsernameTypeSelected == UsernameType.ForwardedEmailAlias)
{
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(
AppResources.AnErrorHasOccurred, string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected), AppResources.Ok));
}
else
{
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, AppResources.GenericErrorMessage, AppResources.Ok));
if (ex is ForwardedEmailInvalidSecretException)
{
message = ForwardedEmailServiceSelected == ForwardedEmailServiceType.AnonAddy || ForwardedEmailServiceSelected == ForwardedEmailServiceType.FirefoxRelay
? AppResources.InvalidAPIToken
: AppResources.InvalidAPIKey;
}
else
{
message = string.Format(AppResources.UnknownXErrorMessage, ForwardedEmailServiceSelected);
}
}
await Device.InvokeOnMainThreadAsync(() => Page.DisplayAlert(AppResources.AnErrorHasOccurred, message, AppResources.Ok));
}
private string GetUsernameTypeLabelDescription(UsernameType value)
@@ -870,27 +859,5 @@ namespace Bit.App.Pages
return string.Empty;
}
}
private async Task ToggleForwardedEmailHiddenValueAsync()
{
switch (ForwardedEmailServiceSelected)
{
case ForwardedEmailServiceType.AnonAddy:
ShowAnonAddyApiAccessToken = !ShowAnonAddyApiAccessToken;
break;
case ForwardedEmailServiceType.FirefoxRelay:
ShowFirefoxRelayApiAccessToken = !ShowFirefoxRelayApiAccessToken;
break;
case ForwardedEmailServiceType.SimpleLogin:
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
break;
case ForwardedEmailServiceType.DuckDuckGo:
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
break;
case ForwardedEmailServiceType.Fastmail:
ShowFastmailApiKey = !ShowFastmailApiKey;
break;
}
}
}
}

View File

@@ -71,7 +71,8 @@
<Label
Text="{u:I18n SendDisabledWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="SendDisabledWarningMessageLabel" />
</Frame>
<Frame
IsVisible="{Binding SendOptionsPolicyInEffect}"
@@ -83,7 +84,8 @@
<Label
Text="{u:I18n SendOptionsPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="SendOptionsPolicyInEffectLabel" />
</Frame>
<StackLayout StyleClass="box-row">
<Label
@@ -93,7 +95,8 @@
x:Name="_nameEntry"
Text="{Binding Send.Name}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value" />
StyleClass="box-value"
AutomationId="SendNameEntry" />
<Label
Text="{u:I18n NameInfo}"
StyleClass="box-footer-label"
@@ -123,6 +126,7 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n File}"
AutomationProperties.HelpText="{Binding FileTypeAccessibilityLabel}"
AutomationId="SendFileButton"
Grid.Column="0">
</Button>
<Button
@@ -135,6 +139,7 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Text}"
AutomationProperties.HelpText="{Binding TextTypeAccessibilityLabel}"
AutomationId="SendTextButton"
Grid.Column="1">
</Button>
</Grid>
@@ -152,12 +157,14 @@
Text="{Binding Send.File.FileName, Mode=OneWay}"
StyleClass="box-value"
VerticalTextAlignment="Center"
HorizontalOptions="StartAndExpand" />
HorizontalOptions="StartAndExpand"
AutomationId="SendFileNameLabel" />
<Label
Text="{Binding Send.File.SizeName, Mode=OneWay}"
StyleClass="box-sub-label"
HorizontalTextAlignment="End"
VerticalTextAlignment="Center" />
VerticalTextAlignment="Center"
AutomationId="SendFileSizeLabel" />
</StackLayout>
<StackLayout
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
@@ -168,20 +175,23 @@
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="SendNoFileChosenLabel" />
<Label
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
Text="{Binding FileName}"
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="SendCurrentFileNameLabel" />
<Button
Text="{u:I18n ChooseFile}"
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-button-row"
Clicked="ChooseFile_Clicked" />
Clicked="ChooseFile_Clicked"
AutomationId="SendChooseFileButton" />
<Label
Margin="0, 5, 0, 0"
Text="{u:I18n MaxFileSize}"
@@ -207,7 +217,8 @@
Text="{Binding Send.Text.Text}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="{Binding EditorMargins}"
Margin="{Binding EditorMargins}"
AutomationId="SendTextContentEntry"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
<Editor.Behaviors>
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
@@ -235,7 +246,8 @@
IsToggled="{Binding Send.Text.Hidden}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
Margin="10,0,0,0"
AutomationId="SendHideTextByDefaultToggle" />
</StackLayout>
</StackLayout>
<StackLayout
@@ -249,7 +261,8 @@
IsToggled="{Binding ShareOnSave}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
Margin="10,0,0,0"
AutomationId="SendShareSendAfterSaveToggle" />
</StackLayout>
<StackLayout
Orientation="Horizontal"
@@ -263,21 +276,24 @@
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
Margin="0"
AutomationProperties.IsInAccessibleTree="False"/>
AutomationProperties.IsInAccessibleTree="False"
AutomationId="SendShowHideOptionsButton" />
<controls:IconButton
x:Name="_btnOptionsUp"
Text="{Binding Source={x:Static core:BitwardenIcons.ChevronUp}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions}"
AutomationProperties.IsInAccessibleTree="False"/>
AutomationProperties.IsInAccessibleTree="False"
AutomationId="SendOptionsDisplayed" />
<controls:IconButton
x:Name="_btnOptionsDown"
Text="{Binding Source={x:Static core:BitwardenIcons.AngleDown}}"
StyleClass="box-row-button"
TextColor="{DynamicResource PrimaryColor}"
IsVisible="{Binding ShowOptions, Converter={StaticResource inverseBool}}"
AutomationProperties.IsInAccessibleTree="False"/>
AutomationProperties.IsInAccessibleTree="False"
AutomationId="SendOptionsHidden" />
</StackLayout>
<StackLayout IsVisible="{Binding ShowOptions}">
<StackLayout
@@ -294,7 +310,8 @@
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionTime}" />
AutomationProperties.Name="{u:I18n DeletionTime}"
AutomationId="SendDeletionOptionsPicker" />
<Grid
IsVisible="{Binding ShowDeletionCustomPickers}"
Margin="0,5,0,0">
@@ -308,14 +325,16 @@
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionDate}"
Grid.Column="0" />
Grid.Column="0"
AutomationId="SendCustomDeletionDatePicker" />
<controls:ExtendedTimePicker
NullableTime="{Binding DeletionDateTimeViewModel.Time, Mode=TwoWay}"
Format="t"
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n DeletionTime}"
Grid.Column="1" />
Grid.Column="1"
AutomationId="SendCustomDeletionTimePicker" />
</Grid>
<Label
Text="{u:I18n DeletionDateInfo}"
@@ -334,7 +353,8 @@
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationTime}" />
AutomationProperties.Name="{u:I18n ExpirationTime}"
AutomationId="SendExpirationOptionsPicker" />
<Grid
IsVisible="{Binding ShowExpirationCustomPickers}"
Margin="0,5,0,0">
@@ -349,7 +369,8 @@
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationDate}"
Grid.Column="0" />
Grid.Column="0"
AutomationId="SendCustomExpirationDatePicker" />
<controls:ExtendedTimePicker
NullableTime="{Binding ExpirationDateTimeViewModel.Time, Mode=TwoWay}"
PlaceHolder="--:-- --"
@@ -357,7 +378,8 @@
IsEnabled="{Binding SendEnabled}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ExpirationTime}"
Grid.Column="1" />
Grid.Column="1"
AutomationId="SendCustomExpirationTimePicker" />
</Grid>
<StackLayout
Orientation="Horizontal"
@@ -374,7 +396,8 @@
FontSize="{Binding SegmentedButtonFontSize}"
IsEnabled="{Binding SendEnabled}"
StyleClass="box-row-button"
Clicked="ClearExpirationDate_Clicked" />
Clicked="ClearExpirationDate_Clicked"
AutomationId="SendClearExpirationDateButton" />
</StackLayout>
</StackLayout>
<StackLayout
@@ -393,13 +416,15 @@
Keyboard="Numeric"
MaxLength="9"
TextChanged="OnMaxAccessCountTextChanged"
HorizontalOptions="FillAndExpand" />
HorizontalOptions="FillAndExpand"
AutomationId="SendMaxAccessCountEntry" />
<controls:ExtendedStepper
x:Name="_maxAccessCountStepper"
Value="{Binding MaxAccessCount}"
Maximum="999999999"
IsEnabled="{Binding SendEnabled}"
Margin="10,0,0,0" />
Margin="10,0,0,0"
AutomationId="SendMaxAccessCountStepper" />
</StackLayout>
<Label
Text="{u:I18n MaximumAccessCountInfo}"
@@ -419,7 +444,8 @@
<Label
Text="{Binding Send.AccessCount, Mode=OneWay}"
StyleClass="box-label"
VerticalTextAlignment="Center" />
VerticalTextAlignment="Center"
AutomationId="SendCurrentAccessCountLabel" />
</StackLayout>
</StackLayout>
<StackLayout
@@ -436,7 +462,8 @@
StyleClass="box-value"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
HorizontalOptions="FillAndExpand" />
HorizontalOptions="FillAndExpand"
AutomationId="SendNewPasswordEntry" />
<controls:IconButton
IsEnabled="{Binding SendEnabled}"
StyleClass="box-row-button, box-row-button-platform"
@@ -445,7 +472,8 @@
Margin="10,0,0,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="SendShowHidePasswordButton" />
</StackLayout>
<Label
Text="{u:I18n PasswordInfo}"
@@ -464,7 +492,8 @@
IsEnabled="{Binding SendEnabled}"
StyleClass="box-value"
Margin="{Binding EditorMargins}"
effects:ScrollEnabledEffect.IsScrollEnabled="false" >
effects:ScrollEnabledEffect.IsScrollEnabled="false"
AutomationId="SendNotesEntry">
<Editor.Behaviors>
<behaviors:EditorPreventAutoBottomScrollingOnFocusedBehavior ParentScrollView="{x:Reference _scrollView}" />
</Editor.Behaviors>
@@ -492,7 +521,8 @@
IsToggled="{Binding Send.HideEmail}"
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
HorizontalOptions="End"
Margin="10,0,0,0" />
Margin="10,0,0,0"
AutomationId="SendHideEmailSwitch" />
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch"
@@ -506,7 +536,8 @@
IsToggled="{Binding Send.Disabled}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
Margin="10,0,0,0"
AutomationId="SendDeactivateSwitch" />
</StackLayout>
</StackLayout>

View File

@@ -25,7 +25,8 @@
Priority="-2"
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
AutomationProperties.Name="{u:I18n Account}"
AutomationId="AccountIconButton" />
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" x:Name="_closeItem" />
<ToolbarItem Text="{u:I18n Save}" Clicked="Save_Clicked" Order="Primary" x:Name="_saveItem"/>
</ContentPage.ToolbarItems>

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