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

Compare commits

..

115 Commits

Author SHA1 Message Date
Federico Maccaroni
0fed85140b PM-115 Fix attachment encryption on new cipher item encryption model 2023-06-01 17:49:58 +03:00
Federico Maccaroni
d8a64f0f75 Merge branch 'master' into feature/PM-115-encryption-update-new-model 2023-06-01 10:37:56 +03: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
Federico Maccaroni
e19b86ee00 PM-115 Temporarily Changed cipher key new encryption config to help testing (this change should be reseted eventually) 2023-05-31 11:01:44 +03:00
Federico Maccaroni
bcd47b3b3e Merge branch 'master' into feature/PM-115-encryption-update-new-model 2023-05-29 18:32:17 +03: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
Federico Maccaroni
b150d883c0 [PM-1690] Added minimum server version restriction to cipher key encryption (#2463)
* PM-1690 added minimum server version restriction to cipher key encryption and also change the force key rotation flag

* PM-1690 Updated min server version for new cipher encryption key and fixed configService registration

* PM-1690 removed forcekeyrotation
2023-05-23 17:48:58 +03:00
Federico Maccaroni
1a0e9e961d PM-115 removed ForceKeyRotation from new cipher encryption model given that another approach will be taken 2023-05-22 21:25:23 +03:00
Federico Maccaroni
7cdc5a6233 Merge branch 'master' into feature/PM-115-encryption-update-new-model 2023-05-22 21:06:04 +03: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
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
Federico Maccaroni
74190e0125 Merge branch 'master' into feature/PM-115-encryption-update-new-model 2023-04-04 19:18:38 +01: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
Federico Andrés Maccaroni
a3b18dced6 PM-115 fix format 2023-03-20 13:45:39 -03:00
Federico Andrés Maccaroni
c8555ef1e7 Merge branch 'master' into feature/PM-115-encryption-update-new-model 2023-03-20 13:42:45 -03:00
Federico Andrés Maccaroni
2bb72076ae PM-115 Added new cipher key and encryption/decryption mechanisms on cipher 2023-03-20 13:41:21 -03: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
Michał Chęciński
f63918aa4e Add apk hash in files to build and release (#2253)
* Add calculating hashes for apk files

* Install checksum

* Fix name of artifact

* Fix

* Add SHA files to github release

* Update .github/workflows/build.yml

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

* Update .github/workflows/build.yml

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

* Update .github/workflows/release.yml

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

---------

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2023-02-14 10:10:29 -05:00
Carlos Gonçalves
f42c677d5a [SG-1056] Fix login prompt loop for added accounts (#2357)
* SG-1056 Fix login prompt loop for added accounts

* SG-1056 Fix PR comment
2023-02-10 17:49:05 +00:00
github-actions[bot]
5a56d64211 Autosync the updated translations (#2355)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-10 07:23:27 +01:00
André Bispo
0bd1b3f45f [SG-1052] Fix copy (#2353) 2023-02-09 15:13:50 -05:00
André Bispo
3780587991 [SG-1055] Init strength bar color (#2352) 2023-02-09 15:12:39 -05:00
André Bispo
6875389948 [SG-1057] Make check breaches true by default (#2351) 2023-02-09 15:09:52 -05:00
aj-rosado
0e5d6e79c5 [PS-1809] Updating the account premium state when syncing the vault (#2290)
* [PS-1809] Updating the account premium state when syncing the vault

* [PS-1809] Added validation to check if HasPremiumPersonally needs to be updated

* PS-1809 Renamed SetPremiumAsync to SetPersonalPremiumAsync
2023-02-08 12:03:02 +00:00
github-actions[bot]
490b74dd26 Autosync the updated translations (#2346)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-06 13:36:22 +01:00
github-actions[bot]
66d05e1b00 Autosync the updated translations (#2345)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-06 11:57:22 +01:00
André Bispo
3d3101c3ab [SG-1035] Fix label for log in with device (#2338) 2023-02-03 19:19:48 +00:00
github-actions[bot]
ccde4270d0 Autosync the updated translations (#2342)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-03 14:18:33 +01:00
github-actions[bot]
a1f799302e Autosync the updated translations (#2340)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-02-03 14:08:03 +01:00
André Bispo
4619e257e8 [SG-995] Remove automatic navigation from Home to Login if remembered (#2335) 2023-02-03 09:53:05 +00:00
Kyle Spearrin
c54a14cd3f set all kdf params on account profile (#2339) 2023-02-02 18:00:29 -05:00
aj-rosado
a96d95c95b [PS-2330][PS-2339][PS-2332][PS-2333] Checking for username terms when adding a password, if they exist don't add. Adding fields that contain username terms to the usernamefields collection (#2331) 2023-02-01 17:41:14 +00:00
André Bispo
e61ca489ce [SG-834] Mobile pending login requests management screen (#2281)
* Bootstrap new classes for settings list

* [SG-834] Add new method GetActivePasswordlessLoginRequestsAsync to AuthService

* [SG-834] Add generic handle exception method to BaseViewModel

* [SG-834] Add request verification to settings entry

* [SG-834] Add text resources

* [SG-834] Update view and viewmodel

* [SG-834] Remove unnecessary property assignment

* [SG-834] removed logger resolve
2023-02-01 12:22:17 +00:00
Bernd Schoolmann
c3ad5f0580 [PS-2358] Add kdf configuration options (#2328)
* Implement kdf configuration

* Remove unused import

* Move kdf parameters to kdfConfiguration struct

* Remove unused state migration service keys

* Revert newline changes in PCLCryptoFunctionService

* Update KdfConfiguration.cs

* Add checks for argon2, clean statemigration service

* Update constants

* Clean up code

* Further cleanup

* Change KdfType to non-nullable in SetKeyConnectorKeyRequest

---------

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2023-01-30 11:34:50 -05:00
Matt Bishop
8b08f906bd Remove fallout from test run (#2332) 2023-01-27 11:27:25 -05:00
github-actions[bot]
4ad5f5ae37 Autosync the updated translations (#2330)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-01-27 01:40:53 +01:00
André Bispo
68a6449339 [SG-516] Additional forwarded email providers for username generator - mobile (#2304)
* [SG-516] Added DuckDuckGo provider

* [SG-516] Add Fastmail as generator provider

* [SG-516] code clean up

* [SG-516] Default to service empty if first time on screen. Order services by alphabetic order.

* [SG-516] Removed unnecessary prop.

* [PS-2278] Fixed inverted eye bug.

* [SG-516] Add icon glyph converter

* [SG-516] Fixed enum default value and ordering
2023-01-26 13:53:48 +00:00
Kyle Spearrin
b8d53b0f81 Argon2id KDF (#2309)
* add gcc_flags for libargon2.a

* fix up ios proj

* remove unused tag

* add gcc_flags to ios projects

* ios libargon2 binary

* fix paths in ios project

* update pathing on other projs

* Argon2id primitive

* fix typing issues

* comment

* remove ds store

* [PS-2249] Implement Argon2 (#2293)

* Implement Argon2

* Fix incorrect argon2 type on iOS

* Switch argon2 implementation to native bindings

* Change argon2 to save iterations instead of memory as 'kdfIterations'

* Remove mistakenly added import

* Remove unused library

* cleanup

* move android libs

* move android libs

* Revert "move android libs"

This reverts commit 0b91b22cd2.

* Revert "move android libs"

This reverts commit 139839c469.

* PR feedback

Co-authored-by: Bernd Schoolmann <mail@quexten.com>
2023-01-25 07:58:36 -05:00
331 changed files with 28091 additions and 6174 deletions

View File

@@ -1,6 +1,9 @@
blank_issues_enabled: false
contact_links:
- name: Report mobile AUTOFILL failure
- 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!
- name: Feature Requests
@@ -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,15 @@ 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 MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
- name: Setup Windows builder
run: choco install checksum --no-progress
- name: Work Around for broken Windows 2022 Runner Image
run: |
@@ -100,7 +105,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
@@ -148,11 +153,11 @@ jobs:
shell: pwsh
- name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" || true
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
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
@@ -186,7 +191,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 }}";
}
@@ -227,7 +232,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
@@ -235,7 +240,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
@@ -243,12 +248,40 @@ 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
if-no-files-found: error
- name: Create checksum for Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
run: |
checksum -f="./com.x8bit.bitwarden.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-android-apk-sha256.txt
- name: Create checksum for Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
run: |
checksum -f="./com.x8bit.bitwarden.${{ matrix.variant }}.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-android-${{ matrix.variant }}-apk-sha256.txt
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
if-no-files-found: error
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
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
if-no-files-found: error
- name: Deploy to Play Store
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0
@@ -270,12 +303,15 @@ 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
- name: Work Around for broken Windows 2022 Runner Image
run: |
@@ -306,7 +342,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:
@@ -441,12 +477,24 @@ 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
if-no-files-found: error
- name: Create checksum for F-Droid artifact
run: |
checksum -f="./com.x8bit.bitwarden-fdroid.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
if-no-files-found: error
ios:
name: Apple iOS
@@ -454,7 +502,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
@@ -467,17 +515,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: |
@@ -648,7 +698,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: |
@@ -721,17 +771,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: |
@@ -743,7 +793,7 @@ jobs:
done
- name: Upload Sources
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
uses: crowdin/github-action@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -789,17 +839,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: |
@@ -811,7 +861,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@ecd7eb0ef6f3cfa16293c79e9cbc4bc5b5fd9c49 # v1.4.9
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 # v2.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 }}'
@@ -64,7 +64,7 @@ jobs:
environment: 'production'
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
@@ -92,7 +92,9 @@ jobs:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip"
./Bitwarden iOS.zip,
./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 }}
@@ -102,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'
@@ -110,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'
@@ -124,7 +126,7 @@ 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' }}

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,14 @@ jobs:
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: "Trigger version bump workflow"
name: "Version bump"
runs-on: ubuntu-22.04
needs:
- setup
steps:
- name: Login to Azure
uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
- name: Bump version to ${{ needs.setup.outputs.version_number }}
uses: ./.github/workflows/version-bump.yml
secrets:
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
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
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

1
.gitignore vendored
View File

@@ -30,6 +30,7 @@ Components/
[Rr]eleases/
x64/
x86/
!src/lib/x86/
build/
bld/
[Bb]in/

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");

1
lib/MessagePack Submodule

Submodule lib/MessagePack added at 1ecb15e311

BIN
lib/ios/libargon2.a Normal file

Binary file not shown.

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>
@@ -169,6 +168,10 @@
<GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" />
<AndroidNativeLibrary Include="lib\arm64-v8a\libargon2.so" />
<AndroidNativeLibrary Include="lib\armeabi-v7a\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86_64\libargon2.so" />
<None Include="Properties\AndroidManifest.xml" />
<None Include="upload-keystore.jks.enc" />
</ItemGroup>

View File

@@ -12,6 +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> _passwordTerms = new HashSet<string> { "password", "pswd" };
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
@@ -98,6 +99,11 @@ namespace Bit.Droid.Autofill
_usernameFields.Add(usernameField);
}
}
if (!_usernameFields.Any())
{
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
}
}
return _usernameFields;
}
@@ -321,13 +327,23 @@ namespace Bit.Droid.Autofill
}
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms);
!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)
{
return ValueContainsAnyTerms(f.IdEntry, _usernameTerms) || ValueContainsAnyTerms(f.Hint, _usernameTerms);
}
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
{
@@ -339,4 +355,4 @@ namespace Bit.Droid.Autofill
return terms.Any(t => lowerValue.Contains(t));
}
}
}
}

View File

@@ -44,6 +44,7 @@ namespace Bit.Droid
private IAppIdService _appIdService;
private IEventService _eventService;
private IPushNotificationListenerService _pushNotificationListenerService;
private IVaultTimeoutService _vaultTimeoutService;
private ILogger _logger;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
@@ -68,6 +69,7 @@ namespace Bit.Droid
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
_logger = ServiceContainer.Resolve<ILogger>("logger");
TabLayoutResource = Resource.Layout.Tabbar;
@@ -170,7 +172,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 +180,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 +188,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 +200,7 @@ namespace Bit.Droid
{
_appOptions.CreateSend = GetCreateSendRequest(intent);
}
_messagingService.Send("popAllAndGoToTabSend");
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE);
}
else
{
@@ -232,6 +234,7 @@ namespace Bit.Droid
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
_vaultTimeoutService.ResetTimeoutDelay = true;
if (resultCode == Result.Ok &&
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
{

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.5.0" 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

@@ -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

@@ -5,6 +5,8 @@ using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using System;
using System.Runtime.InteropServices;
using Java.Lang;
namespace Bit.Droid.Services
{
@@ -33,5 +35,19 @@ namespace Bit.Droid.Services
generator.Init(password, salt, iterations);
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
}
public byte[] Argon2id(byte[] password, byte[] salt, int iterations, int memory, int parallelism)
{
JavaSystem.LoadLibrary("argon2");
int keySize = 32;
var key = new byte[keySize];
argon2id_hash_raw(iterations, memory, parallelism,
password, password.Length, salt, salt.Length, key, key.Length);
return key;
}
[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
}
}

View File

@@ -502,11 +502,6 @@ namespace Bit.Droid.Services
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync())
{

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();
}

Binary file not shown.

Binary file not shown.

BIN
src/Android/lib/x86/libargon2.so Executable file

Binary file not shown.

Binary file not shown.

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

@@ -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>
@@ -436,6 +437,8 @@
<None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
<None Remove="MessagePack" />
<None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
</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
@@ -267,8 +297,10 @@ namespace Bit.App
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null;
_vaultTimeoutService.DelayTimeoutMs = 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
@@ -459,14 +501,7 @@ namespace Bit.App
switch (navTarget)
{
case NavigationTarget.HomeLogin:
if (navParams is HomeNavigationParams homeParams)
{
Current.MainPage = new NavigationPage(new HomePage(Options, homeParams.ShouldCheckRememberEmail));
}
else
{
Current.MainPage = new NavigationPage(new HomePage(Options));
}
Current.MainPage = new NavigationPage(new HomePage(Options));
break;
case NavigationTarget.Login:
if (navParams is LoginNavigationParams loginParams)
@@ -491,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

@@ -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

@@ -1,4 +1,6 @@
using Xamarin.Forms;
using System.Linq;
using Xamarin.CommunityToolkit.Converters;
using Xamarin.Forms;
namespace Bit.App.Controls
{
@@ -6,4 +8,13 @@ namespace Bit.App.Controls
{
public string ExtraDataForLogging { get; set; }
}
public class SelectionChangedEventArgsConverter : BaseNullableConverterOneWay<SelectionChangedEventArgs, object>
{
public override object? ConvertFrom(SelectionChangedEventArgs? value)
{
return value?.CurrentSelection.FirstOrDefault();
}
}
}

View File

@@ -69,6 +69,7 @@ namespace Bit.App.Controls
{
InitializeComponent();
SetBinding(PasswordStrengthProgressBar.PasswordStrengthLevelProperty, new Binding() { Path = nameof(PasswordStrengthViewModel.PasswordStrengthLevel) });
UpdateColors();
}
private static void OnControlPropertyChanged(BindableObject bindable, object oldValue, object newValue)

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

@@ -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

@@ -25,10 +25,6 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<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>
@@ -66,7 +62,27 @@
</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"/>
</StackLayout>
<StackLayout
Orientation="Horizontal"
Margin="0, 20, 0 ,0">
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding RememberEmailCommand}" />

View File

@@ -15,14 +15,15 @@ namespace Bit.App.Pages
private readonly AppOptions _appOptions;
private IBroadcasterService _broadcasterService;
public HomePage(AppOptions appOptions = null, bool shouldCheckRememberEmail = true)
readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public HomePage(AppOptions appOptions = null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_appOptions = appOptions;
InitializeComponent();
_vm = BindingContext as HomeViewModel;
_vm.Page = this;
_vm.ShouldCheckRememberEmail = shouldCheckRememberEmail;
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
_vm.StartLoginAction = async () => await StartLoginAsync();
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
@@ -71,8 +72,14 @@ namespace Bit.App.Pages
});
}
});
_vm.CheckNavigateLoginStep();
try
{
await _vm.UpdateEnvironment();
}
catch (Exception ex)
{
_logger.Value?.Exception(ex);
}
}
protected override bool OnBackButtonPressed()
@@ -131,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,9 +80,14 @@ namespace Bit.App.Pages
additionalPropertyNames: new[] { nameof(CanContinue) });
}
public bool CanContinue => !string.IsNullOrEmpty(Email);
public string SelectedEnvironmentName
{
get => $"{_selectedEnvironmentName} {BitwardenIcons.AngleDown}";
set => SetProperty(ref _selectedEnvironmentName, value);
}
public bool ShouldCheckRememberEmail { get; set; }
public string RegionText => $"{AppResources.Region}:";
public bool CanContinue => !string.IsNullOrEmpty(Email);
public FormattedString CreateAccountText
{
@@ -103,20 +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);
}
public void CheckNavigateLoginStep()
{
if (ShouldCheckRememberEmail && RememberEmail)
{
StartLoginAction();
}
ShouldCheckRememberEmail = false;
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag, forceRefresh: true);
}
public async Task ContinueToLoginStepAsync()
@@ -136,16 +143,16 @@ namespace Bit.App.Pages
AppResources.Ok);
return;
}
await _stateService.SetRememberedEmailAsync(RememberEmail ? Email : null);
var userId = await _stateService.GetUserIdAsync(Email);
if (!string.IsNullOrWhiteSpace(userId))
if (!string.IsNullOrWhiteSpace(userId) &&
(await _stateService.GetEnvironmentUrlsAsync(userId))?.Base == _environmentService.BaseUrl &&
await _stateService.IsAuthenticatedAsync(userId))
{
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
if (userEnvUrls?.Base == _environmentService.BaseUrl)
{
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
return;
}
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
return;
}
StartLoginAction();
}
@@ -155,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

