mirror of
https://github.com/bitwarden/mobile
synced 2026-01-05 01:53:17 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02ad4adff5 | ||
|
|
faa6904ce3 | ||
|
|
c27da8e7c4 | ||
|
|
3ef5ca9cc0 | ||
|
|
2fbd3b4538 | ||
|
|
b3b21ea6b1 | ||
|
|
a3b4ede8f3 | ||
|
|
10ea6a86e3 | ||
|
|
3b2b37b3b0 | ||
|
|
75e27ffbe3 | ||
|
|
a2cff6da28 | ||
|
|
fe80fd0ba1 | ||
|
|
5c6b9fa471 | ||
|
|
d926565358 | ||
|
|
ce0b8bc62d | ||
|
|
04aeddc5de | ||
|
|
13ffbe911a | ||
|
|
ab04759b0e | ||
|
|
798cfef391 | ||
|
|
3b5cdfe03c | ||
|
|
63449a3832 |
14
.github/scripts/android/clean-fdroid.ps1
vendored
14
.github/scripts/android/clean-fdroid.ps1
vendored
@@ -30,16 +30,6 @@ $xml.Load($androidManifest);
|
|||||||
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
||||||
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
|
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
|
||||||
|
|
||||||
$firebaseReceiver1=$xml.SelectSingleNode(`
|
|
||||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
|
|
||||||
$nsAndroid);
|
|
||||||
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
|
|
||||||
|
|
||||||
$firebaseReceiver2=$xml.SelectSingleNode(`
|
|
||||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
|
|
||||||
$nsAndroid);
|
|
||||||
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
|
|
||||||
|
|
||||||
$xml.Save($androidManifest);
|
$xml.Save($androidManifest);
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
@@ -56,6 +46,10 @@ $firebaseNode=$xml.SelectSingleNode(`
|
|||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
||||||
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
||||||
|
|
||||||
|
$daggerNode=$xml.SelectSingleNode(`
|
||||||
|
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
|
||||||
|
$daggerNode.ParentNode.RemoveChild($daggerNode);
|
||||||
|
|
||||||
$safetyNetNode=$xml.SelectSingleNode(`
|
$safetyNetNode=$xml.SelectSingleNode(`
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
||||||
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[](https://ci.appveyor.com/project/bitwarden/mobile)
|
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
|
||||||
[](https://crowdin.com/project/bitwarden-mobile)
|
[](https://crowdin.com/project/bitwarden-mobile)
|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
|
|||||||
146
appveyor.yml
146
appveyor.yml
@@ -1,146 +0,0 @@
|
|||||||
image:
|
|
||||||
- Visual Studio 2019
|
|
||||||
- Ubuntu1804
|
|
||||||
|
|
||||||
branches:
|
|
||||||
except:
|
|
||||||
- l10n_master
|
|
||||||
- gh-pages
|
|
||||||
|
|
||||||
configuration: Release
|
|
||||||
|
|
||||||
stack: node 10
|
|
||||||
|
|
||||||
init:
|
|
||||||
- sh: |
|
|
||||||
if [ "${DEBUG_SSH}" == "true" ]
|
|
||||||
then
|
|
||||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
|
|
||||||
fi
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
|
||||||
iex ((new-object net.webclient).DownloadString(`
|
|
||||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
}
|
|
||||||
- ps: |
|
|
||||||
if($env:APPVEYOR_REPO_TAG -eq "true") {
|
|
||||||
$tagName = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
|
|
||||||
$env:RELEASE_NAME = "Version ${tagName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
install:
|
|
||||||
- sh: |
|
|
||||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/secure-file/master/install.sh' | bash -e -
|
|
||||||
./appveyor-tools/secure-file -decrypt ./store/fdroid/keystore.jks.enc -secret $FDROID_KEYSTORE_ENC_PASSWORD
|
|
||||||
- sh: npm install
|
|
||||||
- sh: |
|
|
||||||
sudo apt-get -qq update
|
|
||||||
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
|
|
||||||
- sh: |
|
|
||||||
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
|
|
||||||
then
|
|
||||||
git config --global credential.helper store
|
|
||||||
echo "https://${GH_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
|
|
||||||
git config --global user.email "ci@bitwarden.com"
|
|
||||||
git config --global user.name "Bitwarden CI"
|
|
||||||
fi
|
|
||||||
- cmd: choco install cloc --no-progress
|
|
||||||
- cmd: "cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML"
|
|
||||||
#- cmd: appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
|
||||||
#- cmd: appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
|
|
||||||
#- cmd: vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
|
|
||||||
#- cmd: ps: .\src\Android\update-android.ps1
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- ps: |
|
|
||||||
if($isWindows) {
|
|
||||||
nuget restore
|
|
||||||
if($env:UPLOAD_KEYSTORE_DEC_SECRET -or$env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
|
|
||||||
nuget install secure-file -ExcludeVersion
|
|
||||||
}
|
|
||||||
if($env:GOOGLE_SERVICES_DEC_SECRET) {
|
|
||||||
secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc `
|
|
||||||
-secret $env:GOOGLE_SERVICES_DEC_SECRET
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- sh: |
|
|
||||||
if [ "${APPVEYOR_REPO_TAG}" == "true" ]
|
|
||||||
then
|
|
||||||
mkdir dist
|
|
||||||
cp CNAME ./dist
|
|
||||||
cd store
|
|
||||||
chmod 600 fdroid/config.py fdroid/keystore.jks
|
|
||||||
mkdir -p temp/fdroid
|
|
||||||
TEMP_DIR="$APPVEYOR_BUILD_FOLDER/store/temp/fdroid"
|
|
||||||
cd fdroid
|
|
||||||
echo "keypass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
|
|
||||||
echo "keystorepass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
|
|
||||||
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
|
|
||||||
mkdir -p repo
|
|
||||||
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
|
|
||||||
https://github.com/bitwarden/mobile/releases/download/$APPVEYOR_REPO_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
|
|
||||||
fdroid update
|
|
||||||
fdroid server update
|
|
||||||
cd ..
|
|
||||||
rm -rf temp/fdroid/archive
|
|
||||||
mv -v temp/fdroid ../dist
|
|
||||||
cd fdroid
|
|
||||||
cp index.html btn.png qr.png ../../dist/fdroid
|
|
||||||
cd $APPVEYOR_BUILD_FOLDER
|
|
||||||
fi
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:KEYSTORE_DEC_SECRET) {
|
|
||||||
msbuild bitwarden-mobile.sln `
|
|
||||||
"/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
|
|
||||||
"/p:Configuration=Release"
|
|
||||||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
|
|
||||||
.\src\Android\ci-build-apks.ps1
|
|
||||||
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
|
|
||||||
Push-AppveyorArtifact .\com.x8bit.bitwarden.aab
|
|
||||||
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
|
|
||||||
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
|
|
||||||
}
|
|
||||||
|
|
||||||
on_success:
|
|
||||||
- sh: |
|
|
||||||
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
|
|
||||||
then
|
|
||||||
npm run deploy
|
|
||||||
fi
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:PLAY_DEC_SECRET -and $env:APPVEYOR_REPO_BRANCH -eq 'master') {
|
|
||||||
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
|
|
||||||
cd store\google\Publisher\bin\Release\netcoreapp2.0
|
|
||||||
dotnet Publisher.dll `
|
|
||||||
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
|
|
||||||
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.aab `
|
|
||||||
alpha
|
|
||||||
cd $env:APPVEYOR_BUILD_FOLDER
|
|
||||||
}
|
|
||||||
|
|
||||||
on_finish:
|
|
||||||
- sh: |
|
|
||||||
if [ "${DEBUG_SSH}" == "true" ]
|
|
||||||
then
|
|
||||||
export APPVEYOR_SSH_BLOCK=true
|
|
||||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
|
|
||||||
fi
|
|
||||||
- ps: |
|
|
||||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
|
||||||
$blockRdp = $true
|
|
||||||
iex ((new-object net.webclient).DownloadString(`
|
|
||||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
|
||||||
}
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
tag: $(APPVEYOR_REPO_TAG_NAME)
|
|
||||||
release: $(RELEASE_NAME)
|
|
||||||
provider: GitHub
|
|
||||||
auth_token: $(GH_TOKEN)
|
|
||||||
artifact: /.*/
|
|
||||||
force_update: true
|
|
||||||
on:
|
|
||||||
branch: master
|
|
||||||
APPVEYOR_REPO_TAG: true
|
|
||||||
@@ -23,7 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
.gitignore = .gitignore
|
.gitignore = .gitignore
|
||||||
appveyor.yml = appveyor.yml
|
|
||||||
.github\workflows\build.yml = .github\workflows\build.yml
|
.github\workflows\build.yml = .github\workflows\build.yml
|
||||||
CONTRIBUTING.md = CONTRIBUTING.md
|
CONTRIBUTING.md = CONTRIBUTING.md
|
||||||
crowdin.yml = crowdin.yml
|
crowdin.yml = crowdin.yml
|
||||||
|
|||||||
@@ -50,10 +50,15 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("com.ecosia.android", "url_bar"),
|
new Browser("com.ecosia.android", "url_bar"),
|
||||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||||
|
new Browser("com.jamal2367.styx", "search"),
|
||||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||||
new Browser("com.microsoft.emmx", "url_bar"),
|
new Browser("com.microsoft.emmx", "url_bar"),
|
||||||
|
new Browser("com.microsoft.emmx.beta", "url_bar"),
|
||||||
|
new Browser("com.microsoft.emmx.canary", "url_bar"),
|
||||||
|
new Browser("com.microsoft.emmx.dev", "url_bar"),
|
||||||
new Browser("com.mmbox.browser", "search_box"),
|
new Browser("com.mmbox.browser", "search_box"),
|
||||||
new Browser("com.mmbox.xbrowser", "search_box"),
|
new Browser("com.mmbox.xbrowser", "search_box"),
|
||||||
|
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
|
||||||
new Browser("com.naver.whale", "url_bar"),
|
new Browser("com.naver.whale", "url_bar"),
|
||||||
new Browser("com.opera.browser", "url_field"),
|
new Browser("com.opera.browser", "url_field"),
|
||||||
new Browser("com.opera.browser.beta", "url_field"),
|
new Browser("com.opera.browser.beta", "url_field"),
|
||||||
@@ -77,6 +82,10 @@ namespace Bit.Droid.Accessibility
|
|||||||
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
||||||
new Browser("mark.via", "am,an"),
|
new Browser("mark.via", "am,an"),
|
||||||
new Browser("mark.via.gp", "as"),
|
new Browser("mark.via.gp", "as"),
|
||||||
|
new Browser("net.slions.fulguris.full.download", "search"),
|
||||||
|
new Browser("net.slions.fulguris.full.download.debug", "search"),
|
||||||
|
new Browser("net.slions.fulguris.full.playstore", "search"),
|
||||||
|
new Browser("net.slions.fulguris.full.playstore.debug", "search"),
|
||||||
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
||||||
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
||||||
new Browser("org.bromite.bromite", "url_bar"),
|
new Browser("org.bromite.bromite", "url_bar"),
|
||||||
@@ -182,7 +191,9 @@ namespace Bit.Droid.Accessibility
|
|||||||
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
|
new KnownUsernameField("amazon.pl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
|
new KnownUsernameField("amazon.se", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
||||||
|
|
||||||
// Amazon Web Services
|
// Amazon Web Services
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
<Version>1.5.3.2</Version>
|
<Version>1.5.3.2</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||||
<Version>71.1740.0</Version>
|
<Version>121.0.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
|
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
|
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
|
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
|
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
|
||||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
|
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
|
||||||
|
<PackageReference Include="Xamarin.Google.Dagger" Version="2.27.0" />
|
||||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
||||||
<Version>71.1600.0</Version>
|
<Version>71.1600.0</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -113,7 +114,6 @@
|
|||||||
<Compile Include="Effects\FixedSizeEffect.cs" />
|
<Compile Include="Effects\FixedSizeEffect.cs" />
|
||||||
<Compile Include="Effects\SelectableLabelEffect.cs" />
|
<Compile Include="Effects\SelectableLabelEffect.cs" />
|
||||||
<Compile Include="Effects\TabBarEffect.cs" />
|
<Compile Include="Effects\TabBarEffect.cs" />
|
||||||
<Compile Include="Push\FirebaseInstanceIdService.cs" />
|
|
||||||
<Compile Include="Push\FirebaseMessagingService.cs" />
|
<Compile Include="Push\FirebaseMessagingService.cs" />
|
||||||
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
|
<Compile Include="Receivers\ClearClipboardAlarmReceiver.cs" />
|
||||||
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
|
<Compile Include="Receivers\RestrictionsChangedReceiver.cs" />
|
||||||
@@ -154,7 +154,6 @@
|
|||||||
<AndroidAsset Include="Assets\RobotoMono_Regular.ttf" />
|
<AndroidAsset Include="Assets\RobotoMono_Regular.ttf" />
|
||||||
<AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" />
|
<AndroidAsset Include="Assets\MaterialIcons_Regular.ttf" />
|
||||||
<None Include="8bit.keystore.enc" />
|
<None Include="8bit.keystore.enc" />
|
||||||
<None Include="ci-build-apks.ps1" />
|
|
||||||
<GoogleServicesJson Include="google-services.json" />
|
<GoogleServicesJson Include="google-services.json" />
|
||||||
<GoogleServicesJson Include="google-services.json.enc" />
|
<GoogleServicesJson Include="google-services.json.enc" />
|
||||||
<None Include="fdroid-keystore.jks.enc" />
|
<None Include="fdroid-keystore.jks.enc" />
|
||||||
|
|||||||
@@ -66,10 +66,15 @@ namespace Bit.Droid.Autofill
|
|||||||
"com.ecosia.android",
|
"com.ecosia.android",
|
||||||
"com.google.android.apps.chrome",
|
"com.google.android.apps.chrome",
|
||||||
"com.google.android.apps.chrome_dev",
|
"com.google.android.apps.chrome_dev",
|
||||||
|
"com.jamal2367.styx",
|
||||||
"com.kiwibrowser.browser",
|
"com.kiwibrowser.browser",
|
||||||
"com.microsoft.emmx",
|
"com.microsoft.emmx",
|
||||||
|
"com.microsoft.emmx.beta",
|
||||||
|
"com.microsoft.emmx.canary",
|
||||||
|
"com.microsoft.emmx.dev",
|
||||||
"com.mmbox.browser",
|
"com.mmbox.browser",
|
||||||
"com.mmbox.xbrowser",
|
"com.mmbox.xbrowser",
|
||||||
|
"com.mycompany.app.soulbrowser",
|
||||||
"com.naver.whale",
|
"com.naver.whale",
|
||||||
"com.opera.browser",
|
"com.opera.browser",
|
||||||
"com.opera.browser.beta",
|
"com.opera.browser.beta",
|
||||||
@@ -92,6 +97,10 @@ namespace Bit.Droid.Autofill
|
|||||||
"io.github.forkmaintainers.iceraven",
|
"io.github.forkmaintainers.iceraven",
|
||||||
"mark.via",
|
"mark.via",
|
||||||
"mark.via.gp",
|
"mark.via.gp",
|
||||||
|
"net.slions.fulguris.full.download",
|
||||||
|
"net.slions.fulguris.full.download.debug",
|
||||||
|
"net.slions.fulguris.full.playstore",
|
||||||
|
"net.slions.fulguris.full.playstore.debug",
|
||||||
"org.adblockplus.browser",
|
"org.adblockplus.browser",
|
||||||
"org.adblockplus.browser.beta",
|
"org.adblockplus.browser.beta",
|
||||||
"org.bromite.bromite",
|
"org.bromite.bromite",
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ namespace Bit.Droid
|
|||||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation |
|
||||||
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
|
ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden |
|
||||||
ConfigChanges.Navigation)]
|
ConfigChanges.Navigation)]
|
||||||
|
[IntentFilter(
|
||||||
|
new[] { Intent.ActionSend },
|
||||||
|
Categories = new[] { Intent.CategoryDefault },
|
||||||
|
DataMimeTypes = new[]
|
||||||
|
{
|
||||||
|
@"application/*",
|
||||||
|
@"image/*",
|
||||||
|
@"video/*",
|
||||||
|
@"text/*"
|
||||||
|
})]
|
||||||
[Register("com.x8bit.bitwarden.MainActivity")]
|
[Register("com.x8bit.bitwarden.MainActivity")]
|
||||||
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||||
{
|
{
|
||||||
@@ -169,7 +179,7 @@ namespace Bit.Droid
|
|||||||
_appOptions.GeneratorTile = true;
|
_appOptions.GeneratorTile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (intent.GetBooleanExtra("myVaultTile", false))
|
else if (intent.GetBooleanExtra("myVaultTile", false))
|
||||||
{
|
{
|
||||||
_messagingService.Send("popAllAndGoToTabMyVault");
|
_messagingService.Send("popAllAndGoToTabMyVault");
|
||||||
if (_appOptions != null)
|
if (_appOptions != null)
|
||||||
@@ -177,6 +187,14 @@ namespace Bit.Droid
|
|||||||
_appOptions.MyVaultTile = true;
|
_appOptions.MyVaultTile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (intent.Action == Intent.ActionSend && intent.Type != null)
|
||||||
|
{
|
||||||
|
if (_appOptions != null)
|
||||||
|
{
|
||||||
|
_appOptions.CreateSend = GetCreateSendRequest(intent);
|
||||||
|
}
|
||||||
|
_messagingService.Send("popAllAndGoToTabSend");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ParseYubiKey(intent.DataString);
|
ParseYubiKey(intent.DataString);
|
||||||
@@ -298,7 +316,8 @@ namespace Bit.Droid
|
|||||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
||||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||||
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
||||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
|
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
|
||||||
|
CreateSend = GetCreateSendRequest(Intent)
|
||||||
};
|
};
|
||||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
||||||
if (fillType > 0)
|
if (fillType > 0)
|
||||||
@@ -320,6 +339,37 @@ namespace Bit.Droid
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Tuple<SendType, string, byte[], string> GetCreateSendRequest(Intent intent)
|
||||||
|
{
|
||||||
|
if (intent.Action == Intent.ActionSend && intent.Type != null)
|
||||||
|
{
|
||||||
|
var type = intent.Type;
|
||||||
|
if (type.Contains("text/"))
|
||||||
|
{
|
||||||
|
var subject = intent.GetStringExtra(Intent.ExtraSubject);
|
||||||
|
var text = intent.GetStringExtra(Intent.ExtraText);
|
||||||
|
return new Tuple<SendType, string, byte[], string>(SendType.Text, subject, null, text);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var data = intent.ClipData?.GetItemAt(0);
|
||||||
|
var uri = data?.Uri;
|
||||||
|
var filename = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var stream = ContentResolver.OpenInputStream(uri))
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
stream.CopyTo(memoryStream);
|
||||||
|
return new Tuple<SendType, string, byte[], string>(SendType.File, filename, memoryStream.ToArray(), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Java.IO.FileNotFoundException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void ParseYubiKey(string data)
|
private void ParseYubiKey(string data)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (data == null)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:versionCode="1"
|
android:versionCode="1"
|
||||||
android:versionName="2.9.1"
|
android:versionName="2.10.0"
|
||||||
android:installLocation="internalOnly"
|
android:installLocation="internalOnly"
|
||||||
package="com.x8bit.bitwarden">
|
package="com.x8bit.bitwarden">
|
||||||
|
|
||||||
@@ -40,20 +40,6 @@
|
|||||||
android:resource="@xml/filepaths" />
|
android:resource="@xml/filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
|
|
||||||
android:exported="false" />
|
|
||||||
<receiver
|
|
||||||
android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
|
||||||
android:exported="true"
|
|
||||||
android:permission="com.google.android.c2dm.permission.SEND">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
|
||||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
|
||||||
<category android:name="com.x8bit.bitwarden" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||||
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
|
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
#if !FDROID
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Firebase.Iid;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Push
|
|
||||||
{
|
|
||||||
[Service]
|
|
||||||
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
|
||||||
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
|
||||||
{
|
|
||||||
public async override void OnTokenRefresh()
|
|
||||||
{
|
|
||||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
|
||||||
await storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
|
|
||||||
await pushNotificationService.RegisterAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#if !FDROID
|
#if !FDROID
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Firebase.Messaging;
|
using Firebase.Messaging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -10,10 +10,19 @@ using Xamarin.Forms;
|
|||||||
|
|
||||||
namespace Bit.Droid.Push
|
namespace Bit.Droid.Push
|
||||||
{
|
{
|
||||||
[Service]
|
[Service(Exported=false)]
|
||||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
||||||
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
||||||
{
|
{
|
||||||
|
public async override void OnNewToken(string token)
|
||||||
|
{
|
||||||
|
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||||
|
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||||
|
|
||||||
|
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
|
||||||
|
await pushNotificationService.RegisterAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async override void OnMessageReceived(RemoteMessage message)
|
public async override void OnMessageReceived(RemoteMessage message)
|
||||||
{
|
{
|
||||||
if (message?.Data == null)
|
if (message?.Data == null)
|
||||||
|
|||||||
@@ -65,18 +65,33 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.google.android.apps.chrome_dev"
|
android:name="com.google.android.apps.chrome_dev"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.jamal2367.styx"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.kiwibrowser.browser"
|
android:name="com.kiwibrowser.browser"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.microsoft.emmx"
|
android:name="com.microsoft.emmx"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.microsoft.emmx.beta"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.microsoft.emmx.canary"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.microsoft.emmx.dev"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.mmbox.browser"
|
android:name="com.mmbox.browser"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.mmbox.xbrowser"
|
android:name="com.mmbox.xbrowser"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="com.mycompany.app.soulbrowser"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="com.naver.whale"
|
android:name="com.naver.whale"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
@@ -143,6 +158,18 @@
|
|||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="mark.via.gp"
|
android:name="mark.via.gp"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="net.slions.fulguris.full.download"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="net.slions.fulguris.full.download.debug"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="net.slions.fulguris.full.playstore"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
<compatibility-package
|
||||||
|
android:name="net.slions.fulguris.full.playstore.debug"
|
||||||
|
android:maxLongVersionCode="10000000000"/>
|
||||||
<compatibility-package
|
<compatibility-package
|
||||||
android:name="org.adblockplus.browser"
|
android:name="org.adblockplus.browser"
|
||||||
android:maxLongVersionCode="10000000000"/>
|
android:maxLongVersionCode="10000000000"/>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Javax.Crypto;
|
||||||
|
using Javax.Crypto.Spec;
|
||||||
using Org.BouncyCastle.Crypto;
|
using Org.BouncyCastle.Crypto;
|
||||||
using Org.BouncyCastle.Crypto.Digests;
|
using Org.BouncyCastle.Crypto.Digests;
|
||||||
using Org.BouncyCastle.Crypto.Generators;
|
using Org.BouncyCastle.Crypto.Generators;
|
||||||
@@ -33,5 +35,25 @@ namespace Bit.Droid.Services
|
|||||||
generator.Init(password, salt, iterations);
|
generator.Init(password, salt, iterations);
|
||||||
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
|
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key)
|
||||||
|
{
|
||||||
|
var secKey = new SecretKeySpec(key, "AES");
|
||||||
|
var cipher = Cipher.GetInstance("AES/GCM/NoPadding");
|
||||||
|
var gcmSpec = new GCMParameterSpec(128, iv);
|
||||||
|
cipher.Init(CipherMode.EncryptMode, secKey, gcmSpec);
|
||||||
|
var result = cipher.DoFinal(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key)
|
||||||
|
{
|
||||||
|
var secKey = new SecretKeySpec(key, "AES");
|
||||||
|
var cipher = Cipher.GetInstance("AES/GCM/NoPadding");
|
||||||
|
var gcmSpec = new GCMParameterSpec(128, iv);
|
||||||
|
cipher.Init(CipherMode.DecryptMode, secKey, gcmSpec);
|
||||||
|
var result = cipher.DoFinal(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -760,6 +760,17 @@ namespace Bit.Droid.Services
|
|||||||
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
|
// ref: https://developer.android.com/reference/android/os/SystemClock#elapsedRealtime()
|
||||||
return SystemClock.ElapsedRealtime();
|
return SystemClock.ElapsedRealtime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CloseMainApp()
|
||||||
|
{
|
||||||
|
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||||
|
if (activity == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activity.Finish();
|
||||||
|
_messagingService.Send("finishMainActivity");
|
||||||
|
}
|
||||||
|
|
||||||
private bool DeleteDir(Java.IO.File dir)
|
private bool DeleteDir(Java.IO.File dir)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
$rootPath = $env:APPVEYOR_BUILD_FOLDER;
|
|
||||||
|
|
||||||
$androidPath = $($rootPath + "\src\Android\Android.csproj");
|
|
||||||
$appPath = $($rootPath + "\src\App\App.csproj");
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Increment Version"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$androidManifest = $($rootPath + "\src\Android\Properties\AndroidManifest.xml");
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($androidManifest);
|
|
||||||
|
|
||||||
$node=$xml.SelectNodes("/manifest");
|
|
||||||
$node.SetAttribute("android:versionCode", $env:APPVEYOR_BUILD_NUMBER);
|
|
||||||
|
|
||||||
$xml.Save($androidManifest);
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Decrypt Keystore"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$encKeystorePath = $($rootPath + "\src\Android\8bit.keystore.enc");
|
|
||||||
$encFdroidKeystorePath = $($rootPath + "\src\Android\fdroid-keystore.jks.enc");
|
|
||||||
$encUploadKeystorePath = $($rootPath + "\src\Android\upload-keystore.jks.enc");
|
|
||||||
$secureFilePath = $($rootPath + "\secure-file\tools\secure-file.exe");
|
|
||||||
|
|
||||||
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encKeystorePath) -secret $($env:keystore_dec_secret)"
|
|
||||||
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encFdroidKeystorePath) -secret $($env:fdroid_apk_keystore_dec_secret)"
|
|
||||||
Invoke-Expression "& `"$secureFilePath`" -decrypt $($encUploadKeystorePath) -secret $($env:upload_keystore_dec_secret)"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Sign Google Play Bundle Release Configuration"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:upload_keystore_password)" `
|
|
||||||
"/p:AndroidSigningKeyStore=upload-keystore.jks" "/p:AndroidSigningStorePass=$($env:upload_keystore_password)" `
|
|
||||||
"/p:AndroidPackageFormat=aab" "/v:quiet"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Copy Google Play Bundle to project root"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$signedAabPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.aab");
|
|
||||||
$signedAabDestPath = $($rootPath + "\com.x8bit.bitwarden.aab");
|
|
||||||
|
|
||||||
Copy-Item $signedAabPath $signedAabDestPath
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Sign APK Release Configuration"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:keystore_password)" `
|
|
||||||
"/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=$($env:keystore_password)" "/v:quiet"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Copy Release APK to project root"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$signedApkPath = $($rootPath + "\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk");
|
|
||||||
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden.apk");
|
|
||||||
|
|
||||||
Copy-Item $signedApkPath $signedApkDestPath
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Clean Android and App"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
|
|
||||||
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Backup project files"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
Copy-Item $androidManifest $($androidManifest + ".original");
|
|
||||||
Copy-Item $androidPath $($androidPath + ".original");
|
|
||||||
Copy-Item $appPath $($appPath + ".original");
|
|
||||||
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Cleanup Android Manifest"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($androidManifest);
|
|
||||||
|
|
||||||
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
|
||||||
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
|
|
||||||
|
|
||||||
$firebaseReceiver1=$xml.SelectSingleNode(`
|
|
||||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
|
|
||||||
$nsAndroid);
|
|
||||||
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
|
|
||||||
|
|
||||||
$firebaseReceiver2=$xml.SelectSingleNode(`
|
|
||||||
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
|
|
||||||
$nsAndroid);
|
|
||||||
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
|
|
||||||
|
|
||||||
$xml.Save($androidManifest);
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Uninstall from Android.csproj"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($androidPath);
|
|
||||||
|
|
||||||
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
|
||||||
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
|
|
||||||
|
|
||||||
$firebaseNode=$xml.SelectSingleNode(`
|
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
|
||||||
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
|
||||||
|
|
||||||
$safetyNetNode=$xml.SelectSingleNode(`
|
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
|
||||||
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
|
||||||
|
|
||||||
$xml.Save($androidPath);
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Uninstall from App.csproj"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($appPath);
|
|
||||||
|
|
||||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
|
||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
|
||||||
|
|
||||||
$xml.Save($appPath);
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Restore NuGet"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
Invoke-Expression "& nuget restore"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Build and Sign FDroid Configuration"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
|
|
||||||
"/p:Configuration=FDroid"
|
|
||||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:fdroid_apk_keystore_password)" `
|
|
||||||
"/p:AndroidSigningKeyStore=fdroid-keystore.jks" "/p:AndroidSigningStorePass=$($env:fdroid_apk_keystore_password)" `
|
|
||||||
"/v:quiet"
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Copy FDroid apk to project root"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
$signedApkPath = $($rootPath + "\src\Android\bin\FDroid\com.x8bit.bitwarden-Signed.apk");
|
|
||||||
$signedApkDestPath = $($rootPath + "\com.x8bit.bitwarden-fdroid.apk");
|
|
||||||
|
|
||||||
Copy-Item $signedApkPath $signedApkDestPath
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Done"
|
|
||||||
echo "########################################"
|
|
||||||
@@ -44,5 +44,6 @@ namespace Bit.App.Abstractions
|
|||||||
void OpenAutofillSettings();
|
void OpenAutofillSettings();
|
||||||
bool UsingDarkTheme();
|
bool UsingDarkTheme();
|
||||||
long GetActiveTime();
|
long GetActiveTime();
|
||||||
|
void CloseMainApp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,8 @@ namespace Bit.App
|
|||||||
await SetMainPageAsync();
|
await SetMainPageAsync();
|
||||||
}
|
}
|
||||||
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
else if (message.Command == "popAllAndGoToTabGenerator" ||
|
||||||
message.Command == "popAllAndGoToTabMyVault")
|
message.Command == "popAllAndGoToTabMyVault" ||
|
||||||
|
message.Command == "popAllAndGoToTabSend")
|
||||||
{
|
{
|
||||||
Device.BeginInvokeOnMainThread(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
@@ -146,11 +147,15 @@ namespace Bit.App
|
|||||||
Options.MyVaultTile = false;
|
Options.MyVaultTile = false;
|
||||||
tabsPage.ResetToVaultPage();
|
tabsPage.ResetToVaultPage();
|
||||||
}
|
}
|
||||||
else
|
else if (message.Command == "popAllAndGoToTabGenerator")
|
||||||
{
|
{
|
||||||
Options.GeneratorTile = false;
|
Options.GeneratorTile = false;
|
||||||
tabsPage.ResetToGeneratorPage();
|
tabsPage.ResetToGeneratorPage();
|
||||||
}
|
}
|
||||||
|
else if (message.Command == "popAllAndGoToTabSend")
|
||||||
|
{
|
||||||
|
tabsPage.ResetToSendPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -244,7 +249,8 @@ namespace Bit.App
|
|||||||
_collectionService.ClearAsync(userId),
|
_collectionService.ClearAsync(userId),
|
||||||
_passwordGenerationService.ClearAsync(),
|
_passwordGenerationService.ClearAsync(),
|
||||||
_vaultTimeoutService.ClearAsync(),
|
_vaultTimeoutService.ClearAsync(),
|
||||||
_stateService.PurgeAsync());
|
_stateService.PurgeAsync(),
|
||||||
|
_deviceActionService.ClearCacheAsync());
|
||||||
_vaultTimeoutService.BiometricLocked = true;
|
_vaultTimeoutService.BiometricLocked = true;
|
||||||
_searchService.ClearIndex();
|
_searchService.ClearIndex();
|
||||||
_authService.LogOut(() =>
|
_authService.LogOut(() =>
|
||||||
@@ -274,6 +280,10 @@ namespace Bit.App
|
|||||||
{
|
{
|
||||||
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
|
||||||
}
|
}
|
||||||
|
else if (Options.CreateSend != null)
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new SendAddEditPage(Options));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Current.MainPage = new TabsPage(Options);
|
Current.MainPage = new TabsPage(Options);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
namespace Bit.App.Models
|
namespace Bit.App.Models
|
||||||
{
|
{
|
||||||
@@ -19,6 +20,7 @@ namespace Bit.App.Models
|
|||||||
public string SaveCardExpYear { get; set; }
|
public string SaveCardExpYear { get; set; }
|
||||||
public string SaveCardCode { get; set; }
|
public string SaveCardCode { get; set; }
|
||||||
public bool IosExtension { get; set; }
|
public bool IosExtension { get; set; }
|
||||||
|
public Tuple<SendType, string, byte[], string> CreateSend { get; set; }
|
||||||
|
|
||||||
public void SetAllFrom(AppOptions o)
|
public void SetAllFrom(AppOptions o)
|
||||||
{
|
{
|
||||||
@@ -41,6 +43,7 @@ namespace Bit.App.Models
|
|||||||
SaveCardExpYear = o.SaveCardExpYear;
|
SaveCardExpYear = o.SaveCardExpYear;
|
||||||
SaveCardCode = o.SaveCardCode;
|
SaveCardCode = o.SaveCardCode;
|
||||||
IosExtension = o.IosExtension;
|
IosExtension = o.IosExtension;
|
||||||
|
CreateSend = o.CreateSend;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ namespace Bit.App.Pages
|
|||||||
_vaultTimeoutService.PinProtectedKey);
|
_vaultTimeoutService.PinProtectedKey);
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
failed = decPin != Pin;
|
failed = decPin != Pin;
|
||||||
if (!failed)
|
if (!failed)
|
||||||
{
|
{
|
||||||
@@ -272,7 +272,7 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
var protectedPin = await _storageService.GetAsync<string>(Constants.ProtectedPin);
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
|
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, _email,
|
||||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||||
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key.Key, pinKey);
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Bit.App.Pages
|
|||||||
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
|
var key = await _cryptoService.MakeKeyAsync(MasterPassword, email, kdf, kdfIterations);
|
||||||
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
var masterPasswordHash = await _cryptoService.HashPasswordAsync(MasterPassword, key);
|
||||||
|
|
||||||
Tuple<SymmetricCryptoKey, CipherString> encKey;
|
Tuple<SymmetricCryptoKey, EncString> encKey;
|
||||||
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
var existingEncKey = await _cryptoService.GetEncKeyAsync();
|
||||||
if (existingEncKey == null)
|
if (existingEncKey == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -89,6 +89,18 @@
|
|||||||
StyleClass="text-muted, text-sm, text-bold"
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center" />
|
||||||
</Frame>
|
</Frame>
|
||||||
|
<Frame
|
||||||
|
IsVisible="{Binding SendOptionsPolicyInEffect}"
|
||||||
|
Padding="10"
|
||||||
|
Margin="0, 12, 0, 0"
|
||||||
|
HasShadow="False"
|
||||||
|
BackgroundColor="Transparent"
|
||||||
|
BorderColor="Accent">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n SendOptionsPolicyInEffect}"
|
||||||
|
StyleClass="text-muted, text-sm, text-bold"
|
||||||
|
HorizontalTextAlignment="Center" />
|
||||||
|
</Frame>
|
||||||
<StackLayout StyleClass="box-row">
|
<StackLayout StyleClass="box-row">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Name}"
|
Text="{u:I18n Name}"
|
||||||
@@ -105,7 +117,7 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row"
|
StyleClass="box-row"
|
||||||
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}">
|
IsVisible="{Binding ShowTypeButtons}">
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n Type}"
|
Text="{u:I18n Type}"
|
||||||
StyleClass="box-label" />
|
StyleClass="box-label" />
|
||||||
@@ -210,6 +222,7 @@
|
|||||||
HorizontalTextAlignment="Center" />
|
HorizontalTextAlignment="Center" />
|
||||||
<Button
|
<Button
|
||||||
Text="{u:I18n ChooseFile}"
|
Text="{u:I18n ChooseFile}"
|
||||||
|
IsVisible="{Binding IsAddFromShare, Converter={StaticResource inverseBool}}"
|
||||||
IsEnabled="{Binding SendEnabled}"
|
IsEnabled="{Binding SendEnabled}"
|
||||||
StyleClass="box-button-row"
|
StyleClass="box-button-row"
|
||||||
Clicked="ChooseFile_Clicked" />
|
Clicked="ChooseFile_Clicked" />
|
||||||
@@ -222,7 +235,7 @@
|
|||||||
</StackLayout>
|
</StackLayout>
|
||||||
<Label
|
<Label
|
||||||
Text="{u:I18n TypeFileInfo}"
|
Text="{u:I18n TypeFileInfo}"
|
||||||
IsVisible="{Binding EditMode, Converter={StaticResource inverseBool}}"
|
IsVisible="{Binding ShowTypeButtons}"
|
||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Margin="0,5,0,0" />
|
Margin="0,5,0,0" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
@@ -490,6 +503,20 @@
|
|||||||
StyleClass="box-footer-label"
|
StyleClass="box-footer-label"
|
||||||
Margin="0,5,0,0" />
|
Margin="0,5,0,0" />
|
||||||
</StackLayout>
|
</StackLayout>
|
||||||
|
<StackLayout
|
||||||
|
StyleClass="box-row, box-row-switch"
|
||||||
|
Margin="0,5,0,0">
|
||||||
|
<Label
|
||||||
|
Text="{u:I18n HideEmail}"
|
||||||
|
StyleClass="box-label-regular"
|
||||||
|
VerticalOptions="Center"
|
||||||
|
HorizontalOptions="StartAndExpand" />
|
||||||
|
<Switch
|
||||||
|
IsToggled="{Binding Send.HideEmail}"
|
||||||
|
IsEnabled="{Binding DisableHideEmailControl, Converter={StaticResource inverseBool}}"
|
||||||
|
HorizontalOptions="End"
|
||||||
|
Margin="10,0,0,0" />
|
||||||
|
</StackLayout>
|
||||||
<StackLayout
|
<StackLayout
|
||||||
StyleClass="box-row, box-row-switch"
|
StyleClass="box-row, box-row-switch"
|
||||||
Margin="0,5,0,0">
|
Margin="0,5,0,0">
|
||||||
@@ -513,4 +540,4 @@
|
|||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</ContentPage.Resources>
|
</ContentPage.Resources>
|
||||||
|
|
||||||
</pages:BaseContentPage>
|
</pages:BaseContentPage>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@@ -15,18 +17,21 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
private readonly IBroadcasterService _broadcasterService;
|
private readonly IBroadcasterService _broadcasterService;
|
||||||
|
|
||||||
|
private AppOptions _appOptions;
|
||||||
private SendAddEditPageViewModel _vm;
|
private SendAddEditPageViewModel _vm;
|
||||||
|
|
||||||
public SendAddEditPage(
|
public SendAddEditPage(
|
||||||
|
AppOptions appOptions = null,
|
||||||
string sendId = null,
|
string sendId = null,
|
||||||
SendType? type = null)
|
SendType? type = null)
|
||||||
{
|
{
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
|
_appOptions = appOptions;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as SendAddEditPageViewModel;
|
_vm = BindingContext as SendAddEditPageViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.SendId = sendId;
|
_vm.SendId = sendId;
|
||||||
_vm.Type = type;
|
_vm.Type = appOptions?.CreateSend?.Item1 ?? type;
|
||||||
SetActivityIndicator();
|
SetActivityIndicator();
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
@@ -95,6 +100,7 @@ namespace Bit.App.Pages
|
|||||||
await Navigation.PopModalAsync();
|
await Navigation.PopModalAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await HandleCreateRequest();
|
||||||
if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
|
if (!_vm.EditMode && string.IsNullOrWhiteSpace(_vm.Send?.Name))
|
||||||
{
|
{
|
||||||
RequestFocus(_nameEntry);
|
RequestFocus(_nameEntry);
|
||||||
@@ -271,5 +277,33 @@ namespace Bit.App.Pages
|
|||||||
ToolbarItems.Remove(_shareLink);
|
ToolbarItems.Remove(_shareLink);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleCreateRequest()
|
||||||
|
{
|
||||||
|
if (_appOptions?.CreateSend == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_vm.IsAddFromShare = true;
|
||||||
|
|
||||||
|
var name = _appOptions.CreateSend.Item2;
|
||||||
|
_vm.Send.Name = name;
|
||||||
|
|
||||||
|
var type = _appOptions.CreateSend.Item1;
|
||||||
|
if (type == SendType.File)
|
||||||
|
{
|
||||||
|
_vm.FileData = _appOptions.CreateSend.Item3;
|
||||||
|
_vm.FileName = name;
|
||||||
|
FileType_Clicked(null, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var text = _appOptions.CreateSend.Item4;
|
||||||
|
_vm.Send.Text.Text = text;
|
||||||
|
TextType_Clicked(null, null);
|
||||||
|
}
|
||||||
|
_appOptions.CreateSend = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private bool _sendEnabled;
|
private bool _sendEnabled;
|
||||||
private bool _canAccessPremium;
|
private bool _canAccessPremium;
|
||||||
|
private bool _emailVerified;
|
||||||
private SendView _send;
|
private SendView _send;
|
||||||
private string _fileName;
|
private string _fileName;
|
||||||
private bool _showOptions;
|
private bool _showOptions;
|
||||||
@@ -42,6 +43,9 @@ namespace Bit.App.Pages
|
|||||||
nameof(IsText),
|
nameof(IsText),
|
||||||
nameof(IsFile),
|
nameof(IsFile),
|
||||||
};
|
};
|
||||||
|
private bool _disableHideEmail;
|
||||||
|
private bool _sendOptionsPolicyInEffect;
|
||||||
|
private bool _disableHideEmailControl;
|
||||||
|
|
||||||
public SendAddEditPageViewModel()
|
public SendAddEditPageViewModel()
|
||||||
{
|
{
|
||||||
@@ -91,6 +95,8 @@ namespace Bit.App.Pages
|
|||||||
public byte[] FileData { get; set; }
|
public byte[] FileData { get; set; }
|
||||||
public string NewPassword { get; set; }
|
public string NewPassword { get; set; }
|
||||||
public bool ShareOnSave { get; set; }
|
public bool ShareOnSave { get; set; }
|
||||||
|
public bool DisableHideEmailControl { get; set; }
|
||||||
|
public bool IsAddFromShare { get; set; }
|
||||||
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
public List<KeyValuePair<string, SendType>> TypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
public List<KeyValuePair<string, string>> DeletionTypeOptions { get; }
|
||||||
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
public List<KeyValuePair<string, string>> ExpirationTypeOptions { get; }
|
||||||
@@ -194,6 +200,17 @@ namespace Bit.App.Pages
|
|||||||
nameof(ShowPasswordIcon)
|
nameof(ShowPasswordIcon)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public bool DisableHideEmail
|
||||||
|
{
|
||||||
|
get => _disableHideEmail;
|
||||||
|
set => SetProperty(ref _disableHideEmail, value);
|
||||||
|
}
|
||||||
|
public bool SendOptionsPolicyInEffect
|
||||||
|
{
|
||||||
|
get => _sendOptionsPolicyInEffect;
|
||||||
|
set => SetProperty(ref _sendOptionsPolicyInEffect, value);
|
||||||
|
}
|
||||||
|
public bool ShowTypeButtons => !EditMode && !IsAddFromShare;
|
||||||
public bool EditMode => !string.IsNullOrWhiteSpace(SendId);
|
public bool EditMode => !string.IsNullOrWhiteSpace(SendId);
|
||||||
public bool IsText => Send?.Type == SendType.Text;
|
public bool IsText => Send?.Type == SendType.Text;
|
||||||
public bool IsFile => Send?.Type == SendType.File;
|
public bool IsFile => Send?.Type == SendType.File;
|
||||||
@@ -205,7 +222,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
|
PageTitle = EditMode ? AppResources.EditSend : AppResources.AddSend;
|
||||||
_canAccessPremium = await _userService.CanAccessPremiumAsync();
|
_canAccessPremium = await _userService.CanAccessPremiumAsync();
|
||||||
|
_emailVerified = await _userService.GetEmailVerifiedAsync();
|
||||||
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
|
SendEnabled = ! await AppHelpers.IsSendDisabledByPolicyAsync();
|
||||||
|
DisableHideEmail = await AppHelpers.IsHideEmailDisabledByPolicyAsync();
|
||||||
|
SendOptionsPolicyInEffect = SendEnabled && DisableHideEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> LoadAsync()
|
public async Task<bool> LoadAsync()
|
||||||
@@ -228,7 +248,7 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var defaultType = _canAccessPremium ? SendType.File : SendType.Text;
|
var defaultType = _canAccessPremium && _emailVerified ? SendType.File : SendType.Text;
|
||||||
Send = new SendView
|
Send = new SendView
|
||||||
{
|
{
|
||||||
Type = Type.GetValueOrDefault(defaultType),
|
Type = Type.GetValueOrDefault(defaultType),
|
||||||
@@ -243,6 +263,10 @@ namespace Bit.App.Pages
|
|||||||
_isOverridingPickers = false;
|
_isOverridingPickers = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisableHideEmailControl = !SendEnabled ||
|
||||||
|
(!EditMode && DisableHideEmail) ||
|
||||||
|
(EditMode && DisableHideEmail && !Send.HideEmail);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +339,12 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (!_canAccessPremium)
|
if (!_canAccessPremium)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!_emailVerified)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!EditMode)
|
if (!EditMode)
|
||||||
@@ -354,10 +383,6 @@ namespace Bit.App.Pages
|
|||||||
var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData);
|
var sendId = await _sendService.SaveWithServerAsync(send, encryptedFileData);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
|
||||||
_platformUtilsService.ShowToast("success", null,
|
|
||||||
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
|
|
||||||
await Page.Navigation.PopModalAsync();
|
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.Android && IsFile)
|
if (Device.RuntimePlatform == Device.Android && IsFile)
|
||||||
{
|
{
|
||||||
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418
|
// Workaround for https://github.com/xamarin/Xamarin.Forms/issues/5418
|
||||||
@@ -366,6 +391,21 @@ namespace Bit.App.Pages
|
|||||||
_messagingService.Send("sendUpdated");
|
_messagingService.Send("sendUpdated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ShareOnSave)
|
||||||
|
{
|
||||||
|
_platformUtilsService.ShowToast("success", null,
|
||||||
|
EditMode ? AppResources.SendUpdated : AppResources.NewSendCreated);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
_deviceActionService.CloseMainApp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Page.Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (ShareOnSave)
|
if (ShareOnSave)
|
||||||
{
|
{
|
||||||
var savedSend = await _sendService.GetAsync(sendId);
|
var savedSend = await _sendService.GetAsync(sendId);
|
||||||
@@ -375,7 +415,7 @@ namespace Bit.App.Pages
|
|||||||
await AppHelpers.ShareSendUrlAsync(savedSendView);
|
await AppHelpers.ShareSendUrlAsync(savedSendView);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (ApiException e)
|
catch (ApiException e)
|
||||||
@@ -412,11 +452,37 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task TypeChangedAsync(SendType type)
|
public async Task TypeChangedAsync(SendType type)
|
||||||
{
|
{
|
||||||
|
if (!SendEnabled)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendDisabledWarning);
|
||||||
|
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
_deviceActionService.CloseMainApp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await Page.Navigation.PopModalAsync();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (Send != null)
|
if (Send != null)
|
||||||
{
|
{
|
||||||
if (!EditMode && type == SendType.File && !_canAccessPremium)
|
if (!EditMode && type == SendType.File && (!_canAccessPremium || !_emailVerified))
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.PremiumRequired);
|
if (!_canAccessPremium)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendFilePremiumRequired);
|
||||||
|
}
|
||||||
|
else if (!_emailVerified)
|
||||||
|
{
|
||||||
|
await _platformUtilsService.ShowDialogAsync(AppResources.SendFileEmailVerificationRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsAddFromShare && Device.RuntimePlatform == Device.Android)
|
||||||
|
{
|
||||||
|
_deviceActionService.CloseMainApp();
|
||||||
|
return;
|
||||||
|
}
|
||||||
type = SendType.Text;
|
type = SendType.Text;
|
||||||
}
|
}
|
||||||
Send.Type = type;
|
Send.Type = type;
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ namespace Bit.App.Pages
|
|||||||
private readonly SendGroupingsPageViewModel _vm;
|
private readonly SendGroupingsPageViewModel _vm;
|
||||||
private readonly string _pageName;
|
private readonly string _pageName;
|
||||||
|
|
||||||
|
private AppOptions _appOptions;
|
||||||
private PreviousPageInfo _previousPage;
|
private PreviousPageInfo _previousPage;
|
||||||
|
|
||||||
public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null,
|
public SendGroupingsPage(bool mainPage, SendType? type = null, string pageTitle = null,
|
||||||
PreviousPageInfo previousPage = null)
|
AppOptions appOptions = null, PreviousPageInfo previousPage = null)
|
||||||
{
|
{
|
||||||
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
_pageName = string.Concat(nameof(GroupingsPage), "_", DateTime.UtcNow.Ticks);
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -35,6 +36,7 @@ namespace Bit.App.Pages
|
|||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.MainPage = mainPage;
|
_vm.MainPage = mainPage;
|
||||||
_vm.Type = type;
|
_vm.Type = type;
|
||||||
|
_appOptions = appOptions;
|
||||||
_previousPage = previousPage;
|
_previousPage = previousPage;
|
||||||
if (pageTitle != null)
|
if (pageTitle != null)
|
||||||
{
|
{
|
||||||
@@ -109,8 +111,8 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await ShowPreviousPageAsync();
|
|
||||||
AdjustToolbar();
|
AdjustToolbar();
|
||||||
|
await CheckAddRequest();
|
||||||
}, _mainContent);
|
}, _mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +124,18 @@ namespace Bit.App.Pages
|
|||||||
_vm.DisableRefreshing();
|
_vm.DisableRefreshing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CheckAddRequest()
|
||||||
|
{
|
||||||
|
if (_appOptions?.CreateSend != null)
|
||||||
|
{
|
||||||
|
if (DoOnce())
|
||||||
|
{
|
||||||
|
var page = new SendAddEditPage(_appOptions);
|
||||||
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
|
private async void RowSelected(object sender, SelectedItemChangedEventArgs e)
|
||||||
{
|
{
|
||||||
((ListView)sender).SelectedItem = null;
|
((ListView)sender).SelectedItem = null;
|
||||||
@@ -172,28 +186,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
if (DoOnce())
|
if (DoOnce())
|
||||||
{
|
{
|
||||||
var page = new SendAddEditPage(null, _vm.Type);
|
var page = new SendAddEditPage(null, null, _vm.Type);
|
||||||
await Navigation.PushModalAsync(new NavigationPage(page));
|
await Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ShowPreviousPageAsync()
|
|
||||||
{
|
|
||||||
if (_previousPage == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_previousPage.Page == "view" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new ViewPage(_previousPage.SendId)));
|
|
||||||
}
|
|
||||||
else if (_previousPage.Page == "edit" && !string.IsNullOrWhiteSpace(_previousPage.SendId))
|
|
||||||
{
|
|
||||||
await Navigation.PushModalAsync(new NavigationPage(new AddEditPage(_previousPage.SendId)));
|
|
||||||
}
|
|
||||||
_previousPage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AdjustToolbar()
|
private void AdjustToolbar()
|
||||||
{
|
{
|
||||||
_addItem.IsEnabled = _vm.SendEnabled;
|
_addItem.IsEnabled = _vm.SendEnabled;
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SelectSendAsync(SendView send)
|
public async Task SelectSendAsync(SendView send)
|
||||||
{
|
{
|
||||||
var page = new SendAddEditPage(send.Id);
|
var page = new SendAddEditPage(null, send.Id);
|
||||||
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task SelectSendAsync(SendView send)
|
public async Task SelectSendAsync(SendView send)
|
||||||
{
|
{
|
||||||
var page = new SendAddEditPage(send.Id);
|
var page = new SendAddEditPage(null, send.Id);
|
||||||
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
await Page.Navigation.PushModalAsync(new NavigationPage(page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Bit.App.Pages
|
|||||||
};
|
};
|
||||||
Children.Add(_groupingsPage);
|
Children.Add(_groupingsPage);
|
||||||
|
|
||||||
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true))
|
_sendGroupingsPage = new NavigationPage(new SendGroupingsPage(true, null, null, appOptions))
|
||||||
{
|
{
|
||||||
Title = AppResources.Send,
|
Title = AppResources.Send,
|
||||||
IconImageSource = "paper_plane.png",
|
IconImageSource = "paper_plane.png",
|
||||||
@@ -60,6 +60,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
appOptions.MyVaultTile = false;
|
appOptions.MyVaultTile = false;
|
||||||
}
|
}
|
||||||
|
else if (appOptions?.CreateSend != null)
|
||||||
|
{
|
||||||
|
ResetToSendPage();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetToVaultPage()
|
public void ResetToVaultPage()
|
||||||
@@ -71,6 +75,11 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
CurrentPage = _generatorPage;
|
CurrentPage = _generatorPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ResetToSendPage()
|
||||||
|
{
|
||||||
|
CurrentPage = _sendGroupingsPage;
|
||||||
|
}
|
||||||
|
|
||||||
protected async override void OnCurrentPageChanged()
|
protected async override void OnCurrentPageChanged()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ namespace Bit.App.Pages
|
|||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Downloading);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(attachment, Cipher.OrganizationId);
|
var data = await _cipherService.DownloadAndDecryptAttachmentAsync(Cipher.Id, attachment, Cipher.OrganizationId);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
|
|||||||
24
src/App/Resources/AppResources.Designer.cs
generated
24
src/App/Resources/AppResources.Designer.cs
generated
@@ -3484,5 +3484,29 @@ namespace Bit.App.Resources {
|
|||||||
return ResourceManager.GetString("AboutSend", resourceCulture);
|
return ResourceManager.GetString("AboutSend", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string HideEmail {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("HideEmail", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SendOptionsPolicyInEffect {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SendOptionsPolicyInEffect", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SendFilePremiumRequired {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SendFilePremiumRequired", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string SendFileEmailVerificationRequired {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("SendFileEmailVerificationRequired", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1972,4 +1972,19 @@
|
|||||||
<value>About Send</value>
|
<value>About Send</value>
|
||||||
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="HideEmail" xml:space="preserve">
|
||||||
|
<value>Hide my email address from recipients.</value>
|
||||||
|
</data>
|
||||||
|
<data name="SendOptionsPolicyInEffect" xml:space="preserve">
|
||||||
|
<value>One or more organization policies are affecting your Send options.</value>
|
||||||
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SendFilePremiumRequired" xml:space="preserve">
|
||||||
|
<value>Free accounts are restricted to sharing text only. A premium membership is required to use files with Send.</value>
|
||||||
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
|
</data>
|
||||||
|
<data name="SendFileEmailVerificationRequired" xml:space="preserve">
|
||||||
|
<value>You must verify your email to use files with Send.</value>
|
||||||
|
<comment>'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ namespace Bit.App.Utilities
|
|||||||
}
|
}
|
||||||
else if (selection == AppResources.Edit)
|
else if (selection == AppResources.Edit)
|
||||||
{
|
{
|
||||||
await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(send.Id)));
|
await page.Navigation.PushModalAsync(new NavigationPage(new SendAddEditPage(null, send.Id)));
|
||||||
}
|
}
|
||||||
else if (selection == AppResources.CopyLink)
|
else if (selection == AppResources.CopyLink)
|
||||||
{
|
{
|
||||||
@@ -311,6 +311,26 @@ namespace Bit.App.Utilities
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<bool> IsHideEmailDisabledByPolicyAsync()
|
||||||
|
{
|
||||||
|
var policyService = ServiceContainer.Resolve<IPolicyService>("policyService");
|
||||||
|
var userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||||
|
|
||||||
|
var policies = await policyService.GetAll(PolicyType.SendOptions);
|
||||||
|
var organizations = await userService.GetAllOrganizationAsync();
|
||||||
|
return organizations.Any(o =>
|
||||||
|
{
|
||||||
|
return o.Enabled &&
|
||||||
|
o.Status == OrganizationUserStatusType.Confirmed &&
|
||||||
|
o.UsePolicies &&
|
||||||
|
!o.canManagePolicies &&
|
||||||
|
policies.Any(p => p.OrganizationId == o.Id &&
|
||||||
|
p.Enabled &&
|
||||||
|
p.Data.ContainsKey("disableHideEmail") &&
|
||||||
|
(bool)p.Data["disableHideEmail"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService,
|
public static async Task<bool> PerformUpdateTasksAsync(ISyncService syncService,
|
||||||
IDeviceActionService deviceActionService, IStorageService storageService)
|
IDeviceActionService deviceActionService, IStorageService storageService)
|
||||||
{
|
{
|
||||||
@@ -389,6 +409,11 @@ namespace Bit.App.Utilities
|
|||||||
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions));
|
Application.Current.MainPage = new NavigationPage(new AutofillCiphersPage(appOptions));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (appOptions.CreateSend != null)
|
||||||
|
{
|
||||||
|
Application.Current.MainPage = new NavigationPage(new SendAddEditPage(appOptions));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,14 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path,
|
||||||
TRequest body, bool authed, bool hasResponse);
|
TRequest body, bool authed, bool hasResponse);
|
||||||
void SetUrls(EnvironmentUrls urls);
|
void SetUrls(EnvironmentUrls urls);
|
||||||
Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data);
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
|
Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data);
|
||||||
|
Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request);
|
||||||
|
Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId);
|
||||||
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||||
string organizationId);
|
string organizationId);
|
||||||
|
Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string id, string attachmentId);
|
||||||
|
Task PostAttachmentFileAsync(string id, string attachmentId, MultipartFormDataContent data);
|
||||||
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
Task<List<BreachAccountResponse>> GetHibpBreachAsync(string username);
|
||||||
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
Task PostTwoFactorEmailAsync(TwoFactorEmailRequest request);
|
||||||
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
Task PutDeviceTokenAsync(string identifier, DeviceTokenRequest request);
|
||||||
@@ -56,7 +61,11 @@ namespace Bit.Core.Abstractions
|
|||||||
|
|
||||||
Task<SendResponse> GetSendAsync(string id);
|
Task<SendResponse> GetSendAsync(string id);
|
||||||
Task<SendResponse> PostSendAsync(SendRequest request);
|
Task<SendResponse> PostSendAsync(SendRequest request);
|
||||||
|
Task<SendFileUploadDataResponse> PostFileTypeSendAsync(SendRequest request);
|
||||||
|
Task PostSendFileAsync(string sendId, string fileId, MultipartFormDataContent data);
|
||||||
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data);
|
Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data);
|
||||||
|
Task<SendFileUploadDataResponse> RenewFileUploadUrlAsync(string sendId, string fileId);
|
||||||
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
||||||
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
||||||
Task DeleteSendAsync(string id);
|
Task DeleteSendAsync(string id);
|
||||||
|
|||||||
11
src/Core/Abstractions/IAzureFileUpoadService.cs
Normal file
11
src/Core/Abstractions/IAzureFileUpoadService.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions
|
||||||
|
{
|
||||||
|
public interface IAzureFileUploadService
|
||||||
|
{
|
||||||
|
Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task UpdateLastUsedDateAsync(string id);
|
Task UpdateLastUsedDateAsync(string id);
|
||||||
Task UpsertAsync(CipherData cipher);
|
Task UpsertAsync(CipherData cipher);
|
||||||
Task UpsertAsync(List<CipherData> cipher);
|
Task UpsertAsync(List<CipherData> cipher);
|
||||||
Task<byte[]> DownloadAndDecryptAttachmentAsync(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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<byte[]> HashAsync(byte[] value, CryptoHashAlgorithm algorithm);
|
Task<byte[]> HashAsync(byte[] value, CryptoHashAlgorithm algorithm);
|
||||||
Task<byte[]> HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm);
|
Task<byte[]> HmacAsync(byte[] value, byte[] key, CryptoHashAlgorithm algorithm);
|
||||||
Task<bool> CompareAsync(byte[] a, byte[] b);
|
Task<bool> CompareAsync(byte[] a, byte[] b);
|
||||||
Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key);
|
Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode);
|
||||||
Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key);
|
Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode);
|
||||||
Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey, CryptoHashAlgorithm algorithm);
|
Task<byte[]> RsaEncryptAsync(byte[] data, byte[] publicKey, CryptoHashAlgorithm algorithm);
|
||||||
Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey, CryptoHashAlgorithm algorithm);
|
Task<byte[]> RsaDecryptAsync(byte[] data, byte[] privateKey, CryptoHashAlgorithm algorithm);
|
||||||
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);
|
Task<byte[]> RsaExtractPublicKeyAsync(byte[] privateKey);
|
||||||
|
|||||||
@@ -5,5 +5,7 @@ namespace Bit.Core.Abstractions
|
|||||||
public interface ICryptoPrimitiveService
|
public interface ICryptoPrimitiveService
|
||||||
{
|
{
|
||||||
byte[] Pbkdf2(byte[] password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations);
|
byte[] Pbkdf2(byte[] password, byte[] salt, CryptoHashAlgorithm algorithm, int iterations);
|
||||||
|
byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key);
|
||||||
|
byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ namespace Bit.Core.Abstractions
|
|||||||
Task ClearOrgKeysAsync(bool memoryOnly = false);
|
Task ClearOrgKeysAsync(bool memoryOnly = false);
|
||||||
Task ClearPinProtectedKeyAsync();
|
Task ClearPinProtectedKeyAsync();
|
||||||
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
|
Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key);
|
||||||
Task<byte[]> DecryptToBytesAsync(CipherString cipherString, SymmetricCryptoKey key = null);
|
Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null);
|
||||||
Task<string> DecryptToUtf8Async(CipherString cipherString, SymmetricCryptoKey key = null);
|
Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null);
|
||||||
Task<CipherString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
||||||
Task<CipherString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
|
Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null);
|
||||||
Task<byte[]> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null);
|
||||||
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
|
Task<SymmetricCryptoKey> GetEncKeyAsync(SymmetricCryptoKey key = null);
|
||||||
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
|
Task<List<string>> GetFingerprintAsync(string userId, byte[] publicKey = null);
|
||||||
Task<SymmetricCryptoKey> GetKeyAsync();
|
Task<SymmetricCryptoKey> GetKeyAsync();
|
||||||
@@ -33,17 +33,17 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<bool> HasEncKeyAsync();
|
Task<bool> HasEncKeyAsync();
|
||||||
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key);
|
Task<string> HashPasswordAsync(string password, SymmetricCryptoKey key);
|
||||||
Task<bool> HasKeyAsync();
|
Task<bool> HasKeyAsync();
|
||||||
Task<Tuple<SymmetricCryptoKey, CipherString>> MakeEncKeyAsync(SymmetricCryptoKey key);
|
Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key);
|
||||||
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
|
Task<SymmetricCryptoKey> MakeKeyAsync(string password, string salt, KdfType? kdf, int? kdfIterations);
|
||||||
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,
|
Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt, KdfType kdf, int kdfIterations,
|
||||||
CipherString protectedKeyCs = null);
|
EncString protectedKeyEs = null);
|
||||||
Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
|
Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null);
|
||||||
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations);
|
Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations);
|
||||||
Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync();
|
Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync();
|
||||||
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
|
Task<SymmetricCryptoKey> MakeSendKeyAsync(byte[] keyMaterial);
|
||||||
Task<int> RandomNumberAsync(int min, int max);
|
Task<int> RandomNumberAsync(int min, int max);
|
||||||
Task<Tuple<SymmetricCryptoKey, CipherString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
|
Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key);
|
||||||
Task<CipherString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null);
|
||||||
Task SetEncKeyAsync(string encKey);
|
Task SetEncKeyAsync(string encKey);
|
||||||
Task SetEncPrivateKeyAsync(string encPrivateKey);
|
Task SetEncPrivateKeyAsync(string encPrivateKey);
|
||||||
Task SetKeyAsync(SymmetricCryptoKey key);
|
Task SetKeyAsync(SymmetricCryptoKey key);
|
||||||
|
|||||||
10
src/Core/Abstractions/IFileUploadService.cs
Normal file
10
src/Core/Abstractions/IFileUploadService.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
|
||||||
|
namespace Bit.Core.Abstractions {
|
||||||
|
public interface IFileUploadService {
|
||||||
|
Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData, string fileName, EncByteArray encryptedFileData);
|
||||||
|
Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,12 @@ namespace Bit.Core.Abstractions
|
|||||||
public interface ISendService
|
public interface ISendService
|
||||||
{
|
{
|
||||||
void ClearCache();
|
void ClearCache();
|
||||||
Task<(Send send, byte[] encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password,
|
Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData, string password,
|
||||||
SymmetricCryptoKey key = null);
|
SymmetricCryptoKey key = null);
|
||||||
Task<Send> GetAsync(string id);
|
Task<Send> GetAsync(string id);
|
||||||
Task<List<Send>> GetAllAsync();
|
Task<List<Send>> GetAllAsync();
|
||||||
Task<List<SendView>> GetAllDecryptedAsync();
|
Task<List<SendView>> GetAllDecryptedAsync();
|
||||||
Task<string> SaveWithServerAsync(Send sendData, byte[] encryptedFileData);
|
Task<string> SaveWithServerAsync(Send sendData, EncByteArray encryptedFileData);
|
||||||
Task UpsertAsync(params SendData[] send);
|
Task UpsertAsync(params SendData[] send);
|
||||||
Task ReplaceAsync(Dictionary<string, SendData> sends);
|
Task ReplaceAsync(Dictionary<string, SendData> sends);
|
||||||
Task ClearAsync(string userId);
|
Task ClearAsync(string userId);
|
||||||
|
|||||||
@@ -17,10 +17,12 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<int?> GetKdfIterationsAsync();
|
Task<int?> GetKdfIterationsAsync();
|
||||||
Task<Organization> GetOrganizationAsync(string id);
|
Task<Organization> GetOrganizationAsync(string id);
|
||||||
Task<string> GetSecurityStampAsync();
|
Task<string> GetSecurityStampAsync();
|
||||||
|
Task<bool> GetEmailVerifiedAsync();
|
||||||
Task<string> GetUserIdAsync();
|
Task<string> GetUserIdAsync();
|
||||||
Task<bool> IsAuthenticatedAsync();
|
Task<bool> IsAuthenticatedAsync();
|
||||||
Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations);
|
Task ReplaceOrganizationsAsync(Dictionary<string, OrganizationData> organizations);
|
||||||
Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations);
|
Task SetInformationAsync(string userId, string email, KdfType kdf, int? kdfIterations);
|
||||||
Task SetSecurityStampAsync(string stamp);
|
Task SetSecurityStampAsync(string stamp);
|
||||||
|
Task SetEmailVerifiedAsync(bool emailVerified);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
public interface IVaultTimeoutService
|
public interface IVaultTimeoutService
|
||||||
{
|
{
|
||||||
CipherString PinProtectedKey { get; set; }
|
EncString PinProtectedKey { get; set; }
|
||||||
bool BiometricLocked { get; set; }
|
bool BiometricLocked { get; set; }
|
||||||
|
|
||||||
Task CheckVaultTimeoutAsync();
|
Task CheckVaultTimeoutAsync();
|
||||||
|
|||||||
8
src/Core/Enums/AesMode.cs
Normal file
8
src/Core/Enums/AesMode.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum AesMode : byte
|
||||||
|
{
|
||||||
|
CBC = 0,
|
||||||
|
GCM = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
Rsa2048_OaepSha256_B64 = 3,
|
Rsa2048_OaepSha256_B64 = 3,
|
||||||
Rsa2048_OaepSha1_B64 = 4,
|
Rsa2048_OaepSha1_B64 = 4,
|
||||||
Rsa2048_OaepSha256_HmacSha256_B64 = 5,
|
Rsa2048_OaepSha256_HmacSha256_B64 = 5,
|
||||||
Rsa2048_OaepSha1_HmacSha256_B64 = 6
|
Rsa2048_OaepSha1_HmacSha256_B64 = 6,
|
||||||
|
AesGcm256_B64 = 7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/Core/Enums/FileUploadType.cs
Normal file
9
src/Core/Enums/FileUploadType.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
namespace Bit.Core.Enums
|
||||||
|
{
|
||||||
|
public enum FileUploadType
|
||||||
|
{
|
||||||
|
Direct = 0,
|
||||||
|
Azure = 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,5 +9,6 @@
|
|||||||
RequireSso = 4, // Requires users to authenticate with SSO
|
RequireSso = 4, // Requires users to authenticate with SSO
|
||||||
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
|
PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items
|
||||||
DisableSend = 6, // Disables the ability to create and edit Sends
|
DisableSend = 6, // Disables the ability to create and edit Sends
|
||||||
|
SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Bit.Core.Models.Data
|
|||||||
DeletionDate = response.DeletionDate;
|
DeletionDate = response.DeletionDate;
|
||||||
Password = response.Password;
|
Password = response.Password;
|
||||||
Disabled = response.Disabled;
|
Disabled = response.Disabled;
|
||||||
|
HideEmail = response.HideEmail.GetValueOrDefault();
|
||||||
|
|
||||||
switch (Type)
|
switch (Type)
|
||||||
{
|
{
|
||||||
@@ -54,5 +55,6 @@ namespace Bit.Core.Models.Data
|
|||||||
public DateTime DeletionDate { get; set; }
|
public DateTime DeletionDate { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
public bool HideEmail { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ namespace Bit.Core.Models.Domain
|
|||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
public string Size { get; set; }
|
public string Size { get; set; }
|
||||||
public string SizeName { get; set; }
|
public string SizeName { get; set; }
|
||||||
public CipherString Key { get; set; }
|
public EncString Key { get; set; }
|
||||||
public CipherString FileName { get; set; }
|
public EncString FileName { get; set; }
|
||||||
|
|
||||||
public async Task<AttachmentView> DecryptAsync(string orgId)
|
public async Task<AttachmentView> DecryptAsync(string orgId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ namespace Bit.Core.Models.Domain
|
|||||||
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString CardholderName { get; set; }
|
public EncString CardholderName { get; set; }
|
||||||
public CipherString Brand { get; set; }
|
public EncString Brand { get; set; }
|
||||||
public CipherString Number { get; set; }
|
public EncString Number { get; set; }
|
||||||
public CipherString ExpMonth { get; set; }
|
public EncString ExpMonth { get; set; }
|
||||||
public CipherString ExpYear { get; set; }
|
public EncString ExpYear { get; set; }
|
||||||
public CipherString Code { get; set; }
|
public EncString Code { get; set; }
|
||||||
|
|
||||||
public Task<CardView> DecryptAsync(string orgId)
|
public Task<CardView> DecryptAsync(string orgId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ namespace Bit.Core.Models.Domain
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string OrganizationId { get; set; }
|
public string OrganizationId { get; set; }
|
||||||
public string FolderId { get; set; }
|
public string FolderId { get; set; }
|
||||||
public CipherString Name { get; set; }
|
public EncString Name { get; set; }
|
||||||
public CipherString Notes { get; set; }
|
public EncString Notes { get; set; }
|
||||||
public Enums.CipherType Type { get; set; }
|
public Enums.CipherType Type { get; set; }
|
||||||
public bool Favorite { get; set; }
|
public bool Favorite { get; set; }
|
||||||
public bool OrganizationUseTotp { get; set; }
|
public bool OrganizationUseTotp { get; set; }
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string OrganizationId { get; set; }
|
public string OrganizationId { get; set; }
|
||||||
public CipherString Name { get; set; }
|
public EncString Name { get; set; }
|
||||||
public string ExternalId { get; set; }
|
public string ExternalId { get; set; }
|
||||||
public bool ReadOnly { get; set; }
|
public bool ReadOnly { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ namespace Bit.Core.Models.Domain
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
domainPropInfo.SetValue(domain,
|
domainPropInfo.SetValue(domain,
|
||||||
dataObjProp != null ? new CipherString(dataObjProp as string) : null, null);
|
dataObjProp != null ? new EncString(dataObjProp as string) : null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void BuildDataModel<D, O>(D domain, O dataObj, HashSet<string> map,
|
protected void BuildDataModel<D, O>(D domain, O dataObj, HashSet<string> map,
|
||||||
HashSet<string> notCipherStringList = null)
|
HashSet<string> notEncryptedStringList = null)
|
||||||
where D : Domain
|
where D : Domain
|
||||||
where O : Data.Data
|
where O : Data.Data
|
||||||
{
|
{
|
||||||
@@ -41,13 +41,13 @@ namespace Bit.Core.Models.Domain
|
|||||||
var domainPropInfo = domainType.GetProperty(prop);
|
var domainPropInfo = domainType.GetProperty(prop);
|
||||||
var domainProp = domainPropInfo.GetValue(domain);
|
var domainProp = domainPropInfo.GetValue(domain);
|
||||||
var dataObjPropInfo = dataObjType.GetProperty(prop);
|
var dataObjPropInfo = dataObjType.GetProperty(prop);
|
||||||
if (notCipherStringList?.Contains(prop) ?? false)
|
if (notEncryptedStringList?.Contains(prop) ?? false)
|
||||||
{
|
{
|
||||||
dataObjPropInfo.SetValue(dataObj, domainProp, null);
|
dataObjPropInfo.SetValue(dataObj, domainProp, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dataObjPropInfo.SetValue(dataObj, (domainProp as CipherString)?.EncryptedString, null);
|
dataObjPropInfo.SetValue(dataObj, (domainProp as EncString)?.EncryptedString, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
{
|
{
|
||||||
var domainPropInfo = domainType.GetProperty(propName);
|
var domainPropInfo = domainType.GetProperty(propName);
|
||||||
string val = null;
|
string val = null;
|
||||||
if (domainPropInfo.GetValue(domain) is CipherString domainProp)
|
if (domainPropInfo.GetValue(domain) is EncString domainProp)
|
||||||
{
|
{
|
||||||
val = await domainProp.DecryptAsync(orgId, key);
|
val = await domainProp.DecryptAsync(orgId, key);
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/Core/Models/Domain/EncByteArray.cs
Normal file
12
src/Core/Models/Domain/EncByteArray.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Bit.Core.Models.Domain
|
||||||
|
{
|
||||||
|
public class EncByteArray
|
||||||
|
{
|
||||||
|
public byte[] Buffer { get; }
|
||||||
|
|
||||||
|
public EncByteArray(byte[] encryptedByteArray)
|
||||||
|
{
|
||||||
|
Buffer = encryptedByteArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,11 +6,11 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Bit.Core.Models.Domain
|
namespace Bit.Core.Models.Domain
|
||||||
{
|
{
|
||||||
public class CipherString
|
public class EncString
|
||||||
{
|
{
|
||||||
private string _decryptedValue;
|
private string _decryptedValue;
|
||||||
|
|
||||||
public CipherString(EncryptionType encryptionType, string data, string iv = null, string mac = null)
|
public EncString(EncryptionType encryptionType, string data, string iv = null, string mac = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(data))
|
if (string.IsNullOrWhiteSpace(data))
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
Mac = mac;
|
Mac = mac;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString(string encryptedString)
|
public EncString(string encryptedString)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(encryptedString))
|
if (string.IsNullOrWhiteSpace(encryptedString))
|
||||||
{
|
{
|
||||||
@@ -73,6 +73,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
Mac = encPieces[2];
|
Mac = encPieces[2];
|
||||||
break;
|
break;
|
||||||
case EncryptionType.AesCbc256_B64:
|
case EncryptionType.AesCbc256_B64:
|
||||||
|
case EncryptionType.AesGcm256_B64:
|
||||||
if (encPieces.Length != 2)
|
if (encPieces.Length != 2)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -22,8 +22,8 @@ namespace Bit.Core.Models.Domain
|
|||||||
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString Name { get; set; }
|
public EncString Name { get; set; }
|
||||||
public CipherString Value { get; set; }
|
public EncString Value { get; set; }
|
||||||
public FieldType Type { get; set; }
|
public FieldType Type { get; set; }
|
||||||
|
|
||||||
public Task<FieldView> DecryptAsync(string orgId)
|
public Task<FieldView> DecryptAsync(string orgId)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public CipherString Name { get; set; }
|
public EncString Name { get; set; }
|
||||||
public DateTime RevisionDate { get; set; }
|
public DateTime RevisionDate { get; set; }
|
||||||
|
|
||||||
public Task<FolderView> DecryptAsync()
|
public Task<FolderView> DecryptAsync()
|
||||||
|
|||||||
@@ -36,24 +36,24 @@ namespace Bit.Core.Models.Domain
|
|||||||
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString Title { get; set; }
|
public EncString Title { get; set; }
|
||||||
public CipherString FirstName { get; set; }
|
public EncString FirstName { get; set; }
|
||||||
public CipherString MiddleName { get; set; }
|
public EncString MiddleName { get; set; }
|
||||||
public CipherString LastName { get; set; }
|
public EncString LastName { get; set; }
|
||||||
public CipherString Address1 { get; set; }
|
public EncString Address1 { get; set; }
|
||||||
public CipherString Address2 { get; set; }
|
public EncString Address2 { get; set; }
|
||||||
public CipherString Address3 { get; set; }
|
public EncString Address3 { get; set; }
|
||||||
public CipherString City { get; set; }
|
public EncString City { get; set; }
|
||||||
public CipherString State { get; set; }
|
public EncString State { get; set; }
|
||||||
public CipherString PostalCode { get; set; }
|
public EncString PostalCode { get; set; }
|
||||||
public CipherString Country { get; set; }
|
public EncString Country { get; set; }
|
||||||
public CipherString Company { get; set; }
|
public EncString Company { get; set; }
|
||||||
public CipherString Email { get; set; }
|
public EncString Email { get; set; }
|
||||||
public CipherString Phone { get; set; }
|
public EncString Phone { get; set; }
|
||||||
public CipherString SSN { get; set; }
|
public EncString SSN { get; set; }
|
||||||
public CipherString Username { get; set; }
|
public EncString Username { get; set; }
|
||||||
public CipherString PassportNumber { get; set; }
|
public EncString PassportNumber { get; set; }
|
||||||
public CipherString LicenseNumber { get; set; }
|
public EncString LicenseNumber { get; set; }
|
||||||
|
|
||||||
public Task<IdentityView> DecryptAsync(string orgId)
|
public Task<IdentityView> DecryptAsync(string orgId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ namespace Bit.Core.Models.Domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<LoginUri> Uris { get; set; }
|
public List<LoginUri> Uris { get; set; }
|
||||||
public CipherString Username { get; set; }
|
public EncString Username { get; set; }
|
||||||
public CipherString Password { get; set; }
|
public EncString Password { get; set; }
|
||||||
public DateTime? PasswordRevisionDate { get; set; }
|
public DateTime? PasswordRevisionDate { get; set; }
|
||||||
public CipherString Totp { get; set; }
|
public EncString Totp { get; set; }
|
||||||
|
|
||||||
public async Task<LoginView> DecryptAsync(string orgId)
|
public async Task<LoginView> DecryptAsync(string orgId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
BuildDomainModel(this, obj, _map, alreadyEncrypted);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString Uri { get; set; }
|
public EncString Uri { get; set; }
|
||||||
public UriMatchType? Match { get; set; }
|
public UriMatchType? Match { get; set; }
|
||||||
|
|
||||||
public Task<LoginUriView> DecryptAsync(string orgId)
|
public Task<LoginUriView> DecryptAsync(string orgId)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
LastUsedDate = obj.LastUsedDate.GetValueOrDefault();
|
LastUsedDate = obj.LastUsedDate.GetValueOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString Password { get; set; }
|
public EncString Password { get; set; }
|
||||||
public DateTime LastUsedDate { get; set; }
|
public DateTime LastUsedDate { get; set; }
|
||||||
|
|
||||||
public Task<PasswordHistoryView> DecryptAsync(string orgId)
|
public Task<PasswordHistoryView> DecryptAsync(string orgId)
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ namespace Bit.Core.Models.Domain
|
|||||||
public string AccessId { get; set; }
|
public string AccessId { get; set; }
|
||||||
public string UserId { get; set; }
|
public string UserId { get; set; }
|
||||||
public SendType Type { get; set; }
|
public SendType Type { get; set; }
|
||||||
public CipherString Name { get; set; }
|
public EncString Name { get; set; }
|
||||||
public CipherString Notes { get; set; }
|
public EncString Notes { get; set; }
|
||||||
public SendFile File { get; set; }
|
public SendFile File { get; set; }
|
||||||
public SendText Text { get; set; }
|
public SendText Text { get; set; }
|
||||||
public CipherString Key { get; set; }
|
public EncString Key { get; set; }
|
||||||
public int? MaxAccessCount { get; set; }
|
public int? MaxAccessCount { get; set; }
|
||||||
public int AccessCount { get; set; }
|
public int AccessCount { get; set; }
|
||||||
public DateTime RevisionDate { get; set; }
|
public DateTime RevisionDate { get; set; }
|
||||||
@@ -27,6 +27,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
public DateTime DeletionDate { get; set; }
|
public DateTime DeletionDate { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
public bool HideEmail { get; set; }
|
||||||
|
|
||||||
public Send() : base() { }
|
public Send() : base() { }
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
RevisionDate = data.RevisionDate;
|
RevisionDate = data.RevisionDate;
|
||||||
DeletionDate = data.DeletionDate;
|
DeletionDate = data.DeletionDate;
|
||||||
ExpirationDate = data.ExpirationDate;
|
ExpirationDate = data.ExpirationDate;
|
||||||
|
HideEmail = data.HideEmail;
|
||||||
|
|
||||||
switch (Type)
|
switch (Type)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Size { get; set; }
|
public string Size { get; set; }
|
||||||
public string SizeName { get; set; }
|
public string SizeName { get; set; }
|
||||||
public CipherString FileName { get; set; }
|
public EncString FileName { get; set; }
|
||||||
|
|
||||||
public SendFile() : base() { }
|
public SendFile() : base() { }
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Bit.Core.Models.Domain
|
|||||||
{
|
{
|
||||||
public class SendText : Domain
|
public class SendText : Domain
|
||||||
{
|
{
|
||||||
public CipherString Text { get; set; }
|
public EncString Text { get; set; }
|
||||||
public bool Hidden { get; set; }
|
public bool Hidden { get; set; }
|
||||||
|
|
||||||
public SendText() : base() { }
|
public SendText() : base() { }
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Domain
|
namespace Bit.Core.Models.Domain
|
||||||
@@ -30,22 +31,24 @@ namespace Bit.Core.Models.Domain
|
|||||||
}
|
}
|
||||||
|
|
||||||
Key = key;
|
Key = key;
|
||||||
EncType = encType.Value;
|
EncTypes.Add(encType.Value);
|
||||||
|
|
||||||
if (EncType == EncryptionType.AesCbc256_B64 && Key.Length == 32)
|
if (encType.Value == EncryptionType.AesCbc256_B64 && Key.Length == 32)
|
||||||
{
|
{
|
||||||
EncKey = Key;
|
EncKey = Key;
|
||||||
MacKey = null;
|
MacKey = null;
|
||||||
|
EncTypes.Add(EncryptionType.AesGcm256_B64);
|
||||||
}
|
}
|
||||||
else if (EncType == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
|
else if (encType.Value == EncryptionType.AesCbc128_HmacSha256_B64 && Key.Length == 32)
|
||||||
{
|
{
|
||||||
EncKey = new ArraySegment<byte>(Key, 0, 16).ToArray();
|
EncKey = new ArraySegment<byte>(Key, 0, 16).ToArray();
|
||||||
MacKey = new ArraySegment<byte>(Key, 16, 16).ToArray();
|
MacKey = new ArraySegment<byte>(Key, 16, 16).ToArray();
|
||||||
}
|
}
|
||||||
else if (EncType == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
|
else if (encType.Value == EncryptionType.AesCbc256_HmacSha256_B64 && Key.Length == 64)
|
||||||
{
|
{
|
||||||
EncKey = new ArraySegment<byte>(Key, 0, 32).ToArray();
|
EncKey = new ArraySegment<byte>(Key, 0, 32).ToArray();
|
||||||
MacKey = new ArraySegment<byte>(Key, 32, 32).ToArray();
|
MacKey = new ArraySegment<byte>(Key, 32, 32).ToArray();
|
||||||
|
EncTypes.Add(EncryptionType.AesGcm256_B64);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -69,9 +72,9 @@ namespace Bit.Core.Models.Domain
|
|||||||
public byte[] Key { get; set; }
|
public byte[] Key { get; set; }
|
||||||
public byte[] EncKey { get; set; }
|
public byte[] EncKey { get; set; }
|
||||||
public byte[] MacKey { get; set; }
|
public byte[] MacKey { get; set; }
|
||||||
public EncryptionType EncType { get; set; }
|
|
||||||
public string KeyB64 { get; set; }
|
public string KeyB64 { get; set; }
|
||||||
public string EncKeyB64 { get; set; }
|
public string EncKeyB64 { get; set; }
|
||||||
public string MacKeyB64 { get; set; }
|
public string MacKeyB64 { get; set; }
|
||||||
|
public HashSet<EncryptionType> EncTypes { get; set; } = new HashSet<EncryptionType>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ namespace Bit.Core.Models.Request
|
|||||||
{
|
{
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
|
public long FileSize { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Bit.Core.Models.Request
|
|||||||
public SendFileApi File { get; set; }
|
public SendFileApi File { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
public bool HideEmail { get; set; }
|
||||||
|
|
||||||
public SendRequest(Send send, long? fileLength)
|
public SendRequest(Send send, long? fileLength)
|
||||||
{
|
{
|
||||||
@@ -32,6 +33,7 @@ namespace Bit.Core.Models.Request
|
|||||||
Key = send.Key?.EncryptedString;
|
Key = send.Key?.EncryptedString;
|
||||||
Password = send.Password;
|
Password = send.Password;
|
||||||
Disabled = send.Disabled;
|
Disabled = send.Disabled;
|
||||||
|
HideEmail = send.HideEmail;
|
||||||
|
|
||||||
switch (Type)
|
switch (Type)
|
||||||
{
|
{
|
||||||
|
|||||||
12
src/Core/Models/Response/AttachmentUploadDataReponse.cs
Normal file
12
src/Core/Models/Response/AttachmentUploadDataReponse.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class AttachmentUploadDataResponse
|
||||||
|
{
|
||||||
|
public string AttachmentId { get; set; }
|
||||||
|
public FileUploadType FileUploadType { get; set; }
|
||||||
|
public CipherResponse CipherResponse { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Core/Models/Response/SendFileUploadDataResponse.cs
Normal file
12
src/Core/Models/Response/SendFileUploadDataResponse.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
namespace Bit.Core.Models.Response
|
||||||
|
{
|
||||||
|
public class SendFileUploadDataResponse
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
public FileUploadType FileUploadType { get; set; }
|
||||||
|
public SendResponse SendResponse { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,5 +21,6 @@ namespace Bit.Core.Models.Response
|
|||||||
public DateTime DeletionDate { get; set; }
|
public DateTime DeletionDate { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
public bool Disabled { get; set; }
|
public bool Disabled { get; set; }
|
||||||
|
public bool? HideEmail { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ namespace Bit.Core.Models.View
|
|||||||
ExpirationDate = send.ExpirationDate;
|
ExpirationDate = send.ExpirationDate;
|
||||||
Disabled = send.Disabled;
|
Disabled = send.Disabled;
|
||||||
Password = send.Password;
|
Password = send.Password;
|
||||||
|
HideEmail = send.HideEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
@@ -45,5 +46,6 @@ namespace Bit.Core.Models.View
|
|||||||
public bool Expired => ExpirationDate.HasValue && ExpirationDate.Value <= DateTime.UtcNow;
|
public bool Expired => ExpirationDate.HasValue && ExpirationDate.Value <= DateTime.UtcNow;
|
||||||
public bool PendingDelete => DeletionDate <= DateTime.UtcNow;
|
public bool PendingDelete => DeletionDate <= DateTime.UtcNow;
|
||||||
public string DisplayDate => DeletionDate.ToLocalTime().ToString("MMM d, yyyy, h:mm tt");
|
public string DisplayDate => DeletionDate.ToLocalTime().ToString("MMM d, yyyy, h:mm tt");
|
||||||
|
public bool HideEmail { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,9 +223,19 @@ namespace Bit.Core.Services
|
|||||||
public Task<SendResponse> PostSendAsync(SendRequest request) =>
|
public Task<SendResponse> PostSendAsync(SendRequest request) =>
|
||||||
SendAsync<SendRequest, SendResponse>(HttpMethod.Post, "/sends", request, true, true);
|
SendAsync<SendRequest, SendResponse>(HttpMethod.Post, "/sends", request, true, true);
|
||||||
|
|
||||||
|
public Task<SendFileUploadDataResponse> PostFileTypeSendAsync(SendRequest request) =>
|
||||||
|
SendAsync<SendRequest, SendFileUploadDataResponse>(HttpMethod.Post, "/sends/file/v2", request, true, true);
|
||||||
|
|
||||||
|
public Task PostSendFileAsync(string sendId, string fileId, MultipartFormDataContent data) =>
|
||||||
|
SendAsync<MultipartFormDataContent, object>(HttpMethod.Post, $"/sends/{sendId}/file/{fileId}", data, true, false);
|
||||||
|
|
||||||
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
public Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data) =>
|
public Task<SendResponse> PostSendFileAsync(MultipartFormDataContent data) =>
|
||||||
SendAsync<MultipartFormDataContent, SendResponse>(HttpMethod.Post, "/sends/file", data, true, true);
|
SendAsync<MultipartFormDataContent, SendResponse>(HttpMethod.Post, "/sends/file", data, true, true);
|
||||||
|
|
||||||
|
public Task<SendFileUploadDataResponse> RenewFileUploadUrlAsync(string sendId, string fileId) =>
|
||||||
|
SendAsync<object, SendFileUploadDataResponse>(HttpMethod.Get, $"/sends/{sendId}/file/{fileId}", null, true, true);
|
||||||
|
|
||||||
public Task<SendResponse> PutSendAsync(string id, SendRequest request) =>
|
public Task<SendResponse> PutSendAsync(string id, SendRequest request) =>
|
||||||
SendAsync<SendRequest, SendResponse>(HttpMethod.Put, $"/sends/{id}", request, true, true);
|
SendAsync<SendRequest, SendResponse>(HttpMethod.Put, $"/sends/{id}", request, true, true);
|
||||||
|
|
||||||
@@ -293,16 +303,26 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
#region Attachments APIs
|
#region Attachments APIs
|
||||||
|
|
||||||
public Task<CipherResponse> PostCipherAttachmentAsync(string id, MultipartFormDataContent data)
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
|
public Task<CipherResponse> PostCipherAttachmentLegacyAsync(string id, MultipartFormDataContent data)
|
||||||
{
|
{
|
||||||
return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post,
|
return SendAsync<MultipartFormDataContent, CipherResponse>(HttpMethod.Post,
|
||||||
string.Concat("/ciphers/", id, "/attachment"), data, true, true);
|
string.Concat("/ciphers/", id, "/attachment"), data, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<AttachmentUploadDataResponse> PostCipherAttachmentAsync(string id, AttachmentRequest request)
|
||||||
|
{
|
||||||
|
return SendAsync<AttachmentRequest, AttachmentUploadDataResponse>(HttpMethod.Post,
|
||||||
|
$"/ciphers/{id}/attachment/v2", request, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AttachmentResponse> GetAttachmentData(string cipherId, string attachmentId) =>
|
||||||
|
SendAsync<AttachmentResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}", true);
|
||||||
|
|
||||||
public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
|
public Task DeleteCipherAttachmentAsync(string id, string attachmentId)
|
||||||
{
|
{
|
||||||
return SendAsync<object, object>(HttpMethod.Delete,
|
return SendAsync(HttpMethod.Delete,
|
||||||
string.Concat("/ciphers/", id, "/attachment/", attachmentId), null, true, false);
|
string.Concat("/ciphers/", id, "/attachment/", attachmentId), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
public Task PostShareCipherAttachmentAsync(string id, string attachmentId, MultipartFormDataContent data,
|
||||||
@@ -313,6 +333,13 @@ namespace Bit.Core.Services
|
|||||||
data, true, false);
|
data, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<AttachmentUploadDataResponse> RenewAttachmentUploadUrlAsync(string cipherId, string attachmentId) =>
|
||||||
|
SendAsync<AttachmentUploadDataResponse>(HttpMethod.Get, $"/ciphers/{cipherId}/attachment/{attachmentId}/renew", true);
|
||||||
|
|
||||||
|
public Task PostAttachmentFileAsync(string cipherId, string attachmentId, MultipartFormDataContent data) =>
|
||||||
|
SendAsync(HttpMethod.Post,
|
||||||
|
$"/ciphers/{cipherId}/attachment/{attachmentId}", data, true);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Sync APIs
|
#region Sync APIs
|
||||||
@@ -427,6 +454,12 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendAsync(HttpMethod method, string path, bool authed) =>
|
||||||
|
SendAsync<object, object>(method, path, null, authed, false);
|
||||||
|
public Task SendAsync<TRequest>(HttpMethod method, string path, TRequest body, bool authed) =>
|
||||||
|
SendAsync<TRequest, object>(method, path, body, authed, false);
|
||||||
|
public Task<TResponse> SendAsync<TResponse>(HttpMethod method, string path, bool authed) =>
|
||||||
|
SendAsync<object, TResponse>(method, path, null, authed, true);
|
||||||
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
public async Task<TResponse> SendAsync<TRequest, TResponse>(HttpMethod method, string path, TRequest body,
|
||||||
bool authed, bool hasResponse)
|
bool authed, bool hasResponse)
|
||||||
{
|
{
|
||||||
|
|||||||
197
src/Core/Services/AzureFileUploadService.cs
Normal file
197
src/Core/Services/AzureFileUploadService.cs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class AzureFileUploadService : IAzureFileUploadService
|
||||||
|
{
|
||||||
|
private const long MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB
|
||||||
|
private const int MAX_BLOCKS_PER_BLOB = 50000;
|
||||||
|
private const decimal MAX_MOBILE_BLOCK_SIZE = 5 * 1024 * 1024; // 5 MB
|
||||||
|
|
||||||
|
private readonly HttpClient _httpClient = new HttpClient();
|
||||||
|
|
||||||
|
public AzureFileUploadService()
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue()
|
||||||
|
{
|
||||||
|
NoCache = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Upload(string uri, EncByteArray data, Func<Task<string>> renewalCallback)
|
||||||
|
{
|
||||||
|
if (data?.Buffer?.Length <= MAX_SINGLE_BLOB_UPLOAD_SIZE)
|
||||||
|
{
|
||||||
|
await AzureUploadBlob(uri, data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await AzureUploadBlocks(uri, data, renewalCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AzureUploadBlob(string uri, EncByteArray data)
|
||||||
|
{
|
||||||
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
|
{
|
||||||
|
var uriBuilder = new UriBuilder(uri);
|
||||||
|
var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||||
|
|
||||||
|
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
|
||||||
|
requestMessage.Headers.Add("x-ms-version", paramValues["sv"]);
|
||||||
|
requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
|
||||||
|
|
||||||
|
requestMessage.Content = new ByteArrayContent(data.Buffer);
|
||||||
|
requestMessage.Version = new Version(1, 0);
|
||||||
|
requestMessage.Method = HttpMethod.Put;
|
||||||
|
requestMessage.RequestUri = uriBuilder.Uri;
|
||||||
|
|
||||||
|
var blobResponse = await _httpClient.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
if (blobResponse.StatusCode != HttpStatusCode.Created)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to create Azure blob");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AzureUploadBlocks(string uri, EncByteArray data, Func<Task<string>> renewalFunc)
|
||||||
|
{
|
||||||
|
_httpClient.Timeout = TimeSpan.FromHours(3);
|
||||||
|
var baseParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
|
||||||
|
var blockSize = MaxBlockSize(baseParams["sv"]);
|
||||||
|
var blockIndex = 0;
|
||||||
|
var numBlocks = Math.Ceiling((decimal)data.Buffer.Length / blockSize);
|
||||||
|
var blocksStaged = new List<string>();
|
||||||
|
|
||||||
|
if (numBlocks > MAX_BLOCKS_PER_BLOB)
|
||||||
|
{
|
||||||
|
throw new Exception($"Cannot upload file, exceeds maximum size of {blockSize * MAX_BLOCKS_PER_BLOB}");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (blockIndex < numBlocks)
|
||||||
|
{
|
||||||
|
uri = await RenewUriIfNecessary(uri, renewalFunc);
|
||||||
|
var blockUriBuilder = new UriBuilder(uri);
|
||||||
|
var blockId = EncodeBlockId(blockIndex);
|
||||||
|
var blockParams = HttpUtility.ParseQueryString(blockUriBuilder.Query);
|
||||||
|
blockParams.Add("comp", "block");
|
||||||
|
blockParams.Add("blockid", blockId);
|
||||||
|
blockUriBuilder.Query = blockParams.ToString();
|
||||||
|
|
||||||
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
|
{
|
||||||
|
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
|
||||||
|
requestMessage.Headers.Add("x-ms-version", baseParams["sv"]);
|
||||||
|
requestMessage.Headers.Add("x-ms-blob-type", "BlockBlob");
|
||||||
|
|
||||||
|
requestMessage.Content = new ByteArrayContent(data.Buffer.Skip(blockIndex * blockSize).Take(blockSize).ToArray());
|
||||||
|
requestMessage.Version = new Version(1, 0);
|
||||||
|
requestMessage.Method = HttpMethod.Put;
|
||||||
|
requestMessage.RequestUri = blockUriBuilder.Uri;
|
||||||
|
|
||||||
|
var blockResponse = await _httpClient.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
if (blockResponse.StatusCode != HttpStatusCode.Created)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to create Azure block");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blocksStaged.Add(blockId);
|
||||||
|
blockIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var requestMessage = new HttpRequestMessage())
|
||||||
|
{
|
||||||
|
uri = await RenewUriIfNecessary(uri, renewalFunc);
|
||||||
|
var blockListXml = GenerateBlockListXml(blocksStaged);
|
||||||
|
var blockListUriBuilder = new UriBuilder(uri);
|
||||||
|
var blockListParams = HttpUtility.ParseQueryString(blockListUriBuilder.Query);
|
||||||
|
blockListParams.Add("comp", "blocklist");
|
||||||
|
blockListUriBuilder.Query = blockListParams.ToString();
|
||||||
|
|
||||||
|
requestMessage.Headers.Add("x-ms-date", DateTime.UtcNow.ToString("R"));
|
||||||
|
requestMessage.Headers.Add("x-ms-version", baseParams["sv"]);
|
||||||
|
|
||||||
|
requestMessage.Content = new StringContent(blockListXml);
|
||||||
|
requestMessage.Version = new Version(1, 0);
|
||||||
|
requestMessage.Method = HttpMethod.Put;
|
||||||
|
requestMessage.RequestUri = blockListUriBuilder.Uri;
|
||||||
|
|
||||||
|
var blockListResponse = await _httpClient.SendAsync(requestMessage);
|
||||||
|
|
||||||
|
if (blockListResponse.StatusCode != HttpStatusCode.Created)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to PUT Azure block list");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> RenewUriIfNecessary(string uri, Func<Task<string>> renewalFunc)
|
||||||
|
{
|
||||||
|
var uriParams = HttpUtility.ParseQueryString(CoreHelpers.GetUri(uri).Query);
|
||||||
|
|
||||||
|
if (DateTime.TryParse(uriParams.Get("se") ?? "", out DateTime expiry) && expiry < DateTime.UtcNow.AddSeconds(1))
|
||||||
|
{
|
||||||
|
return await renewalFunc();
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GenerateBlockListXml(List<string> blocksStaged)
|
||||||
|
{
|
||||||
|
var xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"utf-8\"?><BlockList>");
|
||||||
|
foreach(var blockId in blocksStaged)
|
||||||
|
{
|
||||||
|
xml.Append($"<Latest>{blockId}</Latest>");
|
||||||
|
}
|
||||||
|
xml.Append("</BlockList>");
|
||||||
|
return xml.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string EncodeBlockId(int index)
|
||||||
|
{
|
||||||
|
// Encoded blockId max size is 64, so pre-encoding max size is 48
|
||||||
|
var paddedString = index.ToString("D48");
|
||||||
|
return Convert.ToBase64String(Encoding.UTF8.GetBytes(paddedString));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int MaxBlockSize(string version)
|
||||||
|
{
|
||||||
|
long maxSize = 4194304L; // 4 MiB
|
||||||
|
if (CompareAzureVersions(version, "2019-12-12") >= 0)
|
||||||
|
{
|
||||||
|
maxSize = 4194304000L; // 4000 MiB
|
||||||
|
}
|
||||||
|
else if (CompareAzureVersions(version, "2016-05-31") >= 0)
|
||||||
|
{
|
||||||
|
maxSize = 104857600L; // 100 MiB
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxSize > MAX_MOBILE_BLOCK_SIZE ? (int)MAX_MOBILE_BLOCK_SIZE : (int) maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CompareAzureVersions(string a, string b)
|
||||||
|
{
|
||||||
|
var v1Parts = a.Split('-').Select(p => int.Parse(p));
|
||||||
|
var v2Parts = b.Split('-').Select(p => int.Parse(p));
|
||||||
|
|
||||||
|
return a[0] != b[0] ? a[0] - b[0] :
|
||||||
|
a[1] != b[1] ? a[1] - b[1] :
|
||||||
|
a[2] != b[2] ? a[2] - b[2] :
|
||||||
|
0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/Core/Services/BitwardenFileUploadService.cs
Normal file
27
src/Core/Services/BitwardenFileUploadService.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class BitwardenFileUploadService
|
||||||
|
{
|
||||||
|
public BitwardenFileUploadService(ApiService apiService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ApiService _apiService;
|
||||||
|
|
||||||
|
public async Task Upload(string encryptedFileName, EncByteArray encryptedFileData, Func<MultipartFormDataContent, Task> apiCall)
|
||||||
|
{
|
||||||
|
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
|
||||||
|
{
|
||||||
|
{ new ByteArrayContent(encryptedFileData.Buffer), "data", encryptedFileName }
|
||||||
|
};
|
||||||
|
|
||||||
|
await apiCall(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
|
private readonly IFileUploadService _fileUploadService;
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly Func<ISearchService> _searchService;
|
private readonly Func<ISearchService> _searchService;
|
||||||
@@ -47,6 +48,7 @@ namespace Bit.Core.Services
|
|||||||
IUserService userService,
|
IUserService userService,
|
||||||
ISettingsService settingsService,
|
ISettingsService settingsService,
|
||||||
IApiService apiService,
|
IApiService apiService,
|
||||||
|
IFileUploadService fileUploadService,
|
||||||
IStorageService storageService,
|
IStorageService storageService,
|
||||||
II18nService i18nService,
|
II18nService i18nService,
|
||||||
Func<ISearchService> searchService,
|
Func<ISearchService> searchService,
|
||||||
@@ -57,6 +59,7 @@ namespace Bit.Core.Services
|
|||||||
_userService = userService;
|
_userService = userService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_apiService = apiService;
|
_apiService = apiService;
|
||||||
|
_fileUploadService = fileUploadService;
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_i18nService = i18nService;
|
_i18nService = i18nService;
|
||||||
_searchService = searchService;
|
_searchService = searchService;
|
||||||
@@ -553,21 +556,47 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
public async Task<Cipher> SaveAttachmentRawWithServerAsync(Cipher cipher, string filename, byte[] data)
|
||||||
{
|
{
|
||||||
var key = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
var orgKey = await _cryptoService.GetOrgKeyAsync(cipher.OrganizationId);
|
||||||
var encFileName = await _cryptoService.EncryptAsync(filename, key);
|
var encFileName = await _cryptoService.EncryptAsync(filename, orgKey);
|
||||||
var dataEncKey = await _cryptoService.MakeEncKeyAsync(key);
|
var (attachmentKey, orgEncAttachmentKey) = await _cryptoService.MakeEncKeyAsync(orgKey);
|
||||||
var encData = await _cryptoService.EncryptToBytesAsync(data, dataEncKey.Item1);
|
var encFileData = await _cryptoService.EncryptToBytesAsync(data, attachmentKey);
|
||||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
|
||||||
var fd = new MultipartFormDataContent(boundary);
|
CipherResponse response;
|
||||||
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
try
|
||||||
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString);
|
{
|
||||||
var response = await _apiService.PostCipherAttachmentAsync(cipher.Id, fd);
|
var request = new AttachmentRequest
|
||||||
|
{
|
||||||
|
Key = orgEncAttachmentKey.EncryptedString,
|
||||||
|
FileName = encFileName.EncryptedString,
|
||||||
|
FileSize = encFileData.Buffer.Length,
|
||||||
|
};
|
||||||
|
|
||||||
|
var uploadDataResponse = await _apiService.PostCipherAttachmentAsync(cipher.Id, request);
|
||||||
|
response = uploadDataResponse.CipherResponse;
|
||||||
|
await _fileUploadService.UploadCipherAttachmentFileAsync(uploadDataResponse, encFileName.EncryptedString, encFileData);
|
||||||
|
}
|
||||||
|
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound || e.Error.StatusCode == System.Net.HttpStatusCode.MethodNotAllowed)
|
||||||
|
{
|
||||||
|
response = await LegacyServerAttachmentFileUploadAsync(cipher.Id, encFileName, encFileData, orgEncAttachmentKey);
|
||||||
|
}
|
||||||
|
|
||||||
var userId = await _userService.GetUserIdAsync();
|
var userId = await _userService.GetUserIdAsync();
|
||||||
var cData = new CipherData(response, userId, cipher.CollectionIds);
|
var cData = new CipherData(response, userId, cipher.CollectionIds);
|
||||||
await UpsertAsync(cData);
|
await UpsertAsync(cData);
|
||||||
return new Cipher(cData);
|
return new Cipher(cData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
|
private async Task<CipherResponse> LegacyServerAttachmentFileUploadAsync(string cipherId,
|
||||||
|
EncString encFileName, EncByteArray encFileData, EncString key)
|
||||||
|
{
|
||||||
|
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
||||||
|
var fd = new MultipartFormDataContent(boundary);
|
||||||
|
fd.Add(new StringContent(key.EncryptedString), "key");
|
||||||
|
fd.Add(new StreamContent(new MemoryStream(encFileData.Buffer)), "data", encFileName.EncryptedString);
|
||||||
|
return await _apiService.PostCipherAttachmentLegacyAsync(cipherId, fd);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SaveCollectionsWithServerAsync(Cipher cipher)
|
public async Task SaveCollectionsWithServerAsync(Cipher cipher)
|
||||||
{
|
{
|
||||||
var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList());
|
var request = new CipherCollectionsRequest(cipher.CollectionIds?.ToList());
|
||||||
@@ -706,11 +735,23 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(AttachmentView attachment, string organizationId)
|
public async Task<byte[]> DownloadAndDecryptAttachmentAsync(string cipherId, AttachmentView attachment, string organizationId)
|
||||||
{
|
{
|
||||||
|
string url;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _httpClient.GetAsync(new Uri(attachment.Url));
|
var attachmentDownloadResponse = await _apiService.GetAttachmentData(cipherId, attachment.Id);
|
||||||
|
url = attachmentDownloadResponse.Url;
|
||||||
|
}
|
||||||
|
// TODO: Delete this catch when all Servers are updated to respond to the above method
|
||||||
|
catch (ApiException e) when (e.Error.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
url = attachment.Url;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync(new Uri(url));
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@@ -787,7 +828,7 @@ namespace Bit.Core.Services
|
|||||||
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
var boundary = string.Concat("--BWMobileFormBoundary", DateTime.UtcNow.Ticks);
|
||||||
var fd = new MultipartFormDataContent(boundary);
|
var fd = new MultipartFormDataContent(boundary);
|
||||||
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
fd.Add(new StringContent(dataEncKey.Item2.EncryptedString), "key");
|
||||||
fd.Add(new StreamContent(new MemoryStream(encData)), "data", encFileName.EncryptedString);
|
fd.Add(new StreamContent(new MemoryStream(encData.Buffer)), "data", encFileName.EncryptedString);
|
||||||
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
|
await _apiService.PostShareCipherAttachmentAsync(cipherId, attachmentView.Id, fd, organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,7 +1037,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var modelPropInfo = modelType.GetProperty(propName);
|
var modelPropInfo = modelType.GetProperty(propName);
|
||||||
var modelProp = modelPropInfo.GetValue(model) as string;
|
var modelProp = modelPropInfo.GetValue(model) as string;
|
||||||
CipherString val = null;
|
EncString val = null;
|
||||||
if (!string.IsNullOrWhiteSpace(modelProp))
|
if (!string.IsNullOrWhiteSpace(modelProp))
|
||||||
{
|
{
|
||||||
val = await _cryptoService.EncryptAsync(modelProp, key);
|
val = await _cryptoService.EncryptAsync(modelProp, key);
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte[] decEncKey = null;
|
byte[] decEncKey = null;
|
||||||
var encKeyCipher = new CipherString(encKey);
|
var encKeyCipher = new EncString(encKey);
|
||||||
if (encKeyCipher.EncryptionType == EncryptionType.AesCbc256_B64)
|
if (encKeyCipher.EncryptionType == EncryptionType.AesCbc256_B64)
|
||||||
{
|
{
|
||||||
decEncKey = await DecryptToBytesAsync(encKeyCipher, key);
|
decEncKey = await DecryptToBytesAsync(encKeyCipher, key);
|
||||||
@@ -205,7 +205,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
_privateKey = await DecryptToBytesAsync(new CipherString(encPrivateKey), null);
|
_privateKey = await DecryptToBytesAsync(new EncString(encPrivateKey), null);
|
||||||
return _privateKey;
|
return _privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +389,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt,
|
public async Task<SymmetricCryptoKey> MakeKeyFromPinAsync(string pin, string salt,
|
||||||
KdfType kdf, int kdfIterations, CipherString protectedKeyCs = null)
|
KdfType kdf, int kdfIterations, EncString protectedKeyCs = null)
|
||||||
{
|
{
|
||||||
if (protectedKeyCs == null)
|
if (protectedKeyCs == null)
|
||||||
{
|
{
|
||||||
@@ -398,27 +398,27 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
throw new Exception("No PIN protected key found.");
|
throw new Exception("No PIN protected key found.");
|
||||||
}
|
}
|
||||||
protectedKeyCs = new CipherString(pinProtectedKey);
|
protectedKeyCs = new EncString(pinProtectedKey);
|
||||||
}
|
}
|
||||||
var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations);
|
var pinKey = await MakePinKeyAysnc(pin, salt, kdf, kdfIterations);
|
||||||
var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey);
|
var decKey = await DecryptToBytesAsync(protectedKeyCs, pinKey);
|
||||||
return new SymmetricCryptoKey(decKey);
|
return new SymmetricCryptoKey(decKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<CipherString, SymmetricCryptoKey>> MakeShareKeyAsync()
|
public async Task<Tuple<EncString, SymmetricCryptoKey>> MakeShareKeyAsync()
|
||||||
{
|
{
|
||||||
var shareKey = await _cryptoFunctionService.RandomBytesAsync(64);
|
var shareKey = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||||
var publicKey = await GetPublicKeyAsync();
|
var publicKey = await GetPublicKeyAsync();
|
||||||
var encShareKey = await RsaEncryptAsync(shareKey, publicKey);
|
var encShareKey = await RsaEncryptAsync(shareKey, publicKey);
|
||||||
return new Tuple<CipherString, SymmetricCryptoKey>(encShareKey, new SymmetricCryptoKey(shareKey));
|
return new Tuple<EncString, SymmetricCryptoKey>(encShareKey, new SymmetricCryptoKey(shareKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<string, CipherString>> MakeKeyPairAsync(SymmetricCryptoKey key = null)
|
public async Task<Tuple<string, EncString>> MakeKeyPairAsync(SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
var keyPair = await _cryptoFunctionService.RsaGenerateKeyPairAsync(2048);
|
||||||
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
var publicB64 = Convert.ToBase64String(keyPair.Item1);
|
||||||
var privateEnc = await EncryptAsync(keyPair.Item2, key);
|
var privateEnc = await EncryptAsync(keyPair.Item2, key);
|
||||||
return new Tuple<string, CipherString>(publicB64, privateEnc);
|
return new Tuple<string, EncString>(publicB64, privateEnc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations)
|
public async Task<SymmetricCryptoKey> MakePinKeyAysnc(string pin, string salt, KdfType kdf, int kdfIterations)
|
||||||
@@ -447,20 +447,20 @@ namespace Bit.Core.Services
|
|||||||
return Convert.ToBase64String(hash);
|
return Convert.ToBase64String(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<SymmetricCryptoKey, CipherString>> MakeEncKeyAsync(SymmetricCryptoKey key)
|
public async Task<Tuple<SymmetricCryptoKey, EncString>> MakeEncKeyAsync(SymmetricCryptoKey key)
|
||||||
{
|
{
|
||||||
var theKey = await GetKeyForEncryptionAsync(key);
|
var theKey = await GetKeyForEncryptionAsync(key);
|
||||||
var encKey = await _cryptoFunctionService.RandomBytesAsync(64);
|
var encKey = await _cryptoFunctionService.RandomBytesAsync(64);
|
||||||
return await BuildEncKeyAsync(theKey, encKey);
|
return await BuildEncKeyAsync(theKey, encKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Tuple<SymmetricCryptoKey, CipherString>> RemakeEncKeyAsync(SymmetricCryptoKey key)
|
public async Task<Tuple<SymmetricCryptoKey, EncString>> RemakeEncKeyAsync(SymmetricCryptoKey key)
|
||||||
{
|
{
|
||||||
var encKey = await GetEncKeyAsync();
|
var encKey = await GetEncKeyAsync();
|
||||||
return await BuildEncKeyAsync(key, encKey.Key);
|
return await BuildEncKeyAsync(key, encKey.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CipherString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null)
|
public async Task<EncString> EncryptAsync(string plainValue, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
if (plainValue == null)
|
if (plainValue == null)
|
||||||
{
|
{
|
||||||
@@ -469,7 +469,7 @@ namespace Bit.Core.Services
|
|||||||
return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key);
|
return await EncryptAsync(Encoding.UTF8.GetBytes(plainValue), key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CipherString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null)
|
public async Task<EncString> EncryptAsync(byte[] plainValue, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
if (plainValue == null)
|
if (plainValue == null)
|
||||||
{
|
{
|
||||||
@@ -479,10 +479,10 @@ namespace Bit.Core.Services
|
|||||||
var iv = Convert.ToBase64String(encObj.Iv);
|
var iv = Convert.ToBase64String(encObj.Iv);
|
||||||
var data = Convert.ToBase64String(encObj.Data);
|
var data = Convert.ToBase64String(encObj.Data);
|
||||||
var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null;
|
var mac = encObj.Mac != null ? Convert.ToBase64String(encObj.Mac) : null;
|
||||||
return new CipherString(encObj.Key.EncType, data, iv, mac);
|
return new EncString(encObj.Type, data, iv, mac);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null)
|
public async Task<EncByteArray> EncryptToBytesAsync(byte[] plainValue, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
var encValue = await AesEncryptAsync(plainValue, key);
|
var encValue = await AesEncryptAsync(plainValue, key);
|
||||||
var macLen = 0;
|
var macLen = 0;
|
||||||
@@ -491,17 +491,17 @@ namespace Bit.Core.Services
|
|||||||
macLen = encValue.Mac.Length;
|
macLen = encValue.Mac.Length;
|
||||||
}
|
}
|
||||||
var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length];
|
var encBytes = new byte[1 + encValue.Iv.Length + macLen + encValue.Data.Length];
|
||||||
Buffer.BlockCopy(new byte[] { (byte)encValue.Key.EncType }, 0, encBytes, 0, 1);
|
Buffer.BlockCopy(new byte[] { (byte)encValue.Type }, 0, encBytes, 0, 1);
|
||||||
Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length);
|
Buffer.BlockCopy(encValue.Iv, 0, encBytes, 1, encValue.Iv.Length);
|
||||||
if (encValue.Mac != null)
|
if (encValue.Mac != null)
|
||||||
{
|
{
|
||||||
Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length);
|
Buffer.BlockCopy(encValue.Mac, 0, encBytes, 1 + encValue.Iv.Length, encValue.Mac.Length);
|
||||||
}
|
}
|
||||||
Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length);
|
Buffer.BlockCopy(encValue.Data, 0, encBytes, 1 + encValue.Iv.Length + macLen, encValue.Data.Length);
|
||||||
return encBytes;
|
return new EncByteArray(encBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CipherString> RsaEncryptAsync(byte[] data, byte[] publicKey = null)
|
public async Task<EncString> RsaEncryptAsync(byte[] data, byte[] publicKey = null)
|
||||||
{
|
{
|
||||||
if (publicKey == null)
|
if (publicKey == null)
|
||||||
{
|
{
|
||||||
@@ -512,21 +512,21 @@ namespace Bit.Core.Services
|
|||||||
throw new Exception("Public key unavailable.");
|
throw new Exception("Public key unavailable.");
|
||||||
}
|
}
|
||||||
var encBytes = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey, CryptoHashAlgorithm.Sha1);
|
var encBytes = await _cryptoFunctionService.RsaEncryptAsync(data, publicKey, CryptoHashAlgorithm.Sha1);
|
||||||
return new CipherString(EncryptionType.Rsa2048_OaepSha1_B64, Convert.ToBase64String(encBytes));
|
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Convert.ToBase64String(encBytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> DecryptToBytesAsync(CipherString cipherString, SymmetricCryptoKey key = null)
|
public async Task<byte[]> DecryptToBytesAsync(EncString encString, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
var iv = Convert.FromBase64String(cipherString.Iv);
|
var iv = Convert.FromBase64String(encString.Iv);
|
||||||
var data = Convert.FromBase64String(cipherString.Data);
|
var data = Convert.FromBase64String(encString.Data);
|
||||||
var mac = !string.IsNullOrWhiteSpace(cipherString.Mac) ? Convert.FromBase64String(cipherString.Mac) : null;
|
var mac = !string.IsNullOrWhiteSpace(encString.Mac) ? Convert.FromBase64String(encString.Mac) : null;
|
||||||
return await AesDecryptToBytesAsync(cipherString.EncryptionType, data, iv, mac, key);
|
return await AesDecryptToBytesAsync(encString.EncryptionType, data, iv, mac, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> DecryptToUtf8Async(CipherString cipherString, SymmetricCryptoKey key = null)
|
public async Task<string> DecryptToUtf8Async(EncString encString, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
return await AesDecryptToUtf8Async(cipherString.EncryptionType, cipherString.Data,
|
return await AesDecryptToUtf8Async(encString.EncryptionType, encString.Data,
|
||||||
cipherString.Iv, cipherString.Mac, key);
|
encString.Iv, encString.Mac, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key)
|
public async Task<byte[]> DecryptFromBytesAsync(byte[] encBytes, SymmetricCryptoKey key)
|
||||||
@@ -561,6 +561,14 @@ namespace Bit.Core.Services
|
|||||||
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
|
ivBytes = new ArraySegment<byte>(encBytes, 1, 16).ToArray();
|
||||||
ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray();
|
ctBytes = new ArraySegment<byte>(encBytes, 17, encBytes.Length - 17).ToArray();
|
||||||
break;
|
break;
|
||||||
|
case EncryptionType.AesGcm256_B64:
|
||||||
|
if (encBytes.Length < 13) // 1 + 12 + ctLength
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ivBytes = new ArraySegment<byte>(encBytes, 1, 12).ToArray();
|
||||||
|
ctBytes = new ArraySegment<byte>(encBytes, 13, encBytes.Length - 13).ToArray();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -589,16 +597,27 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var obj = new EncryptedObject
|
var obj = new EncryptedObject
|
||||||
{
|
{
|
||||||
Key = await GetKeyForEncryptionAsync(key),
|
Key = await GetKeyForEncryptionAsync(key)
|
||||||
Iv = await _cryptoFunctionService.RandomBytesAsync(16)
|
|
||||||
};
|
};
|
||||||
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey);
|
if (true)
|
||||||
if (obj.Key.MacKey != null)
|
|
||||||
{
|
{
|
||||||
var macData = new byte[obj.Iv.Length + obj.Data.Length];
|
obj.Type = EncryptionType.AesCbc256_HmacSha256_B64;
|
||||||
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length);
|
obj.Iv = await _cryptoFunctionService.RandomBytesAsync(16);
|
||||||
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length);
|
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey, AesMode.CBC);
|
||||||
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256);
|
if (obj.Key.MacKey != null)
|
||||||
|
{
|
||||||
|
var macData = new byte[obj.Iv.Length + obj.Data.Length];
|
||||||
|
Buffer.BlockCopy(obj.Iv, 0, macData, 0, obj.Iv.Length);
|
||||||
|
Buffer.BlockCopy(obj.Data, 0, macData, obj.Iv.Length, obj.Data.Length);
|
||||||
|
obj.Mac = await _cryptoFunctionService.HmacAsync(macData, obj.Key.MacKey, CryptoHashAlgorithm.Sha256);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: make this case live when we switch to GCM encryption
|
||||||
|
obj.Type = EncryptionType.AesGcm256_B64;
|
||||||
|
obj.Iv = await _cryptoFunctionService.RandomBytesAsync(12);
|
||||||
|
obj.Data = await _cryptoFunctionService.AesEncryptAsync(data, obj.Iv, obj.Key.EncKey, AesMode.GCM);
|
||||||
}
|
}
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@@ -608,12 +627,15 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
var keyForEnc = await GetKeyForEncryptionAsync(key);
|
var keyForEnc = await GetKeyForEncryptionAsync(key);
|
||||||
var theKey = ResolveLegacyKey(encType, keyForEnc);
|
var theKey = ResolveLegacyKey(encType, keyForEnc);
|
||||||
if (theKey.MacKey != null && mac == null)
|
|
||||||
|
var macType = encType == EncryptionType.AesCbc128_HmacSha256_B64 ||
|
||||||
|
encType == EncryptionType.AesCbc256_HmacSha256_B64;
|
||||||
|
if (macType && theKey.MacKey != null && mac == null)
|
||||||
{
|
{
|
||||||
// Mac required.
|
// Mac required.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (theKey.EncType != encType)
|
if (!theKey.EncTypes.Contains(encType))
|
||||||
{
|
{
|
||||||
// encType unavailable.
|
// encType unavailable.
|
||||||
return null;
|
return null;
|
||||||
@@ -652,7 +674,8 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey);
|
var decBytes = await _cryptoFunctionService.AesDecryptAsync(dataBytes, ivBytes, encKey,
|
||||||
|
encType == EncryptionType.AesGcm256_B64 ? AesMode.GCM : AesMode.CBC);
|
||||||
return Encoding.UTF8.GetString(decBytes);
|
return Encoding.UTF8.GetString(decBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,12 +685,15 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
var keyForEnc = await GetKeyForEncryptionAsync(key);
|
var keyForEnc = await GetKeyForEncryptionAsync(key);
|
||||||
var theKey = ResolveLegacyKey(encType, keyForEnc);
|
var theKey = ResolveLegacyKey(encType, keyForEnc);
|
||||||
if (theKey.MacKey != null && mac == null)
|
|
||||||
|
var macType = encType == EncryptionType.AesCbc128_HmacSha256_B64 ||
|
||||||
|
encType == EncryptionType.AesCbc256_HmacSha256_B64;
|
||||||
|
if (macType && theKey.MacKey != null && mac == null)
|
||||||
{
|
{
|
||||||
// Mac required.
|
// Mac required.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (theKey.EncType != encType)
|
if (!theKey.EncTypes.Contains(encType))
|
||||||
{
|
{
|
||||||
// encType unavailable.
|
// encType unavailable.
|
||||||
return null;
|
return null;
|
||||||
@@ -694,7 +720,8 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey);
|
return await _cryptoFunctionService.AesDecryptAsync(data, iv, theKey.EncKey,
|
||||||
|
encType == EncryptionType.AesGcm256_B64 ? AesMode.GCM : AesMode.CBC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<byte[]> RsaDecryptAsync(string encValue)
|
private async Task<byte[]> RsaDecryptAsync(string encValue)
|
||||||
@@ -763,7 +790,8 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key)
|
private SymmetricCryptoKey ResolveLegacyKey(EncryptionType encKey, SymmetricCryptoKey key)
|
||||||
{
|
{
|
||||||
if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 && key.EncType == EncryptionType.AesCbc256_B64)
|
if (encKey == EncryptionType.AesCbc128_HmacSha256_B64 &&
|
||||||
|
key.EncTypes.Contains(EncryptionType.AesCbc256_B64))
|
||||||
{
|
{
|
||||||
// Old encrypt-then-mac scheme, make a new key
|
// Old encrypt-then-mac scheme, make a new key
|
||||||
if (_legacyEtmKey == null)
|
if (_legacyEtmKey == null)
|
||||||
@@ -809,10 +837,10 @@ namespace Bit.Core.Services
|
|||||||
return phrase;
|
return phrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Tuple<SymmetricCryptoKey, CipherString>> BuildEncKeyAsync(SymmetricCryptoKey key,
|
private async Task<Tuple<SymmetricCryptoKey, EncString>> BuildEncKeyAsync(SymmetricCryptoKey key,
|
||||||
byte[] encKey)
|
byte[] encKey)
|
||||||
{
|
{
|
||||||
CipherString encKeyEnc = null;
|
EncString encKeyEnc = null;
|
||||||
if (key.Key.Length == 32)
|
if (key.Key.Length == 32)
|
||||||
{
|
{
|
||||||
var newKey = await StretchKeyAsync(key);
|
var newKey = await StretchKeyAsync(key);
|
||||||
@@ -826,11 +854,12 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
throw new Exception("Invalid key size.");
|
throw new Exception("Invalid key size.");
|
||||||
}
|
}
|
||||||
return new Tuple<SymmetricCryptoKey, CipherString>(new SymmetricCryptoKey(encKey), encKeyEnc);
|
return new Tuple<SymmetricCryptoKey, EncString>(new SymmetricCryptoKey(encKey), encKeyEnc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EncryptedObject
|
private class EncryptedObject
|
||||||
{
|
{
|
||||||
|
public EncryptionType Type { get; set; }
|
||||||
public byte[] Iv { get; set; }
|
public byte[] Iv { get; set; }
|
||||||
public byte[] Data { get; set; }
|
public byte[] Data { get; set; }
|
||||||
public byte[] Mac { get; set; }
|
public byte[] Mac { get; set; }
|
||||||
|
|||||||
80
src/Core/Services/FileUploadService.cs
Normal file
80
src/Core/Services/FileUploadService.cs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services {
|
||||||
|
public class FileUploadService : IFileUploadService
|
||||||
|
{
|
||||||
|
public FileUploadService(ApiService apiService)
|
||||||
|
{
|
||||||
|
_apiService = apiService;
|
||||||
|
_bitwardenFileUploadService = new BitwardenFileUploadService(apiService);
|
||||||
|
_azureFileUploadService = new AzureFileUploadService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly BitwardenFileUploadService _bitwardenFileUploadService;
|
||||||
|
private readonly AzureFileUploadService _azureFileUploadService;
|
||||||
|
private readonly ApiService _apiService;
|
||||||
|
|
||||||
|
public async Task UploadCipherAttachmentFileAsync(AttachmentUploadDataResponse uploadData,
|
||||||
|
string encryptedFileName, EncByteArray encryptedFileData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (uploadData.FileUploadType)
|
||||||
|
{
|
||||||
|
case FileUploadType.Direct:
|
||||||
|
await _bitwardenFileUploadService.Upload(encryptedFileName, encryptedFileData,
|
||||||
|
fd => _apiService.PostAttachmentFileAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId, fd));
|
||||||
|
break;
|
||||||
|
case FileUploadType.Azure:
|
||||||
|
Func<Task<string>> renewalCallback = async () =>
|
||||||
|
{
|
||||||
|
var response = await _apiService.RenewAttachmentUploadUrlAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
|
||||||
|
return response.Url;
|
||||||
|
};
|
||||||
|
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unkown file upload type: {uploadData.FileUploadType}");
|
||||||
|
}
|
||||||
|
} catch
|
||||||
|
{
|
||||||
|
await _apiService.DeleteCipherAttachmentAsync(uploadData.CipherResponse.Id, uploadData.AttachmentId);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadSendFileAsync(SendFileUploadDataResponse uploadData, EncString fileName, EncByteArray encryptedFileData)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (uploadData.FileUploadType)
|
||||||
|
{
|
||||||
|
case FileUploadType.Direct:
|
||||||
|
await _bitwardenFileUploadService.Upload(fileName.EncryptedString, encryptedFileData,
|
||||||
|
fd => _apiService.PostSendFileAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id, fd));
|
||||||
|
break;
|
||||||
|
case FileUploadType.Azure:
|
||||||
|
Func<Task<string>> renewalCallback = async () =>
|
||||||
|
{
|
||||||
|
var response = await _apiService.RenewFileUploadUrlAsync(uploadData.SendResponse.Id, uploadData.SendResponse.File.Id);
|
||||||
|
return response.Url;
|
||||||
|
};
|
||||||
|
await _azureFileUploadService.Upload(uploadData.Url, encryptedFileData, renewalCallback);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception("Unknown file upload type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
await _apiService.DeleteSendAsync(uploadData.SendResponse.Id);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -633,7 +633,7 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
var tasks = history.Select(async item =>
|
var tasks = history.Select(async item =>
|
||||||
{
|
{
|
||||||
var decrypted = await _cryptoService.DecryptToUtf8Async(new CipherString(item.Password));
|
var decrypted = await _cryptoService.DecryptToUtf8Async(new EncString(item.Password));
|
||||||
return new GeneratedPasswordHistory
|
return new GeneratedPasswordHistory
|
||||||
{
|
{
|
||||||
Password = decrypted,
|
Password = decrypted,
|
||||||
|
|||||||
@@ -143,16 +143,24 @@ namespace Bit.Core.Services
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key)
|
public Task<byte[]> AesEncryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode)
|
||||||
{
|
{
|
||||||
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
if (mode == AesMode.GCM)
|
||||||
|
{
|
||||||
|
return Task.FromResult(_cryptoPrimitiveService.AesGcmEncrypt(data, iv, key));
|
||||||
|
}
|
||||||
|
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(AesModeToSymmetricAlgorithm(mode));
|
||||||
var cryptoKey = provider.CreateSymmetricKey(key);
|
var cryptoKey = provider.CreateSymmetricKey(key);
|
||||||
return Task.FromResult(CryptographicEngine.Encrypt(cryptoKey, data, iv));
|
return Task.FromResult(CryptographicEngine.Encrypt(cryptoKey, data, iv));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key)
|
public Task<byte[]> AesDecryptAsync(byte[] data, byte[] iv, byte[] key, AesMode mode)
|
||||||
{
|
{
|
||||||
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
|
if (mode == AesMode.GCM)
|
||||||
|
{
|
||||||
|
return Task.FromResult(_cryptoPrimitiveService.AesGcmDecrypt(data, iv, key));
|
||||||
|
}
|
||||||
|
var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm(AesModeToSymmetricAlgorithm(mode));
|
||||||
var cryptoKey = provider.CreateSymmetricKey(key);
|
var cryptoKey = provider.CreateSymmetricKey(key);
|
||||||
return Task.FromResult(CryptographicEngine.Decrypt(cryptoKey, data, iv));
|
return Task.FromResult(CryptographicEngine.Decrypt(cryptoKey, data, iv));
|
||||||
}
|
}
|
||||||
@@ -286,5 +294,18 @@ namespace Bit.Core.Services
|
|||||||
throw new ArgumentException($"Invalid hkdf algorithm type, {hkdfAlgorithm}");
|
throw new ArgumentException($"Invalid hkdf algorithm type, {hkdfAlgorithm}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SymmetricAlgorithm AesModeToSymmetricAlgorithm(AesMode aesMode)
|
||||||
|
{
|
||||||
|
switch (aesMode)
|
||||||
|
{
|
||||||
|
case AesMode.CBC:
|
||||||
|
return SymmetricAlgorithm.AesCbcPkcs7;
|
||||||
|
case AesMode.GCM:
|
||||||
|
return SymmetricAlgorithm.AesGcm;
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"Invalid aes mode type, {aesMode}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Models.Request;
|
using Bit.Core.Models.Request;
|
||||||
@@ -25,11 +26,13 @@ namespace Bit.Core.Services
|
|||||||
private readonly II18nService _i18nService;
|
private readonly II18nService _i18nService;
|
||||||
private readonly ICryptoFunctionService _cryptoFunctionService;
|
private readonly ICryptoFunctionService _cryptoFunctionService;
|
||||||
private Task<List<SendView>> _getAllDecryptedTask;
|
private Task<List<SendView>> _getAllDecryptedTask;
|
||||||
|
private readonly IFileUploadService _fileUploadService;
|
||||||
|
|
||||||
public SendService(
|
public SendService(
|
||||||
ICryptoService cryptoService,
|
ICryptoService cryptoService,
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
IApiService apiService,
|
IApiService apiService,
|
||||||
|
IFileUploadService fileUploadService,
|
||||||
IStorageService storageService,
|
IStorageService storageService,
|
||||||
II18nService i18nService,
|
II18nService i18nService,
|
||||||
ICryptoFunctionService cryptoFunctionService)
|
ICryptoFunctionService cryptoFunctionService)
|
||||||
@@ -37,6 +40,7 @@ namespace Bit.Core.Services
|
|||||||
_cryptoService = cryptoService;
|
_cryptoService = cryptoService;
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_apiService = apiService;
|
_apiService = apiService;
|
||||||
|
_fileUploadService = fileUploadService;
|
||||||
_storageService = storageService;
|
_storageService = storageService;
|
||||||
_i18nService = i18nService;
|
_i18nService = i18nService;
|
||||||
_cryptoFunctionService = cryptoFunctionService;
|
_cryptoFunctionService = cryptoFunctionService;
|
||||||
@@ -77,7 +81,7 @@ namespace Bit.Core.Services
|
|||||||
await DeleteAsync(id);
|
await DeleteAsync(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(Send send, byte[] encryptedFileData)> EncryptAsync(SendView model, byte[] fileData,
|
public async Task<(Send send, EncByteArray encryptedFileData)> EncryptAsync(SendView model, byte[] fileData,
|
||||||
string password, SymmetricCryptoKey key = null)
|
string password, SymmetricCryptoKey key = null)
|
||||||
{
|
{
|
||||||
if (model.Key == null)
|
if (model.Key == null)
|
||||||
@@ -97,8 +101,9 @@ namespace Bit.Core.Services
|
|||||||
Key = await _cryptoService.EncryptAsync(model.Key, key),
|
Key = await _cryptoService.EncryptAsync(model.Key, key),
|
||||||
Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey),
|
Name = await _cryptoService.EncryptAsync(model.Name, model.CryptoKey),
|
||||||
Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey),
|
Notes = await _cryptoService.EncryptAsync(model.Notes, model.CryptoKey),
|
||||||
|
HideEmail = model.HideEmail
|
||||||
};
|
};
|
||||||
byte[] encryptedFileData = null;
|
EncByteArray encryptedFileData = null;
|
||||||
|
|
||||||
if (password != null)
|
if (password != null)
|
||||||
{
|
{
|
||||||
@@ -192,10 +197,10 @@ namespace Bit.Core.Services
|
|||||||
_decryptedSendsCache = null;
|
_decryptedSendsCache = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> SaveWithServerAsync(Send send, byte[] encryptedFileData)
|
public async Task<string> SaveWithServerAsync(Send send, EncByteArray encryptedFileData)
|
||||||
{
|
{
|
||||||
var request = new SendRequest(send, encryptedFileData?.LongLength);
|
var request = new SendRequest(send, encryptedFileData?.Buffer?.LongLength);
|
||||||
SendResponse response;
|
SendResponse response = default;
|
||||||
if (send.Id == null)
|
if (send.Id == null)
|
||||||
{
|
{
|
||||||
switch (send.Type)
|
switch (send.Type)
|
||||||
@@ -204,13 +209,23 @@ namespace Bit.Core.Services
|
|||||||
response = await _apiService.PostSendAsync(request);
|
response = await _apiService.PostSendAsync(request);
|
||||||
break;
|
break;
|
||||||
case SendType.File:
|
case SendType.File:
|
||||||
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
|
try{
|
||||||
{
|
var uploadDataResponse = await _apiService.PostFileTypeSendAsync(request);
|
||||||
{ new StringContent(JsonConvert.SerializeObject(request)), "model" },
|
response = uploadDataResponse.SendResponse;
|
||||||
{ new ByteArrayContent(encryptedFileData), "data", send.File.FileName.EncryptedString }
|
|
||||||
};
|
|
||||||
|
|
||||||
response = await _apiService.PostSendFileAsync(fd);
|
await _fileUploadService.UploadSendFileAsync(uploadDataResponse, send.File.FileName, encryptedFileData);
|
||||||
|
}
|
||||||
|
catch (ApiException e) when (e.Error.StatusCode == HttpStatusCode.NotFound)
|
||||||
|
{
|
||||||
|
response = await LegacyServerSendFileUpload(request, send, encryptedFileData);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (response != default){
|
||||||
|
await _apiService.DeleteSendAsync(response.Id);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException($"Cannot save unknown Send type {send.Type}");
|
throw new NotImplementedException($"Cannot save unknown Send type {send.Type}");
|
||||||
@@ -227,6 +242,17 @@ namespace Bit.Core.Services
|
|||||||
return response.Id;
|
return response.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete("Mar 25 2021: This method has been deprecated in favor of direct uploads. This method still exists for backward compatibility with old server versions.")]
|
||||||
|
private async Task<SendResponse> LegacyServerSendFileUpload(SendRequest request, Send send, EncByteArray encryptedFileData) {
|
||||||
|
var fd = new MultipartFormDataContent($"--BWMobileFormBoundary{DateTime.UtcNow.Ticks}")
|
||||||
|
{
|
||||||
|
{ new StringContent(JsonConvert.SerializeObject(request)), "model" },
|
||||||
|
{ new ByteArrayContent(encryptedFileData.Buffer), "data", send.File.FileName.EncryptedString }
|
||||||
|
};
|
||||||
|
|
||||||
|
return await _apiService.PostSendFileAsync(fd);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpsertAsync(params SendData[] sends)
|
public async Task UpsertAsync(params SendData[] sends)
|
||||||
{
|
{
|
||||||
var userId = await _userService.GetUserIdAsync();
|
var userId = await _userService.GetUserIdAsync();
|
||||||
|
|||||||
@@ -327,6 +327,7 @@ namespace Bit.Core.Services
|
|||||||
await _userService.SetSecurityStampAsync(response.SecurityStamp);
|
await _userService.SetSecurityStampAsync(response.SecurityStamp);
|
||||||
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
|
var organizations = response.Organizations.ToDictionary(o => o.Id, o => new OrganizationData(o));
|
||||||
await _userService.ReplaceOrganizationsAsync(organizations);
|
await _userService.ReplaceOrganizationsAsync(organizations);
|
||||||
|
await _userService.SetEmailVerifiedAsync(response.EmailVerified);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
private async Task SyncFoldersAsync(string userId, List<FolderResponse> response)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace Bit.Core.Services
|
|||||||
private string _stamp;
|
private string _stamp;
|
||||||
private KdfType? _kdf;
|
private KdfType? _kdf;
|
||||||
private int? _kdfIterations;
|
private int? _kdfIterations;
|
||||||
|
private bool? _emailVerified;
|
||||||
|
|
||||||
private const string Keys_UserId = "userId";
|
private const string Keys_UserId = "userId";
|
||||||
private const string Keys_UserEmail = "userEmail";
|
private const string Keys_UserEmail = "userEmail";
|
||||||
@@ -22,6 +23,7 @@ namespace Bit.Core.Services
|
|||||||
private const string Keys_Kdf = "kdf";
|
private const string Keys_Kdf = "kdf";
|
||||||
private const string Keys_KdfIterations = "kdfIterations";
|
private const string Keys_KdfIterations = "kdfIterations";
|
||||||
private const string Keys_OrganizationsFormat = "organizations_{0}";
|
private const string Keys_OrganizationsFormat = "organizations_{0}";
|
||||||
|
private const string Keys_EmailVerified = "emailVerified";
|
||||||
|
|
||||||
private readonly IStorageService _storageService;
|
private readonly IStorageService _storageService;
|
||||||
private readonly ITokenService _tokenService;
|
private readonly ITokenService _tokenService;
|
||||||
@@ -51,6 +53,12 @@ namespace Bit.Core.Services
|
|||||||
await _storageService.SaveAsync(Keys_Stamp, stamp);
|
await _storageService.SaveAsync(Keys_Stamp, stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetEmailVerifiedAsync(bool emailVerified)
|
||||||
|
{
|
||||||
|
_emailVerified = emailVerified;
|
||||||
|
await _storageService.SaveAsync(Keys_EmailVerified, emailVerified);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> GetUserIdAsync()
|
public async Task<string> GetUserIdAsync()
|
||||||
{
|
{
|
||||||
if (_userId == null)
|
if (_userId == null)
|
||||||
@@ -78,6 +86,15 @@ namespace Bit.Core.Services
|
|||||||
return _stamp;
|
return _stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<bool> GetEmailVerifiedAsync()
|
||||||
|
{
|
||||||
|
if (_emailVerified == null)
|
||||||
|
{
|
||||||
|
_emailVerified = await _storageService.GetAsync<bool>(Keys_EmailVerified);
|
||||||
|
}
|
||||||
|
return _emailVerified.GetValueOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<KdfType?> GetKdfAsync()
|
public async Task<KdfType?> GetKdfAsync()
|
||||||
{
|
{
|
||||||
if (_kdf == null)
|
if (_kdf == null)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Bit.Core.Services
|
|||||||
_loggedOutCallback = loggedOutCallback;
|
_loggedOutCallback = loggedOutCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CipherString PinProtectedKey { get; set; } = null;
|
public EncString PinProtectedKey { get; set; } = null;
|
||||||
public bool BiometricLocked { get; set; } = true;
|
public bool BiometricLocked { get; set; } = true;
|
||||||
|
|
||||||
public async Task<bool> IsLockedAsync()
|
public async Task<bool> IsLockedAsync()
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace Bit.Core.Utilities
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Uri GetUri(string uriString)
|
public static Uri GetUri(string uriString)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(uriString))
|
if (string.IsNullOrWhiteSpace(uriString))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,13 +40,14 @@ namespace Bit.Core.Utilities
|
|||||||
var appIdService = new AppIdService(storageService);
|
var appIdService = new AppIdService(storageService);
|
||||||
var userService = new UserService(storageService, tokenService);
|
var userService = new UserService(storageService, tokenService);
|
||||||
var settingsService = new SettingsService(userService, storageService);
|
var settingsService = new SettingsService(userService, storageService);
|
||||||
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService,
|
var fileUploadService = new FileUploadService(apiService);
|
||||||
|
var cipherService = new CipherService(cryptoService, userService, settingsService, apiService, fileUploadService,
|
||||||
storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys);
|
storageService, i18nService, () => searchService, clearCipherCacheKey, allClearCipherCacheKeys);
|
||||||
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
var folderService = new FolderService(cryptoService, userService, apiService, storageService,
|
||||||
i18nService, cipherService);
|
i18nService, cipherService);
|
||||||
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
var collectionService = new CollectionService(cryptoService, userService, storageService, i18nService);
|
||||||
var sendService = new SendService(cryptoService, userService, apiService, storageService, i18nService,
|
var sendService = new SendService(cryptoService, userService, apiService, fileUploadService, storageService,
|
||||||
cryptoFunctionService);
|
i18nService, cryptoFunctionService);
|
||||||
searchService = new SearchService(cipherService, sendService);
|
searchService = new SearchService(cipherService, sendService);
|
||||||
var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
|
var vaultTimeoutService = new VaultTimeoutService(cryptoService, userService, platformUtilsService,
|
||||||
storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
storageService, folderService, cipherService, collectionService, searchService, messagingService, tokenService,
|
||||||
|
|||||||
@@ -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>2.9.1</string>
|
<string>2.10.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
_vaultTimeoutService.PinProtectedKey);
|
_vaultTimeoutService.PinProtectedKey);
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
var encKey = await _cryptoService.GetEncKeyAsync(key);
|
||||||
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
failed = decPin != inputtedValue;
|
failed = decPin != inputtedValue;
|
||||||
if (!failed)
|
if (!failed)
|
||||||
{
|
{
|
||||||
@@ -191,7 +191,7 @@ namespace Bit.iOS.Core.Controllers
|
|||||||
{
|
{
|
||||||
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
var protectedPin = await _storageService.GetAsync<string>(Bit.Core.Constants.ProtectedPin);
|
||||||
var encKey = await _cryptoService.GetEncKeyAsync(key2);
|
var encKey = await _cryptoService.GetEncKeyAsync(key2);
|
||||||
var decPin = await _cryptoService.DecryptToUtf8Async(new CipherString(protectedPin), encKey);
|
var decPin = await _cryptoService.DecryptToUtf8Async(new EncString(protectedPin), encKey);
|
||||||
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
var pinKey = await _cryptoService.MakePinKeyAysnc(decPin, email,
|
||||||
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
kdf.GetValueOrDefault(KdfType.PBKDF2_SHA256), kdfIterations.GetValueOrDefault(5000));
|
||||||
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
|
_vaultTimeoutService.PinProtectedKey = await _cryptoService.EncryptAsync(key2.Key, pinKey);
|
||||||
|
|||||||
@@ -39,6 +39,17 @@ namespace Bit.iOS.Core.Services
|
|||||||
return keyBytes;
|
return keyBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] AesGcmEncrypt(byte[] data, byte[] iv, byte[] key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] AesGcmDecrypt(byte[] data, byte[] iv, byte[] key)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
// ref: http://opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/CommonCrypto/CommonKeyDerivation.h
|
// ref: http://opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/CommonCrypto/CommonKeyDerivation.h
|
||||||
[DllImport(ObjCRuntime.Constants.libSystemLibrary, EntryPoint = "CCKeyDerivationPBKDF")]
|
[DllImport(ObjCRuntime.Constants.libSystemLibrary, EntryPoint = "CCKeyDerivationPBKDF")]
|
||||||
private extern static int CCKeyCerivationPBKDF(uint algorithm, IntPtr password, nuint passwordLen,
|
private extern static int CCKeyCerivationPBKDF(uint algorithm, IntPtr password, nuint passwordLen,
|
||||||
|
|||||||
@@ -437,6 +437,11 @@ namespace Bit.iOS.Core.Services
|
|||||||
return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
return iOSHelpers.GetSystemUpTimeMilliseconds() ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CloseMainApp()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
|
private void ImagePicker_FinishedPickingMedia(object sender, UIImagePickerMediaPickedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is UIImagePickerController picker)
|
if (sender is UIImagePickerController picker)
|
||||||
|
|||||||
@@ -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>2.9.1</string>
|
<string>2.10.0</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -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>2.9.1</string>
|
<string>2.10.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
@@ -144,13 +144,27 @@
|
|||||||
<ImageAsset Include="Resources\Assets.xcassets\AppIcons.appiconset\Contents.json">
|
<ImageAsset Include="Resources\Assets.xcassets\AppIcons.appiconset\Contents.json">
|
||||||
<Visible>false</Visible>
|
<Visible>false</Visible>
|
||||||
</ImageAsset>
|
</ImageAsset>
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\Contents.json" />
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\Contents.json">
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo.png" />
|
<Visible>false</Visible>
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%402x.png" />
|
</ImageAsset>
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%403x.png" />
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo.png">
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white.png" />
|
<Visible>false</Visible>
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%402x.png" />
|
</ImageAsset>
|
||||||
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%403x.png" />
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%402x.png">
|
||||||
|
<Visible>false</Visible>
|
||||||
|
</ImageAsset>
|
||||||
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo%403x.png">
|
||||||
|
<Visible>false</Visible>
|
||||||
|
</ImageAsset>
|
||||||
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white.png">
|
||||||
|
<Visible>false</Visible>
|
||||||
|
</ImageAsset>
|
||||||
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%402x.png">
|
||||||
|
<Visible>false</Visible>
|
||||||
|
</ImageAsset>
|
||||||
|
<ImageAsset Include="Resources\Assets.xcassets\LaunchScreen.imageset\logo_white%403x.png">
|
||||||
|
<Visible>false</Visible>
|
||||||
|
</ImageAsset>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<InterfaceDefinition Include="LaunchScreen.storyboard" />
|
<InterfaceDefinition Include="LaunchScreen.storyboard" />
|
||||||
|
|||||||
62
test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs
Normal file
62
test/Core.Test/AutoFixture/Cipher/CipherCustomizations.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using AutoFixture;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.AutoFixture
|
||||||
|
|
||||||
|
{
|
||||||
|
internal class OrganizationCipher : ICustomization
|
||||||
|
{
|
||||||
|
public string OrganizationId { get; set; }
|
||||||
|
public void Customize(IFixture fixture)
|
||||||
|
{
|
||||||
|
fixture.Customize<Cipher>(composer => composer
|
||||||
|
.With(c => c.OrganizationId, OrganizationId ?? Guid.NewGuid().ToString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class UserCipher : ICustomization
|
||||||
|
{
|
||||||
|
public void Customize(IFixture fixture)
|
||||||
|
{
|
||||||
|
fixture.Customize<Cipher>(composer => composer
|
||||||
|
.Without(c => c.OrganizationId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class UserCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public UserCipherAutoDataAttribute() : base(new SutProviderCustomization(),
|
||||||
|
new UserCipher())
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
internal class InlineUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public InlineUserCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||||
|
typeof(UserCipher) }, values)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InlineKnownUserCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public InlineKnownUserCipherAutoDataAttribute(string userId, params object[] values) : base(new ICustomization[]
|
||||||
|
{ new SutProviderCustomization(), new UserCipher() }, values)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class OrganizationCipherAutoDataAttribute : CustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public OrganizationCipherAutoDataAttribute(string organizationId = null) : base(new SutProviderCustomization(),
|
||||||
|
new OrganizationCipher { OrganizationId = organizationId ?? null })
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InlineOrganizationCipherAutoDataAttribute : InlineCustomAutoDataAttribute
|
||||||
|
{
|
||||||
|
public InlineOrganizationCipherAutoDataAttribute(params object[] values) : base(new[] { typeof(SutProviderCustomization),
|
||||||
|
typeof(OrganizationCipher) }, values)
|
||||||
|
{ }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,17 +40,17 @@ namespace Bit.Core.Test.Models.Domain
|
|||||||
var prefix = "decrypted_";
|
var prefix = "decrypted_";
|
||||||
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
|
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
|
||||||
|
|
||||||
cryptoService.DecryptToBytesAsync(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>())
|
cryptoService.DecryptToBytesAsync(Arg.Any<EncString>(), Arg.Any<SymmetricCryptoKey>())
|
||||||
.Returns(info => prefixBytes.Concat(Encoding.UTF8.GetBytes(((CipherString)info[0]).EncryptedString)).ToArray());
|
.Returns(info => prefixBytes.Concat(Encoding.UTF8.GetBytes(((EncString)info[0]).EncryptedString)).ToArray());
|
||||||
cryptoService.DecryptFromBytesAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>())
|
cryptoService.DecryptFromBytesAsync(Arg.Any<byte[]>(), Arg.Any<SymmetricCryptoKey>())
|
||||||
.Returns(info => prefixBytes.Concat((byte[])info[0]).ToArray());
|
.Returns(info => prefixBytes.Concat((byte[])info[0]).ToArray());
|
||||||
cryptoService.DecryptToUtf8Async(Arg.Any<CipherString>(), Arg.Any<SymmetricCryptoKey>())
|
cryptoService.DecryptToUtf8Async(Arg.Any<EncString>(), Arg.Any<SymmetricCryptoKey>())
|
||||||
.Returns(info => $"{prefix}{((CipherString)info[0]).EncryptedString}");
|
.Returns(info => $"{prefix}{((EncString)info[0]).EncryptedString}");
|
||||||
ServiceContainer.Register("cryptoService", cryptoService);
|
ServiceContainer.Register("cryptoService", cryptoService);
|
||||||
|
|
||||||
var view = await send.DecryptAsync();
|
var view = await send.DecryptAsync();
|
||||||
|
|
||||||
string expectedDecryptionString(CipherString encryptedString) =>
|
string expectedDecryptionString(EncString encryptedString) =>
|
||||||
encryptedString?.EncryptedString == null ? null : $"{prefix}{encryptedString.EncryptedString}";
|
encryptedString?.EncryptedString == null ? null : $"{prefix}{encryptedString.EncryptedString}";
|
||||||
|
|
||||||
TestHelper.AssertPropertyEqual(send, view, "Name", "Notes", "File", "Text", "Key", "UserId");
|
TestHelper.AssertPropertyEqual(send, view, "Name", "Notes", "File", "Text", "Key", "UserId");
|
||||||
|
|||||||
96
test/Core.Test/Services/CipherServiceTests.cs
Normal file
96
test/Core.Test/Services/CipherServiceTests.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.Domain;
|
||||||
|
using Bit.Core.Models.Request;
|
||||||
|
using Bit.Core.Models.Response;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Test.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using NSubstitute;
|
||||||
|
using NSubstitute.ExceptionExtensions;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Services
|
||||||
|
{
|
||||||
|
public class CipherServiceTests
|
||||||
|
{
|
||||||
|
[Theory, UserCipherAutoData]
|
||||||
|
public async Task SaveWithServerAsync_PrefersFileUploadService(SutProvider<CipherService> sutProvider,
|
||||||
|
Cipher cipher, string fileName, EncByteArray data, AttachmentUploadDataResponse uploadDataResponse, EncString encKey)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(new EncString(fileName));
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(data);
|
||||||
|
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||||
|
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||||
|
.Returns(uploadDataResponse);
|
||||||
|
|
||||||
|
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IFileUploadService>().Received(1)
|
||||||
|
.UploadCipherAttachmentFileAsync(uploadDataResponse, fileName, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineUserCipherAutoData(HttpStatusCode.NotFound)]
|
||||||
|
[InlineUserCipherAutoData(HttpStatusCode.MethodNotAllowed)]
|
||||||
|
public async Task SaveWithServerAsync_FallsBackToLegacyFormData(HttpStatusCode statusCode,
|
||||||
|
SutProvider<CipherService> sutProvider, Cipher cipher, string fileName, EncByteArray data,
|
||||||
|
CipherResponse response, EncString encKey)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(new EncString(fileName));
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(data);
|
||||||
|
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>()).Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||||
|
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||||
|
.Throws(new ApiException(new ErrorResponse {StatusCode = statusCode}));
|
||||||
|
sutProvider.GetDependency<IApiService>().PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>())
|
||||||
|
.Returns(response);
|
||||||
|
|
||||||
|
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IApiService>().Received(1)
|
||||||
|
.PostCipherAttachmentLegacyAsync(cipher.Id, Arg.Any<MultipartFormDataContent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, UserCipherAutoData]
|
||||||
|
public async Task SaveWithServerAsync_ThrowsOnBadRequestApiException(SutProvider<CipherService> sutProvider,
|
||||||
|
Cipher cipher, string fileName, EncByteArray data, EncString encKey)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptAsync(fileName, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(new EncString(fileName));
|
||||||
|
sutProvider.GetDependency<ICryptoService>().EncryptToBytesAsync(data.Buffer, Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(data);
|
||||||
|
sutProvider.GetDependency<ICryptoService>().MakeEncKeyAsync(Arg.Any<SymmetricCryptoKey>())
|
||||||
|
.Returns(new Tuple<SymmetricCryptoKey, EncString>(null, encKey));
|
||||||
|
var expectedException = new ApiException(new ErrorResponse { StatusCode = HttpStatusCode.BadRequest });
|
||||||
|
sutProvider.GetDependency<IApiService>().PostCipherAttachmentAsync(cipher.Id, Arg.Any<AttachmentRequest>())
|
||||||
|
.Throws(expectedException);
|
||||||
|
|
||||||
|
var actualException = await Assert.ThrowsAsync<ApiException>(async () =>
|
||||||
|
await sutProvider.Sut.SaveAttachmentRawWithServerAsync(cipher, fileName, data.Buffer));
|
||||||
|
|
||||||
|
Assert.Equal(expectedException.Error.StatusCode, actualException.Error.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization), typeof(SymmetricCryptoKeyCustomization))]
|
||||||
|
public async Task DownloadAndDecryptAttachmentAsync_RequestsTimeLimitedUrl(SutProvider<CipherService> sutProvider,
|
||||||
|
string cipherId, AttachmentView attachment, AttachmentResponse response)
|
||||||
|
{
|
||||||
|
sutProvider.GetDependency<IApiService>().GetAttachmentData(cipherId, attachment.Id)
|
||||||
|
.Returns(response);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DownloadAndDecryptAttachmentAsync(cipherId, attachment, null);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IApiService>().Received(1).GetAttachmentData(cipherId, attachment.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,8 @@ using Bit.Core.Models.Request;
|
|||||||
using Bit.Core.Test.AutoFixture;
|
using Bit.Core.Test.AutoFixture;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Bit.Core.Models.View;
|
using Bit.Core.Models.View;
|
||||||
|
using Bit.Core.Exceptions;
|
||||||
|
using NSubstitute.ExceptionExtensions;
|
||||||
|
|
||||||
namespace Bit.Core.Test.Services
|
namespace Bit.Core.Test.Services
|
||||||
{
|
{
|
||||||
@@ -172,16 +174,15 @@ namespace Bit.Core.Test.Services
|
|||||||
send.Id = null;
|
send.Id = null;
|
||||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||||
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
||||||
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
|
|
||||||
|
|
||||||
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content");
|
var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
|
||||||
|
|
||||||
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
||||||
|
|
||||||
Predicate<SendRequest> sendRequestPredicate = r =>
|
Predicate<SendRequest> sendRequestPredicate = r =>
|
||||||
{
|
{
|
||||||
// Note Send -> SendRequest tested in SendRequestTests
|
// Note Send -> SendRequest tested in SendRequestTests
|
||||||
TestHelper.AssertPropertyEqual(new SendRequest(send, fileContentBytes?.LongLength), r);
|
TestHelper.AssertPropertyEqual(new SendRequest(send, fileContentBytes.Buffer?.LongLength), r);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -200,35 +201,21 @@ namespace Bit.Core.Test.Services
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||||
public async Task SaveWithServerAsync_NewFileSend_Success(SutProvider<SendService> sutProvider, string userId, SendResponse response, Send send)
|
public async Task SaveWithServerAsync_NewFileSend_AzureUpload_Success(SutProvider<SendService> sutProvider, string userId, SendFileUploadDataResponse response, Send send)
|
||||||
{
|
{
|
||||||
send.Id = null;
|
send.Id = null;
|
||||||
|
response.FileUploadType = FileUploadType.Azure;
|
||||||
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||||
sutProvider.GetDependency<IApiService>().PostSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
sutProvider.GetDependency<IApiService>().PostFileTypeSendAsync(Arg.Any<SendRequest>()).Returns(response);
|
||||||
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
|
|
||||||
|
|
||||||
var fileContentBytes = Encoding.UTF8.GetBytes("This is the file content");
|
var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
|
||||||
|
|
||||||
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
||||||
|
|
||||||
Predicate<MultipartFormDataContent> formDataPredicate = fd =>
|
|
||||||
{
|
|
||||||
Assert.Equal(2, fd.Count()); // expect a request and file content
|
|
||||||
|
|
||||||
var expectedRequest = JsonConvert.SerializeObject(new SendRequest(send, fileContentBytes?.LongLength));
|
|
||||||
var actualRequest = fd.First().ReadAsStringAsync().GetAwaiter().GetResult();
|
|
||||||
Assert.Equal(expectedRequest, actualRequest);
|
|
||||||
|
|
||||||
var actualFileContent = fd.Skip(1).First().ReadAsByteArrayAsync().GetAwaiter().GetResult();
|
|
||||||
Assert.Equal(fileContentBytes, actualFileContent);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (send.Type)
|
switch (send.Type)
|
||||||
{
|
{
|
||||||
case SendType.File:
|
case SendType.File:
|
||||||
await sutProvider.GetDependency<IApiService>().Received(1)
|
await sutProvider.GetDependency<IFileUploadService>().Received(1).UploadSendFileAsync(response, send.File.FileName, fileContentBytes);
|
||||||
.PostSendFileAsync(Arg.Is<MultipartFormDataContent>(f => formDataPredicate(f)));
|
|
||||||
break;
|
break;
|
||||||
case SendType.Text:
|
case SendType.Text:
|
||||||
default:
|
default:
|
||||||
@@ -236,6 +223,23 @@ namespace Bit.Core.Test.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||||
|
public async Task SaveWithServerAsync_NewFileSend_LegacyFallback_Success(SutProvider<SendService> sutProvider, string userId, Send send, SendResponse response)
|
||||||
|
{
|
||||||
|
send.Id = null;
|
||||||
|
sutProvider.GetDependency<IUserService>().GetUserIdAsync().Returns(userId);
|
||||||
|
var error = new ErrorResponse(null, System.Net.HttpStatusCode.NotFound);
|
||||||
|
sutProvider.GetDependency<IApiService>().PostFileTypeSendAsync(Arg.Any<SendRequest>()).Throws(new ApiException(error));
|
||||||
|
sutProvider.GetDependency<IApiService>().PostSendFileAsync(Arg.Any<MultipartFormDataContent>()).Returns(response);
|
||||||
|
|
||||||
|
var fileContentBytes = new EncByteArray(Encoding.UTF8.GetBytes("This is the file content"));
|
||||||
|
|
||||||
|
await sutProvider.Sut.SaveWithServerAsync(send, fileContentBytes);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IApiService>().Received(1).PostSendFileAsync(Arg.Any<MultipartFormDataContent>());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(TextSendCustomization) })]
|
||||||
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization), typeof(FileSendCustomization) })]
|
||||||
@@ -335,12 +339,12 @@ namespace Bit.Core.Test.Services
|
|||||||
|
|
||||||
byte[] getPbkdf(string password, byte[] key) =>
|
byte[] getPbkdf(string password, byte[] key) =>
|
||||||
prefixBytes.Concat(Encoding.UTF8.GetBytes(password)).Concat(key).ToArray();
|
prefixBytes.Concat(Encoding.UTF8.GetBytes(password)).Concat(key).ToArray();
|
||||||
CipherString encryptBytes(byte[] secret, SymmetricCryptoKey key) =>
|
EncString encryptBytes(byte[] secret, SymmetricCryptoKey key) =>
|
||||||
new CipherString($"{prefix}{Convert.ToBase64String(secret)}{Convert.ToBase64String(key.Key)}");
|
new EncString($"{prefix}{Convert.ToBase64String(secret)}{Convert.ToBase64String(key.Key)}");
|
||||||
CipherString encrypt(string secret, SymmetricCryptoKey key) =>
|
EncString encrypt(string secret, SymmetricCryptoKey key) =>
|
||||||
new CipherString($"{prefix}{secret}{Convert.ToBase64String(key.Key)}");
|
new EncString($"{prefix}{secret}{Convert.ToBase64String(key.Key)}");
|
||||||
byte[] encryptFileBytes(byte[] secret, SymmetricCryptoKey key) =>
|
EncByteArray encryptFileBytes(byte[] secret, SymmetricCryptoKey key) =>
|
||||||
secret.Concat(key.Key).ToArray();
|
new EncByteArray(secret.Concat(key.Key).ToArray());
|
||||||
|
|
||||||
sutProvider.GetDependency<ICryptoFunctionService>().Pbkdf2Async(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CryptoHashAlgorithm>(), Arg.Any<int>())
|
sutProvider.GetDependency<ICryptoFunctionService>().Pbkdf2Async(Arg.Any<string>(), Arg.Any<byte[]>(), Arg.Any<CryptoHashAlgorithm>(), Arg.Any<int>())
|
||||||
.Returns(info => getPbkdf((string)info[0], (byte[])info[1]));
|
.Returns(info => getPbkdf((string)info[0], (byte[])info[1]));
|
||||||
@@ -370,7 +374,7 @@ namespace Bit.Core.Test.Services
|
|||||||
case SendType.File:
|
case SendType.File:
|
||||||
// Only set filename
|
// Only set filename
|
||||||
TestHelper.AssertPropertyEqual(encrypt(view.File.FileName, view.CryptoKey), send.File.FileName);
|
TestHelper.AssertPropertyEqual(encrypt(view.File.FileName, view.CryptoKey), send.File.FileName);
|
||||||
Assert.Equal(encryptFileBytes(fileData, view.CryptoKey), encryptedFileData);
|
Assert.Equal(encryptFileBytes(fileData, view.CryptoKey).Buffer, encryptedFileData.Buffer);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Exception("Untested send type");
|
throw new Exception("Untested send type");
|
||||||
|
|||||||
Reference in New Issue
Block a user