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

Compare commits

...

12 Commits

Author SHA1 Message Date
renovate[bot]
e033832261 [deps]: Update actions/setup-dotnet action to v4 (#3139)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-15 10:04:36 -06:00
Álison Fernandes
fa5d92fbf7 [PM-7407] Updates the self-host release date in the unassigned items alert message (#3158) 2024-04-15 15:58:56 +02:00
github-actions[bot]
e672cb132f Autosync the updated translations (#3151)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-04-12 19:51:04 +00:00
Federico Maccaroni
e7a7eed7e8 [PM-7407] Implemented check for organizations with unassigned items (#3150) 2024-04-12 15:52:39 -03:00
Bitwarden DevOps
43a4915323 Bumped version to 2024.4.0 (#3143) 2024-04-08 14:50:08 +00:00
Álison Fernandes
b1ae3cc325 iOS Beta variants now have their own Encryption Export Compliance Code (#3136) 2024-04-08 12:16:48 +01:00
JohanGallardo
b9dada07ea Fixed broken mobile documentation link in README (#3142) 2024-04-08 09:14:30 +00:00
github-actions[bot]
58442389df Autosync the updated translations (#3122)
Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com>
2024-04-08 06:02:32 +00:00
Álison Fernandes
a3378d33ae Removed cake from Platform team ownership (#3137) 2024-04-05 23:37:21 +01:00
renovate[bot]
2e1982b08e [deps]: Update actions/labeler action to v5 (#2895)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 08:51:28 -06:00
renovate[bot]
e9e9b6f7bc [deps]: Update actions/checkout action to v4 (#2756)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-03 08:48:11 -06:00
Opeyemi
9be8fec219 [DEVOPS-1822] - Upload Mobile Beta Native Build (#3015)
* Upload  mobile beta native build

---------

Co-authored-by: Vince Grassia <593223+vgrassia@users.noreply.github.com>
Co-authored-by: Federico Maccaroni <fedemkr@gmail.com>
Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
Co-authored-by: MtnBurrit0 <77340197+mimartin12@users.noreply.github.com>
2024-04-03 15:09:04 +01:00
40 changed files with 589 additions and 53 deletions

1
.github/CODEOWNERS vendored
View File

@@ -21,7 +21,6 @@ src/App/Platforms/iOS/Info.plist
## Platform team files ## ## Platform team files ##
appIcons @bitwarden/team-platform-dev appIcons @bitwarden/team-platform-dev
build.cake @bitwarden/team-platform-dev
## Vault team files ## ## Vault team files ##
src/watchOS @bitwarden/team-vault-dev src/watchOS @bitwarden/team-vault-dev

View File

@@ -3,3 +3,348 @@ name: Build Beta
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
ref:
description: 'Branch or tag to build'
required: true
default: 'main'
type: string
env:
main_app_folder_path: src/App
main_app_project_path: src/App/App.csproj
target-net-version: net8.0
jobs:
setup:
name: Setup
runs-on: ubuntu-22.04
outputs:
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps:
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
submodules: 'true'
- name: Check if special branches exist
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
ios:
name: Apple iOS
runs-on: macos-13
needs: setup
env:
ios_folder_path: src/App/Platforms/iOS
app_output_name: App
app_ci_output_filename: App_x64_Debug
steps:
- name: Set XCode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
with:
xcode-version: 15.1
- name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
with:
nuget-version: 6.4.0
- name: Set up .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: '8.0.x'
# This step might be obsolete at some point as .NET MAUI workloads
# are starting to come pre-installed on the GH Actions build agents.
- name: Install MAUI Workload
run: dotnet workload install maui --ignore-failed-sources
- name: Print environment
run: |
nuget help | grep Version
dotnet --info
echo "GitHub ref: $GITHUB_REF"
echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
ref: ${{ inputs.ref }}
submodules: 'true'
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: "bitwarden-ci"
secrets: "appcenter-ios-token"
- name: Download Provisioning Profiles secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: profiles
run: |
mkdir -p $HOME/secrets
profiles=(
"dist_beta_autofill.mobileprovision"
"dist_beta_bitwarden.mobileprovision"
"dist_beta_extension.mobileprovision"
"dist_beta_share_extension.mobileprovision"
"dist_beta_bitwarden_watch_app.mobileprovision"
"dist_beta_bitwarden_watch_app_extension.mobileprovision"
)
for FILE in "${profiles[@]}"
do
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
done
- name: Download Google Services secret
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
FILE: GoogleService-Info.plist
run: |
mkdir -p $HOME/secrets
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME --name $FILE \
--file $HOME/secrets/$FILE --output none
- name: Increment version
run: |
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
echo "### CFBundleVersion $BUILD_NUMBER" >> $GITHUB_STEP_SUMMARY
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./${{ env.ios_folder_path }}/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
- name: Update Entitlements
run: |
echo "##### Updating Entitlements"
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>beta<\/string>/' ./${{ env.ios_folder_path }}/Entitlements.plist
- name: Get certificates
run: |
mkdir -p $HOME/certificates
az keyvault secret show --id https://bitwarden-ci.vault.azure.net/certificates/ios-distribution |
jq -r .value | base64 -d > $HOME/certificates/ios-distribution.p12
- name: Set up Keychain
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
run: |
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $HOME/certificates/ios-distribution.p12 -k build.keychain -P "" -T /usr/bin/codesign \
-T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
- name: Set up provisioning profiles
run: |
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_beta_autofill.mobileprovision
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_beta_bitwarden_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH"
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
WATCH_APP_UUID=$(grep UUID -A1 -a $WATCH_APP_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_UUID.mobileprovision"
WATCH_APP_EXTENSION_UUID=$(grep UUID -A1 -a $WATCH_APP_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_EXTENSION_UUID.mobileprovision"
- name: Restore packages
run: |
dotnet restore
dotnet tool restore
- name: Setup iOS build CAKE (Testing)
run: dotnet cake build.cake --target iOS --variant beta
- name: Bulid WatchApp
run: |
echo "##### Build WatchApp with Release Configuration"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "##### Done"
- name: Archive Build for App Store
shell: pwsh
run: |
Write-Output "##### Archive for Release ios-arm64"
dotnet publish ${{ env.main_app_project_path }} -c Release -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=ios-arm64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
- name: Archive Build for Mobile Automation
shell: pwsh
run: |
Write-Output "##### Archive Debug for iossimulator-x64"
dotnet build ${{ env.main_app_project_path }} -c Debug -f ${{ env.target-net-version }}-ios /p:RuntimeIdentifier=iossimulator-x64 /p:ArchiveOnBuild=true /p:MtouchUseLlvm=false
Write-Output "##### Done"
ls ~/Library/Developer/Xcode/Archives
- name: Export .ipa for App Store
env:
EXPORT_OPTIONS_PATH: ./.github/resources/export-options-app-store.plist
EXPORT_PATH: ./bitwarden-export
run: |
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
-exportOptionsPlist $EXPORT_OPTIONS_PATH
- name: Export .app for Automation CI
env:
ARCHIVE_PATH: ./${{ env.main_app_folder_path }}/bin/Debug/${{ env.target-net-version }}-ios/iossimulator-x64
EXPORT_PATH: ./bitwarden-export
run: |
zip -r -q ${{ env.app_ci_output_filename }}.app.zip $ARCHIVE_PATH
mv ${{ env.app_ci_output_filename }}.app.zip $EXPORT_PATH
- name: Show Bitwarden Export
shell: bash
run: ls -a -R ./bitwarden-export
- name: Copy all dSYMs files to upload
env:
EXPORT_PATH: ./bitwarden-export
WATCH_ARCHIVE_DSYMS_PATH: ./src/watchOS/bitwarden.xcarchive/dSYMs/
WATCH_DSYMS_EXPORT_PATH: ./bitwarden-export/Watch_dSYMs
run: |
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
- name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: Bitwarden iOS
path: |
./bitwarden-export/Bitwarden*.ipa
./bitwarden-export/dSYMs/*.*
if-no-files-found: error
- name: Upload .app file for Automation CI
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: ${{ env.app_ci_output_filename }}.app.zip
path: ./bitwarden-export/${{ env.app_ci_output_filename }}.app.zip
if-no-files-found: error
- name: Install AppCenter CLI
run: npm install -g appcenter-cli
- name: Upload dSYMs to App Center
env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
- name: Upload Watch dSYMs to Firebase Crashlytics
run: |
echo "##### Uploading Watch dSYMs to Firebase"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
- name: Validate app in App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --validate-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
shell: bash
- name: Deploy to App Store
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
run: |
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden Beta.ipa" \
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
check-failures:
name: Check for failures
if: always()
runs-on: ubuntu-22.04
needs:
- setup
- ios
steps:
- name: Check if any job failed
if: |
(github.ref == 'refs/heads/main'
|| github.ref == 'refs/heads/rc'
|| github.ref == 'refs/heads/hotfix-rc')
&& contains(needs.*.result, 'failure')
run: exit 1
- name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
if: failure()
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@main
if: failure()
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"
- name: Notify Slack on failure
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
with:
status: ${{ job.status }}

View File

@@ -305,7 +305,7 @@ jobs:
nuget-version: 6.4.0 nuget-version: 6.4.0
- name: Set up .NET - name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
@@ -453,7 +453,7 @@ jobs:
nuget-version: 6.4.0 nuget-version: 6.4.0
- name: Set up .NET - name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'

View File

@@ -15,7 +15,7 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0 uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0

View File

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

View File

@@ -38,7 +38,7 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Check Release Version - name: Check Release Version
id: version id: version
@@ -126,7 +126,7 @@ jobs:
if: inputs.fdroid_publish if: inputs.fdroid_publish
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Download F-Droid .apk artifact - name: Download F-Droid .apk artifact
if: ${{ inputs.release_type != 'Dry Run' }} if: ${{ inputs.release_type != 'Dry Run' }}

View File

@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# using .NET MAUI.
# Build/Run # Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/clients/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
# We're Hiring! # We're Hiring!

View File

@@ -15,16 +15,18 @@ abstract record VariantConfig(
string AppName, string AppName,
string AndroidPackageName, string AndroidPackageName,
string iOSBundleId, string iOSBundleId,
string ApsEnvironment string ApsEnvironment,
string DistProvisioningProfilePrefix
); );
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden"; const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden"; const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development"); //NOTE: Beta iOS variants have a different ITSEncryptionExportComplianceCode
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development"); record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development", "Dist:");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production"); record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development", "Dist:");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production"); record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production", "Dist: Beta");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production", "Dist:");
VariantConfig GetVariant() => variant.ToLower() switch{ VariantConfig GetVariant() => variant.ToLower() switch{
"qa" => new QA(), "qa" => new QA(),
@@ -197,7 +199,8 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
var prevBundleId = plist["CFBundleIdentifier"]; var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"]; var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString(); //var newVersion = CreateBuildNumber(prevVersion).ToString();
var newVersionName = GetVersionName(prevVersionName, buildVariant, git); // we need to maintain version formatting here composed of one to three period-separated integers, so we cannot use the GetVersionName method as in Android for non-Prod.
var newVersionName = prevVersionName;
var newBundleId = GetiOSBundleId(buildVariant, projectType); var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType); var newBundleName = GetiOSBundleName(buildVariant, projectType);
@@ -219,6 +222,11 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId); plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
} }
if(buildVariant is Beta)
{
plist["ITSEncryptionExportComplianceCode"] = "3dd3e32f-efa6-4d99-b410-28aa28b1cb77";
}
SerializePlist(plistFile, plist); SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}"); Information($"Changed app name from {prevBundleName} to {newBundleName}");
@@ -228,12 +236,15 @@ private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, Gi
Information($"{plistPath} updated with success!"); Information($"{plistPath} updated with success!");
} }
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant) private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant, bool updateApsEnv)
{ {
var EntitlementlistFile = File(entitlementsPath); var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile); dynamic Entitlements = DeserializePlist(EntitlementlistFile);
Entitlements["aps-environment"] = buildVariant.ApsEnvironment; if (updateApsEnv)
{
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
}
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId }; Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };; Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
@@ -272,9 +283,10 @@ private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
const string pattern = @"MARKETING_VERSION = [^;]*;"; const string pattern = @"MARKETING_VERSION = [^;]*;";
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};"); fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
FileWriteText(pbxprojPath, fileText); FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified successfully.");
Information($"{pbxprojPath} modified Marketing Version successfully.");
} }
/// <summary> /// <summary>
@@ -327,7 +339,7 @@ Task("UpdateiOSPlist")
var infoPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Info.plist"); var infoPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Entitlements.plist"); var entitlementsPath = Path.Combine(_slnPath, "src", "App", "Platforms", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp); UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant); UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, true);
}); });
Task("UpdateiOSAutofillPlist") Task("UpdateiOSAutofillPlist")
@@ -338,7 +350,7 @@ Task("UpdateiOSAutofillPlist")
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist"); var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist"); var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill); UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant); UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, false);
}); });
Task("UpdateiOSExtensionPlist") Task("UpdateiOSExtensionPlist")
@@ -349,7 +361,7 @@ Task("UpdateiOSExtensionPlist")
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist"); var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist"); var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension); UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant); UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, false);
}); });
Task("UpdateiOSShareExtensionPlist") Task("UpdateiOSShareExtensionPlist")
@@ -360,7 +372,7 @@ Task("UpdateiOSShareExtensionPlist")
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist"); var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist"); var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension); UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant); UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant, false);
}); });
Task("UpdateiOSCodeFiles") Task("UpdateiOSCodeFiles")
@@ -397,6 +409,22 @@ Task("UpdateWatchKitAppInfoPlist")
UpdateWatchKitAppInfoPlist(infoPath, buildVariant); UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
}); });
Task("UpdateDistProfiles")
.IsDependentOn("UpdateiOSCodeFiles")
.Does(()=> {
var buildVariant = GetVariant();
var filesToReplace = new string[] {
Path.Combine(".github", "resources", "export-options-app-store.plist"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj")
};
foreach(string path in filesToReplace)
{
ReplaceInFile(path, "Dist:", buildVariant.DistProvisioningProfilePrefix);
}
});
#endregion iOS #endregion iOS
#region Main Tasks #region Main Tasks
@@ -418,6 +446,7 @@ Task("iOS")
.IsDependentOn("UpdateiOSCodeFiles") .IsDependentOn("UpdateiOSCodeFiles")
.IsDependentOn("UpdateWatchProject") .IsDependentOn("UpdateWatchProject")
.IsDependentOn("UpdateWatchKitAppInfoPlist") .IsDependentOn("UpdateWatchKitAppInfoPlist")
.IsDependentOn("UpdateDistProfiles")
.Does(()=> .Does(()=>
{ {
Information("iOS app updated"); Information("iOS app updated");
@@ -437,4 +466,4 @@ Options:
}); });
#endregion Main Tasks #endregion Main Tasks
RunTarget(target); RunTarget(target);

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?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="2024.3.3" 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="2024.4.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden</string> <string>com.8bit.bitwarden</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2024.3.3</string> <string>2024.4.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleIconName</key> <key>CFBundleIconName</key>

View File

@@ -46,6 +46,7 @@ namespace Bit.Core.Abstractions
Task<CipherResponse> PutShareCipherAsync(string id, CipherShareRequest request); Task<CipherResponse> PutShareCipherAsync(string id, CipherShareRequest request);
Task PutDeleteCipherAsync(string id); Task PutDeleteCipherAsync(string id);
Task<CipherResponse> PutRestoreCipherAsync(string id); Task<CipherResponse> PutRestoreCipherAsync(string id);
Task<bool> HasUnassignedCiphersAsync();
Task RefreshIdentityTokenAsync(); Task RefreshIdentityTokenAsync();
Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier); Task<SsoPrevalidateResponse> PreValidateSsoAsync(string identifier);
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,

View File

@@ -37,5 +37,6 @@ namespace Bit.Core.Abstractions
Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId); Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId);
Task SoftDeleteWithServerAsync(string id); Task SoftDeleteWithServerAsync(string id);
Task RestoreWithServerAsync(string id); Task RestoreWithServerAsync(string id);
Task<bool> VerifyOrganizationHasUnassignedItemsAsync();
} }
} }

View File

@@ -186,6 +186,8 @@ namespace Bit.Core.Abstractions
Task<BwRegion?> GetActiveUserRegionAsync(); Task<BwRegion?> GetActiveUserRegionAsync();
Task<BwRegion?> GetPreAuthRegionAsync(); Task<BwRegion?> GetPreAuthRegionAsync();
Task SetPreAuthRegionAsync(BwRegion value); Task SetPreAuthRegionAsync(BwRegion value);
Task<bool> GetShouldCheckOrganizationUnassignedItemsAsync(string userId = null);
Task SetShouldCheckOrganizationUnassignedItemsAsync(bool shouldCheck, string userId = null);
[Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")] [Obsolete("Use GetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]
Task<string> GetPinProtectedAsync(string userId = null); Task<string> GetPinProtectedAsync(string userId = null);
[Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")] [Obsolete("Use SetPinKeyEncryptedUserKeyAsync instead, left for migration purposes")]

View File

@@ -46,6 +46,7 @@ namespace Bit.Core
public const string PreLoginEmailKey = "preLoginEmailKey"; public const string PreLoginEmailKey = "preLoginEmailKey";
public const string ConfigsKey = "configsKey"; public const string ConfigsKey = "configsKey";
public const string DisplayEuEnvironmentFlag = "display-eu-environment"; public const string DisplayEuEnvironmentFlag = "display-eu-environment";
public const string UnassignedItemsBannerFlag = "unassigned-items-banner";
public const string RegionEnvironment = "regionEnvironment"; public const string RegionEnvironment = "regionEnvironment";
public const string DuoCallback = "bitwarden://duo-callback"; public const string DuoCallback = "bitwarden://duo-callback";
@@ -136,6 +137,7 @@ namespace Bit.Core
public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}"; public static string ShouldConnectToWatchKey(string userId) => $"shouldConnectToWatch_{userId}";
public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}"; public static string ScreenCaptureAllowedKey(string userId) => $"screenCaptureAllowed_{userId}";
public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}"; public static string PendingAdminAuthRequest(string userId) => $"pendingAdminAuthRequest_{userId}";
public static string ShouldCheckOrganizationUnassignedItemsKey(string userId) => $"shouldCheckOrganizationUnassignedItems_{userId}";
[Obsolete] [Obsolete]
public static string KeyKey(string userId) => $"key_{userId}"; public static string KeyKey(string userId) => $"key_{userId}";
[Obsolete] [Obsolete]

View File

@@ -25,6 +25,7 @@ namespace Bit.App.Models
public bool CopyInsteadOfShareAfterSaving { get; set; } public bool CopyInsteadOfShareAfterSaving { get; set; }
public bool HideAccountSwitcher { get; set; } public bool HideAccountSwitcher { get; set; }
public OtpData? OtpData { get; set; } public OtpData? OtpData { get; set; }
public bool HasJustLoggedInOrUnlocked { get; set; }
public void SetAllFrom(AppOptions o) public void SetAllFrom(AppOptions o)
{ {

View File

@@ -233,6 +233,7 @@ namespace Bit.App.Pages
} }
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
_appOptions.HasJustLoggedInOrUnlocked = true;
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }
} }

View File

@@ -35,6 +35,8 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appOptions.HasJustLoggedInOrUnlocked = true;
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }

View File

@@ -195,6 +195,8 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appOptions.HasJustLoggedInOrUnlocked = true;
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }

View File

@@ -55,6 +55,8 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appOptions.HasJustLoggedInOrUnlocked = true;
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }

View File

@@ -71,6 +71,8 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appOptions.HasJustLoggedInOrUnlocked = true;
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }

View File

@@ -206,6 +206,8 @@ namespace Bit.App.Pages
{ {
return; return;
} }
_appOptions.HasJustLoggedInOrUnlocked = true;
var previousPage = await AppHelpers.ClearPreviousPage(); var previousPage = await AppHelpers.ClearPreviousPage();
App.MainPage = new TabsPage(_appOptions, previousPage); App.MainPage = new TabsPage(_appOptions, previousPage);
} }

View File

@@ -33,7 +33,7 @@ namespace Bit.App.Pages
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"); _keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_stateService = ServiceContainer.Resolve<IStateService>(); _stateService = ServiceContainer.Resolve<IStateService>();
_groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage)) _groupingsPage = new NavigationPage(new GroupingsPage(true, previousPage: previousPage, appOptions: appOptions))
{ {
Title = AppResources.MyVault, Title = AppResources.MyVault,
IconImageSource = "lock.png" IconImageSource = "lock.png"

View File

@@ -1,5 +1,6 @@
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Models;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
@@ -27,7 +28,7 @@ namespace Bit.App.Pages
public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null, public GroupingsPage(bool mainPage, CipherType? type = null, string folderId = null,
string collectionId = null, string pageTitle = null, string vaultFilterSelection = null, string collectionId = null, string pageTitle = null, string vaultFilterSelection = null,
PreviousPageInfo previousPage = null, bool deleted = false, bool showTotp = false) PreviousPageInfo previousPage = null, bool deleted = false, bool showTotp = false, AppOptions appOptions = null)
{ {
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks); _pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
InitializeComponent(); InitializeComponent();
@@ -50,6 +51,7 @@ namespace Bit.App.Pages
_vm.CollectionId = collectionId; _vm.CollectionId = collectionId;
_vm.Deleted = deleted; _vm.Deleted = deleted;
_vm.ShowTotp = showTotp; _vm.ShowTotp = showTotp;
_vm.AppOptions = appOptions;
_previousPage = previousPage; _previousPage = previousPage;
if (pageTitle != null) if (pageTitle != null)
{ {
@@ -160,6 +162,8 @@ namespace Bit.App.Pages
return; return;
} }
await _vm.CheckOrganizationUnassignedItemsAsync();
// Push registration // Push registration
var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync(); var lastPushRegistration = await _stateService.GetPushLastRegistrationDateAsync();
lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue); lastPushRegistration = lastPushRegistration.GetValueOrDefault(DateTime.MinValue);

View File

@@ -1,6 +1,7 @@
using System.Windows.Input; using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.App.Models;
using Bit.App.Utilities; using Bit.App.Utilities;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
@@ -45,6 +46,8 @@ namespace Bit.App.Pages
private readonly IPasswordRepromptService _passwordRepromptService; private readonly IPasswordRepromptService _passwordRepromptService;
private readonly IOrganizationService _organizationService; private readonly IOrganizationService _organizationService;
private readonly IPolicyService _policyService; private readonly IPolicyService _policyService;
private readonly IConfigService _configService;
private readonly IEnvironmentService _environmentService;
private readonly ILogger _logger; private readonly ILogger _logger;
public GroupingsPageViewModel() public GroupingsPageViewModel()
@@ -61,6 +64,8 @@ namespace Bit.App.Pages
_passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"); _passwordRepromptService = ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService");
_organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService"); _organizationService = ServiceContainer.Resolve<IOrganizationService>("organizationService");
_policyService = ServiceContainer.Resolve<IPolicyService>("policyService"); _policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
_configService = ServiceContainer.Resolve<IConfigService>();
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
Loading = true; Loading = true;
@@ -104,6 +109,7 @@ namespace Bit.App.Pages
public List<Core.Models.View.CollectionView> Collections { get; set; } public List<Core.Models.View.CollectionView> Collections { get; set; }
public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; } public List<TreeNode<Core.Models.View.CollectionView>> NestedCollections { get; set; }
public AppOptions AppOptions { get; internal set; }
protected override ICipherService cipherService => _cipherService; protected override ICipherService cipherService => _cipherService;
protected override IPolicyService policyService => _policyService; protected override IPolicyService policyService => _policyService;
protected override IOrganizationService organizationService => _organizationService; protected override IOrganizationService organizationService => _organizationService;
@@ -699,5 +705,59 @@ namespace Bit.App.Pages
var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList(); var folders = decFolders.Where(f => _allCiphers.Any(c => c.FolderId == f.Id)).ToList();
return folders.Any() ? folders : null; return folders.Any() ? folders : null;
} }
internal async Task CheckOrganizationUnassignedItemsAsync()
{
try
{
if (AppOptions?.HasJustLoggedInOrUnlocked != true)
{
return;
}
AppOptions.HasJustLoggedInOrUnlocked = false;
if (!await _configService.GetFeatureFlagBoolAsync(Core.Constants.UnassignedItemsBannerFlag)
||
!await _stateService.GetShouldCheckOrganizationUnassignedItemsAsync())
{
return;
}
var waitSyncTask = Task.Run(async () =>
{
while (_syncService.SyncInProgress)
{
await Task.Delay(100);
}
});
await waitSyncTask.WaitAsync(TimeSpan.FromMinutes(5));
if (!await _cipherService.VerifyOrganizationHasUnassignedItemsAsync())
{
return;
}
var message = _environmentService.SelectedRegion == Core.Enums.Region.SelfHosted
? AppResources.OrganizationUnassignedItemsMessageSelfHost041624DescriptionLong
: AppResources.OrganizationUnassignedItemsMessageUSEUDescriptionLong;
var response = await _deviceActionService.DisplayAlertAsync(AppResources.Notice,
message,
null,
AppResources.RemindMeLater,
AppResources.Ok);
if (response == AppResources.Ok)
{
await _stateService.SetShouldCheckOrganizationUnassignedItemsAsync(false);
}
}
catch (TimeoutException) { }
catch (Exception ex)
{
_logger.Exception(ex);
}
}
} }
} }

View File

@@ -4866,6 +4866,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to Notice.
/// </summary>
public static string Notice {
get {
return ResourceManager.GetString("Notice", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to This account has two-step login set up, however, none of the configured two-step providers are supported on this device. Please use a supported device and/or add additional providers that are better supported across devices (such as an authenticator app).. /// Looks up a localized string similar to This account has two-step login set up, however, none of the configured two-step providers are supported on this device. Please use a supported device and/or add additional providers that are better supported across devices (such as an authenticator app)..
/// </summary> /// </summary>
@@ -5101,6 +5110,24 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible..
/// </summary>
public static string OrganizationUnassignedItemsMessageSelfHost041624DescriptionLong {
get {
return ResourceManager.GetString("OrganizationUnassignedItemsMessageSelfHost041624DescriptionLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible..
/// </summary>
public static string OrganizationUnassignedItemsMessageUSEUDescriptionLong {
get {
return ResourceManager.GetString("OrganizationUnassignedItemsMessageUSEUDescriptionLong", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Organization identifier. /// Looks up a localized string similar to Organization identifier.
/// </summary> /// </summary>
@@ -5678,6 +5705,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to Remind me later.
/// </summary>
public static string RemindMeLater {
get {
return ResourceManager.GetString("RemindMeLater", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Remove. /// Looks up a localized string similar to Remove.
/// </summary> /// </summary>

View File

@@ -560,7 +560,7 @@
<comment>Message shown when interacting with the server</comment> <comment>Message shown when interacting with the server</comment>
</data> </data>
<data name="LoginOrCreateNewAccount" xml:space="preserve"> <data name="LoginOrCreateNewAccount" xml:space="preserve">
<value>Käytä salattua holviasi kirjautumalla sisään tai tai luo uusi tili.</value> <value>Käytä salattua holviasi kirjautumalla sisään tai luo uusi tili.</value>
</data> </data>
<data name="Manage" xml:space="preserve"> <data name="Manage" xml:space="preserve">
<value>Hallinta</value> <value>Hallinta</value>

View File

@@ -2877,12 +2877,12 @@ Vuoi passare a questo account?</value>
<value>Imposta un metodo di sblocco per modificare l'azione timeout cassaforte.</value> <value>Imposta un metodo di sblocco per modificare l'azione timeout cassaforte.</value>
</data> </data>
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve"> <data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
<value>Duo two-step login is required for your account. </value> <value>Per il tuo account è richiesta la verifica in due passaggi di Duo.</value>
</data> </data>
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve"> <data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
<value>Follow the steps from Duo to finish logging in.</value> <value>Segui i passaggi da Duo per completare l'accesso.</value>
</data> </data>
<data name="LaunchDuo" xml:space="preserve"> <data name="LaunchDuo" xml:space="preserve">
<value>Launch Duo</value> <value>Avvia Duo</value>
</data> </data>
</root> </root>

View File

@@ -2878,12 +2878,12 @@ Você deseja mudar para esta conta?</value>
<value>Configure um método de desbloqueio para alterar o tempo limite do cofre.</value> <value>Configure um método de desbloqueio para alterar o tempo limite do cofre.</value>
</data> </data>
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve"> <data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
<value>Duo two-step login is required for your account. </value> <value>A autenticação em duas etapas do Duo é necessária para sua conta. </value>
</data> </data>
<data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve"> <data name="FollowTheStepsFromDuoToFinishLoggingIn" xml:space="preserve">
<value>Follow the steps from Duo to finish logging in.</value> <value>Siga os passos no Duo para finalizar o login.</value>
</data> </data>
<data name="LaunchDuo" xml:space="preserve"> <data name="LaunchDuo" xml:space="preserve">
<value>Launch Duo</value> <value>Abrir o Duo</value>
</data> </data>
</root> </root>

View File

@@ -2886,4 +2886,16 @@ Do you want to switch to this account?</value>
<data name="LaunchDuo" xml:space="preserve"> <data name="LaunchDuo" xml:space="preserve">
<value>Launch Duo</value> <value>Launch Duo</value>
</data> </data>
<data name="OrganizationUnassignedItemsMessageUSEUDescriptionLong" xml:space="preserve">
<value>Unassigned organization items are no longer visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible.</value>
</data>
<data name="OrganizationUnassignedItemsMessageSelfHost041624DescriptionLong" xml:space="preserve">
<value>On May 16, 2024, unassigned organization items will no longer be visible in the All Vaults view and only accessible via the Admin Console. Assign these items to a collection from the Admin Console to make them visible.</value>
</data>
<data name="RemindMeLater" xml:space="preserve">
<value>Remind me later</value>
</data>
<data name="Notice" xml:space="preserve">
<value>Notice</value>
</data>
</root> </root>

View File

@@ -2641,22 +2641,22 @@
<value>Запомнить это устройство</value> <value>Запомнить это устройство</value>
</data> </data>
<data name="Passkey" xml:space="preserve"> <data name="Passkey" xml:space="preserve">
<value>Ключ доступа</value> <value>Passkey</value>
</data> </data>
<data name="Passkeys" xml:space="preserve"> <data name="Passkeys" xml:space="preserve">
<value>Ключи доступа</value> <value>Passkeys</value>
</data> </data>
<data name="Application" xml:space="preserve"> <data name="Application" xml:space="preserve">
<value>Приложение</value> <value>Приложение</value>
</data> </data>
<data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve"> <data name="YouCannotEditPasskeyApplicationBecauseItWouldInvalidateThePasskey" xml:space="preserve">
<value>Редактирование ключа доступа невозможно, поскольку это приведет к его аннулированию</value> <value>Редактирование passkey невозможно, поскольку это приведет к его аннулированию</value>
</data> </data>
<data name="PasskeyWillNotBeCopied" xml:space="preserve"> <data name="PasskeyWillNotBeCopied" xml:space="preserve">
<value>Ключ доступа не будет скопирован</value> <value>Passkey не будет скопирован</value>
</data> </data>
<data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve"> <data name="ThePasskeyWillNotBeCopiedToTheClonedItemDoYouWantToContinueCloningThisItem" xml:space="preserve">
<value>Ключ доступа не будет скопирован в клонированный элемент. Продолжить клонирование этого элемента?</value> <value>Passkey не будет скопирован в клонированный элемент. Продолжить клонирование этого элемента?</value>
</data> </data>
<data name="CopyApplication" xml:space="preserve"> <data name="CopyApplication" xml:space="preserve">
<value>Скопировать приложение</value> <value>Скопировать приложение</value>

View File

@@ -641,7 +641,7 @@
<value>தற்போதைய கடவுச்சொல்லை மேலெழுத உறுதியா?</value> <value>தற்போதைய கடவுச்சொல்லை மேலெழுத உறுதியா?</value>
</data> </data>
<data name="PushNotificationAlert" xml:space="preserve"> <data name="PushNotificationAlert" xml:space="preserve">
<value>push அறிவிப்புகள் பயன்படுத்தி Bitwarden உம் பெட்டகத்தை தானாக ஒத்திசைகிறது. சிறந்த சாத்தியமான அனுபவத்திற்கு, பின்வரும் தூண்டியில் push அறிவிப்புகளை இயக்கச்சொல்லி கேட்டால் "அனுமதி"ஐ தேர்ந்தெடுக்கவும்.</value> <value>Push அறிவிப்புகள் பயன்படுத்தி Bitwarden உம் பெட்டகத்தை தானாக ஒத்திசைகிறது. சிறந்த சாத்தியமான அனுபவத்திற்கு, பின்வரும் தூண்டியில் push அறிவிப்புகளை இயக்கச்சொல்லி கேட்டால் "அனுமதி"ஐ தேர்ந்தெடுக்கவும்.</value>
<comment>Push notifications for apple products</comment> <comment>Push notifications for apple products</comment>
</data> </data>
<data name="RateTheApp" xml:space="preserve"> <data name="RateTheApp" xml:space="preserve">
@@ -923,7 +923,7 @@
<value>இணைப்பு அழிக்கப்பட்டது</value> <value>இணைப்பு அழிக்கப்பட்டது</value>
</data> </data>
<data name="ChooseFile" xml:space="preserve"> <data name="ChooseFile" xml:space="preserve">
<value>கோப்பை தேர்ந்தெடு</value> <value>கோப்பைத் தேர்ந்தெடு</value>
</data> </data>
<data name="File" xml:space="preserve"> <data name="File" xml:space="preserve">
<value>கோப்பு</value> <value>கோப்பு</value>

View File

@@ -1143,7 +1143,7 @@ Quá trình quét sẽ diễn ra tự động.</value>
<value>Địa chỉ biểu tượng máy chủ</value> <value>Địa chỉ biểu tượng máy chủ</value>
</data> </data>
<data name="AutofillWithBitwarden" xml:space="preserve"> <data name="AutofillWithBitwarden" xml:space="preserve">
<value>Tự động điền với Bitwarden</value> <value>Tự động điền bằng Bitwarden</value>
</data> </data>
<data name="VaultIsLocked" xml:space="preserve"> <data name="VaultIsLocked" xml:space="preserve">
<value>Kho đã khóa</value> <value>Kho đã khóa</value>

View File

@@ -2808,23 +2808,23 @@
<value>附加选项</value> <value>附加选项</value>
</data> </data>
<data name="ContinueToWebApp" xml:space="preserve"> <data name="ContinueToWebApp" xml:space="preserve">
<value>接下来前往网页应用吗?</value> <value>前往网页 App 吗?</value>
</data> </data>
<data name="ContinueToX" xml:space="preserve"> <data name="ContinueToX" xml:space="preserve">
<value>接下来前往 {0} 吗?</value> <value>前往 {0} 吗?</value>
<comment>The parameter is an URL, like bitwarden.com.</comment> <comment>The parameter is an URL, like bitwarden.com.</comment>
</data> </data>
<data name="ContinueToHelpCenter" xml:space="preserve"> <data name="ContinueToHelpCenter" xml:space="preserve">
<value>接下来前往帮助中心吗?</value> <value>前往帮助中心吗?</value>
</data> </data>
<data name="ContinueToContactSupport" xml:space="preserve"> <data name="ContinueToContactSupport" xml:space="preserve">
<value>接下来要联系支持吗?</value> <value>要联系支持吗?</value>
</data> </data>
<data name="ContinueToPrivacyPolicy" xml:space="preserve"> <data name="ContinueToPrivacyPolicy" xml:space="preserve">
<value>接下来查看隐私政策吗?</value> <value>查看隐私政策吗?</value>
</data> </data>
<data name="ContinueToAppStore" xml:space="preserve"> <data name="ContinueToAppStore" xml:space="preserve">
<value>接下来前往 App Store 吗?</value> <value>前往 App Store 吗?</value>
</data> </data>
<data name="TwoStepLoginDescriptionLong" xml:space="preserve"> <data name="TwoStepLoginDescriptionLong" xml:space="preserve">
<value>通过在 Bitwarden 网页应用中设置两步登录,可以使您的账户更加安全。</value> <value>通过在 Bitwarden 网页应用中设置两步登录,可以使您的账户更加安全。</value>
@@ -2874,7 +2874,7 @@
<value>您的组织要求您设置主密码。</value> <value>您的组织要求您设置主密码。</value>
</data> </data>
<data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve"> <data name="SetUpAnUnlockOptionToChangeYourVaultTimeoutAction" xml:space="preserve">
<value>设置解锁选项以更改您的密码库超时作。</value> <value>设置解锁选项以更改您的密码库超时作。</value>
</data> </data>
<data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve"> <data name="DuoTwoStepLoginIsRequiredForYourAccount" xml:space="preserve">
<value>您的账户要求使用 Duo 两步登录。 </value> <value>您的账户要求使用 Duo 两步登录。 </value>

View File

@@ -334,6 +334,11 @@ namespace Bit.Core.Services
return SendAsync<object, CipherResponse>(HttpMethod.Put, string.Concat("/ciphers/", id, "/restore"), null, true, true); return SendAsync<object, CipherResponse>(HttpMethod.Put, string.Concat("/ciphers/", id, "/restore"), null, true, true);
} }
public Task<bool> HasUnassignedCiphersAsync()
{
return SendAsync<object, bool>(HttpMethod.Get, "/ciphers/has-unassigned-ciphers", null, true, true);
}
#endregion #endregion
#region Attachments APIs #region Attachments APIs

View File

@@ -829,6 +829,24 @@ namespace Bit.Core.Services
await ClearCacheAsync(); await ClearCacheAsync();
} }
public async Task<bool> VerifyOrganizationHasUnassignedItemsAsync()
{
var organizations = await _stateService.GetOrganizationsAsync();
if (organizations?.Any() != true)
{
return false;
}
try
{
return await _apiService.HasUnassignedCiphersAsync();
}
catch (ApiException ex) when (ex.Error?.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
return false;
}
}
// Helpers // Helpers
private async Task<Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>> MakeAttachmentKeyAsync(string organizationId, Cipher cipher = null, CipherView cipherView = null) private async Task<Tuple<SymmetricCryptoKey, EncString, SymmetricCryptoKey>> MakeAttachmentKeyAsync(string organizationId, Cipher cipher = null, CipherView cipherView = null)

View File

@@ -1384,6 +1384,16 @@ namespace Bit.Core.Services
await _storageMediatorService.SaveAsync(Constants.RegionEnvironment, value); await _storageMediatorService.SaveAsync(Constants.RegionEnvironment, value);
} }
public async Task<bool> GetShouldCheckOrganizationUnassignedItemsAsync(string userId = null)
{
return await _storageMediatorService.GetAsync<bool?>(await ComposeKeyAsync(Constants.ShouldCheckOrganizationUnassignedItemsKey, userId)) ?? true;
}
public async Task SetShouldCheckOrganizationUnassignedItemsAsync(bool shouldCheck, string userId = null)
{
await _storageMediatorService.SaveAsync<bool?>(await ComposeKeyAsync(Constants.ShouldCheckOrganizationUnassignedItemsKey, userId), shouldCheck);
}
// Helpers // Helpers
[Obsolete("Use IStorageMediatorService instead")] [Obsolete("Use IStorageMediatorService instead")]

View File

@@ -46,6 +46,7 @@ namespace Bit.Core.Utilities
var settingsService = new SettingsService(stateService); var settingsService = new SettingsService(stateService);
var fileUploadService = new FileUploadService(apiService); var fileUploadService = new FileUploadService(apiService);
var configService = new ConfigService(apiService, stateService, logger); var configService = new ConfigService(apiService, stateService, logger);
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService, var cipherService = new CipherService(cryptoService, stateService, settingsService, apiService,
fileUploadService, storageService, i18nService, () => searchService, configService, clearCipherCacheKey, fileUploadService, storageService, i18nService, () => searchService, configService, clearCipherCacheKey,
allClearCipherCacheKeys); allClearCipherCacheKeys);
@@ -87,7 +88,6 @@ namespace Bit.Core.Utilities
keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService); keyConnectorService, passwordGenerationService, policyService, deviceTrustCryptoService, passwordResetEnrollmentService);
var exportService = new ExportService(folderService, cipherService, cryptoService); var exportService = new ExportService(folderService, cipherService, cryptoService);
var auditService = new AuditService(cryptoFunctionService, apiService); var auditService = new AuditService(cryptoFunctionService, apiService);
var environmentService = new EnvironmentService(apiService, stateService, conditionedRunner);
var eventService = new EventService(apiService, stateService, organizationService, cipherService); var eventService = new EventService(apiService, stateService, organizationService, cipherService);
var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService); var usernameGenerationService = new UsernameGenerationService(cryptoService, apiService, stateService);

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.autofill</string> <string>com.8bit.bitwarden.autofill</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2024.3.3</string> <string>2024.4.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>

View File

@@ -11,7 +11,7 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.8bit.bitwarden.find-login-action-extension</string> <string>com.8bit.bitwarden.find-login-action-extension</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2024.3.3</string> <string>2024.4.0</string>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>
<array> <array>
<string>en</string> <string>en</string>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>XPC!</string> <string>XPC!</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>2024.3.3</string> <string>2024.4.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>