@@ -137,7 +137,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"

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());
@@ -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;
@@ -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,8 +31,12 @@ 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 string _email;
private string _masterPassword;
private string _pin;
private bool _showPassword;
private bool _pinLock;
private bool _biometricLock;
@@ -58,6 +63,8 @@ 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>();
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
@@ -70,6 +77,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;
@@ -134,8 +153,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
{
@@ -192,7 +209,7 @@ namespace Bit.App.Pages
if (BiometricLock)
{
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid)
{
BiometricButtonVisible = false;
@@ -228,8 +245,7 @@ namespace Bit.App.Pages
}
ShowPassword = false;
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock)
{
@@ -239,7 +255,7 @@ namespace Bit.App.Pages
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000),
kdfConfig,
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync();
@@ -254,8 +270,7 @@ namespace Bit.App.Pages
}
else
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
failed = false;
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
@@ -280,9 +295,10 @@ namespace Bit.App.Pages
}
else
{
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdf, kdfIterations);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null)
{
@@ -294,9 +310,11 @@ namespace Bit.App.Pages
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, 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);
@@ -314,10 +332,17 @@ namespace Bit.App.Pages
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,
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{
// Save the ForcePasswordResetReason to force a password reset after unlock
await _stateService.SetForcePasswordResetReasonAsync(
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
@@ -342,6 +367,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,
@@ -352,6 +408,20 @@ 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;
@@ -361,7 +431,8 @@ namespace Bit.App.Pages
public async Task PromptBiometricAsync()
{
BiometricIntegrityValid = await _biometricService.ValidateIntegrityAsync();
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricLock || !BiometricIntegrityValid)
{
return;

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
@@ -12,6 +13,7 @@ namespace Bit.App.Pages
{
public partial class LoginPage : BaseContentPage
{
private readonly IBroadcasterService _broadcasterService;
private readonly LoginPageViewModel _vm;
private readonly AppOptions _appOptions;
@@ -23,6 +25,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 +73,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 +114,7 @@ namespace Bit.App.Pages
base.OnDisappearing();
_accountAvatar?.OnDisappearing();
_broadcasterService.Unsubscribe(nameof(LoginPage));
}
private async void LogIn_Clicked(object sender, EventArgs e)

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

@@ -171,7 +171,7 @@ namespace Bit.App.Pages
var response = await _authService.PasswordlessCreateLoginRequestAsync(_email);
if (response != null)
{
FingerprintPhrase = response.RequestFingerprint;
FingerprintPhrase = response.FingerprintPhrase;
_requestId = response.Id;
_requestAccessCode = response.RequestAccessCode;
_requestKeyPair = response.RequestKeyPair;

View File

@@ -118,7 +118,7 @@ namespace Bit.App.Pages
}
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(LoginRequest.Id);
if (loginRequestData.RequestApproved.HasValue && loginRequestData.ResponseDate.HasValue)
if (loginRequestData.IsAnswered)
{
await _platformUtilsService.ShowDialogAsync(AppResources.ThisRequestIsNoLongerValid);
await Page.Navigation.PopModalAsync();

View File

@@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
@@ -27,6 +28,7 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IStateService _stateService;
private readonly ILogger _logger;
private readonly IOrganizationService _organizationService;
private string _orgIdentifier;
@@ -42,6 +44,7 @@ namespace Bit.App.Pages
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
PageTitle = AppResources.Bitwarden;
@@ -63,9 +66,25 @@ namespace Bit.App.Pages
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 +121,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);
@@ -207,5 +225,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

@@ -48,6 +48,7 @@ namespace Bit.App.Pages
SubmitCommand = new Command(async () => await SubmitAsync());
ShowTerms = !_platformUtilsService.IsSelfHost();
PasswordStrengthViewModel = new PasswordStrengthViewModel(this);
CheckExposedMasterPassword = true;
}
public ICommand PoliciesClickCommand => new Command<string>((url) =>
@@ -147,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;
}
@@ -175,8 +176,8 @@ namespace Bit.App.Pages
Name = string.IsNullOrWhiteSpace(Name) ? null : Name;
Email = Email.Trim().ToLower();
var kdf = KdfType.PBKDF2_SHA256;
var key = await _cryptoService.MakeKeyAsync(MasterPassword, Email, kdf, Constants.KdfIterations);
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);
@@ -187,8 +188,10 @@ namespace Bit.App.Pages
MasterPasswordHash = hashedPassword,
MasterPasswordHint = Hint,
Key = encKey.Item2.EncryptedString,
Kdf = kdf,
KdfIterations = Constants.KdfIterations,
Kdf = kdfConfig.Type,
KdfIterations = kdfConfig.Iterations,
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
Keys = new KeysRequest
{
PublicKey = keys.Item1,

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;
}
}
@@ -163,9 +163,9 @@ namespace Bit.App.Pages
return;
}
var kdf = KdfType.PBKDF2_SHA256;
var kdfConfig = new KdfConfig(KdfType.PBKDF2_SHA256, Constants.Pbkdf2Iterations, null, null);
var email = await _stateService.GetEmailAsync();
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, Constants.KdfIterations);
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);
@@ -186,8 +186,10 @@ namespace Bit.App.Pages
MasterPasswordHash = masterPasswordHash,
Key = encKey.Item2.EncryptedString,
MasterPasswordHint = Hint,
Kdf = kdf,
KdfIterations = Constants.KdfIterations,
Kdf = kdfConfig.Type.GetValueOrDefault(KdfType.PBKDF2_SHA256),
KdfIterations = kdfConfig.Iterations.GetValueOrDefault(Constants.Pbkdf2Iterations),
KdfMemory = kdfConfig.Memory,
KdfParallelism = kdfConfig.Parallelism,
OrgIdentifier = OrgIdentifier,
Keys = new KeysRequest
{
@@ -201,8 +203,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.CreatingAccount);
// Set Password and relevant information
await _apiService.SetPasswordAsync(request);
await _stateService.SetKdfTypeAsync(kdf);
await _stateService.SetKdfIterationsAsync(Constants.KdfIterations);
await _stateService.SetKdfConfigurationAsync(kdfConfig);
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetKeyHashAsync(localMasterPasswordHash);
await _cryptoService.SetEncKeyAsync(encKey.Item2.EncryptedString);

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,33 +83,46 @@ namespace Bit.App.Pages
return;
}
if (RequireCurrentPassword &&
!await _userVerificationService.VerifyUser(CurrentMasterPassword, VerificationType.MasterPassword))
{
return;
}
// Retrieve details for key generation
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
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, kdf, kdfIterations);
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// 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
};
// Initiate API action
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.UpdatingPassword);
await _apiService.PutUpdateTempPasswordAsync(request);
switch (_reason)
{
case ForcePasswordResetReason.AdminForcePasswordReset:
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
break;
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.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)
@@ -86,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

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?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"
@@ -20,6 +20,7 @@
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<u:LocalizableEnumConverter x:Key="localizableEnum" />
<u:IconGlyphConverter x:Key="iconGlyphConverter" />
<xct:EnumToBoolConverter x:Key="enumToBool"/>
<ToolbarItem Text="{u:I18n Cancel}" Command="{Binding CloseCommand}" Order="Primary" Priority="-1"
x:Name="_closeItem" x:Key="closeItem" />
@@ -251,7 +252,7 @@
Grid.Row="1"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowAnonAddyHiddenValueIcon}"
Text="{Binding ShowAnonAddyApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
@@ -280,7 +281,7 @@
IsPassword="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowFirefoxRelayHiddenValueIcon}"
Text="{Binding ShowFirefoxRelayApiAccessToken, Converter={StaticResource inverseBool, iconGlyphConverter}, ConverterParameter={x:Static u:BooleanGlyphType.Eye}}"
Command="{Binding ToggleForwardedEmailHiddenValueCommand}"
Grid.Row="1"
Grid.Column="1"/>
@@ -301,7 +302,49 @@
IsPassword="{Binding ShowSimpleLoginApiKey, Converter={StaticResource inverseBool}}"/>
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowSimpleLoginHiddenValueIcon}"
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"/>

View File

