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

Compare commits

..

70 Commits

Author SHA1 Message Date
ifernandezdiaz
b0e7912b76 Merge branch 'master' into PM-4047/fixing-simulator-app-build 2023-09-25 18:00:19 -03:00
ifernandezdiaz
83bd49a298 Fixing zip action for app file 2023-09-25 17:59:30 -03:00
André Bispo
b25c8b0842 [PM-3893] Make PreLogin and Register endpoint use identity endpoints (#2772) 2023-09-25 16:28:58 +01:00
Federico Maccaroni
a4a0d31fc6 [PM-3811] Passkeys unification (#2774)
* PM-3811 Unified passkeys view and moved both inside Login as an array of FIdo2Key

* PM-3811 Passkeys unification => updated cipher details view an helpers

* PM-3811 Updated passkeys creation date time format
2023-09-22 14:55:35 +00:00
ifernandezdiaz
d4117ed7ec Fixing issue on zipped .app file 2023-09-22 11:41:30 -03:00
ifernandezdiaz
6ef6cf5d84 Adding missing IDs (#2786) 2023-09-22 11:24:30 -03:00
github-actions[bot]
597f629920 Autosync the updated translations (#2785)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-22 08:04:57 +00:00
github-actions[bot]
b8cef16711 Bumped version to 2023.9.1 (#2784)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-20 18:36:32 -04:00
Jake Fink
c4f6ae9077 [PM-3726] prevent legacy user login (#2769)
* [PM-3726] prevent legacy user login

* [PM-3726] prevent unlock or auto key migration if legacy user

* [PM-3726] add legacy checks to lock page and refactor

* [PM-3726] rethrow exception from pin

* formatting

* [PM-3726] add changes to LockViewController, consolidate logout calls

* formatting

* [PM-3726] pr feedback

* generate resx

* formatting
2023-09-20 15:56:51 -04:00
github-actions[bot]
8b9658d2c5 Bumped version to 2023.9.0 (#2783)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-20 12:34:30 -04:00
André Bispo
43bf0fbdb3 [PM-3086] Account switcher endpoint use domain string for Bitwarden production environments (#2773) 2023-09-19 10:35:37 +01:00
André Bispo
11922c6f49 [PM-3522] Keep variable value after logout. (#2761) 2023-09-19 10:33:01 +01:00
André Bispo
a6f05338c2 [PM-3393] Excessive Invalid Biometric unlock attempts should automatically log out TDE users (#2747)
* [PM-3393] Log user out on biometric exceed attempts

* [PM-3393] Move duplicated code to AppHelpers

* [PM-3393] Update copy on new pop up

* [PM-3393] Moved VaultTimeoutService to LazyResolve.

* [PM-3382] Change IVaultTimeoutService for messaging

* [PM-3393] Use default values.
2023-09-19 10:32:23 +01:00
Federico Maccaroni
b932824b5a Make dept-development-mobile default code owner (#2780) 2023-09-18 18:23:16 -03:00
github-actions[bot]
efd1671f48 Autosync the updated translations (#2771)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-14 14:14:00 +00:00
André Bispo
3e2005e5ed [PM-3606] TDE user with 2FA isn't able to autofill on iOS (#2723)
* [PM-3606] Fix 2FA for autofill

* [PM-3606] Fix autofill when user doesn't have a login method available.

* [PM-3606] PR fixes

* [PM-3606] Add logout logic to other extension projects

* [PM-3606] Move code to base class.

* Transform into property instead of field

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* Remove double ";"

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* [PM-3606] Fix iOS extension by changing base class of LockPasswordViewController

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2023-09-09 17:38:14 -04:00
Will Browning
382eee2ed3 [PM-3556] Change anonaddy to addy io (#2711)
* Update AppResources.af.resx

* Update AnonAddy references

* Reverted AnonAddy to AddyIo refactor, keeping text and url changes

---------

Co-authored-by: Andre Rosado <arosado@bitwarden.com>
2023-09-08 15:23:51 +01:00
github-actions[bot]
b0f1dd00ee Autosync the updated translations (#2751)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-08 09:33:37 +00:00
André Bispo
5961a001ab [PM-3551] Expired SSO token cached (#2718) 2023-09-08 07:48:37 +01:00
André Bispo
9026dd10e5 [PM-3593] Fix enable biometric on autofill when there is not MP (#2717) 2023-09-07 16:30:46 +01:00
ifernandezdiaz
355261679d Adding missing IDs for Set Password and Update Password pages (#2748) 2023-09-07 11:24:48 -03:00
ifernandezdiaz
7f14ec9b5d QA-508 - Build app for automation CI (#2705)
* Adding build steps for .app

* Uploading .app artifact

* Fixing ARCHIVE_PATH variable

* Fixing missing OutputPath

* Fixing Bitwarden .app file name

* Fixing wrong .app location

* Adding Fede's suggestion

* Update .github/workflows/build.yml

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

---------

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
2023-09-06 14:25:59 -03:00
Opeyemi
0c72626916 UPDATE: all workflows (#2743) 2023-09-06 15:30:47 +01:00
André Bispo
f21fae7fea [PM-3382] User cannot select Email as a secondary 2FA option following SSO (#2719)
* [PM-3382] Update mobile client to receive and use SsoEmail2faSessionToken

* [PM-3382] Fix null 2fa email with local email on MP login.
2023-09-06 10:26:11 +01:00
github-actions[bot]
6d4792bc24 Autosync the updated translations (#2741)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-09-01 12:46:27 +00:00
mpbw2
dbadf8c56f [PM-3222] Migration of data from LiteDB to shared pref storage (#2724)
* Migration of data from LiteDB to shared pref storage

* tweaks
2023-08-30 10:55:20 -04:00
André Bispo
4d0f9d1c03 [PM-3543] [PM-3607] Fix password re-prompt when editing and on autofill. (#2713)
* [PM-3543] [PM-3507] Fix password re-prompt when editing and on autofill.
2023-08-30 09:38:46 +01:00
André Bispo
68759fc608 [PM-3547] Change logic to set user key for inactive account (#2715) 2023-08-29 10:28:51 +01:00
github-actions[bot]
47be3d6aef Bumped version to 2023.8.1 (#2730)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-08-28 16:34:38 -04:00
github-actions[bot]
7ec5c8ccfd Bumped version to 2023.8.0 (#2725)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-08-25 19:52:35 -04:00
Jake Fink
819aabb330 don't clear key needed for bio/auto migration in pin migration (#2721) 2023-08-25 09:47:37 -04:00
github-actions[bot]
9c7ff853d7 Autosync the updated translations (#2720)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-08-25 09:03:57 +00:00
mpbw2
e30f9903d1 fix for TDE pref naming collision (#2712)
* fix for TDE pref naming collision

* fix case
2023-08-22 15:51:11 -04:00
André Bispo
249406e3a8 [PM-3545] Fix biometric unlock for autofill (#2710)
* [PM-3545] Fix biometric unlock for autofill

* [PM-3545] Reuse existing method
2023-08-21 20:30:22 +01:00
github-actions[bot]
8cae840c68 Autosync the updated translations (#2704)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-08-19 14:00:16 +00:00
André Bispo
e274c04107 [PM-3513] Show error SSO policy (#2707)
* [PM-3513] Show API error when SSO policy is enforced.
2023-08-18 23:05:52 +01:00
Robyn MacCallum
7043be67dd Update CODEOWNERS (#2701)
* Update CODEOWNERS

* Update CODEOWNERS
2023-08-18 11:20:26 -04:00
Federico Maccaroni
afb8c515d6 [PM-3071] Remove share on save toggle on Send view (#2659)
* PM-3071 Removed share on save toggle on Send view and now it's done automatically, same for copy after saving from the Share extension

* PM-3071 Fix alignments on Share extension send view
2023-08-17 16:42:08 -03:00
Todd Martin
bfcfd367dd Trusted Device Encryption feature (#2656)
* [PM-1208] Add Device approval options screen. View model waiting for additional logic to be added.

* [PM-1208] Add device related api endpoint. Add AccoundDecryptOptions model and property to user Account.

* [PM-1208] Add continue button and not you option

* [PM-1379] add DeviceTrustCryptoService with establish trust logic (#2535)

* [PM-1379] add DeviceCryptoService with establish trust logic

* PM-1379 update api location and other minor refactors

* pm-1379 fix encoding

* update trusted device keys api call to Put

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

* [PM-1379] rearrange methods in DeviceTrustCryptoService

* [PM-1379] rearrange methods in abstraction

* [PM-1379] deconstruct tuples

* [PM-1379] remove extra tasks

* [PM-2583] Answer auth request with mp field as null if doesn't have it. (#2609)

* [PM-2287][PM-2289][PM-2293] Approval Options (#2608)

* [PM-2293] Add AuthRequestType to PasswordlessLoginPage.

* [PM-2293] Add Actions to ApproveWithDevicePage

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

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

* [PM-2293] Change boolean variable expression.

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

* code format

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

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

* [PM-2287][PM-2289][PM-2293] Rename method

* [PM-1201] Change timeout actions available based on hasMasterPassword (#2610)

* [PM-1201] Change timeout actions available based on hasMasterPassword

* [PM-2731] add user key and master key types

* [PM-2713] add new state for new keys and obsolete old ones
- UserKey
- MasterKey
- UserKeyMasterKey (enc UserKey from User Table)

* [PM-271] add UserKey and MasterKey support to crypto service

* [PM-2713] rename key hash to password hash & begin add methods to crypto service

* [PM-2713] continue organizing crypto service

* [PM-2713] more updates to crypto service

* [PM-2713] add new pin methods to state service

* [PM-2713] fix signature of GetUserKeyPin

* [PM-2713] add make user key method to crypto service

* [PM-2713] refresh pin key when setting user key

* [PM-2713] use new MakeMasterKey method

* [PM-2713] add toggle method to crypto service for keys

* [PM-2713] converting calls to new crypto service api

* [PM-2713] add migration for pin on lock screens

* [PM-2713] more conversions to new crypto service api

* [PM-2713] convert cipher service and others to crypto service api

* [PM-2713] More conversions to crypto api

* [PM-2713] use new crypto service api in auth service

* [PM-2713] remove unused cached values in crypto service

* [PM-2713] set decrypt and set user key in login helper

* fix bad merge

* Update crypto service api call to fix build

* [PM-1208] Fix app resource file

* [PM-1208] Fix merge

* [PM-1208] Fix merge

* [PM-2713] optimize async code in crypto service

* [PM-2713] rename password hash to master key hash

* [PM-2713] fix casting issues and pin

* [PM-2713] remove extra comment

* [PM-2713] remove broken casting

* [PM-2297] Login with trusted device (Flow 2) (#2623)

* [PM-2297] Add DecryptUserKeyWithDeviceKey method

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

* [PM-2297] Update account decryption options model

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

* [PM-2297] Update navigation to decryption options

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

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

* [PM-2297] Fix model bug

* [PM-2297] Fix state var crash

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

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

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

* [PM-2297] Remove unnecessary user id params

* [PM-2289] [PM-2293] TDE Login with device Admin Request (#2642)

* [PM-2713] deconstruct new key pair

* [PM-2713] rename PrivateKey methods to UserPrivateKey on crypto service

* [PM-2713] rename PinLockEnum to PinLockType

* [PM-2713] don't pass user key as param when encrypting

* [PM-2713] rename toggle method, don't reset enc user key

* [PM-2713] pr feedback

* [PM-2713] PR feedback

* [PM-2713] rename get pin lock type method

* [PM-2713] revert feedback for build

* [PM-2713] rename state methods

* [PM-2713] combine makeDataEncKey methods

* [PM-2713] consolidate attachment key creation
- also fix ios files missed during symbol rename

* [PM-2713] replace generic with inherited class

* rename account keys to be more descriptive

* [PM-2713] add auto unlock key to mobile

* [PM-1208] Add TDE flows for new users (#2655)

* [PM-1208] Create new user on SSO. Logout if not password is setup or has pending admin auth request.

* [PM-1208] Fix new user UserKey decryption.

* [PM-1208] Add new user continue to vault logic. Auto enrol user on continue.

* [PM-1208] Trust device only if needed

* [PM-1208] Add logic for New User SSO.

* [PM-1208] Add logic for New User SSO (missing file).

* [PM-2713] set user key on set password page

* [PM-2713] set enc user key during kc onboarding

* fix formatting

* [PM-2713] make method async again
- returning null from a task thats not async throws

* [PM-2713] clear service cache when adding new account

* Fix build after merge

* [PM-3313] Fix Android SSO Login (#2663)

* [PM-3313] Catch exception on AuthPendingRequest

* [PM-3313] Fix lock timeout action if user doesn't have a master password.

* code format

* [PM-3313] Null email in Approval Options screen (#2664)

* [PM-3313] Fix null email in approval options screen

* [PM-3320][PM-3321] Fix labels and UI tweaks (#2666)

* [PM-3320] Fix UI copy and remember me default ON.

* [PM-3321] Fix UI on Log in with device screen.

* [PM-3337] Fix admin request deny error (#2669)

* [PM-3342] Not you button logs user out. (#2672)

* [PM-3319] Check for admin request in Lock page (#2668)

* [PM-3319] Ignore admin auth request when choosing mp as decryption option.

* [PM-2289] Change header title based on auth request type (#2670)

* [PM-2289] Change header title based on auth request type

* [PM-3333] Check for purged admin auth requests (#2671)

* [PM-3333] Check for purged admin auth requests

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* [PM-3341] Vault Timeout Action not persisted correctly (#2673)

* [PM-3341] Fix timeout action change when navigating

* [PM-3357] Fix copy for Login Initiated (#2674)

* [PM-3362] Fix auth request approval (#2675)

* [PM-3362] Fix auth request approval

* [PM-3362] Add new exception type

* [PM-3102] Update Master password reprompt to be based on MP instead of Key Connector (#2653)

* PM-3102 Added check to see if a user has master password set replacing previous usage of key connector.

* PM-3102 Fix formatting

* [PM-2713] Final merge from Key Migration branch to TDE Feature branch (#2667)

* [PM-2713] add async to key connector service methods

* [PM-2713] rename ephemeral pin key

* add state for biometric key and accept UserKey instead of string for auto key

* Get UserKey from bio state on unlock

* PM-2713 Fix auto-migrating EncKeyEncrypted into MasterKey encrypted UserKey when requesting DecryptUserKeyWithMasterKeyAsync is called

* renaming bio key and fix build

* PM-3194 Fix biometrics button to be shown on upgrade when no UserKey is present yet

* revert removal of key connector service from auth service

* PM-2713 set user key when using KC

* clear enc user key after migration

* use is true for nullable bool

* PR feedback, refactor kc service

---------

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* Fix app fresh install user login with master password. (#2676)

* [PM-3303] Fix biometric login after key migration (#2679)

* [PM-3303] Add condition to biometric unlock

* [PM-3381] Fix TDE login 2FA flow (#2678)

* [PM-3381] Check for vault lock on 2FA screen

* [PM-3381] Move logic to ViewModel

* [PM-3381] Fix null vm error

* [PM-3379] Fix key rotation on trusted device. (#2680)

* [PM-3381] Update login flows (#2683)

* [PM-3381] Update login flows

* [PM-3381] Remove _authingWithSso parameter

* PM-3385 Fix MP reprompt item level when no MP hash is stored like logging in with TDE. Also refactor code to be more maintainable (#2687)

* PM-3386 Fix MP reprompt / OTP decision to be also based on the master key hash. (#2688)

* PM-3450 Fix has master password with mp key hash check (#2689)

* [PM-3394] Fix login with device for passwordless approvals (#2686)

* set activeUserId to null when logging in a new account
- Also stop the user key from being set in inactive accounts

* get token for login with device if approving device doesn't have master key

* add comment

* simplify logic

* check for route instead of using isAuthenticated
- we don't clear the user id when logging in new account
- this means we can't trust the state service, so we have to base our logic off the route in login with device

* use authenticated auth request for tde login with device

* [PM-3394] Add authingWithSso parameter to LoginPasswordlessRequestPage.

* pr feedback

* [PM-3394] Refactor condition

Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>

* [PM-3462] Handle force password reset on mobile with TDE (#2694)

* [PM-3462] Handle force password reset on mobile with TDE

* [PM-3462] update references to refactored crypto method
- fix kc bug, we were sending private key instead of user key to server
- rename kc service method to be correct

* [PM-3462] Update TwoFactorPage login logic

* [PM-3462] Added pending admin request check to TwoFactorPage

* [PM-3462] Added new exception types for null keys

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>

* [PM-1029] Fix Async suffix in ApiService. Add UserKeyNullExceptions.

* [PM 3513] Fix passwordless 2fa login with device on mobile (#2700)

* [PM-3513] Fix 2FA for normal login with device with users without mp

* move _userKey

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>

* clear encrypted pin on logout (#2699)

---------

Co-authored-by: André Bispo <abispo@bitwarden.com>
Co-authored-by: Jake Fink <jfink@bitwarden.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
2023-08-17 15:19:35 -04:00
Federico Maccaroni
a23454bc53 [PM-3508] Fix Release iPhoneSimulator configuration for iOS / Extensions (#2698)
* PM-3508 Fix Release iPhoneSimulator configuration for iOS / Extensions

* PM-3508 Fix --deep space on watch app references
2023-08-16 15:55:34 -03:00
mpbw2
6f7100ae4f lib updates (#2696) 2023-08-16 12:48:54 -04:00
ifernandezdiaz
01ac20e6e4 Adding missing AutomationIDs on LoginPasswordlessRequestListPage (#2693) 2023-08-16 11:09:07 -03:00
github-actions[bot]
8474f536ff Autosync the updated translations (#2677)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
2023-08-15 13:48:01 +00:00
Daniel James Smith
f426c0e370 Create section for crowdin sync (#2692) 2023-08-15 15:47:08 +02:00
Daniel James Smith
420dc09fd1 Update codeowners (#2691)
* Set team-leads-eng as owners for translations

This is needed to Crowdin sync PRs can be merged.

* Add team-tools as owner of the email-forwarders

* Fix unescaped whitespace

* Remove team-leads-eng from owning English resources
2023-08-15 15:32:57 +02:00
Federico Maccaroni
6d4793d592 [PM-1768] Set up CODEOWNERS file (#2464)
* PM-1768 Add CODEOWNERS file with some initial setup of folders that don't need to be moved and can have their owners assigned already.

* Update CODEOWNERS

Removed entire projects owners cause I didn't consider auth inside of the extensions.
2023-08-10 13:58:03 -03:00
Bernd Schoolmann
eea7c6b7d7 [PM-2901] Synchronize sends on send creation/update/deletion notification (#2606)
* Add sync on send create/update/delete notification

* Update send notifications to only sync sends

* Fix incorrect notification type in PushNotificationListenerService

Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>

* Invert if to improve readability

* Simplify shouldUpdate logic in SyncUpsertSendAsync

* Further simplify SyncService code

* Fix if condition in SyncService

Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>

* Fixed whitespace formatting

---------

Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com>
Co-authored-by: Andre Rosado <arosado@bitwarden.com>
2023-08-08 14:59:42 +01:00
Bernd Schoolmann
ec93a61275 [PM-3092] Clarify argon2 ios autofill warning (#2630)
* Clarify argon2 ios warning

* Update Argon2 insufficient memory warning message
2023-08-07 10:41:40 -04:00
Federico Maccaroni
c34d1da6e6 PM-3298 Updated region selector to be logging in on and also its options (#2657) 2023-08-04 11:44:39 -03:00
github-actions[bot]
c4e64e082b Autosync the updated translations (#2660)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-08-04 10:55:27 +00:00
Federico Maccaroni
5aaff1ea20 PM-3249 Removed back button from block autofill uris to be aligned to other views (#2654) 2023-08-02 10:54:01 -03:00
github-actions[bot]
0271a4db4c Autosync the updated translations (#2650)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-28 08:19:12 +00:00
Federico Maccaroni
375718f945 PM-3227 Avoid clone on discoverable passkeys. (#2648) 2023-07-27 18:35:09 -03:00
Federico Maccaroni
9eda015371 PM-3165 Finish task completion source when cancelling the dialog tapping on the background. (#2647) 2023-07-27 17:49:52 -03:00
Federico Maccaroni
ea81acb3bf [PM-1575] Display Passkeys (#2523)
* PM-1575 Added new models for Fido2Key

* PM-1575 Added discoverable passkeys and WIP non-discoverable ones

* PM-1575 Fix format

* PM-1575 Added non-discoverable passkeys to login UI

* PM-1575 Added copy application icon to Fido2Key UI

* PM-1575 Updated bwi font with the updated passkey icon

* PM-1575 For now just display Available for two-step login on non-discoverable passkey inside of a cipher login

* PM-1575 Fix non-discoverable passkey visibility

* PM-1575 remove Passkeys as a filter in the vault list

* PM-1575 Display error toast if there is a duplicate passkey when moving a cipher to an org

* Revert "PM-1575 Display error toast if there is a duplicate passkey when moving a cipher to an org"

This reverts commit 78e6353602.

* [PM-2378] Display error toast on duplicate Passkey when moving cipher to an organization (#2594)

* PM-2378 Display error toast if there is a duplicate passkey when moving a cipher to an org

* PM-3097 Fix issue when moving cipher with passkey to an org where the uniqueness should be taken into consideration on different passkeys types and also the Username (#2632)

* PM-3096 Fix non-discoverable passkey to be taken into account when encrypting a cipher which was causing the passkey to be removed when moving to an org (#2637)
2023-07-26 17:59:49 -03:00
René Wang
174549e5bc fix image alt text error (#2641) 2023-07-24 18:27:20 +00:00
Federico Maccaroni
87b1d18872 PM-2320 fix duplicated resource (#2638) 2023-07-21 15:58:28 -03:00
github-actions[bot]
ae9ba810ff Autosync the updated translations (#2634)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-21 09:30:20 +00:00
Federico Maccaroni
dd52ff0dcc [PM-2320] Improve Android block Auto-fill URIs (#2616)
* PM-2320 Added new view for block autofill URIs on Android

* PM-2320 Fix formatting

* PM-2320 Improved validations on block autofill uris

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

* AC-762 Added crowdin config for watchOS localizable files
2023-07-17 13:35:54 -03:00
mpbw2
9adc4d3080 Catch additional exception types when validating intents (#2618) 2023-07-17 08:40:35 -04:00
ifernandezdiaz
1f20f70d13 Fixing show value id button (#2620) 2023-07-16 20:13:55 -03:00
Vince Grassia
a25da68437 Fix syntax in Version Auto Bump workflow (#2615) 2023-07-13 11:55:16 -04:00
Vince Grassia
fdc0313d10 Fix Build Workflow (#2613) 2023-07-13 10:05:57 -04:00
github-actions[bot]
f31c87b52e Bumped version to 2023.7.0 (#2612)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-12 17:34:23 +00:00
Cesar Gonzalez
1e79e1182f [PM-1063] Re-prompt for Master Password Can be Bypassed When Using Gboard Inline Autofill (#2593) 2023-07-11 08:15:37 -05:00
github-actions[bot]
11947ce99a Autosync the updated translations (#2603)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2023-07-07 05:13:18 +00:00
mpbw2
4abb472998 Revert "reset lock delay when returning from activity result (#2539)" (#2597)
This reverts commit 0288a6659c.
2023-07-03 09:56:10 -04:00
328 changed files with 18398 additions and 3600 deletions

31
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,31 @@
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
#
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# The following owners will be the default owners for everything in the repo.
# Unless a later match takes precedence
# @bitwarden/tech-leads
@bitwarden/dept-development-mobile
## Auth team files ##
## Platform team files ##
appIcons @bitwarden/team-platform-dev
build.cake @bitwarden/team-platform-dev
## Vault team files ##
src/watchOS @bitwarden/team-vault-dev
## Tools team files ##
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
## Crowdin Sync files ##
src/App/Resources @bitwarden/tech-leads
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/tech-leads
## Locales ##
src/App/Resources/AppResources.Designer.cs
src/App/Resources/AppResources.resx
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj

View File

@@ -71,6 +71,11 @@ jobs:
with:
nuget-version: 5.9.0
- name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '3.1.x'
- name: Set up MSBuild
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
@@ -520,7 +525,7 @@ jobs:
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -662,6 +667,22 @@ jobs:
$configuration = "AppStore";
$platform = "iPhone";
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
shell: pwsh
- name: Archive Build for Mobile Automation
run: |
$configuration = "Release";
$platform = "iPhoneSimulator";
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
@@ -684,6 +705,15 @@ jobs:
-exportOptionsPlist $EXPORT_OPTIONS_PATH
shell: bash
- name: Export .app for Automation CI
run: |
ARCHIVE_PATH="./src/iOS/bin/iPhoneSimulator/Release/BitwardeniOS.app"
EXPORT_PATH="./bitwarden-export"
zip -r -q -j BitwardeniOS.app.zip $ARCHIVE_PATH
mv BitwardeniOS.app.zip $EXPORT_PATH
shell: bash
- name: Copy all dSYMs files to upload
run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
@@ -706,6 +736,13 @@ jobs:
./bitwarden-export/dSYMs/*.*
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: BitwardeniOS.app.zip
path: ./bitwarden-export/BitwardeniOS.app.zip
if-no-files-found: error
- name: Install AppCenter CLI
if: |
(github.ref == 'refs/heads/master'
@@ -774,7 +811,7 @@ jobs:
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
@@ -840,7 +877,7 @@ jobs:
fi
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

View File

@@ -18,13 +18,13 @@ jobs:
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/get-keyvault-secrets@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"

View File

@@ -12,6 +12,6 @@ jobs:
pull-requests: write
runs-on: ubuntu-20.04
steps:
- uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b # v4.0.3
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
with:
sync-labels: true

View File

@@ -38,11 +38,11 @@ jobs:
fi
- name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/release-version-check@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin
@@ -87,7 +87,7 @@ jobs:
- name: Create release
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
with:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
@@ -126,7 +126,7 @@ jobs:
if: inputs.fdroid_publish
steps:
- name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
@@ -147,9 +147,9 @@ jobs:
name: com.x8bit.bitwarden-fdroid.apk
- name: Set up Node
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
with:
node-version: '10.x'
node-version: '16.x'
- name: Set up F-Droid server
run: |

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: 'Run stale action'
uses: actions/stale@3cc123766321e9f15a6676375c154ccffb12a358 # v5.0.0
uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
with:
stale-issue-label: 'needs-reply'
stale-pr-label: 'needs-changes'

View File

@@ -14,7 +14,7 @@ jobs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Checkout Branch
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Calculate bumped version
id: version
@@ -32,14 +32,8 @@ jobs:
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: "Version bump"
runs-on: ubuntu-22.04
needs:
- setup
steps:
- 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:
version_number: ${{ needs.setup.outputs.version_number }}
name: Bump version to ${{ needs.setup.outputs.version_number }}
needs: setup
uses: ./.github/workflows/version-bump.yml
with:
version_number: ${{ needs.setup.outputs.version_number }}

View File

@@ -12,9 +12,6 @@ on:
version_number:
required: true
type: string
secrets:
AZURE_PROD_KV_CREDENTIALS:
required: true
jobs:
bump_version:
@@ -22,22 +19,22 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout Branch
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/get-keyvault-secrets@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
@@ -48,31 +45,31 @@ jobs:
run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
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@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
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@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
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@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
with:
version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.ShareExtension/Info.plist"
- name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/version-bump@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67
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@34ecb67b2a357795dc893549df0795e7383ff50f
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@4a7ddc1b38ca5cb4e3e43578f4df5cabe4f55a67

View File

@@ -4,7 +4,7 @@
# Bitwarden Mobile Application
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on Google Play" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on F-Droid" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.

View File

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

View File

@@ -77,21 +77,21 @@
<PackageReference Include="Portable.BouncyCastle">
<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.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.AndroidX.AppCompat" Version="1.6.1.3" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.21" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.1.2" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.4.0.2" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.7.5</Version>
<Version>1.8.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>123.1.1.1</Version>
<Version>123.1.2.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.8.0" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.44.2.1" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.9.0.2" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.46.1.2" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>118.0.1.3</Version>
<Version>118.0.1.5</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -233,6 +233,18 @@
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />

Binary file not shown.

View File

@@ -44,7 +44,6 @@ namespace Bit.Droid
private IAppIdService _appIdService;
private IEventService _eventService;
private IPushNotificationListenerService _pushNotificationListenerService;
private IVaultTimeoutService _vaultTimeoutService;
private ILogger _logger;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
@@ -69,7 +68,6 @@ 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;
@@ -234,7 +232,6 @@ 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

@@ -68,9 +68,9 @@ namespace Bit.Droid
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
ServiceContainer.Resolve<IUserVerificationService>());
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
var accountsManager = new AccountsManager(
@@ -156,10 +156,10 @@ namespace Bit.Droid
messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
var biometricService = new BiometricService(stateService, cryptoService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);

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.5.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.9.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />

View File

@@ -1,10 +1,10 @@
using System;
using Bit.App.Controls;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Content;
using Xamarin.Forms;
using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Bit.Droid.Renderers
@@ -15,6 +15,19 @@ namespace Bit.Droid.Renderers
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomLabel label)
{
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var label = sender as CustomLabel;
@@ -28,4 +41,3 @@ namespace Bit.Droid.Renderers
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Android.OS;
using Android.Security.Keystore;
using Bit.App.Services;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Java.Security;
@@ -9,10 +10,8 @@ using Javax.Crypto;
namespace Bit.Droid.Services
{
public class BiometricService : IBiometricService
public class BiometricService : BaseBiometricService
{
private readonly IStateService _stateService;
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
private const string KeyStoreName = "AndroidKeyStore";
@@ -24,14 +23,14 @@ namespace Bit.Droid.Services
private readonly KeyStore _keystore;
public BiometricService(IStateService stateService)
public BiometricService(IStateService stateService, ICryptoService cryptoService)
: base(stateService, cryptoService)
{
_stateService = stateService;
_keystore = KeyStore.GetInstance(KeyStoreName);
_keystore.Load(null);
}
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
@@ -41,7 +40,7 @@ namespace Bit.Droid.Services
return true;
}
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
{
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{

View File

@@ -13,12 +13,13 @@ using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using static Bit.App.Pages.SettingsPageViewModel;
using Xamarin.Forms.Platform.Android;
namespace Bit.Droid.Services
{
@@ -209,10 +210,7 @@ namespace Bit.Droid.Services
}
if (numericKeyboard)
{
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
SetNumericKeyboardTo(input);
}
if (password)
{
@@ -248,6 +246,83 @@ namespace Bit.Droid.Services
return result.Task;
}
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
}
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetTitle(config.Title);
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
alertBuilder.SetView(view);
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
alertBuilder.SetOnCancelListener(new BasicDialogWithResultCancelListener(result));
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
if (!string.IsNullOrEmpty(config.ThirdButtonText))
{
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
}
var alert = alertBuilder.Create();
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
lblHeader.Text = config.Subtitle;
lblValueSubinfo.Text = config.ValueSubInfo;
var defaultSubInfoColor = lblValueSubinfo.TextColors;
input.InputType = InputTypes.ClassText;
if (config.NumericKeyboard)
{
SetNumericKeyboardTo(input);
}
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
input.Text = config.Text ?? string.Empty;
input.SetSelection(config.Text?.Length ?? 0);
input.AfterTextChanged += (sender, args) =>
{
if (lblValueSubinfo.Text != config.ValueSubInfo)
{
lblValueSubinfo.Text = config.ValueSubInfo;
lblHeader.SetTextColor(defaultSubInfoColor);
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
}
};
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
alert.Show();
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
positiveButton.Click += (sender, args) =>
{
var error = config.ValidateText(input.Text);
if (error != null)
{
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.Text = error;
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
return;
}
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
alert.Dismiss();
};
return result.Task;
}
public void RateApp()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -525,5 +600,29 @@ namespace Bit.Droid.Services
// only used by iOS
throw new NotImplementedException();
}
private void SetNumericKeyboardTo(EditText editText)
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
class BasicDialogWithResultCancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
{
private readonly TaskCompletionSource<ValidatablePromptResponse?> _taskCompletionSource;
public BasicDialogWithResultCancelListener(TaskCompletionSource<ValidatablePromptResponse?> taskCompletionSource)
{
_taskCompletionSource = taskCompletionSource;
}
public void OnCancel(IDialogInterface dialog)
{
_taskCompletionSource?.TrySetResult(null);
dialog?.Dismiss();
}
}
}

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Bit.Core.Enums;
namespace Bit.App.Abstractions
{
@@ -6,10 +7,8 @@ namespace Bit.App.Abstractions
{
string[] ProtectedFields { get; }
Task<bool> ShowPasswordPromptAsync();
Task<bool> PromptAndCheckPasswordIfNeededAsync(CipherRepromptType repromptType = CipherRepromptType.Password);
Task<(string password, bool valid)> ShowPasswordPromptAndGetItAsync();
Task<bool> Enabled();
}
}

View File

@@ -16,9 +16,9 @@
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<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.Essentials" Version="1.8.0" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2578" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2612" />
<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" />
@@ -146,6 +146,7 @@
<Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" />
<Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
</ItemGroup>
<ItemGroup>
@@ -442,5 +443,6 @@
<None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
<None Remove="Utilities\Automation\" />
<None Remove="Utilities\Prompts\" />
</ItemGroup>
</Project>

View File

@@ -297,7 +297,7 @@ namespace Bit.App
{
await _vaultTimeoutService.CheckVaultTimeoutAsync();
// Reset delay on every start
_vaultTimeoutService.DelayTimeoutMs = null;
_vaultTimeoutService.DelayLockAndLogoutMs = null;
}
await _configService.GetAsync();

View File

@@ -36,7 +36,7 @@ namespace Bit.App.Controls
public bool ShowHostname
{
get => !string.IsNullOrWhiteSpace(AccountView.Hostname) && AccountView.Hostname != "vault.bitwarden.com";
get => !string.IsNullOrWhiteSpace(AccountView.Hostname);
}
public bool IsActive

View File

@@ -31,7 +31,7 @@ namespace Bit.App.Controls
public bool ShowIconImage
{
get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri)
&& IconImageSource != null;
}
@@ -41,7 +41,7 @@ namespace Bit.App.Controls
{
if (_iconImageSource == string.Empty) // default value since icon source can return null
{
_iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
_iconImageSource = IconImageHelper.GetIconImage(Cipher);
}
return _iconImageSource;
}

View File

@@ -1,5 +1,4 @@
using System;
using Xamarin.Forms;
using Xamarin.Forms;
namespace Bit.App.Controls
{
@@ -8,6 +7,7 @@ namespace Bit.App.Controls
public CustomLabel()
{
}
public int? FontWeight { get; set; }
}
}

View File

@@ -7,17 +7,17 @@ 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;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class HomeViewModel : BaseViewModel
{
private const string LOGGING_IN_ON_US = "bitwarden.com";
private const string LOGGING_IN_ON_EU = "bitwarden.eu";
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IPlatformUtilsService _platformUtilsService;
@@ -30,8 +30,6 @@ namespace Bit.App.Pages
private bool _rememberEmail;
private string _email;
private string _selectedEnvironmentName;
private bool _isEmailEnabled;
private bool _canLogin;
private bool _displayEuEnvironment;
public HomeViewModel()
@@ -86,7 +84,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _selectedEnvironmentName, value);
}
public string RegionText => $"{AppResources.Region}:";
public string RegionText => $"{AppResources.LoggingInOn}:";
public bool CanContinue => !string.IsNullOrEmpty(Email);
public FormattedString CreateAccountText
@@ -167,12 +165,12 @@ namespace Bit.App.Pages
{
_displayEuEnvironment = await _configService.GetFeatureFlagBoolAsync(Constants.DisplayEuEnvironmentFlag);
var options = _displayEuEnvironment
? new string[] { AppResources.US, AppResources.EU, AppResources.SelfHosted }
: new string[] { AppResources.US, AppResources.SelfHosted };
? new string[] { LOGGING_IN_ON_US, LOGGING_IN_ON_EU, AppResources.SelfHosted }
: new string[] { LOGGING_IN_ON_US, AppResources.SelfHosted };
await Device.InvokeOnMainThreadAsync(async () =>
{
var result = await Page.DisplayActionSheet(AppResources.DataRegion, AppResources.Cancel, null, options);
var result = await Page.DisplayActionSheet(AppResources.LoggingInOn, AppResources.Cancel, null, options);
if (result is null || result == AppResources.Cancel)
{
@@ -185,7 +183,7 @@ namespace Bit.App.Pages
return;
}
await _environmentService.SetUrlsAsync(result == AppResources.EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
await _environmentService.SetUrlsAsync(result == LOGGING_IN_ON_EU ? EnvironmentUrlData.DefaultEU : EnvironmentUrlData.DefaultUS);
await _configService.GetAsync(true);
SelectedEnvironmentName = result;
});
@@ -198,17 +196,17 @@ namespace Bit.App.Pages
{
await _environmentService.SetUrlsAsync(EnvironmentUrlData.DefaultUS);
environmentsSaved = EnvironmentUrlData.DefaultUS;
SelectedEnvironmentName = AppResources.US;
SelectedEnvironmentName = LOGGING_IN_ON_US;
return;
}
if (environmentsSaved.Base == EnvironmentUrlData.DefaultUS.Base)
{
SelectedEnvironmentName = AppResources.US;
SelectedEnvironmentName = LOGGING_IN_ON_US;
}
else if (environmentsSaved.Base == EnvironmentUrlData.DefaultEU.Base)
{
SelectedEnvironmentName = AppResources.EU;
SelectedEnvironmentName = LOGGING_IN_ON_EU;
}
else
{

View File

@@ -46,7 +46,7 @@
<StackLayout StyleClass="box">
<Grid
StyleClass="box-row"
IsVisible="{Binding PinLock}"
IsVisible="{Binding PinEnabled}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -89,7 +89,7 @@
<Grid
x:Name="_passwordGrid"
StyleClass="box-row"
IsVisible="{Binding PinLock, Converter={StaticResource inverseBool}}"
IsVisible="{Binding PinEnabled, Converter={StaticResource inverseBool}}"
Padding="0, 10, 0, 0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@@ -20,13 +20,14 @@ namespace Bit.App.Pages
private bool _promptedAfterResume;
private bool _appeared;
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true, bool checkPendingAuthRequests = true)
{
_appOptions = appOptions;
_autoPromptBiometric = autoPromptBiometric;
InitializeComponent();
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>();
_vm = BindingContext as LockPageViewModel;
_vm.CheckPendingAuthRequests = checkPendingAuthRequests;
_vm.Page = this;
_vm.UnlockedAction = () => Device.BeginInvokeOnMainThread(async () => await UnlockedAsync());
@@ -44,7 +45,7 @@ namespace Bit.App.Pages
{
get
{
if (_vm?.PinLock ?? false)
if (_vm?.PinEnabled ?? false)
{
return _pin;
}
@@ -54,7 +55,7 @@ namespace Bit.App.Pages
public async Task PromptBiometricAfterResumeAsync()
{
if (_vm.BiometricLock)
if (_vm.BiometricEnabled)
{
await Task.Delay(500);
if (!_promptedAfterResume)
@@ -91,13 +92,13 @@ namespace Bit.App.Pages
_vm.FocusSecretEntry += PerformFocusSecretEntry;
if (!_vm.BiometricLock)
if (!_vm.BiometricEnabled)
{
RequestFocus(SecretEntry);
}
else
{
if (_vm.UsingKeyConnector && !_vm.PinLock)
if (!_vm.HasMasterPassword && !_vm.PinEnabled)
{
_passwordGrid.IsVisible = false;
_unlockButton.IsVisible = false;

View File

@@ -7,6 +7,7 @@ using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
@@ -27,27 +28,27 @@ namespace Bit.App.Pages
private readonly IEnvironmentService _environmentService;
private readonly IStateService _stateService;
private readonly IBiometricService _biometricService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly ILogger _logger;
private readonly IWatchDeviceService _watchDeviceService;
private readonly WeakEventManager<int?> _secretEntryFocusWeakEventManager = new WeakEventManager<int?>();
private readonly IPolicyService _policyService;
private readonly IPasswordGenerationService _passwordGenerationService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ISyncService _syncService;
private string _email;
private string _masterPassword;
private string _pin;
private bool _showPassword;
private bool _pinLock;
private bool _biometricLock;
private PinLockType _pinStatus;
private bool _pinEnabled;
private bool _biometricEnabled;
private bool _biometricIntegrityValid = true;
private bool _biometricButtonVisible;
private bool _usingKeyConnector;
private bool _hasMasterPassword;
private string _biometricButtonText;
private string _loggedInAsText;
private string _lockedVerifyText;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
public LockPageViewModel()
{
@@ -60,21 +61,24 @@ namespace Bit.App.Pages
_environmentService = ServiceContainer.Resolve<IEnvironmentService>("environmentService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
_logger = ServiceContainer.Resolve<ILogger>("logger");
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_policyService = ServiceContainer.Resolve<IPolicyService>();
_passwordGenerationService = ServiceContainer.Resolve<IPasswordGenerationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
PageTitle = AppResources.VerifyMasterPassword;
TogglePasswordCommand = new Command(TogglePassword);
SubmitCommand = new Command(async () => await SubmitAsync());
AccountSwitchingOverlayViewModel = new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
AccountSwitchingOverlayViewModel =
new AccountSwitchingOverlayViewModel(_stateService, _messagingService, _logger)
{
AllowAddAccountRow = true,
AllowActiveAccountSelection = true
};
}
public string MasterPassword
@@ -100,21 +104,21 @@ namespace Bit.App.Pages
});
}
public bool PinLock
public bool PinEnabled
{
get => _pinLock;
set => SetProperty(ref _pinLock, value);
get => _pinEnabled;
set => SetProperty(ref _pinEnabled, value);
}
public bool UsingKeyConnector
public bool HasMasterPassword
{
get => _usingKeyConnector;
get => _hasMasterPassword;
}
public bool BiometricLock
public bool BiometricEnabled
{
get => _biometricLock;
set => SetProperty(ref _biometricLock, value);
get => _biometricEnabled;
set => SetProperty(ref _biometricEnabled, value);
}
public bool BiometricIntegrityValid
@@ -147,12 +151,18 @@ namespace Bit.App.Pages
set => SetProperty(ref _lockedVerifyText, value);
}
public bool CheckPendingAuthRequests { get; set; }
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
public Command SubmitCommand { get; }
public Command TogglePasswordCommand { get; }
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
public string PasswordVisibilityAccessibilityText => ShowPassword
? AppResources.PasswordIsVisibleTapToHide
: AppResources.PasswordIsNotVisibleTapToShow;
public Action UnlockedAction { get; set; }
public event Action<int?> FocusSecretEntry
{
@@ -162,18 +172,33 @@ namespace Bit.App.Pages
public async Task InitAsync()
{
(_isPinProtected, _isPinProtectedWithKey) = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_isPinProtected && await _stateService.GetPinProtectedKeyAsync() != null) ||
_isPinProtectedWithKey;
BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync() && await _cryptoService.HasKeyAsync();
// Users with key connector and without biometric or pin has no MP to unlock with
_usingKeyConnector = await _keyConnectorService.GetUsesKeyConnector();
if (_usingKeyConnector && !(BiometricLock || PinLock))
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null && CheckPendingAuthRequests)
{
await _vaultTimeoutService.LogOutAsync();
return;
}
_pinStatus = await _vaultTimeoutService.GetPinLockTypeAsync();
var ephemeralPinSet = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync()
?? await _stateService.GetPinProtectedKeyAsync();
PinEnabled = (_pinStatus == PinLockType.Transient && ephemeralPinSet != null) ||
_pinStatus == PinLockType.Persistent;
BiometricEnabled = await IsBiometricsEnabledAsync();
// Users without MP and without biometric or pin has no MP to unlock with
_hasMasterPassword = await _userVerificationService.HasMasterPasswordAsync();
if (await _stateService.IsAuthenticatedAsync()
&& !_hasMasterPassword
&& !BiometricEnabled
&& !PinEnabled)
{
await _vaultTimeoutService.LogOutAsync();
return;
}
_email = await _stateService.GetEmailAsync();
if (string.IsNullOrWhiteSpace(_email))
{
@@ -188,26 +213,20 @@ namespace Bit.App.Pages
}
var webVaultHostname = CoreHelpers.GetHostname(webVault);
LoggedInAsText = string.Format(AppResources.LoggedInAsOn, _email, webVaultHostname);
if (PinLock)
if (PinEnabled)
{
PageTitle = AppResources.VerifyPIN;
LockedVerifyText = AppResources.VaultLockedPIN;
}
else
{
if (_usingKeyConnector)
{
PageTitle = AppResources.UnlockVault;
LockedVerifyText = AppResources.VaultLockedIdentity;
}
else
{
PageTitle = AppResources.VerifyMasterPassword;
LockedVerifyText = AppResources.VaultLockedMasterPassword;
}
PageTitle = _hasMasterPassword ? AppResources.VerifyMasterPassword : AppResources.UnlockVault;
LockedVerifyText = _hasMasterPassword
? AppResources.VaultLockedMasterPassword
: AppResources.VaultLockedIdentity;
}
if (BiometricLock)
if (BiometricEnabled)
{
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
if (!_biometricIntegrityValid)
@@ -223,20 +242,119 @@ namespace Bit.App.Pages
BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
AppResources.UseFingerprintToUnlock;
}
}
}
public async Task SubmitAsync()
{
if (PinLock && string.IsNullOrWhiteSpace(Pin))
ShowPassword = false;
try
{
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinEnabled)
{
await UnlockWithPinAsync(kdfConfig);
}
else
{
await UnlockWithMasterPasswordAsync(kdfConfig);
}
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
}
private async Task UnlockWithPinAsync(KdfConfig kdfConfig)
{
if (PinEnabled && string.IsNullOrWhiteSpace(Pin))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.PIN),
AppResources.Ok);
return;
}
if (!PinLock && string.IsNullOrWhiteSpace(MasterPassword))
var failed = true;
try
{
EncString userKeyPin;
EncString oldPinProtected;
switch (_pinStatus)
{
case PinLockType.Persistent:
{
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyAsync();
var oldEncryptedKey = await _stateService.GetPinProtectedAsync();
oldPinProtected = oldEncryptedKey != null ? new EncString(oldEncryptedKey) : null;
break;
}
case PinLockType.Transient:
userKeyPin = await _stateService.GetPinKeyEncryptedUserKeyEphemeralAsync();
oldPinProtected = await _stateService.GetPinProtectedKeyAsync();
break;
case PinLockType.Disabled:
default:
throw new Exception("Pin is disabled");
}
UserKey userKey;
if (oldPinProtected != null)
{
userKey = await _cryptoService.DecryptAndMigrateOldPinKeyAsync(
_pinStatus == PinLockType.Transient,
Pin,
_email,
kdfConfig,
oldPinProtected
);
}
else
{
userKey = await _cryptoService.DecryptUserKeyWithPinAsync(
Pin,
_email,
kdfConfig,
userKeyPin
);
}
var protectedPin = await _stateService.GetProtectedPinAsync();
var decryptedPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), userKey);
failed = decryptedPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetUserKeyAndContinueAsync(userKey);
}
}
catch (LegacyUserException)
{
throw;
}
catch
{
failed = true;
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
AppResources.AnErrorHasOccurred);
}
}
private async Task UnlockWithMasterPasswordAsync(KdfConfig kdfConfig)
{
if (!PinEnabled && string.IsNullOrWhiteSpace(MasterPassword))
{
await Page.DisplayAlert(AppResources.AnErrorHasOccurred,
string.Format(AppResources.ValidationFieldRequired, AppResources.MasterPassword),
@@ -244,126 +362,78 @@ namespace Bit.App.Pages
return;
}
ShowPassword = false;
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
if (PinLock)
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, _email, kdfConfig);
if (await _cryptoService.IsLegacyUserAsync(masterKey))
{
var failed = true;
throw new LegacyUserException();
}
var storedKeyHash = await _cryptoService.GetMasterKeyHashAsync();
var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null)
{
// Offline unlock possible
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, masterKey);
}
else
{
// Online unlock required
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
if (_isPinProtected)
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email,
kdfConfig,
await _stateService.GetPinProtectedKeyAsync());
var encKey = await _cryptoService.GetEncKeyAsync(key);
var protectedPin = await _stateService.GetProtectedPinAsync();
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
failed = decPin != Pin;
if (!failed)
{
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
}
else
{
var key = await _cryptoService.MakeKeyFromPinAsync(Pin, _email, kdfConfig);
failed = false;
Pin = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await SetKeyAndContinueAsync(key);
}
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true;
var localKeyHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey,
HashPurpose.LocalAuthorization);
await _cryptoService.SetMasterKeyHashAsync(localKeyHash);
}
catch
catch (Exception e)
{
failed = true;
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
if (failed)
await _deviceActionService.HideLoadingAsync();
}
if (passwordValid)
{
if (await RequirePasswordChangeAsync(enforcedMasterPasswordOptions))
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidPIN,
AppResources.AnErrorHasOccurred);
// Save the ForcePasswordResetReason to force a password reset after unlock
await _stateService.SetForcePasswordResetReasonAsync(
ForcePasswordResetReason.WeakMasterPasswordOnLogin);
}
MasterPassword = string.Empty;
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
var userKey = await _cryptoService.DecryptUserKeyWithMasterKeyAsync(masterKey);
await _cryptoService.SetMasterKeyAsync(masterKey);
await SetUserKeyAndContinueAsync(userKey);
// Re-enable biometrics
if (BiometricEnabled & !BiometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync();
}
}
else
{
var key = await _cryptoService.MakeKeyAsync(MasterPassword, _email, kdfConfig);
var storedKeyHash = await _cryptoService.GetKeyHashAsync();
var passwordValid = false;
MasterPasswordPolicyOptions enforcedMasterPasswordOptions = null;
if (storedKeyHash != null)
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
passwordValid = await _cryptoService.CompareAndUpdateKeyHashAsync(MasterPassword, key);
}
else
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
var keyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.ServerAuthorization);
var request = new PasswordVerificationRequest();
request.MasterPasswordHash = keyHash;
try
{
var response = await _apiService.PostAccountVerifyPasswordAsync(request);
enforcedMasterPasswordOptions = response.MasterPasswordPolicy;
passwordValid = true;
var localKeyHash = await _cryptoService.HashPasswordAsync(MasterPassword, key, HashPurpose.LocalAuthorization);
await _cryptoService.SetKeyHashAsync(localKeyHash);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
}
await _deviceActionService.HideLoadingAsync();
}
if (passwordValid)
{
if (_isPinProtected)
{
var protectedPin = await _stateService.GetProtectedPinAsync();
var encKey = await _cryptoService.GetEncKeyAsync(key);
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email, kdfConfig);
await _stateService.SetPinProtectedKeyAsync(await _cryptoService.EncryptAsync(key.Key, pinKey));
}
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);
// Re-enable biometrics
if (BiometricLock & !BiometricIntegrityValid)
{
await _biometricService.SetupBiometricAsync();
}
}
else
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
AppResources.AnErrorHasOccurred);
_messagingService.Send("logout");
return;
}
await _platformUtilsService.ShowDialogAsync(AppResources.InvalidMasterPassword,
AppResources.AnErrorHasOccurred);
}
}
@@ -425,44 +495,82 @@ namespace Bit.App.Pages
public void TogglePassword()
{
ShowPassword = !ShowPassword;
var secret = PinLock ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length, nameof(FocusSecretEntry));
var secret = PinEnabled ? Pin : MasterPassword;
_secretEntryFocusWeakEventManager.RaiseEvent(string.IsNullOrEmpty(secret) ? 0 : secret.Length,
nameof(FocusSecretEntry));
}
public async Task PromptBiometricAsync()
{
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricLock || !BiometricIntegrityValid)
try
{
return;
BiometricIntegrityValid = await _platformUtilsService.IsBiometricIntegrityValidAsync();
BiometricButtonVisible = BiometricIntegrityValid;
if (!BiometricEnabled || !BiometricIntegrityValid)
{
return;
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinEnabled ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)),
!PinEnabled && !HasMasterPassword);
await _stateService.SetBiometricLockedAsync(!success);
if (success)
{
var userKey = await _cryptoService.GetBiometricUnlockKeyAsync();
await SetUserKeyAndContinueAsync(userKey);
}
}
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
PinLock ? AppResources.PIN : AppResources.MasterPassword,
() => _secretEntryFocusWeakEventManager.RaiseEvent((int?)null, nameof(FocusSecretEntry)));
await _stateService.SetBiometricLockedAsync(!success);
if (success)
catch (LegacyUserException)
{
await DoContinueAsync();
await HandleLegacyUserAsync();
}
}
private async Task SetKeyAndContinueAsync(SymmetricCryptoKey key)
private async Task SetUserKeyAndContinueAsync(UserKey key)
{
var hasKey = await _cryptoService.HasKeyAsync();
var hasKey = await _cryptoService.HasUserKeyAsync();
if (!hasKey)
{
await _cryptoService.SetKeyAsync(key);
await _cryptoService.SetUserKeyAsync(key);
}
await _deviceTrustCryptoService.TrustDeviceIfNeededAsync();
await DoContinueAsync();
}
private async Task DoContinueAsync()
{
_syncService.FullSyncAsync(false).FireAndForget();
await _stateService.SetBiometricLockedAsync(false);
_watchDeviceService.SyncDataToWatchAsync().FireAndForget();
_messagingService.Send("unlocked");
UnlockedAction?.Invoke();
}
private async Task<bool> IsBiometricsEnabledAsync()
{
try
{
return await _vaultTimeoutService.IsBiometricLockSetAsync() &&
await _biometricService.CanUseBiometricsUnlockAsync();
}
catch (LegacyUserException)
{
await HandleLegacyUserAsync();
}
return false;
}
private async Task HandleLegacyUserAsync()
{
// Legacy users must migrate on web vault.
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong,
AppResources.AnErrorHasOccurred,
AppResources.Ok);
await _vaultTimeoutService.LogOutAsync();
}
}
}

View File

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

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class LoginApproveDevicePage : BaseContentPage
{
private readonly LoginApproveDeviceViewModel _vm;
private readonly AppOptions _appOptions;
public LoginApproveDevicePage(AppOptions appOptions = null)
{
InitializeComponent();
_vm = BindingContext as LoginApproveDeviceViewModel;
_vm.LogInWithMasterPasswordAction = () => StartLogInWithMasterPasswordAsync().FireAndForget();
_vm.LogInWithDeviceAction = () => StartLoginWithDeviceAsync().FireAndForget();
_vm.RequestAdminApprovalAction = () => RequestAdminApprovalAsync().FireAndForget();
_vm.ContinueToVaultAction = () => ContinueToVaultAsync().FireAndForget();
_vm.Page = this;
_appOptions = appOptions;
}
protected override void OnAppearing()
{
_vm.InitAsync();
}
private async Task ContinueToVaultAsync()
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private async Task StartLogInWithMasterPasswordAsync()
{
var page = new LockPage(_appOptions, checkPendingAuthRequests: false);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions, true);
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task RequestAdminApprovalAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AdminApproval, _appOptions, true);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class LoginApproveDeviceViewModel : BaseViewModel
{
private bool _rememberThisDevice;
private bool _approveWithMyOtherDeviceEnabled;
private bool _requestAdminApprovalEnabled;
private bool _approveWithMasterPasswordEnabled;
private string _email;
private readonly IStateService _stateService;
private readonly IApiService _apiService;
private IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly IAuthService _authService;
private readonly ISyncService _syncService;
private readonly IMessagingService _messagingService;
public ICommand ApproveWithMyOtherDeviceCommand { get; }
public ICommand RequestAdminApprovalCommand { get; }
public ICommand ApproveWithMasterPasswordCommand { get; }
public ICommand ContinueCommand { get; }
public ICommand LogoutCommand { get; }
public Action LogInWithMasterPasswordAction { get; set; }
public Action LogInWithDeviceAction { get; set; }
public Action RequestAdminApprovalAction { get; set; }
public Action ContinueToVaultAction { get; set; }
public LoginApproveDeviceViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_apiService = ServiceContainer.Resolve<IApiService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_authService = ServiceContainer.Resolve<IAuthService>();
_syncService = ServiceContainer.Resolve<ISyncService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>();
PageTitle = AppResources.LogInInitiated;
RememberThisDevice = true;
ApproveWithMyOtherDeviceCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithDeviceAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
RequestAdminApprovalCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(RequestAdminApprovalAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
ApproveWithMasterPasswordCommand = new AsyncCommand(() => SetDeviceTrustAndInvokeAsync(LogInWithMasterPasswordAction),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
ContinueCommand = new AsyncCommand(CreateNewSsoUserAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
LogoutCommand = new Command(() => _messagingService.Send(AccountsManagerMessageCommands.LOGOUT));
}
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
public bool RememberThisDevice
{
get => _rememberThisDevice;
set => SetProperty(ref _rememberThisDevice, value);
}
public bool ApproveWithMyOtherDeviceEnabled
{
get => _approveWithMyOtherDeviceEnabled;
set => SetProperty(ref _approveWithMyOtherDeviceEnabled, value);
}
public bool RequestAdminApprovalEnabled
{
get => _requestAdminApprovalEnabled;
set => SetProperty(ref _requestAdminApprovalEnabled, value,
additionalPropertyNames: new[] { nameof(IsNewUser) });
}
public bool ApproveWithMasterPasswordEnabled
{
get => _approveWithMasterPasswordEnabled;
set => SetProperty(ref _approveWithMasterPasswordEnabled, value,
additionalPropertyNames: new[] { nameof(IsNewUser) });
}
public bool IsNewUser => !RequestAdminApprovalEnabled && !ApproveWithMasterPasswordEnabled;
public string Email
{
get => _email;
set => SetProperty(ref _email, value, additionalPropertyNames:
new string[] {
nameof(LoggingInAsText)
});
}
public async Task InitAsync()
{
try
{
Email = await _stateService.GetActiveUserEmailAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
RequestAdminApprovalEnabled = decryptOptions?.TrustedDeviceOption?.HasAdminApproval ?? false;
ApproveWithMasterPasswordEnabled = decryptOptions?.HasMasterPassword ?? false;
ApproveWithMyOtherDeviceEnabled = decryptOptions?.TrustedDeviceOption?.HasLoginApprovingDevice ?? false;
}
catch (Exception ex)
{
HandleException(ex);
}
}
public async Task CreateNewSsoUserAsync()
{
await _authService.CreateNewSsoUserAsync(await _stateService.GetRememberedOrgIdentifierAsync());
if (RememberThisDevice)
{
await _deviceTrustCryptoService.TrustDeviceAsync();
}
_syncService.FullSyncAsync(true).FireAndForget();
await Device.InvokeOnMainThreadAsync(ContinueToVaultAction);
}
private async Task SetDeviceTrustAndInvokeAsync(Action action)
{
await _deviceTrustCryptoService.SetShouldTrustDeviceAsync(RememberThisDevice);
await Device.InvokeOnMainThreadAsync(action);
}
}
}

View File

@@ -4,6 +4,7 @@ using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -135,7 +136,7 @@ namespace Bit.App.Pages
private async Task StartLoginWithDeviceAsync()
{
var page = new LoginPasswordlessRequestPage(_vm.Email, _appOptions);
var page = new LoginPasswordlessRequestPage(_vm.Email, AuthRequestType.AuthenticateAndUnlock, _appOptions);
await Navigation.PushModalAsync(new NavigationPage(page));
}

View File

@@ -248,6 +248,14 @@ namespace Bit.App.Pages
await _deviceActionService.HideLoadingAsync();
if (response.RequiresEncryptionKeyMigration)
{
// Legacy users must migrate on web vault.
await _platformUtilsService.ShowDialogAsync(AppResources.EncryptionKeyMigrationRequiredDescriptionLong, AppResources.AnErrorHasOccurred,
AppResources.Ok);
return;
}
if (response.TwoFactor)
{
StartTwoFactorAction?.Invoke();

View File

@@ -21,17 +21,17 @@
<StackLayout
Padding="7, 0, 7, 20">
<Label
Text="{u:I18n LogInInitiated}"
Text="{Binding Title}"
FontSize="Title"
FontAttributes="Bold"
Margin="0,14,0,21"
AutomationId="LogInInitiatedLabel" />
<Label
Text="{u:I18n ANotificationHasBeenSentToYourDevice}"
Text="{Binding SubTitle}"
FontSize="Small"
Margin="0,0,0,10"/>
<Label
Text="{u:I18n PleaseMakeSureYourVaultIsUnlockedAndTheFingerprintPhraseMatchesOnTheOtherDevice}"
Text="{Binding Description}"
FontSize="Small"
Margin="0,0,0,24"/>
<Label
@@ -40,41 +40,39 @@
FontAttributes="Bold"/>
<controls:MonoLabel
FormattedText="{Binding FingerprintPhrase}"
FontSize="Medium"
FontSize="Small"
TextColor="{DynamicResource FingerprintPhrase}"
AutomationId="FingerprintPhraseValue" />
<Label
Text="{u:I18n ResendNotification}"
StyleClass="text-md"
IsVisible="{Binding ResendNotificationVisible}"
StyleClass="text-sm"
FontAttributes="Bold"
HorizontalOptions="Start"
Margin="0,40,0,0"
Margin="0,24,0,0"
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="ResendNotificationButton">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CreatePasswordlessLoginCommand}" />
</Label.GestureRecognizers>
</Label>
<StackLayout
Orientation="Horizontal"
Margin="0,30,0,0">
<Label
Text="{u:I18n NeedAnotherOption}"
FontSize="Small"
VerticalTextAlignment="End"/>
<Label
Text="{u:I18n ViewAllLoginOptions}"
StyleClass="text-md"
VerticalTextAlignment="End"
VerticalOptions="CenterAndExpand"
Margin="5, 0"
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="ViewAllLoginOptionsButton">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CloseCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
<BoxView
HeightRequest="1"
Margin="0,24,0,24"
Color="{DynamicResource DisabledIconColor}" />
<Label
Text="{Binding OtherOptions}"
FontSize="Small"/>
<Label
Text="{u:I18n ViewAllLoginOptions}"
StyleClass="text-sm"
FontAttributes="Bold"
TextColor="{DynamicResource HyperlinkColor}"
AutomationId="ViewAllLoginOptionsButton">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding CloseCommand}" />
</Label.GestureRecognizers>
</Label>
</StackLayout>
</ScrollView>
</pages:BaseContentPage>

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
@@ -29,8 +30,11 @@ namespace Bit.App.Pages
private readonly IStateService _stateService;
private readonly ILogger _logger;
private readonly IOrganizationService _organizationService;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private readonly ICryptoService _cryptoService;
private string _orgIdentifier;
private bool _useEphemeralWebBrowserSession;
public LoginSsoPageViewModel()
{
@@ -45,7 +49,8 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
_organizationService = ServiceContainer.Resolve<IOrganizationService>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
_cryptoService = ServiceContainer.Resolve<ICryptoService>();
PageTitle = AppResources.Bitwarden;
LogInCommand = new AsyncCommand(LogInAsync, allowsMultipleExecutions: false);
@@ -61,6 +66,7 @@ namespace Bit.App.Pages
public Action StartTwoFactorAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action SsoAuthSuccessAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
@@ -109,7 +115,7 @@ namespace Bit.App.Pages
await _deviceActionService.ShowLoadingAsync(AppResources.LoggingIn);
var response = await _apiService.PreValidateSso(OrgIdentifier);
var response = await _apiService.PreValidateSsoAsync(OrgIdentifier);
if (string.IsNullOrWhiteSpace(response?.Token))
{
@@ -140,10 +146,12 @@ namespace Bit.App.Pages
"ssoToken=" + Uri.EscapeDataString(ssoToken);
WebAuthenticatorResult authResult = null;
authResult = await WebAuthenticator.AuthenticateAsync(new Uri(url),
new Uri(REDIRECT_URI));
authResult = await WebAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions()
{
CallbackUrl = new Uri(REDIRECT_URI),
Url = new Uri(url),
PrefersEphemeralWebBrowserSession = _useEphemeralWebBrowserSession,
});
var code = GetResultCode(authResult, state);
if (!string.IsNullOrEmpty(code))
@@ -168,6 +176,8 @@ namespace Bit.App.Pages
{
// user canceled
await _deviceActionService.HideLoadingAsync();
// Workaroung for cached expired sso token PM-3551
_useEphemeralWebBrowserSession = true;
}
catch (Exception ex)
{
@@ -197,28 +207,93 @@ namespace Bit.App.Pages
try
{
var response = await _authService.LogInSsoAsync(code, codeVerifier, REDIRECT_URI, orgId);
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
await AppHelpers.ResetInvalidUnlockAttemptsAsync();
await _stateService.SetRememberedOrgIdentifierAsync(OrgIdentifier);
await _deviceActionService.HideLoadingAsync();
if (response.TwoFactor)
{
StartTwoFactorAction?.Invoke();
return;
}
else if (response.ResetMasterPassword)
// Trusted device option is sent regardless if this is a trusted device or not
// If it is trusted, it will have the necessary keys
if (decryptOptions?.TrustedDeviceOption != null)
{
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
return;
}
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true)
{
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
}
else
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
return;
}
// In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options.
if (response.ResetMasterPassword || (decryptOptions?.RequireSetPassword == true))
{
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke();
return;
}
else if (response.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
SsoAuthSuccessAction?.Invoke();
}
_syncService.FullSyncAsync(true).FireAndForget();
SsoAuthSuccessAction?.Invoke();
}
catch (Exception e)
catch (Exception)
{
await _deviceActionService.HideLoadingAsync();
await _platformUtilsService.ShowDialogAsync(AppResources.LoginSsoError,

View File

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

View File

@@ -30,14 +30,14 @@ namespace Bit.App.Pages
public async Task Init()
{
Organization = await _keyConnectorService.GetManagingOrganization();
Organization = await _keyConnectorService.GetManagingOrganizationAsync();
}
public async Task MigrateAccount()
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _keyConnectorService.MigrateUser();
await _keyConnectorService.MigrateUserAsync();
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
{
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
await _apiService.PostLeaveOrganization(Organization.Id);
await _apiService.PostLeaveOrganizationAsync(Organization.Id);
await _syncService.FullSyncAsync(true);
await _deviceActionService.HideLoadingAsync();

View File

@@ -51,7 +51,8 @@
<Label
Text="{u:I18n ResetPasswordAutoEnrollInviteWarning}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
HorizontalTextAlignment="Start"
AutomationId="ResetPasswordAutoEnrollInviteWarningLabel" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
@@ -73,7 +74,8 @@
<Label
Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
HorizontalTextAlignment="Start"
AutomationId="PolicyInEffectLabel" />
</Frame>
</Grid>
<Grid StyleClass="box-row">
@@ -98,7 +100,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="MasterPasswordField" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -108,7 +111,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="ToggleMasterPasswordVisibilityButton" />
</Grid>
<Label
Text="{u:I18n MasterPasswordDescription}"
@@ -137,7 +141,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="RetypePasswordField" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -147,7 +152,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="ToggleRetypePasswordVisibilityButton" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
@@ -158,7 +164,8 @@
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="MasterPasswordHintLabel" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"

View File

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

View File

@@ -4,6 +4,7 @@ using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Utilities;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Xamarin.Forms;
@@ -17,26 +18,29 @@ namespace Bit.App.Pages
private TwoFactorPageViewModel _vm;
private bool _inited;
private bool _authingWithSso;
private string _orgIdentifier;
public TwoFactorPage(bool? authingWithSso = false, AppOptions appOptions = null, string orgIdentifier = null)
{
InitializeComponent();
SetActivityIndicator();
_authingWithSso = authingWithSso ?? false;
_appOptions = appOptions;
_orgIdentifier = orgIdentifier;
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_vm = BindingContext as TwoFactorPageViewModel;
_vm.Page = this;
_vm.AuthingWithSso = authingWithSso ?? false;
_vm.StartSetPasswordAction = () =>
Device.BeginInvokeOnMainThread(async () => await StartSetPasswordAsync());
_vm.TwoFactorAuthSuccessAction = () =>
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessAsync());
Device.BeginInvokeOnMainThread(async () => await TwoFactorAuthSuccessToMainAsync());
_vm.LockAction = () =>
Device.BeginInvokeOnMainThread(TwoFactorAuthSuccessWithSSOLocked);
_vm.UpdateTempPasswordAction =
() => Device.BeginInvokeOnMainThread(async () => await UpdateTempPasswordAsync());
_vm.StartDeviceApprovalOptionsAction =
() => Device.BeginInvokeOnMainThread(async () => await StartDeviceApprovalOptionsAsync());
_vm.CloseAction = async () => await Navigation.PopModalAsync();
DuoWebView = _duoWebView;
if (Device.RuntimePlatform == Device.Android)
@@ -180,21 +184,25 @@ namespace Bit.App.Pages
await Navigation.PushModalAsync(new NavigationPage(page));
}
private async Task TwoFactorAuthSuccessAsync()
private async Task StartDeviceApprovalOptionsAsync()
{
if (_authingWithSso)
var page = new LoginApproveDevicePage();
await Navigation.PushModalAsync(new NavigationPage(page));
}
private void TwoFactorAuthSuccessWithSSOLocked()
{
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
}
private async Task TwoFactorAuthSuccessToMainAsync()
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
Application.Current.MainPage = new NavigationPage(new LockPage(_appOptions));
}
else
{
if (AppHelpers.SetAlternateMainPage(_appOptions))
{
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
return;
}
var previousPage = await AppHelpers.ClearPreviousPage();
Application.Current.MainPage = new TabsPage(_appOptions, previousPage);
}
private void Token_TextChanged(object sender, TextChangedEventArgs e)

View File

@@ -11,6 +11,7 @@ using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Request;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Xamarin.CommunityToolkit.ObjectModel;
@@ -32,12 +33,12 @@ namespace Bit.App.Pages
private readonly IStateService _stateService;
private readonly II18nService _i18nService;
private readonly IAppIdService _appIdService;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly ILogger _logger;
private readonly IDeviceTrustCryptoService _deviceTrustCryptoService;
private TwoFactorProviderType? _selectedProviderType;
private string _totpInstruction;
private string _webVaultUrl = "https://vault.bitwarden.com";
private bool _authingWithSso = false;
private bool _enableContinue = false;
private bool _showContinue = true;
@@ -54,7 +55,9 @@ namespace Bit.App.Pages
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>();
_logger = ServiceContainer.Resolve<ILogger>();
_deviceTrustCryptoService = ServiceContainer.Resolve<IDeviceTrustCryptoService>();
PageTitle = AppResources.TwoStepLogin;
SubmitCommand = new Command(async () => await SubmitAsync());
@@ -69,6 +72,8 @@ namespace Bit.App.Pages
public bool Remember { get; set; }
public bool AuthingWithSso { get; set; }
public string Token { get; set; }
public bool DuoMethod => SelectedProviderType == TwoFactorProviderType.Duo ||
@@ -118,6 +123,8 @@ namespace Bit.App.Pages
public Command SubmitCommand { get; }
public ICommand MoreCommand { get; }
public Action TwoFactorAuthSuccessAction { get; set; }
public Action LockAction { get; set; }
public Action StartDeviceApprovalOptionsAction { get; set; }
public Action StartSetPasswordAction { get; set; }
public Action CloseAction { get; set; }
public Action UpdateTempPasswordAction { get; set; }
@@ -136,8 +143,6 @@ namespace Bit.App.Pages
return;
}
_authingWithSso = _authService.AuthingWithSso();
if (!string.IsNullOrWhiteSpace(_environmentService.BaseUrl))
{
_webVaultUrl = _environmentService.BaseUrl;
@@ -315,21 +320,84 @@ namespace Bit.App.Pages
var task = Task.Run(() => _syncService.FullSyncAsync(true));
await _deviceActionService.HideLoadingAsync();
var decryptOptions = await _stateService.GetAccountDecryptionOptions();
_messagingService.Send("listenYubiKeyOTP", false);
_broadcasterService.Unsubscribe(nameof(TwoFactorPage));
if (_authingWithSso && result.ResetMasterPassword)
if (decryptOptions?.TrustedDeviceOption != null)
{
if (await _deviceTrustCryptoService.IsDeviceTrustedAsync())
{
// If we have a device key but no keys on server, we need to remove the device key
if (decryptOptions.TrustedDeviceOption.EncryptedPrivateKey == null && decryptOptions.TrustedDeviceOption.EncryptedUserKey == null)
{
await _deviceTrustCryptoService.RemoveTrustedDeviceAsync();
StartDeviceApprovalOptionsAction?.Invoke();
return;
}
// If user doesn't have a MP, but has reset password permission, they must set a MP
if (!decryptOptions.HasMasterPassword &&
decryptOptions.TrustedDeviceOption.HasManageResetPasswordPermission)
{
StartSetPasswordAction?.Invoke();
return;
}
// Update temp password only if the device is trusted and therefore has a decrypted User Key set
if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
return;
}
// Device is trusted and has keys, so we can decrypt
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
return;
}
// Check for pending Admin Auth requests before navigating to device approval options
var pendingRequest = await _stateService.GetPendingAdminAuthRequestAsync();
if (pendingRequest != null)
{
var authRequest = await _authService.GetPasswordlessLoginRequestByIdAsync(pendingRequest.Id);
if (authRequest?.RequestApproved == true)
{
var authResult = await _authService.LogInPasswordlessAsync(true, await _stateService.GetActiveUserEmailAsync(), authRequest.RequestAccessCode, pendingRequest.Id, pendingRequest.PrivateKey, authRequest.Key, authRequest.MasterPasswordHash);
if (authResult == null && await _stateService.IsAuthenticatedAsync())
{
await Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(
() => _platformUtilsService.ShowToast("info", null, AppResources.LoginApproved));
await _stateService.SetPendingAdminAuthRequestAsync(null);
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
}
}
else
{
await _stateService.SetPendingAdminAuthRequestAsync(null);
StartDeviceApprovalOptionsAction?.Invoke();
}
}
else
{
StartDeviceApprovalOptionsAction?.Invoke();
}
return;
}
// In the standard, non TDE case, a user must set password if they don't
// have one and they aren't using key connector.
// Note: TDE & Key connector are mutually exclusive org config options.
if (result.ResetMasterPassword || (decryptOptions?.RequireSetPassword ?? false))
{
// TODO: We need to look into how to handle this when Org removes TDE
// Will we have the User Key by now to set a new password?
StartSetPasswordAction?.Invoke();
return;
}
else if (result.ForcePasswordReset)
{
UpdateTempPasswordAction?.Invoke();
}
else
{
TwoFactorAuthSuccessAction?.Invoke();
}
_syncService.FullSyncAsync(true).FireAndForget();
await TwoFactorAuthSuccessAsync();
}
catch (ApiException e)
{
@@ -398,7 +466,8 @@ namespace Bit.App.Pages
{
Email = _authService.Email,
MasterPasswordHash = _authService.MasterPasswordHash,
DeviceIdentifier = await _appIdService.GetAppIdAsync()
DeviceIdentifier = await _appIdService.GetAppIdAsync(),
SsoEmail2FaSessionToken = _authService.SsoEmail2FaSessionToken
};
await _apiService.PostTwoFactorEmailAsync(request);
if (showLoading)
@@ -422,5 +491,17 @@ namespace Bit.App.Pages
return false;
}
}
public async Task TwoFactorAuthSuccessAsync()
{
if (AuthingWithSso && await _vaultTimeoutService.IsLockedAsync())
{
LockAction?.Invoke();
}
else
{
TwoFactorAuthSuccessAction?.Invoke();
}
}
}
}

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"
@@ -48,7 +48,8 @@
<Label
Text="{Binding UpdateMasterPasswordWarningText }"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="UpdatePasswordWarningLabel" />
</Frame>
</Grid>
<Grid IsVisible="{Binding IsPolicyInEffect}"
@@ -71,7 +72,8 @@
<Label
Text="{Binding PolicySummary}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Start" />
HorizontalTextAlignment="Start"
AutomationId="PolicySummaryLabel" />
</Frame>
</Grid>
<Grid StyleClass="box-row" IsVisible="{Binding RequireCurrentPassword }">
@@ -96,7 +98,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="MasterPasswordField" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -106,7 +109,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="ToggleMasterPasswordVisibilityButton" />
</Grid>
<Grid StyleClass="box-row">
<Grid.RowDefinitions>
@@ -130,7 +134,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="NewPasswordField" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -140,7 +145,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="NewPasswordVisibilityButton" />
</Grid>
</StackLayout>
<StackLayout StyleClass="box">
@@ -166,7 +172,8 @@
IsTextPredictionEnabled="False"
IsPassword="{Binding ShowPassword, Converter={StaticResource inverseBool}}"
Grid.Row="1"
Grid.Column="0" />
Grid.Column="0"
AutomationId="RetypePasswordField" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -176,7 +183,8 @@
Grid.RowSpan="2"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}" />
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
AutomationId="ToggleRetypePasswordVisibilityButton" />
</Grid>
<StackLayout StyleClass="box-row">
<Label
@@ -187,7 +195,8 @@
Text="{Binding Hint}"
StyleClass="box-value"
ReturnType="Go"
ReturnCommand="{Binding SubmitCommand}" />
ReturnCommand="{Binding SubmitCommand}"
AutomationId="MasterPasswordHintLabel" />
</StackLayout>
<Label
Text="{u:I18n MasterPasswordHintDescription}"

View File

@@ -93,12 +93,12 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync();
// Create new key and hash new password
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
// Create new master key and hash new password
var masterKey = await _cryptoService.MakeMasterKeyAsync(MasterPassword, email, kdfConfig);
var masterPasswordHash = await _cryptoService.HashMasterKeyAsync(MasterPassword, masterKey);
// Create new encKey for the User
var newEncKey = await _cryptoService.RemakeEncKeyAsync(key);
// Encrypt user key with new master key
var (userKey, newProtectedUserKey) = await _cryptoService.EncryptUserKeyWithMasterKeyAsync(masterKey);
// Initiate API action
try
@@ -108,10 +108,10 @@ namespace Bit.App.Pages
switch (_reason)
{
case ForcePasswordResetReason.AdminForcePasswordReset:
await UpdateTempPasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
await UpdateTempPasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break;
case ForcePasswordResetReason.WeakMasterPasswordOnLogin:
await UpdatePasswordAsync(masterPasswordHash, newEncKey.Item2.EncryptedString);
await UpdatePasswordAsync(masterPasswordHash, newProtectedUserKey.EncryptedString);
break;
default:
throw new ArgumentOutOfRangeException();
@@ -155,7 +155,7 @@ namespace Bit.App.Pages
private async Task UpdatePasswordAsync(string newMasterPasswordHash, string newEncKey)
{
var currentPasswordHash = await _cryptoService.HashPasswordAsync(CurrentMasterPassword, null);
var currentPasswordHash = await _cryptoService.HashMasterKeyAsync(CurrentMasterPassword, null);
var request = new PasswordRequest
{

View File

@@ -150,6 +150,12 @@ namespace Bit.App.Pages
private async Task SaveActivityAsync()
{
SetServices();
if (await _stateService.GetActiveUserIdAsync() == null)
{
// Fresh install and/or all users logged out won't have an active user, skip saving last active time
return;
}
await _stateService.SetLastActiveTimeAsync(_deviceActionService.GetActiveTime());
}
}

View File

@@ -275,7 +275,7 @@
Margin="0,10,0,0"/>
<Entry IsVisible="{Binding ForwardedEmailServiceSelected, Converter={StaticResource enumToBool}, ConverterParameter={x:Static enums:ForwardedEmailServiceType.AnonAddy}}"
x:Name="_anonAddyDomainNameEntry"
Text="{Binding AnonAddyDomainName}"
Text="{Binding AddyIoDomainName}"
StyleClass="box-value"
AutomationId="AnonAddyDomainNameEntry" />
</StackLayout>

View File

@@ -545,7 +545,7 @@ namespace Bit.App.Pages
set => SetProperty(ref _showForwardedEmailApiSecret, value);
}
public string AnonAddyDomainName
public string AddyIoDomainName
{
get => _usernameOptions.AnonAddyDomainName;
set
@@ -553,7 +553,7 @@ namespace Bit.App.Pages
if (_usernameOptions.AnonAddyDomainName != value)
{
_usernameOptions.AnonAddyDomainName = value;
TriggerPropertyChanged(nameof(AnonAddyDomainName));
TriggerPropertyChanged(nameof(AddyIoDomainName));
SaveUsernameOptionsAsync(false).FireAndForget();
}
}
@@ -793,7 +793,7 @@ namespace Bit.App.Pages
TriggerPropertyChanged(nameof(CapitalizeRandomWordUsername));
TriggerPropertyChanged(nameof(ForwardedEmailApiSecret));
TriggerPropertyChanged(nameof(ForwardedEmailApiSecretLabel));
TriggerPropertyChanged(nameof(AnonAddyDomainName));
TriggerPropertyChanged(nameof(AddyIoDomainName));
TriggerPropertyChanged(nameof(CatchAllEmailDomain));
TriggerPropertyChanged(nameof(ForwardedEmailServiceSelected));
TriggerPropertyChanged(nameof(UsernameTypeSelected));

View File

@@ -175,7 +175,7 @@
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
HorizontalTextAlignment="Start"
AutomationId="SendNoFileChosenLabel" />
<Label
IsVisible="{Binding FileName, Converter={StaticResource notNull}}"
@@ -183,7 +183,7 @@
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center"
HorizontalTextAlignment="Start"
AutomationId="SendCurrentFileNameLabel" />
<Button
Text="{u:I18n ChooseFile}"
@@ -197,7 +197,7 @@
Text="{u:I18n MaxFileSize}"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Start" />
</StackLayout>
<Label
Text="{u:I18n TypeFileInfo}"
@@ -250,20 +250,6 @@
AutomationId="SendHideTextByDefaultToggle" />
</StackLayout>
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch">
<Label
Text="{Binding ShareOnSaveText}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding ShareOnSave}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0"
AutomationId="SendShareSendAfterSaveToggle" />
</StackLayout>
<StackLayout
Orientation="Horizontal"
Spacing="0"

View File

@@ -127,10 +127,9 @@ namespace Bit.App.Pages
public SendType? Type { get; set; }
public byte[] FileData { get; set; }
public string NewPassword { get; set; }
public bool ShareOnSave { get; set; }
public bool DisableHideEmailControl { get; set; }
public bool IsAddFromShare { get; set; }
public string ShareOnSaveText => CopyInsteadOfShareAfterSaving ? AppResources.CopySendLinkOnSave : AppResources.ShareOnSave;
public bool CopyInsteadOfShareAfterSaving { get; set; }
public string OptionsAccessilibityText => ShowOptions ? AppResources.OptionsExpanded : AppResources.OptionsCollapsed;
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
@@ -184,15 +183,6 @@ namespace Bit.App.Pages
}
}
}
public bool CopyInsteadOfShareAfterSaving
{
get => _copyInsteadOfShareAfterSaving;
set
{
SetProperty(ref _copyInsteadOfShareAfterSaving, value);
TriggerPropertyChanged(nameof(ShareOnSaveText));
}
}
public SendView Send
{
get => _send;
@@ -412,34 +402,25 @@ namespace Bit.App.Pages
_messagingService.Send("sendUpdated");
}
if (!ShareOnSave)
{
_platformUtilsService.ShowToast("success", null,
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
}
if (!CopyInsteadOfShareAfterSaving)
{
await CloseAsync();
}
if (ShareOnSave)
var savedSend = await _sendService.GetAsync(sendId);
if (savedSend != null)
{
var savedSend = await _sendService.GetAsync(sendId);
if (savedSend != null)
var savedSendView = await savedSend.DecryptAsync();
if (CopyInsteadOfShareAfterSaving)
{
var savedSendView = await savedSend.DecryptAsync();
if (CopyInsteadOfShareAfterSaving)
{
await AppHelpers.CopySendUrlAsync(savedSendView);
await AppHelpers.CopySendUrlAsync(savedSendView);
// wait so that the user sees the message before the view gets dismissed
await Task.Delay(1300);
}
else
{
await AppHelpers.ShareSendUrlAsync(savedSendView);
}
// wait so that the user sees the message before the view gets dismissed
await Task.Delay(1300);
}
else
{
await AppHelpers.ShareSendUrlAsync(savedSendView);
}
}

View File

@@ -94,13 +94,13 @@
LineBreakMode="CharacterWrap"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Start" />
<Label
Margin="0, 5, 0, 0"
Text="{u:I18n MaxFileSize}"
StyleClass="text-sm, text-muted"
HorizontalOptions="FillAndExpand"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Start" />
</StackLayout>
</StackLayout>
<StackLayout
@@ -145,19 +145,6 @@
Margin="10,0,0,0" />
</StackLayout>
</StackLayout>
<StackLayout
StyleClass="box-row, box-row-switch">
<Label
Text="{Binding ShareOnSaveText}"
StyleClass="box-label-regular"
VerticalOptions="Center"
HorizontalOptions="StartAndExpand" />
<Switch
IsToggled="{Binding ShareOnSave}"
IsEnabled="{Binding SendEnabled}"
HorizontalOptions="End"
Margin="10,0,0,0" />
</StackLayout>
<StackLayout
Orientation="Horizontal"
Spacing="0"

View File

@@ -0,0 +1,86 @@
<?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.BlockAutofillUrisPage"
xmlns:pages="clr-namespace:Bit.App.Pages"
xmlns:controls="clr-namespace:Bit.App.Controls"
xmlns:u="clr-namespace:Bit.App.Utilities"
xmlns:core="clr-namespace:Bit.Core;assembly=BitwardenCore"
x:DataType="pages:BlockAutofillUrisPageViewModel"
NavigationPage.HasBackButton="False"
Title="{u:I18n BlockAutoFill}">
<ContentPage.BindingContext>
<pages:BlockAutofillUrisPageViewModel />
</ContentPage.BindingContext>
<ContentPage.Resources>
<ResourceDictionary>
<u:InverseBoolConverter x:Key="inverseBool" />
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Orientation="Vertical">
<Image
x:Name="_emptyUrisPlaceholder"
HorizontalOptions="Center"
WidthRequest="120"
HeightRequest="120"
Margin="0,100,0,0"
IsVisible="{Binding ShowList, Converter={StaticResource inverseBool}}"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ThereAreNoBlockedURIs}" />
<controls:CustomLabel
StyleClass="box-label-regular"
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
FontWeight="500"
HorizontalTextAlignment="Center"
Margin="14,10,14,0"/>
<controls:ExtendedCollectionView
ItemsSource="{Binding BlockedUris}"
IsVisible="{Binding ShowList}"
VerticalOptions="FillAndExpand"
Margin="0,5,0,0"
SelectionMode="None"
StyleClass="list, list-platform"
ExtraDataForLogging="Blocked Autofill Uris"
AutomationId="BlockedUrisCellList">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="pages:BlockAutofillUriItemViewModel">
<StackLayout
Orientation="Vertical"
AutomationId="BlockedUriCell">
<StackLayout
Orientation="Horizontal">
<controls:CustomLabel
VerticalOptions="Center"
StyleClass="box-label-regular"
Text="{Binding Uri}"
MaxLines="2"
LineBreakMode="TailTruncation"
FontWeight="500"
Margin="15,0,0,0"
HorizontalOptions="StartAndExpand"/>
<controls:IconButton
StyleClass="box-row-button-muted, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.PencilSquare}}"
Command="{Binding EditUriCommand}"
Margin="5,0,15,0"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n EditURI}"
AutomationId="EditUriButton" />
</StackLayout>
<BoxView StyleClass="box-row-separator" />
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</controls:ExtendedCollectionView>
<Button
Text="{u:I18n NewBlockedURI}"
Command="{Binding AddUriCommand}"
VerticalOptions="End"
HeightRequest="40"
Opacity="0.8"
Margin="14,5,14,10"
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n NewBlockedURI}"
AutomationId="NewBlockedUriButton" />
</StackLayout>
</pages:BaseContentPage>

View File

@@ -0,0 +1,44 @@
using System.Threading.Tasks;
using Bit.App.Styles;
using Bit.App.Utilities;
using Bit.Core.Utilities;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public partial class BlockAutofillUrisPage : BaseContentPage, IThemeDirtablePage
{
private readonly BlockAutofillUrisPageViewModel _vm;
public BlockAutofillUrisPage()
{
InitializeComponent();
_vm = BindingContext as BlockAutofillUrisPageViewModel;
_vm.Page = this;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.InitAsync().FireAndForget(_ => Navigation.PopAsync());
UpdatePlaceholder();
}
public override async Task UpdateOnThemeChanged()
{
await base.UpdateOnThemeChanged();
UpdatePlaceholder();
}
private void UpdatePlaceholder()
{
MainThread.BeginInvokeOnMainThread(() =>
_emptyUrisPlaceholder.Source = ImageSource.FromFile(ThemeManager.UsingLightTheme ? "empty_uris_placeholder" : "empty_uris_placeholder_dark"));
}
}
}

View File

@@ -0,0 +1,186 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Bit.App.Pages
{
public class BlockAutofillUrisPageViewModel : BaseViewModel
{
private const char URI_SEPARARTOR = ',';
private const string URI_FORMAT = "https://domain.com";
private readonly IStateService _stateService;
private readonly IDeviceActionService _deviceActionService;
public BlockAutofillUrisPageViewModel()
{
_stateService = ServiceContainer.Resolve<IStateService>();
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>();
AddUriCommand = new AsyncCommand(AddUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
EditUriCommand = new AsyncCommand<BlockAutofillUriItemViewModel>(EditUriAsync,
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
public ObservableRangeCollection<BlockAutofillUriItemViewModel> BlockedUris { get; set; } = new ObservableRangeCollection<BlockAutofillUriItemViewModel>();
public bool ShowList => BlockedUris.Any();
public ICommand AddUriCommand { get; }
public ICommand EditUriCommand { get; }
public async Task InitAsync()
{
var blockedUrisList = await _stateService.GetAutofillBlacklistedUrisAsync();
if (blockedUrisList?.Any() != true)
{
return;
}
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.AddRange(blockedUrisList.OrderBy(uri => uri).Select(u => new BlockAutofillUriItemViewModel(u, EditUriCommand)).ToList());
TriggerPropertyChanged(nameof(ShowList));
});
}
private async Task AddUriAsync()
{
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
{
Title = AppResources.NewUri,
Subtitle = AppResources.EnterURI,
ValueSubInfo = string.Format(AppResources.FormatXSeparateMultipleURIsWithAComma, URI_FORMAT),
OkButtonText = AppResources.Save,
ValidateText = text => ValidateUris(text, true)
});
if (response?.Text is null)
{
return;
}
await MainThread.InvokeOnMainThreadAsync(() =>
{
foreach (var uri in response.Value.Text.Split(URI_SEPARARTOR).Where(s => !string.IsNullOrEmpty(s)))
{
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
}
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
TriggerPropertyChanged(nameof(BlockedUris));
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URISaved);
}
private async Task EditUriAsync(BlockAutofillUriItemViewModel uriItemViewModel)
{
var response = await _deviceActionService.DisplayValidatablePromptAsync(new Utilities.Prompts.ValidatablePromptConfig
{
Title = AppResources.EditURI,
Subtitle = AppResources.EnterURI,
Text = uriItemViewModel.Uri,
ValueSubInfo = string.Format(AppResources.FormatX, URI_FORMAT),
OkButtonText = AppResources.Save,
ThirdButtonText = AppResources.Remove,
ValidateText = text => ValidateUris(text, false)
});
if (response is null)
{
return;
}
if (response.Value.ExecuteThirdAction)
{
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.Remove(uriItemViewModel);
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URIRemoved);
return;
}
var cleanedUri = response.Value.Text.Replace(Environment.NewLine, string.Empty).Trim();
await MainThread.InvokeOnMainThreadAsync(() =>
{
BlockedUris.Remove(uriItemViewModel);
BlockedUris.Add(new BlockAutofillUriItemViewModel(cleanedUri, EditUriCommand));
BlockedUris = new ObservableRangeCollection<BlockAutofillUriItemViewModel>(BlockedUris.OrderBy(b => b.Uri));
TriggerPropertyChanged(nameof(BlockedUris));
TriggerPropertyChanged(nameof(ShowList));
});
await UpdateAutofillBlacklistedUrisAsync();
_deviceActionService.Toast(AppResources.URISaved);
}
private string ValidateUris(string uris, bool allowMultipleUris)
{
if (string.IsNullOrWhiteSpace(uris))
{
return string.Format(AppResources.FormatX, URI_FORMAT);
}
if (!allowMultipleUris && uris.Contains(URI_SEPARARTOR))
{
return AppResources.CannotEditMultipleURIsAtOnce;
}
foreach (var uri in uris.Split(URI_SEPARARTOR).Where(u => !string.IsNullOrWhiteSpace(u)))
{
var cleanedUri = uri.Replace(Environment.NewLine, string.Empty).Trim();
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
{
return AppResources.InvalidFormatUseHttpsHttpOrAndroidApp;
}
if (!Uri.TryCreate(cleanedUri, UriKind.Absolute, out var _))
{
return AppResources.InvalidURI;
}
if (BlockedUris.Any(uriItem => uriItem.Uri == cleanedUri))
{
return string.Format(AppResources.TheURIXIsAlreadyBlocked, cleanedUri);
}
}
return null;
}
private async Task UpdateAutofillBlacklistedUrisAsync()
{
await _stateService.SetAutofillBlacklistedUrisAsync(BlockedUris.Any() ? BlockedUris.Select(bu => bu.Uri).ToList() : null);
}
}
public class BlockAutofillUriItemViewModel : ExtendedViewModel
{
public BlockAutofillUriItemViewModel(string uri, ICommand editUriCommand)
{
Uri = uri;
EditUriCommand = new Command(() => editUriCommand.Execute(this));
}
public string Uri { get; }
public ICommand EditUriCommand { get; }
}
}

View File

@@ -38,7 +38,8 @@
<Label
Text="{u:I18n DisablePersonalVaultExportPolicyInEffect}"
StyleClass="text-muted, text-sm, text-bold"
HorizontalTextAlignment="Center" />
HorizontalTextAlignment="Center"
AutomationId="DisablePrivateVaultPolicyLabel" />
</Frame>
<Grid
RowSpacing="10"
@@ -55,7 +56,8 @@
SelectedIndex="{Binding FileFormatSelectedIndex}"
SelectedIndexChanged="FileFormat_Changed"
StyleClass="box-value"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}" />
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
AutomationId="FileFormatPicker" />
</StackLayout>
<StackLayout
StyleClass="box-row"
@@ -72,7 +74,8 @@
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
Margin="0,0,0,10"/>
Margin="0,0,0,10"
AutomationId="SendTOTPCodeButton" />
</StackLayout>
<Grid
StyleClass="box-row"
@@ -96,7 +99,8 @@
Grid.Row="1"
Grid.Column="0"
ReturnType="Go"
ReturnCommand="{Binding ExportVaultCommand}" />
ReturnCommand="{Binding ExportVaultCommand}"
AutomationId="MasterPasswordEntry" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding ShowPasswordIcon}"
@@ -106,7 +110,8 @@
AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"/>
IsVisible="{Binding UseOTPVerification, Converter={StaticResource inverseBool}}"
AutomationId="TogglePasswordVisibilityButton" />
<Label
Text="{u:I18n ConfirmYourIdentity}"
StyleClass="box-footer-label"
@@ -128,7 +133,8 @@
Clicked="ExportVault_Clicked"
HorizontalOptions="Fill"
VerticalOptions="End"
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"/>
IsEnabled="{Binding DisablePrivateVaultPolicyEnabled, Converter={StaticResource inverseBool}}"
AutomationId="ExportVaultButton" />
</StackLayout>
</StackLayout>
</ScrollView>

View File

@@ -21,7 +21,6 @@ namespace Bit.App.Pages
private readonly II18nService _i18nService;
private readonly IExportService _exportService;
private readonly IPolicyService _policyService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly IApiService _apiService;
private readonly ILogger _logger;
@@ -45,8 +44,7 @@ namespace Bit.App.Pages
_i18nService = ServiceContainer.Resolve<II18nService>("i18nService");
_exportService = ServiceContainer.Resolve<IExportService>("exportService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>("userVerificationService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
_apiService = ServiceContainer.Resolve<IApiService>("apiService");
_logger = ServiceContainer.Resolve<ILogger>("logger");
@@ -67,7 +65,7 @@ namespace Bit.App.Pages
_initialized = true;
FileFormatSelectedIndex = FileFormatOptions.FindIndex(k => k.Key == "json");
DisablePrivateVaultPolicyEnabled = await _policyService.PolicyAppliesToUser(PolicyType.DisablePersonalVaultExport);
UseOTPVerification = await _keyConnectorService.GetUsesKeyConnector();
UseOTPVerification = !await _userVerificationService.HasMasterPasswordAsync(true);
if (UseOTPVerification)
{
@@ -165,9 +163,9 @@ namespace Bit.App.Pages
return;
}
var verificationType = await _keyConnectorService.GetUsesKeyConnector()
? VerificationType.OTP
: VerificationType.MasterPassword;
var verificationType = await _userVerificationService.HasMasterPasswordAsync(true)
? VerificationType.MasterPassword
: VerificationType.OTP;
if (!await _userVerificationService.VerifyUser(Secret, verificationType))
{
return;

View File

@@ -32,7 +32,8 @@
Padding="10, 0"
RowSpacing="0"
RowDefinitions="*, Auto, *, 10"
ColumnDefinitions="*, *">
ColumnDefinitions="*, *"
AutomationId="LoginRequestCell">
<Label
Text="{u:I18n FingerprintPhrase}"
FontSize="Small"
@@ -45,20 +46,23 @@
FontSize="Small"
Padding="0, 5, 0, 10"
VerticalTextAlignment="Center"
TextColor="{DynamicResource FingerprintPhrase}"/>
TextColor="{DynamicResource FingerprintPhrase}"
AutomationId="FingerprintPhraseLabel" />
<Label
Grid.Row="2"
HorizontalOptions="Start"
HorizontalTextAlignment="Start"
Text="{Binding RequestDeviceType}"
StyleClass="list-header-sub" />
StyleClass="list-header-sub"
AutomationId="RequestDeviceLabel" />
<Label
Grid.Row="2"
Grid.Column="1"
HorizontalOptions="End"
HorizontalTextAlignment="End"
Text="{Binding CreationDate, Converter={StaticResource dateTime}}"
StyleClass="list-header-sub" />
StyleClass="list-header-sub"
AutomationId="RequestDateLabel" />
<BoxView
StyleClass="list-section-separator-top, list-section-separator-top-platform"
VerticalOptions="End"
@@ -94,7 +98,8 @@
Margin="10,0"
Icon="{Binding Source={x:Static core:BitwardenIcons.Trash}}"
Label="{u:I18n DeclineAllRequests}"
ButtonCommand="{Binding DeclineAllRequestsCommand}"/>
ButtonCommand="{Binding DeclineAllRequestsCommand}"
AutomationId="DeleteAllRequestsButton" />
</StackLayout>
</ResourceDictionary>
</ContentPage.Resources>

View File

@@ -153,22 +153,14 @@
StyleClass="box-footer-label, box-footer-label-switch" />
</StackLayout>
<StackLayout StyleClass="box" IsVisible="{Binding ShowAndroidAutofillSettings}">
<StackLayout StyleClass="box-row, box-row-input">
<Label
Text="{u:I18n AutofillBlockedUris}"
StyleClass="box-label" />
<Editor
x:Name="_autofillBlockedUrisEditor"
Text="{Binding AutofillBlockedUris}"
StyleClass="box-value"
AutoSize="TextChanges"
IsSpellCheckEnabled="False"
IsTextPredictionEnabled="False"
Keyboard="Url"
Unfocused="AutofillBlockedUrisEditor_Unfocused" />
</StackLayout>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Command="{Binding GoToBlockAutofillUrisCommand}" />
</StackLayout.GestureRecognizers>
<Label
Text="{u:I18n AutofillBlockedUrisDescription}"
Text="{u:I18n BlockAutoFill}"
StyleClass="box-label-regular" />
<Label
Text="{u:I18n AutoFillWillNotBeOfferedForTheseURIs}"
StyleClass="box-footer-label" />
</StackLayout>
</StackLayout>

View File

@@ -1,6 +1,4 @@
using Bit.App.Abstractions;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Xamarin.Forms;
using Xamarin.Forms.PlatformConfiguration;
@@ -44,17 +42,6 @@ namespace Bit.App.Pages
await _vm.InitAsync();
}
protected async override void OnDisappearing()
{
base.OnDisappearing();
await _vm.UpdateAutofillBlockedUris();
}
private async void AutofillBlockedUrisEditor_Unfocused(object sender, FocusEventArgs e)
{
await _vm.UpdateAutofillBlockedUris();
}
private async void Close_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())

View File

@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
using Xamarin.Forms;
namespace Bit.App.Pages
@@ -19,7 +20,6 @@ namespace Bit.App.Pages
private readonly IPlatformUtilsService _platformUtilsService;
private bool _autofillSavePrompt;
private string _autofillBlockedUris;
private bool _favicon;
private bool _autoTotpCopy;
private int _clearClipboardSelectedIndex;
@@ -84,6 +84,10 @@ namespace Bit.App.Pages
new KeyValuePair<string, string>(null, AppResources.DefaultSystem)
};
LocalesOptions.AddRange(_i18nService.LocaleNames.ToList());
GoToBlockAutofillUrisCommand = new AsyncCommand(() => Page.Navigation.PushAsync(new BlockAutofillUrisPage()),
onException: ex => HandleException(ex),
allowsMultipleExecutions: false);
}
public List<KeyValuePair<int?, string>> ClearClipboardOptions { get; set; }
@@ -192,25 +196,18 @@ namespace Bit.App.Pages
}
}
public string AutofillBlockedUris
{
get => _autofillBlockedUris;
set => SetProperty(ref _autofillBlockedUris, value);
}
public bool ShowAndroidAutofillSettings
{
get => _showAndroidAutofillSettings;
set => SetProperty(ref _showAndroidAutofillSettings, value);
}
public ICommand GoToBlockAutofillUrisCommand { get; }
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();
@@ -288,41 +285,6 @@ namespace Bit.App.Pages
}
}
public async Task UpdateAutofillBlockedUris()
{
if (_inited)
{
if (string.IsNullOrWhiteSpace(AutofillBlockedUris))
{
await _stateService.SetAutofillBlacklistedUrisAsync(null);
AutofillBlockedUris = null;
return;
}
try
{
var csv = AutofillBlockedUris;
var urisList = new List<string>();
foreach (var uri in csv.Split(','))
{
if (string.IsNullOrWhiteSpace(uri))
{
continue;
}
var cleanedUri = uri.Replace(System.Environment.NewLine, string.Empty).Trim();
if (!cleanedUri.StartsWith("http://") && !cleanedUri.StartsWith("https://") &&
!cleanedUri.StartsWith(Constants.AndroidAppProtocol))
{
continue;
}
urisList.Add(cleanedUri);
}
await _stateService.SetAutofillBlacklistedUrisAsync(urisList);
AutofillBlockedUris = string.Join(", ", urisList);
}
catch { }
}
}
private async Task UpdateCurrentLocaleAsync()
{
if (!_inited)

View File

@@ -29,7 +29,7 @@ namespace Bit.App.Pages
private readonly IBiometricService _biometricService;
private readonly IPolicyService _policyService;
private readonly ILocalizeService _localizeService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private readonly IClipboardService _clipboardService;
private readonly ILogger _loggerService;
private readonly IPushNotificationService _pushNotificationService;
@@ -48,6 +48,7 @@ namespace Bit.App.Pages
private bool _reportLoggingEnabled;
private bool _approvePasswordlessLoginRequests;
private bool _shouldConnectToWatch;
private bool _hasMasterPassword;
private readonly static List<KeyValuePair<string, int?>> VaultTimeoutOptions =
new List<KeyValuePair<string, int?>>
{
@@ -88,7 +89,7 @@ namespace Bit.App.Pages
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_localizeService = ServiceContainer.Resolve<ILocalizeService>("localizeService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
_clipboardService = ServiceContainer.Resolve<IClipboardService>("clipboardService");
_loggerService = ServiceContainer.Resolve<ILogger>("logger");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
@@ -100,12 +101,17 @@ namespace Bit.App.Pages
ExecuteSettingItemCommand = new AsyncCommand<SettingsPageListItem>(item => item.ExecuteAsync(), onException: _loggerService.Exception, allowsMultipleExecutions: false);
}
private bool IsVaultTimeoutActionLockAllowed => _hasMasterPassword || _biometric || _pin;
public ObservableRangeCollection<ISettingsPageListItem> GroupedItems { get; set; }
public IAsyncCommand<SettingsPageListItem> ExecuteSettingItemCommand { get; }
public async Task InitAsync()
{
var decryptionOptions = await _stateService.GetAccountDecryptionOptions();
// set has true for backwards compatibility
_hasMasterPassword = decryptionOptions?.HasMasterPassword ?? true;
_supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync != null)
@@ -124,8 +130,17 @@ namespace Bit.App.Pages
_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;
var pinSet = await _vaultTimeoutService.GetPinLockTypeAsync();
_pin = pinSet != PinLockType.Disabled;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
var timeoutAction = await _vaultTimeoutService.GetVaultTimeoutAction() ?? VaultTimeoutAction.Lock;
if (!IsVaultTimeoutActionLockAllowed && timeoutAction == VaultTimeoutAction.Lock)
{
timeoutAction = VaultTimeoutAction.Logout;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
}
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.FirstOrDefault(o => o.Value == timeoutAction).Key;
if (await _policyService.PolicyAppliesToUser(PolicyType.MaximumVaultTimeout))
{
@@ -137,10 +152,6 @@ namespace Bit.App.Pages
(t.Value > 0 || t.Value == CustomVaultTimeoutValue) &&
t.Value != null).ToList();
}
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2;
_biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
_screenCaptureAllowed = await _stateService.GetScreenCaptureAllowedAsync();
if (_vaultTimeoutDisplayValue == null)
@@ -148,8 +159,7 @@ namespace Bit.App.Pages
_vaultTimeoutDisplayValue = AppResources.Custom;
}
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() &&
!await _keyConnectorService.GetUsesKeyConnector();
_showChangeMasterPassword = IncludeLinksWithSubscriptionInfo() && await _userVerificationService.HasMasterPasswordAsync();
_reportLoggingEnabled = await _loggerService.IsEnabled();
_approvePasswordlessLoginRequests = await _stateService.GetApprovePasswordlessLoginsAsync();
_shouldConnectToWatch = await _stateService.GetShouldConnectToWatchAsync();
@@ -323,6 +333,7 @@ namespace Bit.App.Pages
}
if (oldTimeout != newTimeout)
{
await _cryptoService.RefreshKeysAsync();
await Device.InvokeOnMainThreadAsync(BuildList);
}
}
@@ -387,8 +398,11 @@ namespace Bit.App.Pages
// do nothing if we have a policy set
return;
}
var options = _vaultTimeoutActionOptions.Select(o =>
o.Key == _vaultTimeoutActionDisplayValue ? $"✓ {o.Key}" : o.Key).ToArray();
var options = IsVaultTimeoutActionLockAllowed
? _vaultTimeoutActionOptions.Select(o => CreateSelectableOption(o.Key, _vaultTimeoutActionDisplayValue == o.Key)).ToArray()
: _vaultTimeoutActionOptions.Where(o => o.Value == VaultTimeoutAction.Logout).Select(v => ToSelectedOption(v.Key)).ToArray();
var selection = await Page.DisplayActionSheet(AppResources.VaultTimeoutAction,
AppResources.Cancel, null, options);
if (selection == null || selection == AppResources.Cancel)
@@ -428,7 +442,7 @@ namespace Bit.App.Pages
if (!string.IsNullOrWhiteSpace(pin))
{
var masterPassOnRestart = false;
if (!await _keyConnectorService.GetUsesKeyConnector())
if (await _userVerificationService.HasMasterPasswordAsync())
{
masterPassOnRestart = await _platformUtilsService.ShowDialogAsync(
AppResources.PINRequireMasterPasswordRestart, AppResources.UnlockWithPIN,
@@ -437,19 +451,20 @@ namespace Bit.App.Pages
var kdfConfig = await _stateService.GetActiveUserCustomDataAsync(a => new KdfConfig(a?.Profile));
var email = await _stateService.GetEmailAsync();
var pinKey = await _cryptoService.MakePinKeyAysnc(pin, email, kdfConfig);
var key = await _cryptoService.GetKeyAsync();
var pinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
var pinKey = await _cryptoService.MakePinKeyAsync(pin, email, kdfConfig);
var userKey = await _cryptoService.GetUserKeyAsync();
var protectedPinKey = await _cryptoService.EncryptAsync(userKey.Key, pinKey);
var encPin = await _cryptoService.EncryptAsync(pin);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
if (masterPassOnRestart)
{
var encPin = await _cryptoService.EncryptAsync(pin);
await _stateService.SetProtectedPinAsync(encPin.EncryptedString);
await _stateService.SetPinProtectedKeyAsync(pinProtectedKey);
await _stateService.SetPinKeyEncryptedUserKeyEphemeralAsync(protectedPinKey);
}
else
{
await _stateService.SetPinProtectedAsync(pinProtectedKey.EncryptedString);
await _stateService.SetPinKeyEncryptedUserKeyAsync(protectedPinKey);
}
}
else
@@ -459,8 +474,8 @@ namespace Bit.App.Pages
}
if (!_pin)
{
await _cryptoService.ClearPinProtectedKeyAsync();
await _vaultTimeoutService.ClearAsync();
await UpdateVaultTimeoutActionIfNeededAsync();
}
BuildList();
}
@@ -489,9 +504,10 @@ namespace Bit.App.Pages
else
{
await _stateService.SetBiometricUnlockAsync(null);
await UpdateVaultTimeoutActionIfNeededAsync();
}
await _stateService.SetBiometricLockedAsync(false);
await _cryptoService.ToggleKeyAsync();
await _cryptoService.RefreshKeysAsync();
BuildList();
}
@@ -835,9 +851,11 @@ namespace Bit.App.Pages
return _vaultTimeoutOptions.FirstOrDefault(o => o.Key == key).Value;
}
private string CreateSelectableOption(string option, bool selected) => selected ? $"✓ {option}" : option;
private string CreateSelectableOption(string option, bool selected) => selected ? ToSelectedOption(option) : option;
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == $"✓ {compareTo}";
private bool CompareSelection(string selection, string compareTo) => selection == compareTo || selection == ToSelectedOption(compareTo);
private string ToSelectedOption(string option) => $"✓ {option}";
public async Task SetScreenCaptureAllowedAsync()
{
@@ -869,5 +887,17 @@ namespace Bit.App.Pages
await _watchDeviceService.SetShouldConnectToWatchAsync(_shouldConnectToWatch);
BuildList();
}
private async Task UpdateVaultTimeoutActionIfNeededAsync()
{
if (IsVaultTimeoutActionLockAllowed)
{
return;
}
_vaultTimeoutActionDisplayValue = _vaultTimeoutActionOptions.First(o => o.Value == VaultTimeoutAction.Logout).Key;
await _vaultTimeoutService.SetVaultTimeoutOptionsAsync(_vaultTimeout, VaultTimeoutAction.Logout);
_deviceActionService.Toast(AppResources.VaultTimeoutActionChangedToLogOut);
}
}
}

View File

@@ -94,7 +94,7 @@ namespace Bit.App.Pages
}
});
await UpdateVaultButtonTitleAsync();
if (await _keyConnectorService.UserNeedsMigration())
if (await _keyConnectorService.UserNeedsMigrationAsync())
{
_messagingService.Send("convertAccountToKeyConnector");
}

View File

@@ -74,7 +74,7 @@ namespace Bit.App.Pages
_cipherDomain = await _cipherService.GetAsync(CipherId);
Cipher = await _cipherDomain.DecryptAsync();
LoadAttachments();
_hasUpdatedKey = await _cryptoService.HasEncKeyAsync();
_hasUpdatedKey = await _cryptoService.HasUserKeyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
_canAccessAttachments = canAccessPremium || Cipher.OrganizationId != null;
if (!_canAccessAttachments)
@@ -156,7 +156,7 @@ namespace Bit.App.Pages
// Prevent Android from locking if vault timeout set to "immediate"
if (Device.RuntimePlatform == Device.Android)
{
_vaultTimeoutService.DelayTimeoutMs = 60000;
_vaultTimeoutService.DelayLockAndLogoutMs = 60000;
}
await _fileService.SelectFileAsync();
}

View File

@@ -82,7 +82,7 @@ namespace Bit.App.Pages
return;
}
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
return;
}

View File

@@ -37,6 +37,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _cipher, value, additionalPropertyNames: AdditionalPropertiesToRaiseOnCipherChanged);
}
public string CreationDate => string.Format(AppResources.CreatedXY, Cipher.CreationDate.ToShortDateString(), Cipher.CreationDate.ToShortTimeString());
public AsyncCommand CheckPasswordCommand { get; }
protected async Task CheckPasswordAsync()

View File

@@ -223,6 +223,17 @@
AutomationId="RegeneratePasswordButton" />
</Grid>
<Label
Text="{u:I18n Passkey}"
StyleClass="box-label"
Margin="0,10,0,0"
IsVisible="{Binding ShowPasskeyInfo}"/>
<Entry
Text="{Binding CreationDate}"
IsEnabled="False"
StyleClass="box-value,text-muted"
IsVisible="{Binding ShowPasskeyInfo}" />
<Grid StyleClass="box-row, box-row-input">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />

View File

@@ -21,7 +21,7 @@ namespace Bit.App.Pages
private readonly IDeviceActionService _deviceActionService;
private readonly IAutofillHandler _autofillHandler;
private readonly IVaultTimeoutService _vaultTimeoutService;
private readonly IKeyConnectorService _keyConnectorService;
private readonly IUserVerificationService _userVerificationService;
private CipherAddEditPageViewModel _vm;
private bool _fromAutofill;
@@ -43,7 +43,7 @@ namespace Bit.App.Pages
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_userVerificationService = ServiceContainer.Resolve<IUserVerificationService>();
_appOptions = appOptions;
_fromAutofill = fromAutofill;
@@ -175,8 +175,8 @@ namespace Bit.App.Pages
RequestFocus(_nameEntry);
}
});
// Hide password reprompt option if using key connector
_passwordPrompt.IsVisible = !await _keyConnectorService.GetUsesKeyConnector();
_passwordPrompt.IsVisible = await _userVerificationService.HasMasterPasswordAsync();
}
protected override void OnDisappearing()

View File

@@ -88,7 +88,6 @@ namespace Bit.App.Pages
_watchDeviceService = ServiceContainer.Resolve<IWatchDeviceService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>();
GeneratePasswordCommand = new Command(GeneratePassword);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
@@ -309,6 +308,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 bool ShowPasskeyInfo => Cipher?.HasFido2Key == true && !CloneMode;
public void Init()
{
@@ -367,6 +367,11 @@ namespace Bit.App.Pages
{
Cipher.OrganizationId = OrganizationId;
}
if (Cipher.Type == CipherType.Login)
{
// passkeys can't be cloned
Cipher.Login.Fido2Keys = null;
}
}
if (appOptions?.OtpData != null && Cipher.Type == CipherType.Login)
{

View File

@@ -45,7 +45,7 @@
x:Name="_attachmentsItem" x:Key="attachmentsItem" />
<ToolbarItem Text="{u:I18n Delete}" Clicked="Delete_Clicked" Order="Secondary" IsDestructive="True"
x:Name="_deleteItem" x:Key="deleteItem" />
<ToolbarItem Text="{u:I18n Clone}" Clicked="Clone_Clicked" Order="Secondary"
<ToolbarItem Text="{u:I18n Clone}" Command="{Binding CloneCommand}" Order="Secondary"
x:Name="_cloneItem" x:Key="cloneItem" />
<DataTemplate x:Key="TextCustomFieldDataTemplate">
@@ -179,7 +179,7 @@
AutomationProperties.Name="{u:I18n ToggleVisibility}"
AutomationProperties.HelpText="{Binding PasswordVisibilityAccessibilityText}"
IsVisible="{Binding Cipher.ViewPassword}"
AutomationId="ViewValueButton" />
AutomationId="ShowValueButton" />
<controls:IconButton
StyleClass="box-row-button, box-row-button-platform"
Text="{Binding Source={x:Static core:BitwardenIcons.Clone}}"
@@ -195,6 +195,16 @@
</Grid>
<BoxView StyleClass="box-row-separator"
IsVisible="{Binding Cipher.Login.Password, Converter={StaticResource stringHasValue}}" />
<Label
Text="{u:I18n Passkey}"
StyleClass="box-label"
Margin="0,10,0,0"
IsVisible="{Binding Cipher.Login.MainFido2Key, Converter={StaticResource notNull}}"/>
<Entry
Text="{Binding CreationDate}"
IsEnabled="False"
StyleClass="box-value,text-muted"
IsVisible="{Binding Cipher.Login.MainFido2Key, Converter={StaticResource notNull}}" />
<Grid StyleClass="box-row"
IsVisible="{Binding ShowTotp}"
AutomationId="ItemRow">

View File

@@ -204,20 +204,7 @@ namespace Bit.App.Pages
}
}
private async void Clone_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
if (!await _vm.PromptPasswordAsync())
{
return;
}
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
}
}
private async void More_Clicked(object sender, System.EventArgs e)
private async void More_Clicked(object sender, EventArgs e)
{
if (!DoOnce())
{
@@ -227,7 +214,11 @@ namespace Bit.App.Pages
var options = new List<string> { AppResources.Attachments };
if (_vm.Cipher.OrganizationId == null)
{
options.Add(AppResources.Clone);
if (_vm.CanClone)
{
options.Add(AppResources.Clone);
}
options.Add(AppResources.MoveToOrganization);
}
else
@@ -267,8 +258,7 @@ namespace Bit.App.Pages
}
else if (selection == AppResources.Clone)
{
var page = new CipherAddEditPage(_vm.CipherId, cloneMode: true, cipherDetailsPage: this);
await Navigation.PushModalAsync(new NavigationPage(page));
_vm.CloneCommand.Execute(null);
}
}
@@ -302,13 +292,13 @@ namespace Bit.App.Pages
{
ToolbarItems.Remove(_collectionsItem);
}
if (!ToolbarItems.Contains(_cloneItem))
if (_vm.CanClone && !ToolbarItems.Contains(_cloneItem))
{
ToolbarItems.Insert(1, _cloneItem);
}
if (!ToolbarItems.Contains(_shareItem))
{
ToolbarItems.Insert(2, _shareItem);
ToolbarItems.Insert(_vm.CanClone ? 2 : 1, _shareItem);
}
}
else

View File

@@ -68,7 +68,8 @@ namespace Bit.App.Pages
CopyCommand = new AsyncCommand<string>((id) => CopyAsync(id, null), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyUriCommand = new AsyncCommand<LoginUriView>(uriView => CopyAsync("LoginUri", uriView.Uri), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
CopyFieldCommand = new AsyncCommand<FieldView>(field => CopyAsync(field.Type == FieldType.Hidden ? "H_FieldValue" : "FieldValue", field.Value), onException: ex => _logger.Exception(ex), allowsMultipleExecutions: false);
LaunchUriCommand = new Command<LoginUriView>(LaunchUri);
LaunchUriCommand = new Command<ILaunchableView>(LaunchUri);
CloneCommand = new AsyncCommand(CloneAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false);
TogglePasswordCommand = new Command(TogglePassword);
ToggleCardNumberCommand = new Command(ToggleCardNumber);
ToggleCardCodeCommand = new Command(ToggleCardCode);
@@ -81,6 +82,7 @@ namespace Bit.App.Pages
public ICommand CopyUriCommand { get; set; }
public ICommand CopyFieldCommand { get; set; }
public Command LaunchUriCommand { get; set; }
public ICommand CloneCommand { get; set; }
public Command TogglePasswordCommand { get; set; }
public Command ToggleCardNumberCommand { get; set; }
public Command ToggleCardCodeCommand { get; set; }
@@ -246,6 +248,7 @@ namespace Bit.App.Pages
public double TotpProgress => string.IsNullOrEmpty(TotpSec) ? 0 : double.Parse(TotpSec) * 100 / _totpInterval;
public bool IsDeleted => Cipher.IsDeleted;
public bool CanEdit => !Cipher.IsDeleted;
public bool CanClone => Cipher.IsClonable;
public async Task<bool> LoadAsync(Action finishedLoadingAction = null)
{
@@ -668,22 +671,43 @@ namespace Bit.App.Pages
}
}
private void LaunchUri(LoginUriView uri)
private void LaunchUri(ILaunchableView launchableView)
{
if (uri.CanLaunch && (Page as BaseContentPage).DoOnce())
if (launchableView.CanLaunch && (Page as BaseContentPage).DoOnce())
{
_platformUtilsService.LaunchUri(uri.LaunchUri);
_platformUtilsService.LaunchUri(launchableView.LaunchUri);
}
}
private async Task CloneAsync()
{
if (!await CanCloneAsync() || !await PromptPasswordAsync())
{
return;
}
var page = new CipherAddEditPage(CipherId, cloneMode: true, cipherDetailsPage: Page as CipherDetailsPage);
await Page.Navigation.PushModalAsync(new NavigationPage(page));
}
public async Task<bool> PromptPasswordAsync()
{
if (Cipher.Reprompt == CipherRepromptType.None || _passwordReprompted)
if (_passwordReprompted)
{
return true;
}
return _passwordReprompted = await _passwordRepromptService.ShowPasswordPromptAsync();
return _passwordReprompted = await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Cipher.Reprompt);
}
private async Task<bool> CanCloneAsync()
{
if (!Cipher.HasFido2Key)
{
return true;
}
return await _platformUtilsService.ShowDialogAsync(AppResources.ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem, AppResources.PasskeyWillNotBeCopied, AppResources.Yes, AppResources.No);
}
}
}

View File

@@ -191,7 +191,7 @@ namespace Bit.App.Pages
if (_appOptions?.OtpData != null)
{
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
return;
}
@@ -208,6 +208,11 @@ namespace Bit.App.Pages
}
else if (selection == AppResources.Autofill || selection == AppResources.AutofillAndSave)
{
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
return;
}
if (selection == AppResources.AutofillAndSave)
{
var uris = cipher.Login?.Uris?.ToList();

View File

@@ -235,34 +235,17 @@ namespace Bit.App.Pages
{
AddTotpGroupItem(groupedItems, uppercaseGroupNames);
groupedItems.Add(new GroupingsPageListGroup(
AppResources.Types, 4, uppercaseGroupNames, !hasFavorites)
var types = new CipherType[] { CipherType.Login, CipherType.Card, CipherType.Identity, CipherType.SecureNote };
var typesGroup = new GroupingsPageListGroup(AppResources.Types, types.Length, uppercaseGroupNames, !hasFavorites);
foreach (CipherType t in types)
{
new GroupingsPageListItem
typesGroup.Add(new GroupingsPageListItem
{
Type = CipherType.Login,
ItemCount = (_typeCounts.ContainsKey(CipherType.Login) ?
_typeCounts[CipherType.Login] : 0).ToString("N0")
},
new GroupingsPageListItem
{
Type = CipherType.Card,
ItemCount = (_typeCounts.ContainsKey(CipherType.Card) ?
_typeCounts[CipherType.Card] : 0).ToString("N0")
},
new GroupingsPageListItem
{
Type = CipherType.Identity,
ItemCount = (_typeCounts.ContainsKey(CipherType.Identity) ?
_typeCounts[CipherType.Identity] : 0).ToString("N0")
},
new GroupingsPageListItem
{
Type = CipherType.SecureNote,
ItemCount = (_typeCounts.ContainsKey(CipherType.SecureNote) ?
_typeCounts[CipherType.SecureNote] : 0).ToString("N0")
},
});
Type = t,
ItemCount = _typeCounts.GetValueOrDefault(t).ToString("N0")
});
}
groupedItems.Add(typesGroup);
}
if (NestedFolders?.Any() ?? false)
{
@@ -330,13 +313,16 @@ namespace Bit.App.Pages
items.AddRange(itemGroup);
}
if (Device.RuntimePlatform == Device.iOS)
Device.BeginInvokeOnMainThread(() =>
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(items);
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(items);
});
}
else
{
@@ -356,21 +342,24 @@ namespace Bit.App.Pages
items.AddRange(itemGroup);
}
if (groupedItems.Any())
Device.BeginInvokeOnMainThread(() =>
{
if (Device.RuntimePlatform == Device.iOS)
if (groupedItems.Any())
{
if (Device.RuntimePlatform == Device.iOS)
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{
// HACK: [PS-536] Fix to avoid blank list after back navigation on unlocking with previous page info
// because of update to XF v5.0.0.2401
GroupedItems.Clear();
}
GroupedItems.ReplaceRange(new List<IGroupingsPageListItem> { new GroupingsPageHeaderListItem(groupedItems[0].Name, groupedItems[0].ItemCount) });
GroupedItems.AddRange(items);
}
else
{
GroupedItems.Clear();
}
});
}
}
finally
@@ -378,9 +367,12 @@ namespace Bit.App.Pages
_doingLoad = false;
Loaded = true;
Loading = false;
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
ShowList = !ShowNoData;
DisableRefreshing();
Device.BeginInvokeOnMainThread(() =>
{
ShowNoData = (MainPage && !HasCiphers) || !groupedItems.Any();
ShowList = !ShowNoData;
DisableRefreshing();
});
}
}
@@ -575,7 +567,9 @@ namespace Bit.App.Pages
}
else if (Type != null)
{
Filter = c => c.Type == Type.Value && !c.IsDeleted;
Filter = c => !c.IsDeleted
&&
Type.Value == c.Type;
}
else if (FolderId != null)
{
@@ -642,14 +636,9 @@ namespace Bit.App.Pages
NoFolderCiphers.Add(c);
}
if (_typeCounts.ContainsKey(c.Type))
{
_typeCounts[c.Type] = _typeCounts[c.Type] + 1;
}
else
{
_typeCounts.Add(c.Type, 1);
}
_typeCounts[c.Type] = _typeCounts.TryGetValue(c.Type, out var currentTypeCount)
? currentTypeCount + 1
: 1;
}
if (c.IsDeleted)

View File

@@ -60,7 +60,7 @@ namespace Bit.App.Pages
var cipher = listItem.Cipher;
if (cipher.Reprompt != CipherRepromptType.None && !await _passwordRepromptService.ShowPasswordPromptAsync())
if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(cipher.Reprompt))
{
return;
}

View File

@@ -15,7 +15,7 @@
<ContentPage.ToolbarItems>
<ToolbarItem Text="{u:I18n Cancel}" Clicked="Close_Clicked" Order="Primary" Priority="-1" />
<ToolbarItem Text="{u:I18n Move}" Clicked="Save_Clicked" />
<ToolbarItem Text="{u:I18n Move}" Command="{Binding MoveCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Resources>

View File

@@ -32,19 +32,6 @@ namespace Bit.App.Pages
await LoadOnAppearedAsync(_scrollView, true, () => _vm.LoadAsync());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
}
private async void Save_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())
{
await _vm.SubmitAsync();
}
}
private async void Close_Clicked(object sender, System.EventArgs e)
{
if (DoOnce())

View File

@@ -1,6 +1,7 @@
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;
@@ -8,6 +9,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Xamarin.CommunityToolkit.ObjectModel;
namespace Bit.App.Pages
{
@@ -34,6 +36,8 @@ namespace Bit.App.Pages
Collections = new ExtendedObservableCollection<CollectionViewModel>();
OrganizationOptions = new List<KeyValuePair<string, string>>();
PageTitle = AppResources.MoveToOrganization;
MoveCommand = new AsyncCommand(MoveAsync, onException: ex => HandleException(ex), allowsMultipleExecutions: false);
}
public string CipherId { get; set; }
@@ -62,6 +66,8 @@ namespace Bit.App.Pages
set => SetProperty(ref _hasOrganizations, value);
}
public ICommand MoveCommand { get; }
public async Task LoadAsync()
{
var allCollections = await _collectionService.GetAllDecryptedAsync();
@@ -84,7 +90,7 @@ namespace Bit.App.Pages
FilterCollections();
}
public async Task<bool> SubmitAsync()
public async Task<bool> MoveAsync()
{
var selectedCollectionIds = Collections?.Where(c => c.Checked).Select(c => c.Collection.Id);
if (!selectedCollectionIds?.Any() ?? true)
@@ -107,8 +113,15 @@ namespace Bit.App.Pages
try
{
await _deviceActionService.ShowLoadingAsync(AppResources.Saving);
await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds);
var error = await _cipherService.ShareWithServerAsync(cipherView, OrganizationId, checkedCollectionIds);
await _deviceActionService.HideLoadingAsync();
if (error == ICipherService.ShareWithServerError.DuplicatedPasskeyInOrg)
{
_platformUtilsService.ShowToast(null, null, AppResources.ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey);
return false;
}
var movedItemToOrgText = string.Format(AppResources.MovedItemToOrg, cipherView.Name,
(await _organizationService.GetAsync(OrganizationId)).Name);
_platformUtilsService.ShowToast("success", null, movedItemToOrgText);

View File

@@ -256,6 +256,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Account logged out..
/// </summary>
public static string AccountLoggedOutBiometricExceeded {
get {
return ResourceManager.GetString("AccountLoggedOutBiometricExceeded", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Account logged out successfully.
/// </summary>
@@ -418,6 +427,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to addy.io.
/// </summary>
public static string AddyIo {
get {
return ResourceManager.GetString("AddyIo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Admin approval requested.
/// </summary>
public static string AdminApprovalRequested {
get {
return ResourceManager.GetString("AdminApprovalRequested", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to All.
/// </summary>
@@ -499,15 +526,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to AnonAddy.
/// </summary>
public static string AnonAddy {
get {
return ResourceManager.GetString("AnonAddy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A notification has been sent to your device..
/// </summary>
@@ -553,6 +571,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Application.
/// </summary>
public static string Application {
get {
return ResourceManager.GetString("Application", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve login requests.
/// </summary>
@@ -562,6 +589,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Approve with master password.
/// </summary>
public static string ApproveWithMasterPassword {
get {
return ResourceManager.GetString("ApproveWithMasterPassword", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Approve with my other device.
/// </summary>
public static string ApproveWithMyOtherDevice {
get {
return ResourceManager.GetString("ApproveWithMyOtherDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to April.
/// </summary>
@@ -796,15 +841,6 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: &quot;https://twitter.com, androidapp://com.twitter.android&quot;..
/// </summary>
public static string AutofillBlockedUrisDescription {
get {
return ResourceManager.GetString("AutofillBlockedUrisDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Do you want to auto-fill or view this item?.
/// </summary>
@@ -940,6 +976,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Auto-fill will not be offered for these URIs..
/// </summary>
public static string AutoFillWillNotBeOfferedForTheseURIs {
get {
return ResourceManager.GetString("AutoFillWillNotBeOfferedForTheseURIs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Auto-fill with Bitwarden.
/// </summary>
@@ -949,6 +994,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Available for two-step login.
/// </summary>
public static string AvailableForTwoStepLogin {
get {
return ResourceManager.GetString("AvailableForTwoStepLogin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A verification code was sent to your email.
/// </summary>
@@ -1228,6 +1282,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Block auto-fill.
/// </summary>
public static string BlockAutoFill {
get {
return ResourceManager.GetString("BlockAutoFill", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Brand.
/// </summary>
@@ -1264,6 +1327,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Cannot edit multiple URIs at once.
/// </summary>
public static string CannotEditMultipleURIsAtOnce {
get {
return ResourceManager.GetString("CannotEditMultipleURIsAtOnce", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Cannot open the app &quot;{0}&quot;..
/// </summary>
@@ -1552,6 +1624,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Copy application.
/// </summary>
public static string CopyApplication {
get {
return ResourceManager.GetString("CopyApplication", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy link.
/// </summary>
@@ -1660,6 +1741,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Created {0}.
/// </summary>
public static string CreatedX {
get {
return ResourceManager.GetString("CreatedX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Created {0}, {1}.
/// </summary>
public static string CreatedXY {
get {
return ResourceManager.GetString("CreatedXY", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Creating account....
/// </summary>
@@ -2146,6 +2245,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Edit URI.
/// </summary>
public static string EditURI {
get {
return ResourceManager.GetString("EditURI", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Email.
/// </summary>
@@ -2263,6 +2371,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Encryption key migration required. Please login through the web vault to update your encryption key..
/// </summary>
public static string EncryptionKeyMigrationRequiredDescriptionLong {
get {
return ResourceManager.GetString("EncryptionKeyMigrationRequiredDescriptionLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter your account email address to receive your master password hint..
/// </summary>
@@ -2299,6 +2416,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Enter URI.
/// </summary>
public static string EnterURI {
get {
return ResourceManager.GetString("EnterURI", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Enter the 6 digit verification code from your authenticator app..
/// </summary>
@@ -2947,6 +3073,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Format: {0}.
/// </summary>
public static string FormatX {
get {
return ResourceManager.GetString("FormatX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Format: {0}. Separate multiple URIs with a comma..
/// </summary>
public static string FormatXSeparateMultipleURIsWithAComma {
get {
return ResourceManager.GetString("FormatXSeparateMultipleURIsWithAComma", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Forwarded email alias.
/// </summary>
@@ -3289,6 +3433,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Invalid format. Use https://, http://, or androidapp://.
/// </summary>
public static string InvalidFormatUseHttpsHttpOrAndroidApp {
get {
return ResourceManager.GetString("InvalidFormatUseHttpsHttpOrAndroidApp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Invalid master password. Try again..
/// </summary>
@@ -3307,6 +3460,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Invalid URI.
/// </summary>
public static string InvalidURI {
get {
return ResourceManager.GetString("InvalidURI", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Invalid verification code.
/// </summary>
@@ -3586,6 +3748,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Logged in!.
/// </summary>
public static string LoggedIn {
get {
return ResourceManager.GetString("LoggedIn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logged in as {0} on {1}..
/// </summary>
@@ -3604,6 +3775,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Logging in as {0}.
/// </summary>
public static string LoggingInAsX {
get {
return ResourceManager.GetString("LoggingInAsX", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Logging in as {0} on {1}.
/// </summary>
@@ -3613,6 +3793,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Logging in on.
/// </summary>
public static string LoggingInOn {
get {
return ResourceManager.GetString("LoggingInOn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log In.
/// </summary>
@@ -3631,6 +3820,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Login approved.
/// </summary>
public static string LoginApproved {
get {
return ResourceManager.GetString("LoginApproved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Login attempt by {0} on {1}.
/// </summary>
@@ -3670,7 +3868,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Log in initiated.
/// Looks up a localized string similar to Login initiated.
/// </summary>
public static string LogInInitiated {
get {
@@ -3768,6 +3966,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Log in with device.
/// </summary>
public static string LogInWithDevice {
get {
return ResourceManager.GetString("LogInWithDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log in with device must be set up in the settings of the Bitwarden app. Need another option?.
/// </summary>
public static string LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption {
get {
return ResourceManager.GetString("LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Log in with master password.
/// </summary>
@@ -4218,6 +4434,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to New blocked URI.
/// </summary>
public static string NewBlockedURI {
get {
return ResourceManager.GetString("NewBlockedURI", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to New custom field.
/// </summary>
@@ -4705,6 +4930,33 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Passkey.
/// </summary>
public static string Passkey {
get {
return ResourceManager.GetString("Passkey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Passkeys.
/// </summary>
public static string Passkeys {
get {
return ResourceManager.GetString("Passkeys", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Passkey will not be copied.
/// </summary>
public static string PasskeyWillNotBeCopied {
get {
return ResourceManager.GetString("PasskeyWillNotBeCopied", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Passphrase.
/// </summary>
@@ -5192,6 +5444,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Remember this device.
/// </summary>
public static string RememberThisDevice {
get {
return ResourceManager.GetString("RememberThisDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remove.
/// </summary>
@@ -5264,6 +5525,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Request admin approval.
/// </summary>
public static string RequestAdminApproval {
get {
return ResourceManager.GetString("RequestAdminApproval", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Request one-time password.
/// </summary>
@@ -6119,6 +6389,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to The passkey will not be copied to the cloned item. Do you want to continue cloning this item?.
/// </summary>
public static string ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem {
get {
return ResourceManager.GetString("ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There are no blocked URIs.
/// </summary>
public static string ThereAreNoBlockedURIs {
get {
return ResourceManager.GetString("ThereAreNoBlockedURIs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to There are no items in your vault that match &quot;{0}&quot;.
/// </summary>
@@ -6137,6 +6425,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to The URI {0} is already blocked.
/// </summary>
public static string TheURIXIsAlreadyBlocked {
get {
return ResourceManager.GetString("TheURIXIsAlreadyBlocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 30 days.
/// </summary>
@@ -6164,6 +6461,16 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to This item cannot be shared with the organization because there is one already with the same passkey..
/// </summary>
public static string ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey {
get {
return ResourceManager.GetString("ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePassk" +
"ey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to This request is no longer valid.
/// </summary>
@@ -6218,6 +6525,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Too many attempts.
/// </summary>
public static string TooManyAttempts {
get {
return ResourceManager.GetString("TooManyAttempts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to TOTP.
/// </summary>
@@ -6254,6 +6570,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Trouble logging in?.
/// </summary>
public static string TroubleLoggingIn {
get {
return ResourceManager.GetString("TroubleLoggingIn", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Try again.
/// </summary>
@@ -6263,6 +6588,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Turn off using a public device.
/// </summary>
public static string TurnOffUsingPublicDevice {
get {
return ResourceManager.GetString("TurnOffUsingPublicDevice", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to 20 seconds.
/// </summary>
@@ -6453,7 +6787,7 @@ namespace Bit.App.Resources {
}
/// <summary>
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve..
/// Looks up a localized string similar to Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve..
/// </summary>
public static string UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve {
get {
@@ -6587,6 +6921,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to URI removed.
/// </summary>
public static string URIRemoved {
get {
return ResourceManager.GetString("URIRemoved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to URIs.
/// </summary>
@@ -6596,6 +6939,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to URI saved.
/// </summary>
public static string URISaved {
get {
return ResourceManager.GetString("URISaved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to US.
/// </summary>
@@ -6776,6 +7128,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Vault timeout action changed to log out.
/// </summary>
public static string VaultTimeoutActionChangedToLogOut {
get {
return ResourceManager.GetString("VaultTimeoutActionChangedToLogOut", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your organization policies have set your vault timeout action to {0}..
/// </summary>
@@ -7118,6 +7479,15 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to You cannot edit passkey application because it would invalidate the passkey.
/// </summary>
public static string YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey {
get {
return ResourceManager.GetString("YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Your account has been permanently deleted.
/// </summary>
@@ -7145,6 +7515,24 @@ namespace Bit.App.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to Your request has been sent to your admin..
/// </summary>
public static string YourRequestHasBeenSentToYourAdmin {
get {
return ResourceManager.GetString("YourRequestHasBeenSentToYourAdmin", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to You will be notified once approved. .
/// </summary>
public static string YouWillBeNotifiedOnceApproved {
get {
return ResourceManager.GetString("YouWillBeNotifiedOnceApproved", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to To continue, hold your YubiKey NEO against the back of the device or insert your YubiKey into your device&apos;s USB port, then touch its button..
/// </summary>

View File

@@ -954,6 +954,9 @@ Skandering gebeur outomaties.</value>
<data name="UpdateKey" xml:space="preserve">
<value>U kan nie hierdie funksie gebruik tot u u enkripsiesleutel bygewerk het nie.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Leer meer</value>
</data>
@@ -1585,9 +1588,6 @@ Skandering gebeur outomaties.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Versperde URIs vir outovul</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Outovul sal nie vir versperde URIs gebied word nie. Skei meerdere URIs met n komma. Byvoorbeeld: “https://twitter.com, androidapp://com.twitter.android”.</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Vra om aantekening toe te voeg</value>
</data>
@@ -2416,9 +2416,9 @@ kies u Voeg TOTP toe om die sleutel veilig te bewaar</value>
<data name="Service" xml:space="preserve">
<value>Diens</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,16 +2630,144 @@ Wil u na die rekening omskakel?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Huidige hoofwagwoord</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Created {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Application</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Available for two-step login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
<value>Hulpteks vir vra weer vir hoofwagwoord</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
<value>Ongeldige API-sleutel</value>
</data>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
<value>Ongeldige API-teken</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -125,14 +125,14 @@
<comment>Add/create a new entity (verb).</comment>
</data>
<data name="AddFolder" xml:space="preserve">
<value>مجلد مضاف</value>
<value>إضافة مجلّد</value>
</data>
<data name="AddItem" xml:space="preserve">
<value>تمت إضافة العنصر</value>
<value>إضافة عنصر</value>
<comment>The title for the add item page.</comment>
</data>
<data name="AnErrorHasOccurred" xml:space="preserve">
<value>كان هناك خطأ.</value>
<value>لقد حدث خطأ.</value>
<comment>Alert title when something goes wrong.</comment>
</data>
<data name="Back" xml:space="preserve">
@@ -140,7 +140,7 @@
<comment>Navigate back to the previous screen.</comment>
</data>
<data name="Bitwarden" xml:space="preserve">
<value>bitwarden</value>
<value>Bitwarden</value>
<comment>App name. Shouldn't ever change.</comment>
</data>
<data name="Cancel" xml:space="preserve">
@@ -156,11 +156,11 @@
<comment>The button text that allows a user to copy the login's password to their clipboard.</comment>
</data>
<data name="CopyUsername" xml:space="preserve">
<value>المصادقة باستخدام FIDO2 WebAuthn، يمكنك المصادقة باستخدام مفتاح أمان خارجي.</value>
<value>انسخ اسم المستخدم</value>
<comment>The button text that allows a user to copy the login's username to their clipboard.</comment>
</data>
<data name="Credits" xml:space="preserve">
<value>شكر</value>
<value>شكر وتقدير</value>
<comment>Title for page that we use to give credit to resources that we use.</comment>
</data>
<data name="Delete" xml:space="preserve">
@@ -168,42 +168,42 @@
<comment>Delete an entity (verb).</comment>
</data>
<data name="Deleting" xml:space="preserve">
<value>حذف ...</value>
<value>جارِ الحذف...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="DoYouReallyWantToDelete" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد الحذف؟ لا يمكن إلغاؤه</value>
<value>هل تريد حقا أن تحذف؟ هذا لا يمكن التراجع عنها.</value>
<comment>Confirmation alert message when deleteing something.</comment>
</data>
<data name="Edit" xml:space="preserve">
<value>تعديل</value>
</data>
<data name="EditFolder" xml:space="preserve">
<value>تعديل مجلد</value>
<value>تحرير المجلد</value>
</data>
<data name="Email" xml:space="preserve">
<value>بريد الكتروني</value>
<comment>Short label for an email address.</comment>
</data>
<data name="EmailAddress" xml:space="preserve">
<value>عنوان بريد الكتروني</value>
<value>عنوان البريد الإلكتروني</value>
<comment>Full label for a email address.</comment>
</data>
<data name="EmailUs" xml:space="preserve">
<value>اكتب إلينا</value>
<value>راسلنا عبر البريد الإلكتروني</value>
</data>
<data name="EmailUsDescription" xml:space="preserve">
<value>اتصل بنا مباشرة للحصول على المساعدة أو التعليق.</value>
<value>أرسل لنا رسالة مباشرة للحصول على المساعدة أو ترك ملاحظات.</value>
</data>
<data name="EnterPIN" xml:space="preserve">
<value>أدخل رقم التعريف الشخصي الخاص بك.</value>
<value>أدخل رَقم تعريفك الشخصي.</value>
</data>
<data name="Favorites" xml:space="preserve">
<value>المفضلات</value>
<comment>Title for your favorite items in the vault.</comment>
</data>
<data name="FileBugReport" xml:space="preserve">
<value>إرسال تقرير خطأ</value>
<value>إرسال تقرير عن خطأ</value>
</data>
<data name="FileBugReportDescription" xml:space="preserve">
<value>افتح تذكرة في مستودع Github لدينا.</value>
@@ -212,42 +212,42 @@
<value>استخدم بصمة إصبعك للتعريف بنفسك.</value>
</data>
<data name="Folder" xml:space="preserve">
<value>مجلد</value>
<value>المجلد</value>
<comment>Label for a folder.</comment>
</data>
<data name="FolderCreated" xml:space="preserve">
<value>مجلد جديد أنشئ</value>
<value>نشأ مجلد جديد.</value>
</data>
<data name="FolderDeleted" xml:space="preserve">
<value>مجلد محذوف</value>
<value>حذفت المجلد.</value>
</data>
<data name="FolderNone" xml:space="preserve">
<value>لا مجلد</value>
<comment>Items that have no folder specified go in this special "catch-all" folder.</comment>
</data>
<data name="Folders" xml:space="preserve">
<value>مجلدات</value>
<value>المجلدات</value>
</data>
<data name="FolderUpdated" xml:space="preserve">
<value>مجلد محدّث</value>
<value>حُفظ المجلد</value>
</data>
<data name="GoToWebsite" xml:space="preserve">
<value>زر الموقع الالكتروني</value>
<value>الذهاب إلى الموقع الالكتروني</value>
<comment>The button text that allows user to launch the website to their web browser.</comment>
</data>
<data name="HelpAndFeedback" xml:space="preserve">
<value>مساعدة ورجوع</value>
<value>المساعدة و الملاحظات</value>
</data>
<data name="Hide" xml:space="preserve">
<value>إخفاء</value>
<comment>Hide a secret value that is currently shown (password).</comment>
</data>
<data name="InternetConnectionRequiredMessage" xml:space="preserve">
<value>عليك الاتصال بالانترنت قبل المواصلة</value>
<value>عليك الاتصال بالإنترنت قبل المواصلة.</value>
<comment>Description message for the alert when internet connection is required to continue.</comment>
</data>
<data name="InternetConnectionRequiredTitle" xml:space="preserve">
<value>اتصال بالانترنت مطلوب</value>
<value>الاتصال بالإنترنت مطلوب</value>
<comment>Title for the alert when internet connection is required to continue.</comment>
</data>
<data name="InvalidMasterPassword" xml:space="preserve">
@@ -261,11 +261,11 @@
<comment>The button text that allows user to launch the website to their web browser.</comment>
</data>
<data name="LogIn" xml:space="preserve">
<value>تعريف</value>
<value>تسجيل الدخول</value>
<comment>The login button text (verb).</comment>
</data>
<data name="LogInNoun" xml:space="preserve">
<value>اسم المستخدم</value>
<value>تسجيل الدخول</value>
<comment>Title for login page. (noun)</comment>
</data>
<data name="LogOut" xml:space="preserve">
@@ -330,11 +330,11 @@
<value>نقل</value>
</data>
<data name="Saving" xml:space="preserve">
<value>حفظ...</value>
<value>جارِ حفظ...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="Settings" xml:space="preserve">
<value>إعدادات</value>
<value>الإعدادات</value>
<comment>The title for the settings page.</comment>
</data>
<data name="Show" xml:space="preserve">
@@ -360,7 +360,7 @@
<comment>The title for the tools page.</comment>
</data>
<data name="URI" xml:space="preserve">
<value>الرابط</value>
<value>عنوان الـ URI</value>
<comment>Label for a uri/url.</comment>
</data>
<data name="UseFingerprintToUnlock" xml:space="preserve">
@@ -464,7 +464,7 @@
<value>إنشاء حساب</value>
</data>
<data name="CreatingAccount" xml:space="preserve">
<value>إنشاء الحساب...</value>
<value>جارِ إنشاء الحساب...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="EditItem" xml:space="preserve">
@@ -493,16 +493,16 @@
<value>احصل على إمكانية الوصول الفوري إلى كلمات المرور الخاصة بك!</value>
</data>
<data name="ExtensionReady" xml:space="preserve">
<value>أنت مستعد للتعريف بنفسك!</value>
<value>أنت مستعد لتسجيل الدخول!</value>
</data>
<data name="ExtensionSetup" xml:space="preserve">
<value>يمكن الآن الوصول إلى المعرفات الخاصة بك بسهولة من Safari وChrome والتطبيقات الأخرى المدعومة.</value>
</data>
<data name="ExtensionSetup2" xml:space="preserve">
<value>في Safari و Chrome، ابحث عن bitwarden باستخدام رمز المشاركة (المساعدة: انتقل إلى اليمين في السطر السفلي من القائمة).</value>
<value>في Safari و Chrome، ابحث عن bitwarden باستخدام أيقونة المشاركة (المساعدة: انتقل إلى اليمين في السطر السفلي من القائمة).</value>
</data>
<data name="ExtensionTapIcon" xml:space="preserve">
<value>اضغط على رمز bitwarden في القائمة لإطلاق الملحق.</value>
<value>اضغط على أيقونة Bitwarden في القائمة لإطلاق الملحق.</value>
</data>
<data name="ExtensionTurnOn" xml:space="preserve">
<value>لتنشيط bitwarden على Safari والتطبيقات الأخرى، اضغط على رمز "المزيد" على السطر السفلي من القائمة.</value>
@@ -514,10 +514,10 @@
<value>البصمة</value>
</data>
<data name="GeneratePassword" xml:space="preserve">
<value>إنشاء كلمة مرور</value>
<value>توليد كلمة مرور</value>
</data>
<data name="GetPasswordHint" xml:space="preserve">
<value>الحصول على دليل كلمة المرور الرئيسية</value>
<value>احصل على تلميح لكلمة مرورك الرئيسية</value>
</data>
<data name="ImportItems" xml:space="preserve">
<value>استيراد العناصر</value>
@@ -532,7 +532,7 @@
<value>آخر مزامنة:</value>
</data>
<data name="Length" xml:space="preserve">
<value>طول</value>
<value>الطول</value>
</data>
<data name="Lock" xml:space="preserve">
<value>قفل</value>
@@ -562,11 +562,11 @@
<value>سيؤدي تسجيل الخروج إلى إزالة جميع الوصول إلى الخزنة الخاصة بك ويتطلب المصادقة عبر الإنترنت بعد انتهاء المهلة. هل أنت متأكد من أنك تريد استخدام هذا الإعداد؟</value>
</data>
<data name="LoggingIn" xml:space="preserve">
<value>تعريف...</value>
<value>جارِ تسجيل الدخول...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="LoginOrCreateNewAccount" xml:space="preserve">
<value>قم بالتسجيل أو إنشاء حساب جديد للوصول إلى خزنتك الآمنة.</value>
<value>قم بتسجيل الدخول أو إنشاء حساب جديد للوصول إلى خزنتك الآمنة.</value>
</data>
<data name="Manage" xml:space="preserve">
<value>إدارة</value>
@@ -598,7 +598,7 @@
<value>المزيد من الإعدادات</value>
</data>
<data name="MustLogInMainApp" xml:space="preserve">
<value>تحتاج إلى التعريف بنفسك على التطبيق bitwarden الرئيسي قبل استخدام الامتداد.</value>
<value>يجب عليك تسجيل الدخول إلى تطبيق Bitwarden الرئيسي قبل استخدام الامتداد.</value>
</data>
<data name="Never" xml:space="preserve">
<value>أبداً</value>
@@ -623,25 +623,25 @@
<comment>Confirmation, like "Ok, I understand it"</comment>
</data>
<data name="OptionDefaults" xml:space="preserve">
<value>يتم تعيين الخيارات الافتراضية من أداة إنشاء كلمة المرور في تطبيق bitwarden الرئيسي.</value>
<value>يتم تعيين الخيارات الافتراضية من أداة إنشاء كلمة المرور في تطبيق Bitwarden الرئيسي.</value>
</data>
<data name="Options" xml:space="preserve">
<value>خيارات</value>
<value>الخيارات</value>
</data>
<data name="Other" xml:space="preserve">
<value>الأخرى</value>
</data>
<data name="PasswordGenerated" xml:space="preserve">
<value>كلمة المرور التي تم إنشاؤها.</value>
<value>كلمة المرور المولدة</value>
</data>
<data name="PasswordGenerator" xml:space="preserve">
<value>مولد كلمة المرور</value>
</data>
<data name="PasswordHint" xml:space="preserve">
<value>فهرس كلمة المرور</value>
<value>تلميح كلمة المرور</value>
</data>
<data name="PasswordHintAlert" xml:space="preserve">
<value>أرسلنا بريدًا إلكترونيًا مع دليل كلمة المرور الخاص بك.</value>
<value>لقد أرسلنا لك رسالة بريد إلكتروني تحتوي على تلميح لكلمة مرورك الرئيسية.</value>
</data>
<data name="PasswordOverrideAlert" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد سحق كلمة المرور الموجودة؟</value>
@@ -657,7 +657,7 @@
<value>شكرا على مساعدتنا من خلال كتابة تعليق إيجابي!</value>
</data>
<data name="RegeneratePassword" xml:space="preserve">
<value>إعادة إنشاء كلمة مرور</value>
<value>إعادة توليد كلمة المرور</value>
</data>
<data name="RetypeMasterPassword" xml:space="preserve">
<value>إعادة إدخال كلمة المرور الرئيسية</value>
@@ -684,11 +684,11 @@
<value>تم تحديث العنصر</value>
</data>
<data name="Submitting" xml:space="preserve">
<value>تقديم...</value>
<value>جارِ الإرسال...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="Syncing" xml:space="preserve">
<value>المزامنة...</value>
<value>جارِ المزامنة...</value>
<comment>Message shown when interacting with the server</comment>
</data>
<data name="SyncingComplete" xml:space="preserve">
@@ -701,14 +701,14 @@
<value>مزامنة الخزنة الآن</value>
</data>
<data name="TouchID" xml:space="preserve">
<value>Touch ID معرف اتصال</value>
<value>Touch ID معرف اتصال البصمة</value>
<comment>What Apple calls their fingerprint reader.</comment>
</data>
<data name="TwoStepLogin" xml:space="preserve">
<value>تحديد المصادقة الثنائية</value>
</data>
<data name="TwoStepLoginConfirmation" xml:space="preserve">
<value>تجعل المصادقة الثنائية المعامل حسابك أكثر أمانًا من خلال طلب إدخال رمز أمان مع كل معرف من تطبيق المصادقة. يمكن تنشيط تعريف العامل المزدوج في خزنة الويب في bitwarden.com. هل تريد زيارة الموقع الآن؟</value>
<value>تجعل المصادقة الثنائية المعامل حسابك أكثر أمانًا من خلال طلب إدخال رمز أمان مع كل معرف من تطبيق المصادقة. يمكن تنشيط تعريف العامل المزدوج في خزنة الويب في bitwarden.com هل تريد زيارة الموقع الآن؟</value>
</data>
<data name="UnlockWith" xml:space="preserve">
<value>فتح مع {0}</value>
@@ -879,7 +879,7 @@
<value>لا يمكن لجهازك فتح هذا النوع من الملفات.</value>
</data>
<data name="Downloading" xml:space="preserve">
<value>تحميل...</value>
<value>جارِ التحميل...</value>
<comment>Message shown when downloading a file</comment>
</data>
<data name="AttachmentLargeWarning" xml:space="preserve">
@@ -954,6 +954,9 @@
<data name="UpdateKey" xml:space="preserve">
<value>لا يمكنك استخدام هذه الميزة حتى تقوم بتحديث مفتاح التشفير الخاص بك.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>مطلوب ترحيل مفتاح التشفير. الرجاء تسجيل الدخول بواسطة مخزن الويب لتحديث مفتاح التشفير الخاص بك.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>اعرف المزيد</value>
</data>
@@ -1035,7 +1038,7 @@
<value>أغسطس</value>
</data>
<data name="Brand" xml:space="preserve">
<value>العلامة</value>
<value>العلامة التجارية</value>
</data>
<data name="CardholderName" xml:space="preserve">
<value>اسم حامل البطاقة</value>
@@ -1077,7 +1080,7 @@
<value>يونيو</value>
</data>
<data name="LastName" xml:space="preserve">
<value>اسم العائلة</value>
<value>الأسم الأخير</value>
</data>
<data name="FullName" xml:space="preserve">
<value>الاسم الكامل</value>
@@ -1337,7 +1340,7 @@
<value>الهويات</value>
</data>
<data name="Logins" xml:space="preserve">
<value>معرفات</value>
<value>تسجيلات الدخول</value>
</data>
<data name="SecureNotes" xml:space="preserve">
<value>ملاحظات آمنة</value>
@@ -1492,7 +1495,7 @@
<value>تعيين رمز PIN الخاص بك لإلغاء قفل Bitwarden. سيتم إعادة تعيين إعدادات PIN الخاصة بك إذا قمت بتسجيل الخروج بالكامل من التطبيق.</value>
</data>
<data name="LoggedInAsOn" xml:space="preserve">
<value>تم تسجيل الدخول كـ {0} في {1}.</value>
<value>مسجل الدخول كـ {0} على {1}.</value>
<comment>ex: Logged in as user@example.com on bitwarden.com.</comment>
</data>
<data name="VaultLockedMasterPassword" xml:space="preserve">
@@ -1585,9 +1588,6 @@
<data name="AutofillBlockedUris" xml:space="preserve">
<value>تعبئة العناوين المحجوبة تلقائياً</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>لن يتم عرض التعبئة التلقائية للعناوين المحجوبة. افصل عناوين متعددة بفاصلة. مثال: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>اطلب إضافة تسجيل الدخول</value>
</data>
@@ -1620,7 +1620,7 @@
<value>إظهار / إخفاء</value>
</data>
<data name="LoginExpired" xml:space="preserve">
<value>انتهت صلاحية جلسة تسجيل الدخول الخاصة بك.</value>
<value>انتهت صَلاحِيَة جَلسة تسجيل دخولك.</value>
</data>
<data name="BiometricsDirection" xml:space="preserve">
<value>المصادقة البيومترية</value>
@@ -2331,7 +2331,7 @@
<value>هل تحاول تسجيل الدخول؟</value>
</data>
<data name="LogInAttemptByXOnY" xml:space="preserve">
<value>محاولة تسجيل الدخول بواسطة {0} في {1}</value>
<value>محاولة تسجيل الدخول بواسطة {0} على {1}</value>
</data>
<data name="DeviceType" xml:space="preserve">
<value>نوع الجهاز</value>
@@ -2417,9 +2417,9 @@
<data name="Service" xml:space="preserve">
<value>الخدمة</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>FirefoxRelay</value>
@@ -2631,16 +2631,144 @@
<data name="CurrentMasterPassword" xml:space="preserve">
<value>كلمة المرور الرئيسية الحالية</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>سجلت دخولك!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>الموافقة بجهازي الآخر</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>طلب موافقة المسؤول</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>الموافقة بكلمة مرور رئيسية</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>أوقف باستخدام جهاز عمومي</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>تذكر هذا الجهاز</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>مفتاح المرور</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>مفاتيح المرور</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>أُنشِئ {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>تطبيق</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>لا يمكنك تعديل تطبيق مفتاح المرور لأنه سيبطل مفتاح المرور</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>لن يتم نسخ مفتاح المرور</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>لن يتم نسخ مفتاح المرور إلى العنصر المستنسخ. هل تريد الاستمرار في استنساخ هذا العنصر؟</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>نسخ التطبيق</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>متاح لتسجيل الدخول بخطوتين</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
<value>مساعدة إعادة طلب كلمة المرور الرئيسية</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>قد يفشل إلغاء القُفْل بسبب عدم كفاية الذاكرة. قم بتقليل إعدادات ذاكرة KDF أو قم بإعداد إلغاء القُفْل البيومتري لحل المشكلة.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
<value>مفتاح API غير صالح</value>
</data>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
<value>رمز API غير صالح</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>طلبت موافقة المسؤول</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>أرسلت طلبك إلى مسؤولك.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>سيتم إخطارك بمجرد الموافقة عليها. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>مشكلة في تسجيل الدخول؟</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>تسجيل الدخول كـ {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>تَغيير إجراء مهلة المخزن لتسجيل الخروج</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>لا يمكن مشاركة هذا العنصر مع المؤسسة لأنه يوجد بالفعل واحد مع نفس مفتاح المرور.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>حظر التعبئة التلقائية</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>لن يتم تقديم التعبئة التلقائية لعناوين الـ URIs هذه.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>عنوان URL جديد محظور</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>تم حفظ عنوان URI</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>تنسيق غير صالح. استخدم https:// أو http:// أو androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>تعديل عنوان URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>إدخال URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>التنسيق: {0}. افصل العديد من عناوين URIs بفاصلة.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>التنسيق: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>الرابط غير صالح</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>تم حذف الرابط</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>لا يوجد أي عناوين URIs محظورة</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>تم حظر URI {0} بالفعل</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>لا يمكن تعديل العديد من عناوين URIs في وقت واحد</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>تمت الموافقة على تسجيل الدخول</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>يجب إعداد تسجيل الدخول باستخدام الجهاز في إعدادات تطبيق Bitwarden. هل تحتاج إلى خِيار آخر؟</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>تسجيل الدخول بالجهاز</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>جارٍ تسجيل الدخول</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>لقد أجريت محاولات كثيرة</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>تم تسجيل الخروج من الحساب.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="UpdateKey" xml:space="preserve">
<value>Şifrələmə açarınızı güncəlləyənə qədər bu özəlliyi istifadə edə bilməzsiniz.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Şifrələmə açarının daşınması tələb olunur. Şifrələmə açarınızı güncəlləmək üçün zəhmət olmasa veb anbar üzərindən giriş edin.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Daha ətraflı</value>
</data>
@@ -1585,9 +1588,6 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Əngəllənən URI-lərin avto-doldurulması</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Əngəllənən URI-lər üçün avto-doldurma təklif edilmir. Çoxlu URI-ni vergüllə ayırır. Nümunə: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Giriş əlavə etmək üçün soruş</value>
</data>
@@ -2415,9 +2415,9 @@ Skan prosesi avtomatik baş tutacaq.</value>
<data name="Service" xml:space="preserve">
<value>Xidmət</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2629,11 +2629,57 @@ Bu hesaba keçmək istəyirsiniz?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Hazırkı ana parol</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Giriş edildi!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Digər cihazımla təsdiqlə</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Admin təsdiqini tələb et</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Ana parolla təsdiqlə</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Hər kəsə açıq bir cihaz istifadə edərək söndür</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Bu cihazı xatırla</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Keçid açarı</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Keçid açarı</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>{0} yaradıldı</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Tətbiq</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Keçid açarı tətbiqinə düzəliş edə bilməzsiniz, çünki bu, keçid açarını yararsız edəcək</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Keçid açarı kopyalanmır</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Keçid açarı, klonlanmış elementə kopyalanmayacaq. Bu elementi klonlamağa davam etmək istəyirsiniz?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Tətbiqi kopyala</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>İki addımlı giriş üçün əlçatandır</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Ana parolu təkrar soruş köməyi</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Yetərsiz yaddaşa görə kilid açma uğursuz ola bilər. Həll etmək üçün KDF yaddaş tənzimləmələrinizi azaldın</value>
<value>Yetərsiz yaddaşa görə kilid açma uğursuz ola bilər. Həll etmək üçün KDF yaddaş tənzimləmələrinizi azaldın və ya biometrik kilid açmanı quraşdırın.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Yararsız API açarı</value>
@@ -2641,4 +2687,86 @@ Bu hesaba keçmək istəyirsiniz?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Yararsız API tokeni</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin təsdiqi tələb olunur</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Tələbiniz admininizə göndərildi.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Təsdiqləndikdən sonra məlumatlandırılacaqsınız. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Girişdə problem var?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>{0} olaraq giriş edilir</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Anbar vaxt bitməsi əməliyyatııxış et" olaraq dəyişdirildi</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>Bu element təşkilatla paylaşıla bilmir, çünki eyni keçid açarına sahib bir element artıq mövcuddur.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Avto-doldurmanı əngəllə</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Bu URI-lər üçün avto-doldurma təklif olunmayacaq.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Yeni əngəllənən URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saxlanıldı</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Yararsız format. https://, http:// və ya androidapp:// istifadə edin</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>URI-ə düzəliş et</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>URI-ni daxil et</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Bir neçə URI-ı vergüllə ayırın.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Yararsız URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI silindi</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Əngəllənən URI yoxdur</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>URI {0} artıq əngəllənib</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Bir dəfəyə bir neçə URI-a düzəliş etmək mümkün deyil</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Giriş təsdiqləndi</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Cihazla giriş etmə, Bitwarden tətbiqinin tənzimləmələrində quraşdırılmalıdır. Başqa bir seçimə ehtiyacınız var?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Cihazla giriş et</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Giriş edilir</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Həddən artıq cəhd</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Hesabdan çıxış edildi.</value>
</data>
</root>

View File

@@ -953,6 +953,9 @@
<data name="UpdateKey" xml:space="preserve">
<value>Вы не зможаце выкарыстоўваць гэту функцыю, пакуль не абнавіце свой ключ шыфравання.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Даведацца больш</value>
</data>
@@ -1584,9 +1587,6 @@
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Аўтазапаўненне заблакіраваных URI</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Аўтазапаўненне не будзе прапаноўвацца для заблакіраваных URI. Раздзяляйце некалькі URI з дапамогай коскі. Напрыклад: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Пытацца пры дадаванні лагіна</value>
</data>
@@ -2416,9 +2416,9 @@
<data name="Service" xml:space="preserve">
<value>Сэрвіс</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,11 +2630,57 @@
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Бягучы асноўны пароль</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Created {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Праграма</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Available for two-step login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Дапамога з паўторным запытам асноўнага пароля</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Па прычыне недахопу памяці можа адбыцца збой разблакіроўкі. Паменшыце налады памяці KDF, каб вырашыць гэту праблему</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Памылковы ключ API</value>
@@ -2642,4 +2688,86 @@
<data name="InvalidAPIToken" xml:space="preserve">
<value>Памылковы токен API</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Блакіраваць аўтазапаўненне</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Аўтазапаўненне для гэтых URI прапаноўвацца не будзе.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Новы заблакіраваны URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI захаваны</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Памылковы фармат. Выкарыстоўвайце https://, http:// або androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Рэдагаваць URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Увесці URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Фармат: {0}. Раздзяліце некалькі значэнняў URI коскай.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Фармат: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Памылковы URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI выдалены</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Заблакіраваных URI пакуль няма</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>Гэты URI {0} ужо заблакіраваны</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Немагчыма рэдагаваць некалькі URI адначасова</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@
<data name="UpdateKey" xml:space="preserve">
<value>Трябва да обновите шифриращия си ключ, за да използвате тази възможност.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Необходима е промяна на шифриращия ключ. Впишете се в трезора си по уеб, за да обновите своя шифриращ ключ.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Научете повече</value>
</data>
@@ -1585,9 +1588,6 @@
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Блокирани за авт. попълване адреси</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>За посочените блокирани адреси няма да се прави автоматично попълване. Разделяйте отделните адреси със запетая. Пример: „https://twitter.com, androidapp://com.twitter.android“.</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Питане за добавяне на запис</value>
</data>
@@ -2416,9 +2416,9 @@ select Add TOTP to store the key safely</value>
<data name="Service" xml:space="preserve">
<value>Услуга</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,11 +2630,57 @@ select Add TOTP to store the key safely</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Текуща главна парола</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Вписахте се!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Одобряване с другото ми устройство</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Подаване на заявка за одобрение от администратор</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Одобряване с главната парола</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Изключване на използването на публично устройство</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Запомняне на това устройство</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Създадено на {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Приложение</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Копиране на приложението</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Налично за двустепенно удостоверяване</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Помощ за повторното запитване за главната парола</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Отключването може да бъде неуспешно заради недостатъчно памет. Намалете настройките на паметта за KDF, за да разрешите проблема.</value>
<value>Отключването може да бъде неуспешно заради недостатъчно памет. Намалете настройките на паметта за KDF или настройте отключването чрез биометрични данни, за да разрешите проблема.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Неправилен ключ за ППИ</value>
@@ -2642,4 +2688,86 @@ select Add TOTP to store the key safely</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Неправилен идентификатор за ППИ</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Заявено е одобрение от администратор</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Вашата заявка беше изпратена до администратора Ви.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Ще получите известие, когато тя бъде одобрена. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Имате проблем с вписването?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Вписване като {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Действието при изтичане на времето за достъп до трезора е променено на отписване</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Блокиране на авт. попълване</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Автоматичното попълване няма да бъде предлагано за тези адреси.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Нов блокиран адрес</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>Адресът е запазен</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Неправилен формат. Използвайте https://, http:// или androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Редактиране на адреса</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Въведете адрес</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Формат: {0}. Ако въвеждате повече адреси, разделяйте ги със запетая.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Формат: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Неправилен адрес</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>Адресът е премахнат</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Няма блокирани адреси</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>Адресът {0} вече е блокиран</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Не може да редактирате повече от един адрес едновременно</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Вписването е одобрено</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Вписването с устройство трябва да е включено в настройките на приложението на Битуорден. Друга настройка ли търсите?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Вписване с устройство</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Вписване в</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Твърде много опити</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Акаунтът е отписан.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@ Scanning will happen automatically.</value>
<data name="UpdateKey" xml:space="preserve">
<value>আপনি আপনার এনক্রিপশন কী হালনাগাদ না করা পর্যন্ত এই বৈশিষ্ট্যটি ব্যবহার করতে পারবেন না।</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>আরও জানুন</value>
</data>
@@ -1585,9 +1588,6 @@ Scanning will happen automatically.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
@@ -2417,9 +2417,9 @@ select Add TOTP to store the key safely</value>
<data name="Service" xml:space="preserve">
<value>Service</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2509,7 +2509,7 @@ Do you want to switch to this account?</value>
<value>Log in with device</value>
</data>
<data name="LogInInitiated" xml:space="preserve">
<value>Log in initiated</value>
<value>Login initiated</value>
</data>
<data name="ANotificationHasBeenSentToYourDevice" xml:space="preserve">
<value>A notification has been sent to your device.</value>
@@ -2631,11 +2631,57 @@ Do you want to switch to this account?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Created {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Application</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Available for two-step login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
@@ -2643,4 +2689,86 @@ Do you want to switch to this account?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@ Skeniranje će biti izvršeno automatski.</value>
<data name="UpdateKey" xml:space="preserve">
<value>Ovu funkciju ne možete koristiti dok ne ažurirate ključ za šifrovanje.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Saznajte više</value>
</data>
@@ -1585,9 +1588,6 @@ Skeniranje će biti izvršeno automatski.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-popunjavanje blokiranih URI-ja</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-popunjavanje neće biti ponuđeno za blokirane URI-je. Odvoji višestruke URI-je zarezom. Npr. „https://twitter.com, androidapp://com.twitter.android”.</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Upitaj za dodavanje prijave</value>
</data>
@@ -2415,9 +2415,9 @@ Skeniranje će biti izvršeno automatski.</value>
<data name="Service" xml:space="preserve">
<value>Service</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2629,11 +2629,57 @@ Skeniranje će biti izvršeno automatski.</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Created {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Application</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Available for two-step login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
@@ -2641,4 +2687,86 @@ Skeniranje će biti izvršeno automatski.</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@ L'escaneig es farà automàticament.</value>
<data name="UpdateKey" xml:space="preserve">
<value>No podeu utilitzar aquesta característica fins que no actualitzeu la vostra clau de xifratge.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Més informació</value>
</data>
@@ -1585,9 +1588,6 @@ L'escaneig es farà automàticament.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Emplena automàticament els URI bloquejats</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>L'emplenament automàtic no s'oferirà per als URI bloquejats. Separeu diversos URI amb una coma. Per exemple: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Sol·licita afegir els inicis de sessió</value>
</data>
@@ -2416,9 +2416,9 @@ seleccioneu Afegeix TOTP per emmagatzemar la clau de manera segura</value>
<data name="Service" xml:space="preserve">
<value>Servei</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,11 +2630,57 @@ Voleu canviar a aquest compte?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Contrasenya mestra actual</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Connectat!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Aproveu des d'un altre dispositiu vostre</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Sol·liciteu l'aprovació de l'administrador</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Aprova amb contrasenya mestra</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Apagueu amb un dispositiu públic</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Recorda aquest dispositiu</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Clau de pas</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Claus de pas</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Creats {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Aplicació</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>No podeu editar l'aplicació de clau de pas perquè invalidaria la clau de pas</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>La clau de pas no es copiarà</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>La clau de pas no es copiarà a l'element clonat. Voleu continuar clonant aquest element?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copia la aplicació</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Disponible per iniciar sessió en dos passos</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Ajuda per tornar a demanar la contrasenya mestra</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>El desbloqueig pot fallar a causa de memòria insuficient. Disminueix la configuració de memòria KDF per resoldre-ho</value>
<value>El desbloqueig pot fallar a causa de memòria insuficient. Disminueix la configuració de memòria KDF per resoldre-ho.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Clau API no vàlida</value>
@@ -2642,4 +2688,86 @@ Voleu canviar a aquest compte?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Token API no vàlid</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>S'ha sol·licitat l'aprovació de l'administrador</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>La vostra sol·licitud s'ha enviat a l'administrador.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Se us notificarà una vegada aprovat. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Teniu problemes per iniciar la sessió?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Connectat com {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>L'acció de temps d'espera de la caixa forta ha canviat per tancar la sessió</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>Aquest element no es pot compartir amb l'organització perquè ja n'hi ha un amb la mateixa clau de pas.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Bloqueja l'emplenament automàtic</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>L'emplenament automàtic no s'oferirà per a aquests URI.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Nou URI bloquejat</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI guardat</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Format no vàlid. Utilitzeu https://, http:// o androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edita URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Introduex URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separa diversos URI amb una coma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>URI no vàlid</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI suprimit</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>No hi ha cap URI bloquejat</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>L'URI {0} ja està bloquejat</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>No es poden editar diversos URI alhora</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>S'ha aprovat l'inici de sessió</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>L'inici de sessió amb el dispositiu ha d'estar activat a la configuració de l'aplicació Bitwarden. Necessiteu una altra opció?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Inici de sessió amb dispositiu</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Inici de sessió en</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -505,7 +505,7 @@
<value>Pro spuštění rozšíření klepněte na ikonu Bitwardenu v menu.</value>
</data>
<data name="ExtensionTurnOn" xml:space="preserve">
<value>Pro zapnutí Bitwardenu v prohlížeči Safari a dalších aplikacích klepněte na ikonu "Více" v dolní části menu.</value>
<value>Pro zapnutí Bitwardenu v prohlížeči Safari a dalších aplikacích klepněte na ikonu „Další“ v dolní části menu.</value>
</data>
<data name="Favorite" xml:space="preserve">
<value>Oblíbené</value>
@@ -954,6 +954,9 @@ Načtení proběhne automaticky.</value>
<data name="UpdateKey" xml:space="preserve">
<value>Dokud neaktualizujete svůj šifrovací klíč, nemůžete tuto funkci použít.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Vyžaduje se migrace šifrovacího klíče. Pro aktualizaci šifrovacího klíče se přihlaste přes webový trezor.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Dozvědět se více</value>
</data>
@@ -1304,22 +1307,22 @@ Načtení proběhne automaticky.</value>
<value>Přistupujte k Vašemu trezoru přímo z Vaší klávesnice pro rychlejší automatické vyplnění hesel.</value>
</data>
<data name="AutofillTurnOn" xml:space="preserve">
<value>Pokyny pro zapnutí automatického vyplňování hesel na Vašem zařízení:</value>
<value>Pokyny pro zapnutí automatického vyplňování hesel na vašem zařízení:</value>
</data>
<data name="AutofillTurnOn1" xml:space="preserve">
<value>1. Přejděte do aplikace "Nastavení" v iOS</value>
</data>
<data name="AutofillTurnOn2" xml:space="preserve">
<value>2. Klepněte na "Hesla" > "Volby hesla"</value>
<value>2. Klepněte na Hesla > Volby hesla</value>
</data>
<data name="AutofillTurnOn3" xml:space="preserve">
<value>3. Zapněte přepínač u položky "Automatické vyplnění hesel"</value>
<value>3. Povolte „Automatické vyplnění hesel</value>
</data>
<data name="AutofillTurnOn4" xml:space="preserve">
<value>4. Najděte sekci "Povolit vyplňování z:"</value>
<value>4. Najděte sekci Povolit vyplňování z</value>
</data>
<data name="AutofillTurnOn5" xml:space="preserve">
<value>5. Zvolte "Bitwarden"</value>
<value>5. Zvolte Bitwarden</value>
</data>
<data name="PasswordAutofill" xml:space="preserve">
<value>Automatické vyplňování hesel</value>
@@ -1585,9 +1588,6 @@ Načtení proběhne automaticky.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Automatické vyplňování blokovaných URI</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Automatické vyplňování nebude pro blokované URI nabídnuto. Více URI oddělte čárkou. Například: "https://twitter.com,androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ptát se na přidání přihlášení</value>
</data>
@@ -2415,9 +2415,9 @@ Načtení proběhne automaticky.</value>
<data name="Service" xml:space="preserve">
<value>Služba</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2629,11 +2629,57 @@ Chcete se přepnout na tento účet?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Aktuální hlavní heslo</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Přihlášeno!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Schválit s mým dalším zařízením</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Žádost o schválení správcem</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Schválit hlavním heslem</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Vypnout pomocí veřejného zařízení</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Zapamatovat toto zařízení</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Přístupový klíč</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Přístupové klíče</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Vytvořeno {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Aplikace</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Nemůžete upravovat aplikaci pro přístupové klíče, protože by to zneplatnilo přístupový klíč</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Přístupový klíč nebude zkopírován</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Přístupový klíč nebude zkopírován do duplikované položky. Chete pokračovat v duplikování této položky?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Kopírovat aplikaci</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Dostupné pro dvoufázové přihlášení</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Nápověda pro znovuzeptání se na hlavní heslo</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Odemknutí může selhat z důvodu nedostatku paměti. Pro vyřešení snižte nastavení paměti KDF.</value>
<value>Odemknutí může selhat z důvodu nedostatku paměti. Snižte nastavení KDF paměti nebo nastavte odemykání pomocí biometrie.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Neplatný klíč API</value>
@@ -2641,4 +2687,86 @@ Chcete se přepnout na tento účet?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Neplatný token API</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Bylo vyžádáno schválení správcem</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Váš požadavek byl odeslán Vašemu správci.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Po schválení budete upozorněni. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Potíže s přihlášením?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Přihlášování jako {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Akce časového limitu trezoru byla změněna na odhlášení</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>Tuto položku nelze sdílet s organizací, protože již existuje jedna se stejným přístupovým klíčem.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Blokovat automatické vyplňování</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Pro tento URI nebude nabízeno automatické vyplňování.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Nový blokovaný URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI byl uložen</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Neplatný formát. Použijte https://, http:// nebo androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Upravit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Zadat URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Formát: {0}. Oddělte více URI čárkou.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Formát: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Neplatný URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI byl odebrán</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Nejsou žádné blokované URI</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>URI {0} je již blokován</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Najednou nelze upravovat více URI</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Přihlášení bylo schváleno</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Přihlášení zařízením musí být nastaveno v aplikaci Bitwarden. Potřebujete další volby?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Přihlásit se zařízením</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Přihlašování na</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Příliš mnoho pokusů</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Účet byl odhlášen.</value>
</data>
</root>

File diff suppressed because it is too large Load Diff

View File

@@ -954,6 +954,9 @@ Skanning vil ske automatisk.</value>
<data name="UpdateKey" xml:space="preserve">
<value>Du kan ikke bruge denne funktion, før du opdaterer din krypteringsnøgle.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Krypteringsnøglemigrering nødvendig. Log ind gennem web-boksen for at opdatere krypteringsnøglen.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Få mere at vide</value>
</data>
@@ -1585,9 +1588,6 @@ Skanning vil ske automatisk.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Autoudfyld blokerede URI'er</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Autoudfyldning tilbydes ikke for blokerede URI'er. Adskil flere URI'er med komma. F.eks.: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Spørg om at tilføje login</value>
</data>
@@ -2416,9 +2416,9 @@ vælg Tilføj TOTP for at gemme nøglen sikkert</value>
<data name="Service" xml:space="preserve">
<value>Tjeneste</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,11 +2630,57 @@ Vil du skifte til denne konto?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Aktuel hovedadgangskode</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Indlogget!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Godkend med min anden enhed</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Anmod om admin-godkendelse</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Godkendt med hovedadgangskode</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Slå fra vha. en offentlig enhed</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Husk denne enhed</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Adgangsnøgle</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Adgangsnøgler</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Oprettet {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Applikation</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Adgangsnøgleapplikationen kan ikke redigeres, da det ville ugyldiggøre adgangsnøglen</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Adgangsnøglen kopieres ikke</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Adgangsnøglen kopieres ikke til det klonede emne. Fortsæt med at klone dette emne alligevel?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Kopiér applikation</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Tilgængelig for totrins-login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Hjælp til genanmodning om hovedadgangskode</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Oplåsning kan fejle grundet utilstrækkelig hukommelse. Reducér KDF-hukommelsesindstillinger for at afhjælpe</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Ugyldig API-nøgle</value>
@@ -2642,4 +2688,86 @@ Vil du skifte til denne konto?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Ugyldigt API-token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin-godkendelse udbedt</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Din anmodning er sendt til din gruppe-admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Du underrettes, når godkendelse foreligger. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Problemer med at logge ind?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logger ind som {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Boks-timeouthandling ændret til udlogning</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>Dette emne kan ikke deles med organisationen, da der allerede er et med den samme adgangsnøgle.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Blokér autoudfyldning</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Autoudfyldning vil ikke blive tilbudt for disse URI'er.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Ny blokeret URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI gemt</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Ugyldigt format. Brug https://, http:// eller androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Redigér URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Angiv URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separér flere URI'er med komma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Ugyldig URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI fjernet</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Der er ingen blokerede URI'er</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>URI'en {0} er allerede blokeret</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Kan ikke redigere flere URI'er på én gang</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login godkendt</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Indlogning med enhed skal være opsat i indstillingerne i Bitwarden-appen. Behov for en anden mulighed?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log ind med enhed</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logger ind på</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>For mange forsøg</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Konto logget ud.</value>
</data>
</root>

View File

@@ -598,7 +598,7 @@
<value>Weitere Einstellungen</value>
</data>
<data name="MustLogInMainApp" xml:space="preserve">
<value>Du musst dich in der Bitwarden App einloggen, bevor du die Erweiterung benutzen kannst.</value>
<value>Du musst dich in der Bitwarden App anmelden, bevor du die Erweiterung benutzen kannst.</value>
</data>
<data name="Never" xml:space="preserve">
<value>Niemals</value>
@@ -838,7 +838,7 @@
<comment>For 2FA</comment>
</data>
<data name="RememberMe" xml:space="preserve">
<value>Eingeloggt bleiben</value>
<value>Angemeldet bleiben</value>
<comment>Remember my two-step login</comment>
</data>
<data name="SendVerificationCodeAgain" xml:space="preserve">
@@ -954,6 +954,9 @@ Das Scannen erfolgt automatisch.</value>
<data name="UpdateKey" xml:space="preserve">
<value>Du kannst diese Funktion nicht nutzen, solange du deinen Verschlüsselungsschlüssel nicht aktualisiert hast.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Mehr erfahren</value>
</data>
@@ -1083,7 +1086,7 @@ Das Scannen erfolgt automatisch.</value>
<value>Vollständiger Name</value>
</data>
<data name="LicenseNumber" xml:space="preserve">
<value>Führerscheinnummer</value>
<value>Lizenznummer</value>
</data>
<data name="March" xml:space="preserve">
<value>März</value>
@@ -1292,7 +1295,7 @@ Das Scannen erfolgt automatisch.</value>
<value>Auto-Ausfüllen aktiviert!</value>
</data>
<data name="MustLogInMainAppAutofill" xml:space="preserve">
<value>Du musst dich in der Bitwarden App einloggen, bevor du AutoFill nutzen kannst.</value>
<value>Du musst dich in der Bitwarden App anmelden, bevor du AutoFill nutzen kannst.</value>
</data>
<data name="AutofillSetup" xml:space="preserve">
<value>Du kannst nun direkt von der Tastatur auf deine Zugangsdaten zugreifen, wenn du dich auf Webseiten oder in Apps anmeldest.</value>
@@ -1585,9 +1588,6 @@ Das Scannen erfolgt automatisch.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Blockierte URIs automatisch ausfüllen</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-Ausfüllen wird für blockierte URIs nicht angeboten. Trenne mehrere URIs mit einem Komma. Beispiel: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Nach dem Hinzufügen von Zugangsdaten fragen</value>
</data>
@@ -2415,9 +2415,9 @@ Das Scannen erfolgt automatisch.</value>
<data name="Service" xml:space="preserve">
<value>Dienst</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2629,11 +2629,57 @@ Möchtest du zu diesem Konto wechseln?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Aktuelles Master-Passwort</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Angemeldet!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Mit meinem anderen Gerät genehmigen</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Admin-Genehmigung anfordern</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Mit Master-Passwort genehmigen</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Mit einem öffentlichen Gerät ausschalten</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Dieses Gerät merken</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Erstellt am {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Anwendung</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Du kannst die Passkey-Anwendung nicht bearbeiten, da dies den Passkey ungültig machen würde.</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey wird nicht kopiert</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Der Passkey wird nicht in den duplizierten Eintrag kopiert. Möchtest du mit dem Duplizieren dieses Eintrags fortfahren?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Anwendung kopieren</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Verfügbar für Zwei-Faktor Authentifizierung</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Hilfe zum erneuten Abfragen des Master-Passworts</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Das Entsperren kann aufgrund von unzureichendem Arbeitsspeicher fehlschlagen. Verringere deine KDF-Speichereinstellungen, um das Problem zu beheben.</value>
<value>Das Entsperren kann aufgrund unzureichenden Arbeitsspeichers fehlschlagen. Verringere deine KDF-Speichereinstellungen oder richte die biometrische Entsperrung, um dies zu lösen.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Ungültiger API-Schlüssel</value>
@@ -2641,4 +2687,86 @@ Möchtest du zu diesem Konto wechseln?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Ungültiger API-Token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin-Genehmigung angefordert</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Deine Anfrage wurde an deinen Administrator gesendet.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>Du wirst benachrichtigt, sobald sie genehmigt wurde.</value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Probleme beim Einloggen?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Anmelden als {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Tresor-Timeoutaktion auf "Abmelden" geändert</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>Dieser Eintrag kann nicht mit der Organisation geteilt werden, da bereits einer mit dem gleichen Passkey existiert.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Auto-Ausfüllen blockieren</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-Ausfüllen wird für diese URIs nicht angeboten.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>Neue gesperrte URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI gespeichert</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Ungültiges Format. Verwende https://, http://, oder androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>URI bearbeiten</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>URI eingeben</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Trenne mehrere URIs mit einem Komma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Ungültige URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI entfernt</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>Es gibt keine gesperrten URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>Die URI {0} ist bereits gesperrt</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Es können nicht mehrere URIs auf einmal bearbeitet werden</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login genehmigt</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Die Anmeldung über ein Gerät muss in den Einstellungen der Bitwarden App eingerichtet werden. Benötigst du eine andere Option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Mit Gerät anmelden</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Anmelden bei</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Zu viele Versuche</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Konto abgemeldet.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@
<data name="UpdateKey" xml:space="preserve">
<value>Δεν μπορείτε να χρησιμοποιήσετε αυτήν τη δυνατότητα μέχρι να ενημερώσετε το κλειδί κρυπτογράφησης.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Μάθετε Περισσότερα</value>
</data>
@@ -1585,9 +1588,6 @@
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Αυτόματη συμπλήρωση μπλοκαρισμένων URIs</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Η αυτόματη συμπλήρωση δεν θα προσφερθεί για μπλοκαρισμένα URI. Ξεχωρίστε πολλαπλά URI με κόμμα. Για παράδειγμα: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ζητήστε να προσθέστε σύνδεση</value>
</data>
@@ -2416,9 +2416,9 @@
<data name="Service" xml:space="preserve">
<value>Υπηρεσία</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,16 +2630,144 @@
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Τρέχων κύριος κωδικός</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Συνθηματικό</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Συνθηματικά</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Δημιουργήθηκε {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Εφαρμογή</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Δεν μπορείτε να επεξεργαστείτε την εφαρμογή κλειδιού πρόσβασης επειδή θα ακυρώσει το κλειδί</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Το κλειδί πρόσβασης δεν θα αντιγραφεί</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Το κλειδί πρόσβασης δεν θα αντιγραφεί στο κλωνοποιημένο στοιχείο. Θέλετε να συνεχίσετε την κλωνοποίηση αυτού του στοιχείου;</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Διαθέσιμο για σύνδεση με δύο βήματα</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>Το ξεκλείδωμα μπορεί να αποτύχει λόγω ανεπαρκούς μνήμης. Μειώστε τις ρυθμίσεις μνήμης KDF ή ρυθμίστε το βιομετρικό ξεκλείδωμα για επίλυση.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
<value>Μη έγκυρο κλειδί API</value>
</data>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
<value>Μη έγκυρο API token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

View File

@@ -954,6 +954,9 @@ Scanning will happen automatically.</value>
<data name="UpdateKey" xml:space="preserve">
<value>You cannot use this feature until you update your encryption key.</value>
</data>
<data name="EncryptionKeyMigrationRequiredDescriptionLong" xml:space="preserve">
<value>Encryption key migration required. Please login through the web vault to update your encryption key.</value>
</data>
<data name="LearnMore" xml:space="preserve">
<value>Learn more</value>
</data>
@@ -1585,9 +1588,6 @@ Scanning will happen automatically.</value>
<data name="AutofillBlockedUris" xml:space="preserve">
<value>Auto-fill blocked URIs</value>
</data>
<data name="AutofillBlockedUrisDescription" xml:space="preserve">
<value>Auto-fill will not be offered for blocked URIs. Separate multiple URIs with a comma. For example: "https://twitter.com, androidapp://com.twitter.android".</value>
</data>
<data name="AskToAddLogin" xml:space="preserve">
<value>Ask to add login</value>
</data>
@@ -2416,9 +2416,9 @@ select Add TOTP to store the key safely</value>
<data name="Service" xml:space="preserve">
<value>Service</value>
</data>
<data name="AnonAddy" xml:space="preserve">
<value>AnonAddy</value>
<comment>"AnonAddy" is the product name and should not be translated.</comment>
<data name="AddyIo" xml:space="preserve">
<value>addy.io</value>
<comment>"addy.io" is the product name and should not be translated.</comment>
</data>
<data name="FirefoxRelay" xml:space="preserve">
<value>Firefox Relay</value>
@@ -2630,11 +2630,57 @@ Do you want to switch to this account?</value>
<data name="CurrentMasterPassword" xml:space="preserve">
<value>Current master password</value>
</data>
<data name="LoggedIn" xml:space="preserve">
<value>Logged in!</value>
</data>
<data name="ApproveWithMyOtherDevice" xml:space="preserve">
<value>Approve with my other device</value>
</data>
<data name="RequestAdminApproval" xml:space="preserve">
<value>Request admin approval</value>
</data>
<data name="ApproveWithMasterPassword" xml:space="preserve">
<value>Approve with master password</value>
</data>
<data name="TurnOffUsingPublicDevice" xml:space="preserve">
<value>Turn off using a public device</value>
</data>
<data name="RememberThisDevice" xml:space="preserve">
<value>Remember this device</value>
</data>
<data name="Passkey" xml:space="preserve">
<value>Passkey</value>
</data>
<data name="Passkeys" xml:space="preserve">
<value>Passkeys</value>
</data>
<data name="CreatedX" xml:space="preserve">
<value>Created {0}</value>
<comment>To state the date in which the cipher was created: Created 03/21/2023</comment>
</data>
<data name="Application" xml:space="preserve">
<value>Application</value>
</data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>You cannot edit passkey application because it would invalidate the passkey</value>
</data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Passkey will not be copied</value>
</data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>The passkey will not be copied to the cloned item. Do you want to continue cloning this item?</value>
</data>
<data name="CopyApplication" xml:space="preserve">
<value>Copy application</value>
</data>
<data name="AvailableForTwoStepLogin" xml:space="preserve">
<value>Available for two-step login</value>
</data>
<data name="MasterPasswordRePromptHelp" xml:space="preserve">
<value>Master password re-prompt help</value>
</data>
<data name="UnlockingMayFailDueToInsufficientMemoryDecreaseYourKDFMemorySettingsToResolve" xml:space="preserve">
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings to resolve.</value>
<value>Unlocking may fail due to insufficient memory. Decrease your KDF memory settings or set up biometric unlock to resolve.</value>
</data>
<data name="InvalidAPIKey" xml:space="preserve">
<value>Invalid API key</value>
@@ -2642,4 +2688,86 @@ Do you want to switch to this account?</value>
<data name="InvalidAPIToken" xml:space="preserve">
<value>Invalid API token</value>
</data>
<data name="AdminApprovalRequested" xml:space="preserve">
<value>Admin approval requested</value>
</data>
<data name="YourRequestHasBeenSentToYourAdmin" xml:space="preserve">
<value>Your request has been sent to your admin.</value>
</data>
<data name="YouWillBeNotifiedOnceApproved" xml:space="preserve">
<value>You will be notified once approved. </value>
</data>
<data name="TroubleLoggingIn" xml:space="preserve">
<value>Trouble logging in?</value>
</data>
<data name="LoggingInAsX" xml:space="preserve">
<value>Logging in as {0}</value>
</data>
<data name="VaultTimeoutActionChangedToLogOut" xml:space="preserve">
<value>Vault timeout action changed to log out</value>
</data>
<data name="ThisItemCannotBeSharedWithTheOrganizationBecauseThereIsOneAlreadyWithTheSamePasskey" xml:space="preserve">
<value>This item cannot be shared with the organization because there is one already with the same passkey.</value>
</data>
<data name="BlockAutoFill" xml:space="preserve">
<value>Block auto-fill</value>
</data>
<data name="AutoFillWillNotBeOfferedForTheseURIs" xml:space="preserve">
<value>Auto-fill will not be offered for these URIs.</value>
</data>
<data name="NewBlockedURI" xml:space="preserve">
<value>New blocked URI</value>
</data>
<data name="URISaved" xml:space="preserve">
<value>URI saved</value>
</data>
<data name="InvalidFormatUseHttpsHttpOrAndroidApp" xml:space="preserve">
<value>Invalid format. Use https://, http://, or androidapp://</value>
<comment>https://, http://, androidapp:// should not be translated</comment>
</data>
<data name="EditURI" xml:space="preserve">
<value>Edit URI</value>
</data>
<data name="EnterURI" xml:space="preserve">
<value>Enter URI</value>
</data>
<data name="FormatXSeparateMultipleURIsWithAComma" xml:space="preserve">
<value>Format: {0}. Separate multiple URIs with a comma.</value>
</data>
<data name="FormatX" xml:space="preserve">
<value>Format: {0}</value>
</data>
<data name="InvalidURI" xml:space="preserve">
<value>Invalid URI</value>
</data>
<data name="URIRemoved" xml:space="preserve">
<value>URI removed</value>
</data>
<data name="ThereAreNoBlockedURIs" xml:space="preserve">
<value>There are no blocked URIs</value>
</data>
<data name="TheURIXIsAlreadyBlocked" xml:space="preserve">
<value>The URI {0} is already blocked</value>
</data>
<data name="CannotEditMultipleURIsAtOnce" xml:space="preserve">
<value>Cannot edit multiple URIs at once</value>
</data>
<data name="LoginApproved" xml:space="preserve">
<value>Login approved</value>
</data>
<data name="LogInWithDeviceMustBeSetUpInTheSettingsOfTheBitwardenAppNeedAnotherOption" xml:space="preserve">
<value>Log in with device must be set up in the settings of the Bitwarden app. Need another option?</value>
</data>
<data name="LogInWithDevice" xml:space="preserve">
<value>Log in with device</value>
</data>
<data name="LoggingInOn" xml:space="preserve">
<value>Logging in on</value>
</data>
<data name="TooManyAttempts" xml:space="preserve">
<value>Too many attempts</value>
</data>
<data name="AccountLoggedOutBiometricExceeded" xml:space="preserve">
<value>Account logged out.</value>
</data>
</root>

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