@@ -52,6 +52,8 @@ namespace Bit.App.Pages
private bool _showFirefoxRelayApiAccessToken;
private bool _showAnonAddyApiAccessToken;
private bool _showSimpleLoginApiKey;
private bool _showDuckDuckGoApiKey;
private bool _showFastmailApiKey;
private bool _editMode;
public GeneratorPageViewModel()
@@ -79,6 +81,8 @@ namespace Bit.App.Pages
ForwardedEmailServiceTypeOptions = new List<ForwardedEmailServiceType> {
ForwardedEmailServiceType.AnonAddy,
ForwardedEmailServiceType.DuckDuckGo,
ForwardedEmailServiceType.Fastmail,
ForwardedEmailServiceType.FirefoxRelay,
ForwardedEmailServiceType.SimpleLogin
};
@@ -461,15 +465,9 @@ namespace Bit.App.Pages
{
return _showAnonAddyApiAccessToken;
}
set => SetProperty(ref _showAnonAddyApiAccessToken, value,
additionalPropertyNames: new string[]
{
nameof(ShowAnonAddyHiddenValueIcon)
});
set => SetProperty(ref _showAnonAddyApiAccessToken, value);
}
public string ShowAnonAddyHiddenValueIcon => _showAnonAddyApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string AnonAddyDomainName
{
get => _usernameOptions.AnonAddyDomainName;
@@ -504,15 +502,9 @@ namespace Bit.App.Pages
{
return _showFirefoxRelayApiAccessToken;
}
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value,
additionalPropertyNames: new string[]
{
nameof(ShowFirefoxRelayHiddenValueIcon)
});
set => SetProperty(ref _showFirefoxRelayApiAccessToken, value);
}
public string ShowFirefoxRelayHiddenValueIcon => _showFirefoxRelayApiAccessToken ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string SimpleLoginApiKey
{
get => _usernameOptions.SimpleLoginApiKey;
@@ -533,14 +525,55 @@ namespace Bit.App.Pages
{
return _showSimpleLoginApiKey;
}
set => SetProperty(ref _showSimpleLoginApiKey, value,
additionalPropertyNames: new string[]
{
nameof(ShowSimpleLoginHiddenValueIcon)
});
set => SetProperty(ref _showSimpleLoginApiKey, value);
}
public string ShowSimpleLoginHiddenValueIcon => _showSimpleLoginApiKey ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
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
{
@@ -778,6 +811,8 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(FirefoxRelayApiAccessToken));
TriggerPropertyChanged(nameof(AnonAddyDomainName));
TriggerPropertyChanged(nameof(AnonAddyApiAccessToken));
TriggerPropertyChanged(nameof(DuckDuckGoApiKey));
TriggerPropertyChanged(nameof(FastmailApiKey));
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
TriggerPropertyChanged(nameof(UsernameTypeSelected));
@@ -792,7 +827,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;
@@ -849,6 +884,12 @@ namespace Bit.App.Pages
case ForwardedEmailServiceType.SimpleLogin:
ShowSimpleLoginApiKey = !ShowSimpleLoginApiKey;
break;
case ForwardedEmailServiceType.DuckDuckGo:
ShowDuckDuckGoApiKey = !ShowDuckDuckGoApiKey;
break;
case ForwardedEmailServiceType.Fastmail:
ShowFastmailApiKey = !ShowFastmailApiKey;
break;
}
}
}

View File

@@ -0,0 +1,107 @@
<?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"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Pages.LoginPasswordlessRequestsListPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
x:DataType="pages:LoginPasswordlessRequestsListViewModel"
xmlns:models="clr-namespace:Bit.Core.Models.Response;assembly=BitwardenCore"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
Title="{Binding PageTitle}">
<ContentPage.BindingContext>
<pages:LoginPasswordlessRequestsListViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Close}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>
<ResourceDictionary>
<u:DateTimeConverter x:Key="dateTime" />
<xct:ItemSelectedEventArgsConverter x:Key="ItemSelectedEventArgsConverter" />
<controls:SelectionChangedEventArgsConverter x:Key="SelectionChangedEventArgsConverter" />
<DataTemplate
x:Key="loginRequestTemplate"
x:DataType="models:PasswordlessLoginResponse">
<Grid
Padding="10, 0"
RowSpacing="0"
RowDefinitions="*, Auto, *, 10"
ColumnDefinitions="*, *">
<Label
Text="{u:I18n FingerprintPhrase}"
FontSize="Small"
Padding="0, 10, 0 ,0"
FontAttributes="Bold"/>
<controls:MonoLabel
FormattedText="{Binding FingerprintPhrase}"
Grid.Row="1"
Grid.ColumnSpan="2"
FontSize="Small"
Padding="0, 5, 0, 10"
VerticalTextAlignment="Center"
TextColor="{DynamicResource FingerprintPhrase}"/>
<Label
Grid.Row="2"
HorizontalOptions="Start"
HorizontalTextAlignment="Start"
Text="{Binding RequestDeviceType}"
StyleClass="list-header-sub" />
<Label
Grid.Row="2"
Grid.Column="1"
HorizontalOptions="End"
HorizontalTextAlignment="End"
Text="{Binding CreationDate, Converter={StaticResource dateTime}}"
StyleClass="list-header-sub" />
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform"
VerticalOptions="End"
Grid.Row="3"
Grid.ColumnSpan="2"/>
</Grid>
</DataTemplate>
<StackLayout
x:Key="mainLayout"
x:Name="_mainLayout"
Padding="0, 10">
<RefreshView
IsRefreshing="{Binding IsRefreshing}"
Command="{Binding RefreshCommand}"
VerticalOptions="FillAndExpand"
BackgroundColor="{DynamicResource BackgroundColor}">
<controls:ExtendedCollectionView
ItemsSource="{Binding LoginRequests}"
ItemTemplate="{StaticResource loginRequestTemplate}"
SelectionMode="Single"
ExtraDataForLogging="Login requests page" >
<controls:ExtendedCollectionView.Behaviors>
<xct:EventToCommandBehavior
EventName="SelectionChanged"
Command="{Binding AnswerRequestCommand}"
EventArgsConverter="{StaticResource SelectionChangedEventArgsConverter}" />
</controls:ExtendedCollectionView.Behaviors>
</controls:ExtendedCollectionView>
</RefreshView>
<controls:IconLabelButton
VerticalOptions="End"
Margin="10,0"
Icon="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
Label="{u:I18n DeclineAllRequests}"
ButtonCommand="{Binding DeclineAllRequestsCommand}"/>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
<ContentView
x:Name="_mainContent">
</ContentView>
</pages:BaseContentPage>

View File

@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Bit.Core.Abstractions;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginPasswordlessRequestsListPage : BaseContentPage
{
private LoginPasswordlessRequestsListViewModel _vm;
public LoginPasswordlessRequestsListPage()
{
InitializeComponent();
SetActivityIndicator(_mainContent);
_vm = BindingContext as LoginPasswordlessRequestsListViewModel;
_vm.Page = this;
}
protected override async void OnAppearing()
{
base.OnAppearing();
await LoadOnAppearedAsync(_mainLayout, false, _vm.RefreshAsync, _mainContent);
}
private async void Close_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
await Navigation.PopModalAsync();
}
}
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.Response;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginPasswordlessRequestsListViewModel : BaseViewModel
{
private readonly IAuthService _authService;
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _isRefreshing;
public LoginPasswordlessRequestsListViewModel()
{
_authService = ServiceContainer.Resolve<IAuthService>();
_stateService = ServiceContainer.Resolve<IStateService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
PageTitle = AppResources.PendingLogInRequests;
LoginRequests = new ObservableRangeCollection<PasswordlessLoginResponse>();
AnswerRequestCommand = new AsyncCommand<PasswordlessLoginResponse>(PasswordlessLoginAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
DeclineAllRequestsCommand = new AsyncCommand(DeclineAllRequestsAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
RefreshCommand = new Command(async () => await RefreshAsync());
}
public ICommand RefreshCommand { get; }
public AsyncCommand<PasswordlessLoginResponse> AnswerRequestCommand { get; }
public AsyncCommand DeclineAllRequestsCommand { get; }
public ObservableRangeCollection<PasswordlessLoginResponse> LoginRequests { get; }
public bool IsRefreshing
{
get => _isRefreshing;
set => SetProperty(ref _isRefreshing, value);
}
public async Task RefreshAsync()
{
try
{
IsRefreshing = true;
LoginRequests.ReplaceRange(await _authService.GetActivePasswordlessLoginRequestsAsync());
if (!LoginRequests.Any())
{
Page.Navigation.PopModalAsync().FireAndForget();
}
}
catch (Exception ex)
{
HandleException(ex);
}
finally
{
IsRefreshing = false;
}
}
private async Task PasswordlessLoginAsync(PasswordlessLoginResponse request)
{
if (request.IsExpired)
{
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
await Page.Navigation.PopModalAsync();
return;
}
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(request.Id);
if (loginRequestData.IsAnswered)
{
await _platformUtilsService.ShowDialogAsync(AppResources.ThisRequestIsNoLongerValid);
return;
}
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
{
PubKey = loginRequestData.PublicKey,
Id = loginRequestData.Id,
IpAddress = loginRequestData.RequestIpAddress,
Email = await _stateService.GetEmailAsync(),
FingerprintPhrase = loginRequestData.FingerprintPhrase,
RequestDate = loginRequestData.CreationDate,
DeviceType = loginRequestData.RequestDeviceType,
Origin = loginRequestData.Origin
});
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
}
private async Task DeclineAllRequestsAsync()
{
try
{
if (!await _platformUtilsService.ShowDialogAsync(AppResources.AreYouSureYouWantToDeclineAllPendingLogInRequests, null, AppResources.Yes, AppResources.No))
{
return;
}
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var taskList = new List<Task>();
foreach (var request in LoginRequests)
{
taskList.Add(_authService.PasswordlessLoginAsync(request.Id, request.PublicKey, false));
}
await Task.WhenAll(taskList);
await _deviceActionService.HideLoadingAsync();
await RefreshAsync();
_platformUtilsService.ShowToast("info", null, AppResources.RequestsDeclined);
}
catch (Exception ex)
{
HandleException(ex);
RefreshAsync().FireAndForget();
}
}
}
}

View File

@@ -80,6 +80,22 @@
Text="{u:I18n ClearClipboardDescription}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-input, box-row-input-options-platform">
<Label
Text="{u:I18n Language}"
StyleClass="box-label" />
<Picker
x:Name="_languagePicker"
ItemsSource="{Binding LocalesOptions, Mode=OneTime}"
SelectedItem="{Binding SelectedLocale}"
ItemDisplayBinding="{Binding Value}"
StyleClass="box-value" />
</StackLayout>
<Label
Text="{u:I18n LanguageChangeRequiresAppRestart}"
StyleClass="box-footer-label" />
</StackLayout>
<StackLayout StyleClass="box">
<StackLayout StyleClass="box-row, box-row-switch">
<Label

View File

@@ -34,6 +34,7 @@ namespace Bit.App.Pages
_autoDarkThemePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_uriMatchPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_clearClipboardPicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
_languagePicker.On<iOS>().SetUpdateMode(UpdateMode.WhenFinished);
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.App.Utilities;
@@ -14,7 +15,8 @@ namespace Bit.App.Pages
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly II18nService _i18nService;
private readonly IPlatformUtilsService _platformUtilsService;
private bool _autofillSavePrompt;
private string _autofillBlockedUris;
@@ -24,6 +26,7 @@ namespace Bit.App.Pages
private int _themeSelectedIndex;
private int _autoDarkThemeSelectedIndex;
private int _uriMatchSelectedIndex;
private KeyValuePair<string, string> _selectedLocale;
private bool _inited;
private bool _updatingAutofill;
private bool _showAndroidAutofillSettings;
@@ -32,6 +35,8 @@ namespace Bit.App.Pages
{
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_i18nService = ServiceContainer.Resolve<II18nService>();
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
PageTitle = AppResources.Options;
var iosIos = Device.RuntimePlatform == Device.iOS;
@@ -74,12 +79,18 @@ namespace Bit.App.Pages
new KeyValuePair<UriMatchType?, string>(UriMatchType.Exact, AppResources.Exact),
new KeyValuePair<UriMatchType?, string>(UriMatchType.Never, AppResources.Never),
};
LocalesOptions = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
};
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
}
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
public List<KeyValuePair<string, string>> ThemeOptions { get; set; }
public List<KeyValuePair<string, string>> AutoDarkThemeOptions { get; set; }
public List<KeyValuePair<UriMatchType?, string>> UriMatchOptions { get; set; }
public List<KeyValuePair<string, string>> LocalesOptions { get; }
public int ClearClipboardSelectedIndex
{
@@ -133,6 +144,18 @@ namespace Bit.App.Pages
}
}
public KeyValuePair<string, string> SelectedLocale
{
get => _selectedLocale;
set
{
if (SetProperty(ref _selectedLocale, value))
{
UpdateCurrentLocaleAsync().FireAndForget();
}
}
}
public bool Favicon
{
get => _favicon;
@@ -184,19 +207,30 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
AutofillSavePrompt = !(await _stateService.GetAutofillDisableSavePromptAsync()).GetValueOrDefault();
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
AutofillBlockedUris = blockedUrisList != null ? string.Join(", ", blockedUrisList) : null;
AutoTotpCopy = !(await _stateService.GetDisableAutoTotpCopyAsync() ?? false);
Favicon = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var theme = await _stateService.GetThemeAsync();
ThemeSelectedIndex = ThemeOptions.FindIndex(k => k.Key == theme);
var autoDarkTheme = await _stateService.GetAutoDarkThemeAsync() ?? "dark";
AutoDarkThemeSelectedIndex = AutoDarkThemeOptions.FindIndex(k => k.Key == autoDarkTheme);
var defaultUriMatch = await _stateService.GetDefaultUriMatchAsync();
UriMatchSelectedIndex = defaultUriMatch == null ? 0 :
UriMatchOptions.FindIndex(k => (int?)k.Key == defaultUriMatch);
var clearClipboard = await _stateService.GetClearClipboardAsync();
ClearClipboardSelectedIndex = ClearClipboardOptions.FindIndex(k => k.Key == clearClipboard);
var appLocale = _stateService.GetLocale();
SelectedLocale = appLocale == null ? LocalesOptions.First() : LocalesOptions.FirstOrDefault(kv => kv.Key == appLocale);
_inited = true;
}
@@ -288,5 +322,17 @@ namespace Bit.App.Pages
catch { }
}
}
private async Task UpdateCurrentLocaleAsync()
{
if (!_inited)
{
return;
}
_stateService.SetLocale(SelectedLocale.Key);
await _platformUtilsService.ShowDialogAsync(string.Format(AppResources.LanguageChangeXDescription, SelectedLocale.Value), AppResources.Language, AppResources.Ok);
}
}
}

View File

@@ -7,9 +7,7 @@ using Bit.App.Pages.Accounts;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Models.Domain;
using Bit.Core.Models.View;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -35,6 +33,7 @@ namespace Bit.App.Pages
private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService;
private readonly IPushNotificationService _pushNotificationService;
private readonly IAuthService _authService;
private readonly IWatchDeviceService _watchDeviceService;
private const int CustomVaultTimeoutValue = -100;
@@ -49,8 +48,7 @@ namespace Bit.App.Pages
private bool _reportLoggingEnabled;
private bool _approvePasswordlessLoginRequests;
private bool _shouldConnectToWatch;
private List<KeyValuePair<string, int?>> _vaultTimeouts =
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
new List<KeyValuePair<string, int?>>
{
new KeyValuePair<string, int?>(AppResources.Immediately, 0),
@@ -64,7 +62,7 @@ namespace Bit.App.Pages
new KeyValuePair<string, int?>(AppResources.Never, null),
new KeyValuePair<string, int?>(AppResources.Custom, CustomVaultTimeoutValue),
};
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActions =
private readonly static List<KeyValuePair<string, VaultTimeoutAction>> VaultTimeoutActionOptions =
new List<KeyValuePair<string, VaultTimeoutAction>>
{
new KeyValuePair<string, VaultTimeoutAction>(AppResources.Lock, VaultTimeoutAction.Lock),
@@ -73,6 +71,8 @@ namespace Bit.App.Pages
private Policy _vaultTimeoutPolicy;
private int? _vaultTimeout;
private List<KeyValuePair<string, int?>> _vaultTimeoutOptions = VaultTimeoutOptions;
private List<KeyValuePair<string, VaultTimeoutAction>> _vaultTimeoutActionOptions = VaultTimeoutActionOptions;
public SettingsPageViewModel()
{
@@ -92,8 +92,8 @@ namespace Bit.App.Pages
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_authService = ServiceContainer.Resolve<IAuthService>();
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
GroupedItems = new ObservableRangeCollection<ISettingsPageListItem>();
PageTitle = AppResources.Settings;
@@ -116,20 +116,28 @@ namespace Bit.App.Pages
_localizeService.GetLocaleShortTime(lastSync.Value));
}
_vaultTimeoutPolicy = null;
_vaultTimeoutOptions = VaultTimeoutOptions;
_vaultTimeoutActionOptions = VaultTimeoutActionOptions;
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
_vaultTimeoutDisplayValue ??= _vaultTimeoutOptions.Where(o => o.Value == CustomVaultTimeoutValue).First().Key;
var action = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == action).Key;
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
{
// if we have a vault timeout policy, we need to filter the timeout options
_vaultTimeoutPolicy = (await _policyService.GetAll(PolicyType.MaximumVaultTimeout)).First();
var minutes = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
_vaultTimeouts = _vaultTimeouts.Where(t =>
t.Value <= minutes &&
var policyMinutes = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
_vaultTimeoutOptions = _vaultTimeoutOptions.Where(t =>
t.Value <= policyMinutes &&
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
t.Value != null).ToList();
}
_vaultTimeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == _vaultTimeout).Key;
var action = await _stateService.GetVaultTimeoutActionAsync() ?? VaultTimeoutAction.Lock;
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
@@ -144,7 +152,6 @@ namespace Bit.App.Pages
!await _keyConnectorService.GetUsesKeyConnector();
_reportLoggingEnabled = await _loggerService.IsEnabled();
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
BuildList();
@@ -266,7 +273,7 @@ namespace Bit.App.Pages
{
var oldTimeout = _vaultTimeout;
var options = _vaultTimeouts.Select(
var options = _vaultTimeoutOptions.Select(
o => o.Key == _vaultTimeoutDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
if (promptOptions)
{
@@ -277,7 +284,7 @@ namespace Bit.App.Pages
return;
}
var cleanSelection = selection.Replace("✓ ", string.Empty);
var selectionOption = _vaultTimeouts.FirstOrDefault(o => o.Key == cleanSelection);
var selectionOption = _vaultTimeoutOptions.FirstOrDefault(o => o.Key == cleanSelection);
// Check if the selected Timeout action is "Never" and if it's different from the previous selected value
if (selectionOption.Value == null && selectionOption.Value != oldTimeout)
@@ -295,13 +302,13 @@ namespace Bit.App.Pages
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes");
var maximumTimeout = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
if (newTimeout > maximumTimeout)
{
await _platformUtilsService.ShowDialogAsync(AppResources.VaultTimeoutToLarge, AppResources.Warning);
var timeout = await _vaultTimeoutService.GetVaultTimeout();
_vaultTimeoutDisplayValue = _vaultTimeouts.FirstOrDefault(o => o.Value == timeout).Key ??
_vaultTimeoutDisplayValue = _vaultTimeoutOptions.FirstOrDefault(o => o.Value == timeout).Key ??
AppResources.Custom;
return;
}
@@ -374,7 +381,13 @@ namespace Bit.App.Pages
public async Task VaultTimeoutActionAsync()
{
var options = _vaultTimeoutActions.Select(o =>
if (_vaultTimeoutPolicy != null &&
!string.IsNullOrEmpty(_vaultTimeoutPolicy.GetString(Policy.ACTION_KEY)))
{
// do nothing if we have a policy set
return;
}
var options = _vaultTimeoutActionOptions.Select(o =>
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
AppResources.Cancel, null, options);
@@ -393,7 +406,7 @@ namespace Bit.App.Pages
cleanSelection = AppResources.Lock;
}
}
var selectionOption = _vaultTimeoutActions.FirstOrDefault(o => o.Key == cleanSelection);
var selectionOption = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Key == cleanSelection);
var changed = _vaultTimeoutActionDisplayValue != selectionOption.Key;
_vaultTimeoutActionDisplayValue = selectionOption.Key;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout,
@@ -422,12 +435,9 @@ namespace Bit.App.Pages
AppResources.Yes, AppResources.No);
}
var kdf = await _stateService.GetKdfTypeAsync();
var kdfIterations = await _stateService.GetKdfIterationsAsync();
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email,
kdf.GetValueOrDefault(Core.Enums.KdfType.PBKDF2_SHA256),
kdfIterations.GetValueOrDefault(5000));
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
@@ -566,6 +576,14 @@ namespace Bit.App.Pages
ExecuteAsync = () => TwoStepAsync()
}
};
if (_approvePasswordlessLoginRequests)
{
manageItems.Add(new SettingsPageListItem
{
Name = AppResources.PendingLogInRequests,
ExecuteAsync = () => PendingLoginRequestsAsync()
});
}
if (_supportsBiometric || _biometric)
{
var biometricName = AppResources.Biometrics;
@@ -592,14 +610,36 @@ namespace Bit.App.Pages
}
if (_vaultTimeoutPolicy != null)
{
var maximumTimeout = _policyService.GetPolicyInt(_vaultTimeoutPolicy, "minutes").GetValueOrDefault();
securityItems.Insert(0, new SettingsPageListItem
var policyMinutes = _vaultTimeoutPolicy.GetInt(Policy.MINUTES_KEY);
var policyAction = _vaultTimeoutPolicy.GetString(Policy.ACTION_KEY);
if (policyMinutes.HasValue || !string.IsNullOrWhiteSpace(policyAction))
{
Name = string.Format(AppResources.VaultTimeoutPolicyInEffect,
Math.Floor((float)maximumTimeout / 60),
maximumTimeout % 60),
UseFrame = true,
});
string policyAlert;
if (policyMinutes.HasValue && string.IsNullOrWhiteSpace(policyAction))
{
policyAlert = string.Format(AppResources.VaultTimeoutPolicyInEffect,
Math.Floor((float)policyMinutes / 60),
policyMinutes % 60);
}
else if (!policyMinutes.HasValue && !string.IsNullOrWhiteSpace(policyAction))
{
policyAlert = string.Format(AppResources.VaultTimeoutActionPolicyInEffect,
policyAction == Policy.ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
}
else
{
policyAlert = string.Format(AppResources.VaultTimeoutPolicyWithActionInEffect,
Math.Floor((float)policyMinutes / 60),
policyMinutes % 60,
policyAction == Policy.ACTION_LOCK ? AppResources.Lock : AppResources.LogOut);
}
securityItems.Insert(0, new SettingsPageListItem
{
Name = policyAlert,
UseFrame = true,
});
}
}
if (Device.RuntimePlatform == Device.Android)
{
@@ -757,6 +797,25 @@ namespace Bit.App.Pages
}
}
private async Task PendingLoginRequestsAsync()
{
try
{
var requests = await _authService.GetActivePasswordlessLoginRequestsAsync();
if (requests == null || !requests.Any())
{
_platformUtilsService.ShowToast("info", null, AppResources.NoPendingRequests);
return;
}
Page.Navigation.PushModalAsync(new NavigationPage(new LoginPasswordlessRequestsListPage())).FireAndForget();
}
catch (Exception ex)
{
HandleException(ex);
}
}
private bool IncludeLinksWithSubscriptionInfo()
{
if (Device.RuntimePlatform == Device.iOS)
@@ -768,12 +827,12 @@ namespace Bit.App.Pages
private VaultTimeoutAction GetVaultTimeoutActionFromKey(string key)
{
return _vaultTimeoutActions.FirstOrDefault(o => o.Key == key).Value;
return _vaultTimeoutActionOptions.FirstOrDefault(o => o.Key == key).Value;
}
private int? GetVaultTimeoutFromKey(string key)
{
return _vaultTimeouts.FirstOrDefault(o => o.Key == key).Value;
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
}
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
@@ -782,11 +841,6 @@ namespace Bit.App.Pages
public async Task SetScreenCaptureAllowedAsync()
{
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
try
{
if (!_screenCaptureAllowed

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Bit.App.Effects;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.Data;
using Bit.Core.Utilities;
@@ -15,6 +16,7 @@ namespace Bit.App.Pages
private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IStateService _stateService;
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
private NavigationPage _groupingsPage;
@@ -26,6 +28,7 @@ namespace Bit.App.Pages
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_stateService = ServiceContainer.Resolve<IStateService>();
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage))
{
@@ -95,6 +98,13 @@ namespace Bit.App.Pages
{
_messagingService.Send("convertAccountToKeyConnector");
}
var forcePasswordResetReason = await _stateService.GetForcePasswordResetReasonAsync();
if (forcePasswordResetReason.HasValue)
{
_messagingService.Send(Constants.ForceUpdatePassword);
}
}
protected override void OnDisappearing()

View File

@@ -123,7 +123,7 @@ namespace Bit.App.Pages
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
_cipherDomain = await _cipherService.SaveAttachmentRawWithServerAsync(
_cipherDomain, FileName, FileData);
_cipherDomain, Cipher, FileName, FileData);
Cipher = await _cipherDomain.DecryptAsync();
await _deviceActionService.HideLoadingAsync();
_platformUtilsService.ShowToast("success", null, AppResources.AttachementAdded);
@@ -156,7 +156,7 @@ namespace Bit.App.Pages
// Prevent Android from locking if vault timeout set to "immediate"
if (Device.RuntimePlatform == Device.Android)
{
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
_vaultTimeoutService.DelayTimeoutMs = 60000;
}
await _fileService.SelectFileAsync();
}

View File

@@ -1,91 +1,29 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class AutofillCiphersPageViewModel : BaseViewModel
public class AutofillCiphersPageViewModel : CipherSelectionPageViewModel
{
private readonly IPlatformUtilsService _platformUtilsService;
private readonly IDeviceActionService _deviceActionService;
private readonly IAutofillHandler _autofillHandler;
private readonly ICipherService _cipherService;
private readonly IStateService _stateService;
private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IMessagingService _messagingService;
private readonly ILogger _logger;
private CipherType? _fillType;
private bool _showNoData;
private bool _showList;
private string _noDataText;
private bool _websiteIconsEnabled;
public AutofillCiphersPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = false
};
}
public string Name { get; set; }
public string Uri { get; set; }
public Command CipherOptionsCommand { get; set; }
public bool LoadedOnce { get; set; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public bool ShowNoData
{
get => _showNoData;
set => SetProperty(ref _showNoData, value);
}
public bool ShowList
{
get => _showList;
set => SetProperty(ref _showList, value);
}
public string NoDataText
{
get => _noDataText;
set => SetProperty(ref _noDataText, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public void Init(AppOptions appOptions)
public override void Init(AppOptions appOptions)
{
Uri = appOptions?.Uri;
_fillType = appOptions.FillType;
string name = null;
if (Uri?.StartsWith(Constants.AndroidAppProtocol) ?? false)
{
@@ -104,14 +42,11 @@ namespace Bit.App.Pages
NoDataText = string.Format(AppResources.NoItemsForUri, Name ?? "--");
}
public async Task LoadAsync()
protected override async Task<List<GroupingsPageListGroup>> LoadGroupedItemsAsync()
{
LoadedOnce = true;
ShowList = false;
ShowNoData = false;
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var groupedItems = new List<GroupingsPageListGroup>();
var ciphers = await _cipherService.GetAllDecryptedByUrlAsync(Uri, null);
var matching = ciphers.Item1?.Select(c => new GroupingsPageListItem { Cipher = c }).ToList();
var hasMatching = matching?.Any() ?? false;
if (matching?.Any() ?? false)
@@ -119,6 +54,7 @@ namespace Bit.App.Pages
groupedItems.Add(
new GroupingsPageListGroup(matching, AppResources.MatchingItems, matching.Count, false, true));
}
var fuzzy = ciphers.Item2?.Select(c =>
new GroupingsPageListItem { Cipher = c, FuzzyAutofill = true }).ToList();
if (fuzzy?.Any() ?? false)
@@ -128,123 +64,88 @@ namespace Bit.App.Pages
!hasMatching));
}
// TODO: refactor this
if (Device.RuntimePlatform == Device.Android
||
GroupedItems.Any())
{
var items = new List<IGroupingsPageListItem>();
foreach (var itemGroup in groupedItems)
{
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
items.AddRange(itemGroup);
}
GroupedItems.ReplaceRange(items);
}
else
{
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
var first = true;
var items = new List<IGroupingsPageListItem>();
foreach (var itemGroup in groupedItems)
{
if (!first)
{
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
}
else
{
first = false;
}
items.AddRange(itemGroup);
}
if (groupedItems.Any())
{
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{
GroupedItems.Clear();
}
}
ShowList = groupedItems.Any();
ShowNoData = !ShowList;
return groupedItems;
}
public async Task SelectCipherAsync(CipherView cipher, bool fuzzy)
protected override async Task SelectCipherAsync(IGroupingsPageListItem item)
{
if (cipher == null)
if (!(item is GroupingsPageListItem listItem) || listItem.Cipher is null)
{
return;
}
var cipher = listItem.Cipher;
if (_deviceActionService.SystemMajorVersion() < 21)
{
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
return;
}
else
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
return;
}
var autofillResponse = AppResources.Yes;
if (listItem.FuzzyAutofill)
{
var options = new List<string> { AppResources.Yes };
if (cipher.Type == CipherType.Login &&
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
return;
options.Add(AppResources.YesAndSave);
}
var autofillResponse = AppResources.Yes;
if (fuzzy)
autofillResponse = await _deviceActionService.DisplayAlertAsync(null,
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, Name), AppResources.No,
options.ToArray());
}
if (autofillResponse == AppResources.YesAndSave && cipher.Type == CipherType.Login)
{
var uris = cipher.Login?.Uris?.ToList();
if (uris == null)
{
var options = new List<string> { AppResources.Yes };
if (cipher.Type == CipherType.Login &&
Xamarin.Essentials.Connectivity.NetworkAccess != Xamarin.Essentials.NetworkAccess.None)
{
options.Add(AppResources.YesAndSave);
}
autofillResponse = await _deviceActionService.DisplayAlertAsync(null,
string.Format(AppResources.BitwardenAutofillServiceMatchConfirm, Name), AppResources.No,
options.ToArray());
uris = new List<LoginUriView>();
}
if (autofillResponse == AppResources.YesAndSave && cipher.Type == CipherType.Login)
uris.Add(new LoginUriView
{
var uris = cipher.Login?.Uris?.ToList();
if (uris == null)
Uri = Uri,
Match = null
});
cipher.Login.Uris = uris;
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
await _cipherService.SaveWithServerAsync(await _cipherService.EncryptAsync(cipher));
await _deviceActionService.HideLoadingAsync();
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
uris = new List<LoginUriView>();
}
uris.Add(new LoginUriView
{
Uri = Uri,
Match = null
});
cipher.Login.Uris = uris;
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
await _cipherService.SaveWithServerAsync(await _cipherService.EncryptAsync(cipher));
await _deviceActionService.HideLoadingAsync();
}
catch (ApiException e)
{
await _deviceActionService.HideLoadingAsync();
if (e?.Error != null)
{
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
await _platformUtilsService.ShowDialogAsync(e.Error.GetSingleMessage(),
AppResources.AnErrorHasOccurred);
}
}
if (autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
{
_autofillHandler.Autofill(cipher);
}
}
if (autofillResponse == AppResources.Yes || autofillResponse == AppResources.YesAndSave)
{
_autofillHandler.Autofill(cipher);
}
}
private async void CipherOptionsAsync(CipherView cipher)
protected override async Task AddCipherAsync()
{
if ((Page as BaseContentPage).DoOnce())
if (_fillType.HasValue && _fillType != CipherType.Login)
{
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
var pageForOther = new CipherAddEditPage(type: _fillType, fromAutofill: true);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForOther));
return;
}
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, uri: Uri, name: Name,
fromAutofill: true);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Abstractions;
using Bit.App.Lists.ItemViewModels.CustomFields;
using Bit.App.Models;
using Bit.App.Resources;
@@ -30,6 +31,7 @@ namespace Bit.App.Pages
private readonly IClipboardService _clipboardService;
private readonly IAutofillHandler _autofillHandler;
private readonly IWatchDeviceService _watchDeviceService;
private readonly IAccountsManager _accountsManager;
private bool _showNotesSeparator;
private bool _showPassword;
@@ -44,6 +46,8 @@ namespace Bit.App.Pages
private bool _hasCollections;
private string _previousCipherId;
private List<Core.Models.View.CollectionView> _writeableCollections;
private bool _fromOtp;
protected override string[] AdditionalPropertiesToRaiseOnCipherChanged => new string[]
{
nameof(IsLogin),
@@ -82,6 +86,8 @@ namespace Bit.App.Pages
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
@@ -115,6 +121,7 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>("JCB", "JCB"),
new KeyValuePair<string, string>("Maestro", "Maestro"),
new KeyValuePair<string, string>("UnionPay", "UnionPay"),
new KeyValuePair<string, string>("RuPay", "RuPay"),
new KeyValuePair<string, string>(AppResources.Other, "Other")
};
CardExpMonthOptions = new List<KeyValuePair<string, string>>
@@ -302,6 +309,7 @@ namespace Bit.App.Pages
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public bool HasTotpValue => IsLogin && !string.IsNullOrEmpty(Cipher?.Login?.Totp);
public string SetupTotpText => $"{BitwardenIcons.Camera} {AppResources.SetupTotp}";
public void Init()
{
PageTitle = EditMode && !CloneMode ? AppResources.EditItem : AppResources.AddItem;
@@ -309,6 +317,8 @@ namespace Bit.App.Pages
public async Task<bool> LoadAsync(AppOptions appOptions = null)
{
_fromOtp = appOptions?.OtpData != null;
var myEmail = await _stateService.GetEmailAsync();
OwnershipOptions.Add(new KeyValuePair<string, string>(myEmail, null));
var orgs = await _organizationService.GetAllAsync();
@@ -358,6 +368,10 @@ namespace Bit.App.Pages
Cipher.OrganizationId = OrganizationId;
}
}
if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login)
{
Cipher.Login.Totp = appOptions.OtpData.Value.Uri;
}
}
else
{
@@ -380,6 +394,7 @@ namespace Bit.App.Pages
Cipher.Type = appOptions.SaveType.GetValueOrDefault(Cipher.Type);
Cipher.Login.Username = appOptions.SaveUsername;
Cipher.Login.Password = appOptions.SavePassword;
Cipher.Login.Totp = appOptions.OtpData?.Uri;
Cipher.Card.Code = appOptions.SaveCardCode;
if (int.TryParse(appOptions.SaveCardExpMonth, out int month) && month <= 12 && month >= 1)
{
@@ -424,6 +439,11 @@ namespace Bit.App.Pages
{
Fields.ResetWithRange(Cipher.Fields?.Select(f => _customFieldItemFactory.CreateCustomFieldItem(f, true, Cipher, null, null, FieldOptionsCommand)));
}
if (appOptions?.OtpData != null)
{
_platformUtilsService.ShowToast(null, AppResources.AuthenticatorKey, AppResources.AuthenticatorKeyAdded);
}
}
if (EditMode && _previousCipherId != CipherId)
@@ -517,6 +537,10 @@ namespace Bit.App.Pages
// Close and go back to app
_autofillHandler.CloseAutofill();
}
else if (_fromOtp)
{
await _accountsManager.StartDefaultNavigationFlowAsync(op => op.OtpData = null);
}
else
{
if (CloneMode)

View File

@@ -1,19 +1,15 @@
<?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.AutofillCiphersPage"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
x:Class="Bit.App.Pages.CipherSelectionPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:effects="clr-namespace:Bit.App.Effects;assembly=BitwardenApp"
x:DataType="pages:AutofillCiphersPageViewModel"
x:DataType="pages:CipherSelectionPageViewModel"
Title="{Binding PageTitle}"
x:Name="_page">
<ContentPage.BindingContext>
<pages:AutofillCiphersPageViewModel />
</ContentPage.BindingContext>
<ContentPage.ToolbarItems>
<controls:ExtendedToolbarItem
x:Name="_accountAvatar"
@@ -24,7 +20,7 @@
UseOriginalImage="True"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Account}" />
<ToolbarItem Icon="search.png" Clicked="Search_Clicked"
<ToolbarItem IconImageSource="search.png" Clicked="Search_Clicked"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Search}" />
</ContentPage.ToolbarItems>
@@ -32,6 +28,21 @@
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
<controls:SelectionChangedEventArgsConverter x:Key="SelectionChangedEventArgsConverter" />
<ToolbarItem
x:Name="_closeItem"
x:Key="_closeItem"
Text="{u:I18n Close}"
Clicked="CloseItem_Clicked"
Order="Primary"
Priority="-1" />
<ToolbarItem x:Name="_addItem" x:Key="addItem"
IconImageSource="plus.png"
Command="{Binding AddCipherCommand}"
Order="Primary"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n AddItem}" />
<DataTemplate x:Key="cipherTemplate"
x:DataType="pages:GroupingsPageListItem">
@@ -70,23 +81,44 @@
Padding="20, 0"
Spacing="20"
IsVisible="{Binding ShowNoData}">
<Image
Source="empty_items_state" />
<Label
Text="{Binding NoDataText}"
HorizontalTextAlignment="Center"></Label>
<Button
Text="{u:I18n AddAnItem}"
Clicked="AddButton_Clicked"></Button>
Command="{Binding AddCipherCommand}" />
</StackLayout>
<Frame
IsVisible="{Binding ShowCallout}"
Padding="10"
Margin="20, 10"
HasShadow="False"
BackgroundColor="Transparent"
BorderColor="{DynamicResource PrimaryColor}">
<Label
Text="{u:I18n AddTheKeyToAnExistingOrNewItem}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
</Frame>
<controls:ExtendedCollectionView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding GroupedItems}"
VerticalOptions="FillAndExpand"
ItemTemplate="{StaticResource listItemDataTemplateSelector}"
SelectionMode="Single"
SelectionChanged="RowSelected"
StyleClass="list, list-platform"
ExtraDataForLogging="Autofill Ciphers Page" />
ExtraDataForLogging="Autofill Ciphers Page">
<controls:ExtendedCollectionView.Behaviors>
<xct:EventToCommandBehavior
EventName="SelectionChanged"
Command="{Binding SelectCipherCommand}"
EventArgsConverter="{StaticResource SelectionChangedEventArgsConverter}" />
</controls:ExtendedCollectionView.Behaviors>
</controls:ExtendedCollectionView>
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>
@@ -104,9 +136,10 @@
<!-- Android FAB -->
<Button
x:Name="_fab"
Image="plus.png"
Clicked="AddButton_Clicked"
ImageSource="plus.png"
Command="{Binding AddCipherCommand}"
Style="{StaticResource btn-fab}"
IsVisible="{OnPlatform iOS=false, Android=true}"
AbsoluteLayout.LayoutFlags="PositionProportional"
AbsoluteLayout.LayoutBounds="1, 1, AutoSize, AutoSize">
<Button.Effects>

View File

@@ -1,7 +1,6 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Controls;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
@@ -12,27 +11,46 @@ using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class AutofillCiphersPage : BaseContentPage
public partial class CipherSelectionPage : BaseContentPage
{
private readonly AppOptions _appOptions;
private readonly IBroadcasterService _broadcasterService;
private readonly ISyncService _syncService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IAccountsManager _accountsManager;
private AutofillCiphersPageViewModel _vm;
private readonly CipherSelectionPageViewModel _vm;
public AutofillCiphersPage(AppOptions appOptions)
public CipherSelectionPage(AppOptions appOptions)
{
_appOptions = appOptions;
if (appOptions?.OtpData is null)
{
BindingContext = new AutofillCiphersPageViewModel();
}
else
{
BindingContext = new OTPCipherSelectionPageViewModel();
}
InitializeComponent();
if (Device.RuntimePlatform == Device.iOS)
{
ToolbarItems.Add(_closeItem);
ToolbarItems.Add(_addItem);
}
SetActivityIndicator(_mainContent);
_vm = BindingContext as AutofillCiphersPageViewModel;
_vm = BindingContext as CipherSelectionPageViewModel;
_vm.Page = this;
_vm.Init(appOptions);
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
}
protected async override void OnAppearing()
@@ -51,10 +69,18 @@ namespace Bit.App.Pages
return;
}
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
try
{
// don't crash the app if the avatar can't be loaded, just log the ex
_accountAvatar?.OnAppearing();
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
_broadcasterService.Subscribe(nameof(AutofillCiphersPage), async (message) =>
_broadcasterService.Subscribe(nameof(CipherSelectionPage), async (message) =>
{
try
{
@@ -116,40 +142,44 @@ namespace Bit.App.Pages
_accountAvatar?.OnDisappearing();
}
private async void RowSelected(object sender, SelectionChangedEventArgs e)
private void AddButton_Clicked(object sender, System.EventArgs e)
{
((ExtendedCollectionView)sender).SelectedItem = null;
if (!DoOnce())
{
return;
}
if (e.CurrentSelection?.FirstOrDefault() is GroupingsPageListItem item && item.Cipher != null)
if (_vm is AutofillCiphersPageViewModel autofillVM)
{
await _vm.SelectCipherAsync(item.Cipher, item.FuzzyAutofill);
AddFromAutofill(autofillVM).FireAndForget();
}
}
private async void AddButton_Clicked(object sender, System.EventArgs e)
private async Task AddFromAutofill(AutofillCiphersPageViewModel autofillVM)
{
if (!DoOnce())
{
return;
}
if (_appOptions.FillType.HasValue && _appOptions.FillType != CipherType.Login)
{
var pageForOther = new CipherAddEditPage(type: _appOptions.FillType, fromAutofill: true);
await Navigation.PushModalAsync(new NavigationPage(pageForOther));
return;
}
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, uri: _vm.Uri, name: _vm.Name,
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, uri: autofillVM.Uri, name: _vm.Name,
fromAutofill: true);
await Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
private void Search_Clicked(object sender, System.EventArgs e)
private void Search_Clicked(object sender, EventArgs e)
{
var page = new CiphersPage(null, autofillUrl: _vm.Uri);
Application.Current.MainPage = new NavigationPage(page);
var page = new CiphersPage(null, appOptions: _appOptions);
Navigation.PushModalAsync(new NavigationPage(page)).FireAndForget();
}
void CloseItem_Clicked(object sender, EventArgs e)
{
if (DoOnce())
{
_accountsManager.StartDefaultNavigationFlowAsync(op => op.OtpData = null).FireAndForget();
}
}
}
}

View File

@@ -0,0 +1,167 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public abstract class CipherSelectionPageViewModel : BaseViewModel
{
protected readonly IPlatformUtilsService _platformUtilsService;
protected readonly IDeviceActionService _deviceActionService;
protected readonly IAutofillHandler _autofillHandler;
protected readonly ICipherService _cipherService;
protected readonly IStateService _stateService;
protected readonly IPasswordRepromptService _passwordRepromptService;
protected readonly IMessagingService _messagingService;
protected readonly ILogger _logger;
protected bool _showNoData;
protected bool _showList;
protected string _noDataText;
protected bool _websiteIconsEnabled;
public CipherSelectionPageViewModel()
{
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
_cipherService = ServiceContainer.Resolve<ICipherService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_stateService = ServiceContainer.Resolve<IStateService>();
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>();
_logger = ServiceContainer.Resolve<ILogger>();
GroupedItems = new ObservableRangeCollection<IGroupingsPageListItem>();
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
SelectCipherCommand = new AsyncCommand<IGroupingsPageListItem>(SelectCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddCipherCommand = new AsyncCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = false
};
}
public string Name { get; set; }
public bool LoadedOnce { get; set; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ICommand CipherOptionsCommand { get; set; }
public ICommand SelectCipherCommand { get; set; }
public ICommand AddCipherCommand { get; set; }
public bool ShowNoData
{
get => _showNoData;
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new string[] { nameof(ShowCallout) });
}
public bool ShowList
{
get => _showList;
set => SetProperty(ref _showList, value);
}
public string NoDataText
{
get => _noDataText;
set => SetProperty(ref _noDataText, value);
}
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
public virtual bool ShowCallout => false;
public abstract void Init(Models.AppOptions options);
public async Task LoadAsync()
{
LoadedOnce = true;
ShowList = false;
ShowNoData = false;
WebsiteIconsEnabled = !(await _stateService.GetDisableFaviconAsync()).GetValueOrDefault();
var groupedItems = await LoadGroupedItemsAsync();
// TODO: refactor this
if (Device.RuntimePlatform == Device.Android
||
GroupedItems.Any())
{
var items = new List<IGroupingsPageListItem>();
foreach (var itemGroup in groupedItems)
{
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
items.AddRange(itemGroup);
}
GroupedItems.ReplaceRange(items);
}
else
{
// HACK: we need this on iOS, so that it doesn't crash when adding coming from an empty list
var first = true;
var items = new List<IGroupingsPageListItem>();
foreach (var itemGroup in groupedItems)
{
if (!first)
{
items.Add(new GroupingsPageHeaderListItem(itemGroup.Name, itemGroup.ItemCount));
}
else
{
first = false;
}
items.AddRange(itemGroup);
}
await Device.InvokeOnMainThreadAsync(() =>
{
if (groupedItems.Any())
{
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{
GroupedItems.Clear();
}
});
}
await Device.InvokeOnMainThreadAsync(() =>
{
ShowList = groupedItems.Any();
ShowNoData = !ShowList;
});
}
protected abstract Task<List<GroupingsPageListGroup>> LoadGroupedItemsAsync();
protected abstract Task SelectCipherAsync(IGroupingsPageListItem item);
protected abstract Task AddCipherAsync();
}
}

View File

@@ -81,12 +81,22 @@
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<Label IsVisible="{Binding ShowNoData}"
Text="{u:I18n NoItemsToList}"
Margin="20, 0"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
HorizontalTextAlignment="Center" />
<StackLayout
HorizontalOptions="Center"
VerticalOptions="StartAndExpand"
Margin="20, 80, 20, 0"
Spacing="20"
IsVisible="{Binding ShowNoData}">
<Image
Source="empty_items_state" />
<Label
Text="{u:I18n ThereAreNoItemsThatMatchTheSearch}"
HorizontalTextAlignment="Center" />
<Button
Text="{u:I18n AddAnItem}"
Command="{Binding AddCipherCommand}"
IsVisible="{Binding ShowAddCipher}"/>
</StackLayout>
<controls:ExtendedCollectionView
IsVisible="{Binding ShowList}"
ItemsSource="{Binding Ciphers}"

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Models.View;
@@ -17,15 +18,18 @@ namespace Bit.App.Pages
private CiphersPageViewModel _vm;
private bool _hasFocused;
public CiphersPage(Func<CipherView, bool> filter, string pageTitle = null, string vaultFilterSelection = null,
string autofillUrl = null, bool deleted = false)
public CiphersPage(Func<CipherView, bool> filter,
string pageTitle = null,
string vaultFilterSelection = null,
bool deleted = false,
AppOptions appOptions = null)
{
InitializeComponent();
_vm = BindingContext as CiphersPageViewModel;
_vm.Page = this;
_vm.Filter = filter;
_vm.AutofillUrl = _autofillUrl = autofillUrl;
_vm.Deleted = deleted;
_autofillUrl = appOptions?.Uri;
_vm.Prepare(filter, deleted, appOptions);
if (pageTitle != null)
{
_vm.PageTitle = string.Format(AppResources.SearchGroup, pageTitle);

View File

@@ -3,13 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -31,6 +34,7 @@ namespace Bit.App.Pages
private bool _showNoData;
private bool _showList;
private bool _websiteIconsEnabled;
private AppOptions _appOptions;
public CiphersPageViewModel()
{
@@ -46,14 +50,21 @@ namespace Bit.App.Pages
_logger = ServiceContainer.Resolve<ILogger>("logger");
Ciphers = new ExtendedObservableCollection<CipherView>();
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
AddCipherCommand = new AsyncCommand(AddCipherAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
public Command CipherOptionsCommand { get; set; }
public ICommand CipherOptionsCommand { get; }
public ICommand AddCipherCommand { get; }
public ExtendedObservableCollection<CipherView> Ciphers { get; set; }
public Func<CipherView, bool> Filter { get; set; }
public string AutofillUrl { get; set; }
public bool Deleted { get; set; }
public bool ShowAllIfSearchTextEmpty { get; set; }
protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService;
@@ -65,7 +76,8 @@ namespace Bit.App.Pages
get => _showNoData;
set => SetProperty(ref _showNoData, value, additionalPropertyNames: new string[]
{
nameof(ShowSearchDirection)
nameof(ShowSearchDirection),
nameof(ShowAddCipher)
});
}
@@ -80,12 +92,23 @@ namespace Bit.App.Pages
public bool ShowSearchDirection => !ShowList && !ShowNoData;
public bool ShowAddCipher => ShowNoData && _appOptions?.OtpData != null;
public bool WebsiteIconsEnabled
{
get => _websiteIconsEnabled;
set => SetProperty(ref _websiteIconsEnabled, value);
}
internal void Prepare(Func<CipherView, bool> filter, bool deleted, AppOptions appOptions)
{
Filter = filter;
AutofillUrl = appOptions?.Uri;
Deleted = deleted;
ShowAllIfSearchTextEmpty = appOptions?.OtpData != null;
_appOptions = appOptions;
}
public async Task InitAsync()
{
await InitVaultFilterAsync(true);
@@ -101,25 +124,33 @@ namespace Bit.App.Pages
{
List<CipherView> ciphers = null;
var searchable = !string.IsNullOrWhiteSpace(searchText) && searchText.Length > 1;
if (searchable)
var shouldShowAllWhenEmpty = ShowAllIfSearchTextEmpty && string.IsNullOrEmpty(searchText);
if (searchable || shouldShowAllWhenEmpty)
{
if (timeout != null)
{
await Task.Delay(timeout.Value);
}
if (searchText != (Page as CiphersPage).SearchBar.Text)
if (searchText != (Page as CiphersPage).SearchBar.Text
&&
!shouldShowAllWhenEmpty)
{
return;
}
else
{
previousCts?.Cancel();
}
previousCts?.Cancel();
try
{
var vaultFilteredCiphers = await GetAllCiphersAsync();
ciphers = await _searchService.SearchCiphersAsync(searchText,
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
if (!shouldShowAllWhenEmpty)
{
ciphers = await _searchService.SearchCiphersAsync(searchText,
Filter ?? (c => c.IsDeleted == Deleted), vaultFilteredCiphers, cts.Token);
}
else
{
ciphers = vaultFilteredCiphers;
}
cts.Token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
@@ -134,8 +165,8 @@ namespace Bit.App.Pages
Device.BeginInvokeOnMainThread(() =>
{
Ciphers.ResetWithRange(ciphers);
ShowNoData = searchable && Ciphers.Count == 0;
ShowList = searchable && !ShowNoData;
ShowNoData = !shouldShowAllWhenEmpty && searchable && Ciphers.Count == 0;
ShowList = (searchable || shouldShowAllWhenEmpty) && !ShowNoData;
});
}, cts.Token);
_searchCancellationTokenSource = cts;
@@ -144,6 +175,7 @@ namespace Bit.App.Pages
public async Task SelectCipherAsync(CipherView cipher)
{
string selection = null;
if (!string.IsNullOrWhiteSpace(AutofillUrl))
{
var options = new List<string> { AppResources.Autofill };
@@ -156,6 +188,19 @@ namespace Bit.App.Pages
selection = await Page.DisplayActionSheet(AppResources.AutofillOrView, AppResources.Cancel, null,
options.ToArray());
}
if (_appOptions?.OtpData != null)
{
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}
var editCipherPage = new CipherAddEditPage(cipher.Id, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(editCipherPage));
return;
}
if (selection == AppResources.View || string.IsNullOrWhiteSpace(AutofillUrl))
{
var page = new CipherDetailsPage(cipher.Id);
@@ -205,7 +250,7 @@ namespace Bit.App.Pages
private void PerformSearchIfPopulated()
{
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text))
if (!string.IsNullOrWhiteSpace((Page as CiphersPage).SearchBar.Text) || ShowAllIfSearchTextEmpty)
{
Search((Page as CiphersPage).SearchBar.Text, 200);
}
@@ -216,12 +261,10 @@ namespace Bit.App.Pages
PerformSearchIfPopulated();
}
private async void CipherOptionsAsync(CipherView cipher)
private async Task AddCipherAsync()
{
if ((Page as BaseContentPage).DoOnce())
{
await Utilities.AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, name: _appOptions?.OtpData?.Issuer ?? _appOptions?.OtpData?.AccountName, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
}
}

View File

@@ -78,7 +78,9 @@ namespace Bit.App.Pages
Refreshing = true;
await LoadAsync();
});
CipherOptionsCommand = new Command<CipherView>(CipherOptionsAsync);
CipherOptionsCommand = new AsyncCommand<CipherView>(cipher => AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService),
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
VaultFilterCommand = new AsyncCommand(VaultFilterOptionsAsync,
onException: ex => _logger.Exception(ex),
allowsMultipleExecutions: false);
@@ -168,7 +170,7 @@ namespace Bit.App.Pages
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public ObservableRangeCollection<IGroupingsPageListItem> GroupedItems { get; set; }
public Command RefreshCommand { get; set; }
public Command<CipherView> CipherOptionsCommand { get; set; }
public ICommand CipherOptionsCommand { get; }
public bool LoadedOnce { get; set; }
public async Task LoadAsync()
@@ -709,13 +711,5 @@ namespace Bit.App.Pages
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
return folders.Any() ? folders : null;
}
private async void CipherOptionsAsync(CipherView cipher)
{
if ((Page as BaseContentPage).DoOnce())
{
await AppHelpers.CipherListOptions(Page, cipher, _passwordRepromptService);
}
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class OTPCipherSelectionPageViewModel : CipherSelectionPageViewModel
{
private readonly ISearchService _searchService = ServiceContainer.Resolve<ISearchService>();
private OtpData _otpData;
private Models.AppOptions _appOptions;
public override bool ShowCallout => !ShowNoData;
public override void Init(Models.AppOptions options)
{
_appOptions = options;
_otpData = options.OtpData.Value;
Name = _otpData.Issuer ?? _otpData.AccountName;
PageTitle = string.Format(AppResources.ItemsForUri, Name ?? "--");
NoDataText = string.Format(AppResources.ThereAreNoItemsInYourVaultThatMatchX, Name ?? "--")
+ Environment.NewLine
+ AppResources.SearchForAnItemOrAddANewItem;
}
protected override async Task<List<GroupingsPageListGroup>> LoadGroupedItemsAsync()
{
var groupedItems = new List<GroupingsPageListGroup>();
var allCiphers = await _cipherService.GetAllDecryptedAsync();
var ciphers = await _searchService.SearchCiphersAsync(_otpData.Issuer ?? _otpData.AccountName,
c => c.Type == CipherType.Login && !c.IsDeleted, allCiphers);
if (ciphers?.Any() ?? false)
{
groupedItems.Add(
new GroupingsPageListGroup(ciphers.Select(c => new GroupingsPageListItem { Cipher = c }).ToList(),
AppResources.MatchingItems,
ciphers.Count,
false,
true));
}
return groupedItems;
}
protected override async Task SelectCipherAsync(IGroupingsPageListItem item)
{
if (!(item is GroupingsPageListItem listItem) || listItem.Cipher is null)
{
return;
}
var cipher = listItem.Cipher;
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
{
return;
}
var editCipherPage = new CipherAddEditPage(cipher.Id, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(editCipherPage));
return;
}
protected override async Task AddCipherAsync()
{
var pageForLogin = new CipherAddEditPage(null, CipherType.Login, name: Name, appOptions: _appOptions);
await Page.Navigation.PushModalAsync(new NavigationPage(pageForLogin));
}
}
}

View File

@@ -382,6 +382,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Add the key to an existing or new item.
/// </summary>
public static string AddTheKeyToAnExistingOrNewItem {
get {
return ResourceManager.GetString("AddTheKeyToAnExistingOrNewItem", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add TOTP.
/// </summary>
@@ -562,6 +571,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to decline all pending login requests?.
/// </summary>
public static string AreYouSureYouWantToDeclineAllPendingLogInRequests {
get {
return ResourceManager.GetString("AreYouSureYouWantToDeclineAllPendingLogInRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to turn on screen capture?.
/// </summary>
@@ -952,18 +970,18 @@ namespace Bit.App.Resources {
/// <summary>
/// Looks up a localized string similar to Biometric unlock disabled pending verification of master password..
/// </summary>
public static string BiometricInvalidated {
public static string AccountBiometricInvalidated {
get {
return ResourceManager.GetString("BiometricInvalidated", resourceCulture);
return ResourceManager.GetString("AccountBiometricInvalidated", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Biometric unlock for autofill disabled pending verification of master password..
/// </summary>
public static string BiometricInvalidatedExtension {
public static string AccountBiometricInvalidatedExtension {
get {
return ResourceManager.GetString("BiometricInvalidatedExtension", resourceCulture);
return ResourceManager.GetString("AccountBiometricInvalidatedExtension", resourceCulture);
}
}
@@ -1669,6 +1687,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Current master password.
/// </summary>
public static string CurrentMasterPassword {
get {
return ResourceManager.GetString("CurrentMasterPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Custom.
/// </summary>
@@ -1723,6 +1750,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Data region.
/// </summary>
public static string DataRegion {
get {
return ResourceManager.GetString("DataRegion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password updated.
/// </summary>
@@ -1759,6 +1795,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Decline all requests.
/// </summary>
public static string DeclineAllRequests {
get {
return ResourceManager.GetString("DeclineAllRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default.
/// </summary>
@@ -1786,6 +1831,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Default (System).
/// </summary>
public static string DefaultSystem {
get {
return ResourceManager.GetString("DefaultSystem", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Default URI match detection.
/// </summary>
@@ -2047,6 +2101,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to DuckDuckGo.
/// </summary>
public static string DuckDuckGo {
get {
return ResourceManager.GetString("DuckDuckGo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit.
/// </summary>
@@ -2272,6 +2335,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to EU.
/// </summary>
public static string EU {
get {
return ResourceManager.GetString("EU", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Exact.
/// </summary>
@@ -2542,6 +2614,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Fastmail.
/// </summary>
public static string Fastmail {
get {
return ResourceManager.GetString("Fastmail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Favorite.
/// </summary>
@@ -3334,6 +3415,33 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Language.
/// </summary>
public static string Language {
get {
return ResourceManager.GetString("Language", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Language change requires app restart.
/// </summary>
public static string LanguageChangeRequiresAppRestart {
get {
return ResourceManager.GetString("LanguageChangeRequiresAppRestart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The language has been changed to {0}. Please restart the app to see the change.
/// </summary>
public static string LanguageChangeXDescription {
get {
return ResourceManager.GetString("LanguageChangeXDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last name.
/// </summary>
@@ -3479,11 +3587,11 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Logging in as {0}.
/// Looks up a localized string similar to Logging in as {0} on {1}.
/// </summary>
public static string LoggingInAsX {
public static string LoggingInAsXOnY {
get {
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
return ResourceManager.GetString("LoggingInAsXOnY", resourceCulture);
}
}
@@ -3634,7 +3742,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Log In with another device.
/// Looks up a localized string similar to Log in with device.
/// </summary>
public static string LogInWithAnotherDevice {
get {
@@ -3751,11 +3859,11 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Master password must be at least 8 characters long..
/// Looks up a localized string similar to Master password must be at least {0} characters long..
/// </summary>
public static string MasterPasswordLengthValMessage {
public static string MasterPasswordLengthValMessageX {
get {
return ResourceManager.GetString("MasterPasswordLengthValMessage", resourceCulture);
return ResourceManager.GetString("MasterPasswordLengthValMessageX", resourceCulture);
}
}
@@ -3983,18 +4091,7 @@ namespace Bit.App.Resources {
return ResourceManager.GetString("Ms", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Mx.
/// </summary>
public static string Mx
{
get
{
return ResourceManager.GetString("Mx", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You must log into the main Bitwarden app before you can use the extension..
/// </summary>
@@ -4013,6 +4110,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Mx.
/// </summary>
public static string Mx {
get {
return ResourceManager.GetString("Mx", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to My vault.
/// </summary>
@@ -4265,6 +4371,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to No pending requests.
/// </summary>
public static string NoPendingRequests {
get {
return ResourceManager.GetString("NoPendingRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Nord.
/// </summary>
@@ -4518,6 +4633,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Organization SSO identifier required..
/// </summary>
public static string OrganizationSsoIdentifierRequired {
get {
return ResourceManager.GetString("OrganizationSsoIdentifierRequired", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Organization identifier.
/// </summary>
@@ -4752,6 +4876,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Pending login requests.
/// </summary>
public static string PendingLogInRequests {
get {
return ResourceManager.GetString("PendingLogInRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to An organization policy is affecting your ownership options..
/// </summary>
@@ -5014,6 +5147,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Region.
/// </summary>
public static string Region {
get {
return ResourceManager.GetString("Region", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remember me.
/// </summary>
@@ -5104,6 +5246,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Requests declined.
/// </summary>
public static string RequestsDeclined {
get {
return ResourceManager.GetString("RequestsDeclined", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Resend code.
/// </summary>
@@ -5230,6 +5381,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Search for an item or add a new item.
/// </summary>
public static string SearchForAnItemOrAddANewItem {
get {
return ResourceManager.GetString("SearchForAnItemOrAddANewItem", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search {0}.
/// </summary>
@@ -5329,6 +5489,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Self-hosted.
/// </summary>
public static string SelfHosted {
get {
return ResourceManager.GetString("SelfHosted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Self-hosted environment.
/// </summary>
@@ -5923,6 +6092,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;.
/// </summary>
public static string ThereAreNoItemsInYourVaultThatMatchX {
get {
return ResourceManager.GetString("ThereAreNoItemsInYourVaultThatMatchX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There are no items that match the search.
/// </summary>
public static string ThereAreNoItemsThatMatchTheSearch {
get {
return ResourceManager.GetString("ThereAreNoItemsThatMatchTheSearch", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 30 days.
/// </summary>
@@ -6310,6 +6497,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour..
/// </summary>
public static string UpdateWeakMasterPasswordWarning {
get {
return ResourceManager.GetString("UpdateWeakMasterPasswordWarning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Updating password.
/// </summary>
@@ -6364,6 +6560,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to US.
/// </summary>
public static string US {
get {
return ResourceManager.GetString("US", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use another two-step login method.
/// </summary>
@@ -6535,6 +6740,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
/// </summary>
public static string VaultTimeoutActionPolicyInEffect {
get {
return ResourceManager.GetString("VaultTimeoutActionPolicyInEffect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logging out will remove all access to your vault and requires online authentication after the timeout period. Are you sure you want to use this setting?.
/// </summary>
@@ -6545,7 +6759,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s).
/// Looks up a localized string similar to Your organization policies have set your maximum allowed vault timeout to {0} hour(s) and {1} minute(s)..
/// </summary>
public static string VaultTimeoutPolicyInEffect {
get {
@@ -6553,6 +6767,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your organization policies are affecting your vault timeout. Maximum allowed vault timeout is {0} hour(s) and {1} minute(s). Your vault timeout action is set to {2}..
/// </summary>
public static string VaultTimeoutPolicyWithActionInEffect {
get {
return ResourceManager.GetString("VaultTimeoutPolicyWithActionInEffect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your vault timeout exceeds the restrictions set by your organization..
/// </summary>

View File

@@ -583,8 +583,8 @@
<data name="MasterPasswordHintDescription" xml:space="preserve">
<value>n Hoofwagwoordwenk kan u help om u wagwoord te onthou indien u dit sou vergeet.</value>
</data>
<data name="MasterPasswordLengthValMessage" xml:space="preserve">
<value>Hoofwagwoord moet ten minste 8 karakters lank wees.</value>
<data name="MasterPasswordLengthValMessageX" xml:space="preserve">
<value>Die hoofwagwoord moet ten minste {0} karakters lank wees.</value>
</data>
<data name="MinNumbers" xml:space="preserve">
<value>Min. aantal syfers</value>
@@ -1103,6 +1103,9 @@ Skandering gebeur outomaties.</value>
<data name="Ms" xml:space="preserve">
<value>Mej.</value>
</data>
<data name="Mx" xml:space="preserve">
<value>Mx</value>
</data>
<data name="November" xml:space="preserve">
<value>November</value>
</data>
@@ -1749,11 +1752,11 @@ Skandering gebeur outomaties.</value>
<value>Wil u dit regtig na die asblik stuur?</value>
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
</data>
<data name="BiometricInvalidated" xml:space="preserve">
<value>Biometriese ontgrendeling gedeaktiveer hangende bevestiging van hoofwagwoord.</value>
<data name="AccountBiometricInvalidated" xml:space="preserve">
<value>Biometric unlock for this account is disabled pending verification of master password.</value>
</data>
<data name="BiometricInvalidatedExtension" xml:space="preserve">
<value>Biometriese ontgrendeling vir outovul gedeaktiveer hangende bevestiging van hoofwagwoord.</value>
<data name="AccountBiometricInvalidatedExtension" xml:space="preserve">
<value>Autofill biometric unlock for this account is disabled pending verification of master password.</value>
</data>
<data name="EnableSyncOnRefresh" xml:space="preserve">
<value>Aktiveer sinchronisering by verfrissing</value>
@@ -2139,10 +2142,16 @@ Skandering gebeur outomaties.</value>
<data name="VaultTimeoutPolicyInEffect" xml:space="preserve">
<value>U organisasiebeleide beïnvloed u kluisuitelling. Maksimum toegelate kluisuittelling is {0} uur en {1} minuut(e).</value>
</data>
<data name="VaultTimeoutPolicyWithActionInEffect" xml:space="preserve">
<value>Jou organisasie se beleid affekteer jou kluis-uittel tyd. Maksimum toelaatbare kluis-uittel tyd is {0} uur(ure) en {1} minuut(minute). Jou kluis-uittel aksie is gestel na {2}.</value>
</data>
<data name="VaultTimeoutActionPolicyInEffect" xml:space="preserve">
<value>Jou organisasie se beleid het jou kluis-uittel aksie na {0} verander.</value>
</data>
<data name="VaultTimeoutToLarge" xml:space="preserve">
<value>U kluisuittelling oorskry die beperkinge wat deur u organisasie daargestel is.</value>
</data>
<data name="DisablePersonalVaultExportPolicyInEffect">
<data name="DisablePersonalVaultExportPolicyInEffect" xml:space="preserve">
<value>Een of meer organisasiebeleide verhoed u om u persoonlike kluis uit te stuur.</value>
</data>
<data name="AddAccount" xml:space="preserve">
@@ -2419,6 +2428,14 @@ kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
<value>SimpleLogin</value>
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
</data>
<data name="DuckDuckGo" xml:space="preserve">
<value>DuckDuckGo</value>
<comment>"DuckDuckGo" is the product name and should not be translated.</comment>
</data>
<data name="Fastmail" xml:space="preserve">
<value>Fastmail</value>
<comment>"Fastmail" is the product name and should not be translated.</comment>
</data>
<data name="APIAccessToken" xml:space="preserve">
<value>API-toegangsteken</value>
</data>
@@ -2478,8 +2495,8 @@ Wil u na die rekening omskakel?</value>
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>Kry hoofwagwoord wenk</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Teken in as {0}</value>
<data name="LoggingInAsXOnY" xml:space="preserve">
<value>Logging in as {0} on {1}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>Nie jy nie?</value>
@@ -2511,7 +2528,106 @@ Wil u na die rekening omskakel?</value>
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>Hierdie versoek is nie langer gelding nie</value>
</data>
<data name="PendingLogInRequests" xml:space="preserve">
<value>In afwagting vir aantekeningsversoeke</value>
</data>
<data name="DeclineAllRequests" xml:space="preserve">
<value>Weier alle versoeke</value>
</data>
<data name="AreYouSureYouWantToDeclineAllPendingLogInRequests" xml:space="preserve">
<value>Is jy seker dat jy alle hangende aantekeningsversoeke wil weier?</value>
</data>
<data name="RequestsDeclined" xml:space="preserve">
<value>Versoeke geweier</value>
</data>
<data name="NoPendingRequests" xml:space="preserve">
<value>Geen hangende versoeke</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>Laat die kamera versoek toe om die skandeerder te gebruik</value>
</data>
<data name="Language" xml:space="preserve">
<value>Taal</value>
</data>
<data name="LanguageChangeXDescription" xml:space="preserve">
<value>Die taal is na {0} verander. Asseblief herbegin die toep om die veranderinge te sien</value>
</data>
<data name="LanguageChangeRequiresAppRestart" xml:space="preserve">
<value>Die taal verandering vereis 'n herbegin van die toep</value>
</data>
<data name="DefaultSystem" xml:space="preserve">
<value>Verstek (Sisteem)</value>
</data>
<data name="Important" xml:space="preserve">
<value>Belangrik</value>
</data>
<data name="YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum" xml:space="preserve">
<value>Jou hoofwagwoord kan nie geherstel word as jy dit vergeet het nie! {0} karakters minimum.</value>
</data>
<data name="WeakMasterPassword" xml:space="preserve">
<value>Swak Hoofwagwoord</value>
</data>
<data name="WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount" xml:space="preserve">
<value>Swak wagwoord geïdentifiseer. Gebruik 'n sterk wagwoord om jou rekening te beskerm. Is jy seker dat jy 'n swak wagwoord wil gebruik?</value>
</data>
<data name="Weak" xml:space="preserve">
<value>Swak</value>
</data>
<data name="Good" xml:space="preserve">
<value>Goed</value>
</data>
<data name="Strong" xml:space="preserve">
<value>Sterk</value>
</data>
<data name="CheckKnownDataBreachesForThisPassword" xml:space="preserve">
<value>Ondersoek bekende data lekkasies vir dié wagwoord</value>
</data>
<data name="ExposedMasterPassword" xml:space="preserve">
<value>Blootgestelde Hoofwagwoord</value>
</data>
<data name="PasswordFoundInADataBreachAlertDescription" xml:space="preserve">
<value>Dié wagwoord was in data lekkasie gevind. Gebruik 'n unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy 'n blootgestelde wagwoord wil gebruik?</value>
</data>
<data name="WeakAndExposedMasterPassword" xml:space="preserve">
<value>Swak en Blootgestelde Hoofwagwoord</value>
</data>
<data name="WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription" xml:space="preserve">
<value>Swak wagwoord geïdentifiseer en in 'n data lekkasie gevind. Gebruik 'n sterk en unieke wagwoord om jou rekening te beskerm. Is jy seker dat jy hierdie wagwoord wil gebruik?</value>
</data>
<data name="OrganizationSsoIdentifierRequired" xml:space="preserve">
<value>Organisasie SSO identifiseerder vereis.</value>
</data>
<data name="AddTheKeyToAnExistingOrNewItem" xml:space="preserve">
<value>Voeg die sleutel by 'n huidige of nuwe item</value>
</data>
<data name="ThereAreNoItemsInYourVaultThatMatchX" xml:space="preserve">
<value>Daar is geen items in jou kluis wat met "{0}" ooreenstem nie</value>
</data>
<data name="SearchForAnItemOrAddANewItem" xml:space="preserve">
<value>Soek vir 'n item of voeg 'n nuwe een by</value>
</data>
<data name="ThereAreNoItemsThatMatchTheSearch" xml:space="preserve">
<value>Daar is geen items wat met die soekterm ooreenstem nie</value>
</data>
<data name="US" xml:space="preserve">
<value>US</value>
</data>
<data name="EU" xml:space="preserve">
<value>EU</value>
</data>
<data name="SelfHosted" xml:space="preserve">
<value>Self-hosted</value>
</data>
<data name="DataRegion" xml:space="preserve">
<value>Data region</value>
</data>
<data name="Region" xml:space="preserve">
<value>Region</value>
</data>
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
<value>Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now. Proceeding will log you out of your current session, requiring you to log back in. Active sessions on other devices may continue to remain active for up to one hour.</value>
</data>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value>
</data>
</root>

View File

@@ -583,8 +583,8 @@
<data name="MasterPasswordHintDescription" xml:space="preserve">
<value>يمكن أن تساعدك فكرة كلمة المرور الرئيسية على تذكر كلمة المرور الخاصة بك إذا نسيت.</value>
</data>
<data name="MasterPasswordLengthValMessage" xml:space="preserve">
<value>يجب أن تكون كلمة المرور الرئيسية أكثر من 8 أحرف.</value>
<data name="MasterPasswordLengthValMessageX" xml:space="preserve">
<value>كلمة المرور الرئيسية يجب أن تكون على الأقل {0} حرفاً.</value>
</data>
<data name="MinNumbers" xml:space="preserve">
<value>الحد الأدنى للأرقام</value>
@@ -1103,6 +1103,9 @@
<data name="Ms" xml:space="preserve">
<value>آنسة</value>
</data>
<data name="Mx" xml:space="preserve">
<value>Mx</value>
</data>
<data name="November" xml:space="preserve">
<value>نوفمبر</value>
</data>
@@ -1749,11 +1752,11 @@
<value>هل تريد حقاً أن ترسل إلى سلة المهملات؟</value>
<comment>Confirmation alert message when soft-deleting a cipher.</comment>
</data>
<data name="BiometricInvalidated" xml:space="preserve">
<value>تم تعطيل فتح القفل الحيوي في انتظار التحقق من كلمة المرور الرئيسية.</value>
<data name="AccountBiometricInvalidated" xml:space="preserve">
<value>Biometric unlock for this account is disabled pending verification of master password.</value>
</data>
<data name="BiometricInvalidatedExtension" xml:space="preserve">
<value>تم تعطيل إلغاء القفل البيومتري للملء التلقائي في انتظار التحقق من كلمة المرور الرئيسية.</value>
<data name="AccountBiometricInvalidatedExtension" xml:space="preserve">
<value>Autofill biometric unlock for this account is disabled pending verification of master password.</value>
</data>
<data name="EnableSyncOnRefresh" xml:space="preserve">
<value>تمكين المزامنة عند التحديث</value>
@@ -2140,10 +2143,16 @@
<data name="VaultTimeoutPolicyInEffect" xml:space="preserve">
<value>سياسات مؤسستك تؤثر على مهلة الخزنة الخاص بك. الحد الأقصى المسموح به لمهلة الخزنة هو {0} ساعة و {1} دقيقة</value>
</data>
<data name="VaultTimeoutPolicyWithActionInEffect" xml:space="preserve">
<value>سياسات مؤسستك تؤثر على مهلة خزنتك. الحد الأقصى المسموح به لمهلة الخزنة هو {0} ساعة(ساعات) و {1} دقيقة(دقائق). يتم تعيين إجراء مهلة المخزن الخاص بك إلى {2}.</value>
</data>
<data name="VaultTimeoutActionPolicyInEffect" xml:space="preserve">
<value>سياسات مؤسستك قامت بتعيين إجراء مهلة خزنتك إلى {0}.</value>
</data>
<data name="VaultTimeoutToLarge" xml:space="preserve">
<value>مهلة خزنتك تتجاوز القيود التي تضعها مؤسستك.</value>
</data>
<data name="DisablePersonalVaultExportPolicyInEffect">
<data name="DisablePersonalVaultExportPolicyInEffect" xml:space="preserve">
<value>واحدة أو أكثر من سياسات المؤسسة تمنعك من تصدير خزانتك الشخصية.</value>
</data>
<data name="AddAccount" xml:space="preserve">
@@ -2420,6 +2429,14 @@
<value>SimpleLogin</value>
<comment>"SimpleLogin" is the product name and should not be translated.</comment>
</data>
<data name="DuckDuckGo" xml:space="preserve">
<value>DuckDuckGo</value>
<comment>"DuckDuckGo" is the product name and should not be translated.</comment>
</data>
<data name="Fastmail" xml:space="preserve">
<value>Fastmail</value>
<comment>"Fastmail" is the product name and should not be translated.</comment>
</data>
<data name="APIAccessToken" xml:space="preserve">
<value>رمز الوصول API</value>
</data>
@@ -2479,8 +2496,8 @@
<data name="GetMasterPasswordwordHint" xml:space="preserve">
<value>احصل على تلميح كلمة المرور الرئيسية</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>تسجيل الدخول كـ {0}</value>
<data name="LoggingInAsXOnY" xml:space="preserve">
<value>Logging in as {0} on {1}</value>
</data>
<data name="NotYou" xml:space="preserve">
<value>ليس أنت؟</value>
@@ -2512,7 +2529,106 @@
<data name="ThisRequestIsNoLongerValid" xml:space="preserve">
<value>هذا الطلب لم يعد صالحًا</value>
</data>
<data name="PendingLogInRequests" xml:space="preserve">
<value>طلبات تسجيل الدخول المعلقة</value>
</data>
<data name="DeclineAllRequests" xml:space="preserve">
<value>رفض جميع الطلبات</value>
</data>
<data name="AreYouSureYouWantToDeclineAllPendingLogInRequests" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد رفض جميع طلبات تسجيل الدخول المعلقة؟</value>
</data>
<data name="RequestsDeclined" xml:space="preserve">
<value>تم رفض الطلبات</value>
</data>
<data name="NoPendingRequests" xml:space="preserve">
<value>لا توجد طلبات معلقة</value>
</data>
<data name="EnableCamerPermissionToUseTheScanner" xml:space="preserve">
<value>تمكين إذن الكاميرا لاستخدام الماسح الضوئي</value>
</data>
<data name="Language" xml:space="preserve">
<value>اللّغة</value>
</data>
<data name="LanguageChangeXDescription" xml:space="preserve">
<value>تم تغيير اللغة إلى {0}. الرجاء إعادة تشغيل التطبيق لرؤية التغيير</value>
</data>
<data name="LanguageChangeRequiresAppRestart" xml:space="preserve">
<value>تغيير اللّغة يتطلب إعادة تشغيل التطبيق</value>
</data>
<data name="DefaultSystem" xml:space="preserve">
<value>الافتراضي (النظام)</value>
</data>
<data name="Important" xml:space="preserve">
<value>مهم</value>
</data>
<data name="YourMasterPasswordCannotBeRecoveredIfYouForgetItXCharactersMinimum" xml:space="preserve">
<value>لا يمكن استعادة كلمة المرور الرئيسية الخاصة بك إذا نسيت ذلك! {0} أحرف الحد الأدنى.</value>
</data>
<data name="WeakMasterPassword" xml:space="preserve">
<value>كلمة المرور الرئيسية الضعيفة</value>
</data>
<data name="WeakPasswordIdentifiedUseAStrongPasswordToProtectYourAccount" xml:space="preserve">
<value>تم تحديد كلمة المرور ضعيفة. استخدم كلمة مرور قوية لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة مرور ضعيفة؟</value>
</data>
<data name="Weak" xml:space="preserve">
<value>ضعيفة</value>
</data>
<data name="Good" xml:space="preserve">
<value>جيدة</value>
</data>
<data name="Strong" xml:space="preserve">
<value>قوية</value>
</data>
<data name="CheckKnownDataBreachesForThisPassword" xml:space="preserve">
<value>تحقق من خروقات البيانات المعروفة لكلمة المرور هذه</value>
</data>
<data name="ExposedMasterPassword" xml:space="preserve">
<value>كلمة المرور الرئيسية مكشوفة</value>
</data>
<data name="PasswordFoundInADataBreachAlertDescription" xml:space="preserve">
<value>كلمة المرور موجودة في خرق البيانات. استخدم كلمة مرور فريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة مرور مكشوفة؟</value>
</data>
<data name="WeakAndExposedMasterPassword" xml:space="preserve">
<value>كلمة المرور الرئيسية ضعيفة ومكشوفة</value>
</data>
<data name="WeakPasswordIdentifiedAndFoundInADataBreachAlertDescription" xml:space="preserve">
<value>كلمة مرور ضعيفة محددة وموجودة في خرق البيانات. استخدم كلمة مرور قوية وفريدة لحماية حسابك. هل أنت متأكد من أنك تريد استخدام كلمة المرور هذه؟</value>
</data>
<data name="OrganizationSsoIdentifierRequired" xml:space="preserve">
<value>معرف الـSSO للمنظمة مطلوب.</value>
</data>
<data name="AddTheKeyToAnExistingOrNewItem" xml:space="preserve">
<value>إضافة المفتاح إلى عنصر موجود أو جديد</value>
</data>
<data name="ThereAreNoItemsInYourVaultThatMatchX" xml:space="preserve">
<value>لا توجد عناصر في خزانتك تتطابق مع "{0}"</value>
</data>
<data name="SearchForAnItemOrAddANewItem" xml:space="preserve">
<value>البحث عن عنصر أو إضافة عنصر جديد</value>
</data>
<data name="ThereAreNoItemsThatMatchTheSearch" xml:space="preserve">
<value>لا توجد عناصر تطابق البحث</value>
</data>
<data name="US" xml:space="preserve">
<value>US</value>
</data>
<data name="EU" xml:space="preserve">
<value>EU</value>
</data>
<data name="SelfHosted" xml:space="preserve">
<value>Self-hosted</value>
</data>
<data name="DataRegion" xml:space="preserve">
<value>Data region</value>
</data>
<data name="Region" xml:space="preserve">
<value>Region</value>
</data>
<data name="UpdateWeakMasterPasswordWarning" xml:space="preserve">
<value>كلمة المرور الرئيسية الخاصة بك لا تفي بواحدة أو أكثر من سياسات مؤسستك. من أجل الوصول إلى الخزنة، يجب عليك تحديث كلمة المرور الرئيسية الآن. سيتم تسجيل خروجك من الجلسة الحالية، مما يتطلب منك تسجيل الدخول مرة أخرى. وقد تظل الجلسات النشطة على أجهزة أخرى نشطة لمدة تصل إلى ساعة واحدة.</value>
</data>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>كلمة المرور الرئيسية الحالية</value>
</data>
</root>

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