diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index 740a83ff9..030c56350 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -323,7 +323,7 @@
-
+
@@ -872,6 +872,9 @@
+
+
+
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index 78e6f59b9..485a2bca3 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -207,7 +207,7 @@ namespace Bit.Android
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
+ container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml
index 12298232d..7cbe1d085 100644
--- a/src/Android/Properties/AndroidManifest.xml
+++ b/src/Android/Properties/AndroidManifest.xml
@@ -12,5 +12,15 @@
-
+
+
+
+
+
diff --git a/src/Android/Resources/Resource.Designer.cs b/src/Android/Resources/Resource.Designer.cs
index 519598826..553e91046 100644
--- a/src/Android/Resources/Resource.Designer.cs
+++ b/src/Android/Resources/Resource.Designer.cs
@@ -5296,6 +5296,9 @@ namespace Bit.Android
// aapt resource value: 0x7f060000
public const int accessibilityservice = 2131099648;
+ // aapt resource value: 0x7f060001
+ public const int filepaths = 2131099649;
+
static Xml()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
diff --git a/src/Android/Resources/xml/filepaths.xml b/src/Android/Resources/xml/filepaths.xml
new file mode 100644
index 000000000..3ae5d6901
--- /dev/null
+++ b/src/Android/Resources/xml/filepaths.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/Android/Services/ClipboardService.cs b/src/Android/Services/ClipboardService.cs
deleted file mode 100644
index c2308d289..000000000
--- a/src/Android/Services/ClipboardService.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using Android.Content;
-using Bit.App.Abstractions;
-using Xamarin.Forms;
-
-namespace Bit.Android.Services
-{
- public class ClipboardService : IClipboardService
- {
- public void CopyToClipboard(string text)
- {
- var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
- clipboardManager.Text = text;
- }
- }
-}
diff --git a/src/Android/Services/DeviceActionService.cs b/src/Android/Services/DeviceActionService.cs
new file mode 100644
index 000000000..32351fa39
--- /dev/null
+++ b/src/Android/Services/DeviceActionService.cs
@@ -0,0 +1,63 @@
+using System;
+using Android.Content;
+using Bit.App.Abstractions;
+using Xamarin.Forms;
+using Java.IO;
+using Android.Webkit;
+using Plugin.CurrentActivity;
+using System.IO;
+using System.Diagnostics;
+using Android.Support.V4.Content;
+
+namespace Bit.Android.Services
+{
+ public class DeviceActionService : IDeviceActionService
+ {
+ public void CopyToClipboard(string text)
+ {
+ var clipboardManager = (ClipboardManager)Forms.Context.GetSystemService(Context.ClipboardService);
+ clipboardManager.Text = text;
+ }
+
+ public bool OpenFile(byte[] fileData, string id, string fileName)
+ {
+ var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName);
+ if(extension == null)
+ {
+ return false;
+ }
+
+ var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension.ToLower());
+ if(mimeType == null)
+ {
+ return false;
+ }
+
+ var cachePath = CrossCurrentActivity.Current.Activity.CacheDir;
+ var filePath = Path.Combine(cachePath.Path, fileName);
+ System.IO.File.WriteAllBytes(filePath, fileData);
+ var file = new Java.IO.File(cachePath, fileName);
+ try
+ {
+ var packageManager = CrossCurrentActivity.Current.Activity.PackageManager;
+ var testIntent = new Intent(Intent.ActionView);
+ testIntent.SetType(mimeType);
+ var list = packageManager.QueryIntentActivities(testIntent,
+ global::Android.Content.PM.PackageInfoFlags.MatchDefaultOnly);
+ if(list.Count > 0 && file.IsFile)
+ {
+ var intent = new Intent(Intent.ActionView);
+ var uri = FileProvider.GetUriForFile(CrossCurrentActivity.Current.Activity.ApplicationContext,
+ "com.x8bit.bitwarden.fileprovider", file);
+ intent.SetDataAndType(uri, mimeType);
+ intent.SetFlags(ActivityFlags.GrantReadUriPermission);
+ CrossCurrentActivity.Current.Activity.StartActivity(intent);
+ return true;
+ }
+ }
+ catch { }
+
+ return false;
+ }
+ }
+}
diff --git a/src/App/Abstractions/Services/IClipboardService.cs b/src/App/Abstractions/Services/IClipboardService.cs
deleted file mode 100644
index ba4d54c58..000000000
--- a/src/App/Abstractions/Services/IClipboardService.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Bit.App.Abstractions
-{
- public interface IClipboardService
- {
- void CopyToClipboard(string text);
- }
-}
diff --git a/src/App/Abstractions/Services/ICryptoService.cs b/src/App/Abstractions/Services/ICryptoService.cs
index d59e2835e..2bd6457d5 100644
--- a/src/App/Abstractions/Services/ICryptoService.cs
+++ b/src/App/Abstractions/Services/ICryptoService.cs
@@ -19,6 +19,7 @@ namespace Bit.App.Abstractions
void ClearKeys();
string Decrypt(CipherString encyptedValue, SymmetricCryptoKey key = null);
byte[] DecryptToBytes(CipherString encyptedValue, SymmetricCryptoKey key = null);
+ byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null);
byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey);
CipherString Encrypt(string plaintextValue, SymmetricCryptoKey key = null);
SymmetricCryptoKey MakeKeyFromPassword(string password, string salt);
diff --git a/src/App/Abstractions/Services/IDeviceActionService.cs b/src/App/Abstractions/Services/IDeviceActionService.cs
new file mode 100644
index 000000000..6e4ce8f6a
--- /dev/null
+++ b/src/App/Abstractions/Services/IDeviceActionService.cs
@@ -0,0 +1,8 @@
+namespace Bit.App.Abstractions
+{
+ public interface IDeviceActionService
+ {
+ void CopyToClipboard(string text);
+ bool OpenFile(byte[] fileData, string id, string fileName);
+ }
+}
diff --git a/src/App/Abstractions/Services/ILoginService.cs b/src/App/Abstractions/Services/ILoginService.cs
index c4fa3b373..0e6167b9a 100644
--- a/src/App/Abstractions/Services/ILoginService.cs
+++ b/src/App/Abstractions/Services/ILoginService.cs
@@ -14,5 +14,6 @@ namespace Bit.App.Abstractions
Task, IEnumerable>> GetAllAsync(string uriString);
Task> SaveAsync(Login login);
Task DeleteAsync(string id);
+ Task DownloadAndDecryptAttachmentAsync(SymmetricCryptoKey key, string url);
}
}
diff --git a/src/App/App.csproj b/src/App/App.csproj
index 78ad71328..ca9d541dd 100644
--- a/src/App/App.csproj
+++ b/src/App/App.csproj
@@ -51,7 +51,7 @@
-
+
diff --git a/src/App/Models/Page/VaultViewLoginPageModel.cs b/src/App/Models/Page/VaultViewLoginPageModel.cs
index 99fad4de7..6b35b07ab 100644
--- a/src/App/Models/Page/VaultViewLoginPageModel.cs
+++ b/src/App/Models/Page/VaultViewLoginPageModel.cs
@@ -223,7 +223,8 @@ namespace Bit.App.Models.Page
{
Id = attachment.Id,
Name = attachment.FileName?.Decrypt(login.OrganizationId),
- Size = attachment.SizeName
+ Size = attachment.SizeName,
+ Url = attachment.Url
});
}
Attachments = attachments;
@@ -239,6 +240,7 @@ namespace Bit.App.Models.Page
public string Id { get; set; }
public string Name { get; set; }
public string Size { get; set; }
+ public string Url { get; set; }
}
}
}
diff --git a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
index bd60ec64a..d19c40198 100644
--- a/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
+++ b/src/App/Pages/Tools/ToolsPasswordGeneratorPage.cs
@@ -16,7 +16,7 @@ namespace Bit.App.Pages
private readonly IUserDialogs _userDialogs;
private readonly IPasswordGenerationService _passwordGenerationService;
private readonly ISettings _settings;
- private readonly IClipboardService _clipboardService;
+ private readonly IDeviceActionService _clipboardService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly Action _passwordValueAction;
private readonly bool _fromAutofill;
@@ -26,7 +26,7 @@ namespace Bit.App.Pages
_userDialogs = Resolver.Resolve();
_passwordGenerationService = Resolver.Resolve();
_settings = Resolver.Resolve();
- _clipboardService = Resolver.Resolve();
+ _clipboardService = Resolver.Resolve();
_googleAnalyticsService = Resolver.Resolve();
_passwordValueAction = passwordValueAction;
_fromAutofill = fromAutofill;
diff --git a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs
index 809f85eb4..84fe99386 100644
--- a/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs
+++ b/src/App/Pages/Vault/VaultAutofillListLoginsPage.cs
@@ -19,7 +19,7 @@ namespace Bit.App.Pages
{
private readonly ILoginService _loginService;
private readonly IDeviceInfoService _deviceInfoService;
- private readonly IClipboardService _clipboardService;
+ private readonly IDeviceActionService _clipboardService;
private readonly ISettingsService _settingsService;
private CancellationTokenSource _filterResultsCancellationTokenSource;
private readonly string _name;
@@ -47,7 +47,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve();
_deviceInfoService = Resolver.Resolve();
- _clipboardService = Resolver.Resolve();
+ _clipboardService = Resolver.Resolve();
_settingsService = Resolver.Resolve();
UserDialogs = Resolver.Resolve();
GoogleAnalyticsService = Resolver.Resolve();
diff --git a/src/App/Pages/Vault/VaultListLoginsPage.cs b/src/App/Pages/Vault/VaultListLoginsPage.cs
index 4b6dd1158..0d6d0ecf8 100644
--- a/src/App/Pages/Vault/VaultListLoginsPage.cs
+++ b/src/App/Pages/Vault/VaultListLoginsPage.cs
@@ -24,7 +24,7 @@ namespace Bit.App.Pages
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity;
- private readonly IClipboardService _clipboardService;
+ private readonly IDeviceActionService _clipboardService;
private readonly ISyncService _syncService;
private readonly IPushNotification _pushNotification;
private readonly IDeviceInfoService _deviceInfoService;
@@ -41,7 +41,7 @@ namespace Bit.App.Pages
_loginService = Resolver.Resolve();
_connectivity = Resolver.Resolve();
_userDialogs = Resolver.Resolve();
- _clipboardService = Resolver.Resolve();
+ _clipboardService = Resolver.Resolve();
_syncService = Resolver.Resolve();
_pushNotification = Resolver.Resolve();
_deviceInfoService = Resolver.Resolve();
diff --git a/src/App/Pages/Vault/VaultViewLoginPage.cs b/src/App/Pages/Vault/VaultViewLoginPage.cs
index ebc329a44..b4e513d07 100644
--- a/src/App/Pages/Vault/VaultViewLoginPage.cs
+++ b/src/App/Pages/Vault/VaultViewLoginPage.cs
@@ -16,14 +16,14 @@ namespace Bit.App.Pages
private readonly string _loginId;
private readonly ILoginService _loginService;
private readonly IUserDialogs _userDialogs;
- private readonly IClipboardService _clipboardService;
+ private readonly IDeviceActionService _deviceActionService;
public VaultViewLoginPage(string loginId)
{
_loginId = loginId;
_loginService = Resolver.Resolve();
_userDialogs = Resolver.Resolve();
- _clipboardService = Resolver.Resolve();
+ _deviceActionService = Resolver.Resolve();
Init();
}
@@ -194,9 +194,9 @@ namespace Bit.App.Pages
AttachmentsSection = new TableSection(AppResources.Attachments);
foreach(var attachment in Model.Attachments)
{
- AttachmentsSection.Add(new AttachmentViewCell(attachment, () =>
+ AttachmentsSection.Add(new AttachmentViewCell(attachment, async () =>
{
-
+ await SaveAttachmentAsync(attachment);
}));
}
Table.Root.Add(AttachmentsSection);
@@ -211,6 +211,23 @@ namespace Bit.App.Pages
EditItem.Dispose();
}
+ private async Task SaveAttachmentAsync(VaultViewLoginPageModel.Attachment attachment)
+ {
+ var data = await _loginService.DownloadAndDecryptAttachmentAsync(null, attachment.Url);
+ if(data == null)
+ {
+ await _userDialogs.AlertAsync(AppResources.UnableToDownloadFile, null, AppResources.Ok);
+ return;
+ }
+
+ var opened = _deviceActionService.OpenFile(data, attachment.Id, attachment.Name);
+ if(!opened)
+ {
+ await _userDialogs.AlertAsync(AppResources.UnableToOpenFile, null, AppResources.Ok);
+ return;
+ }
+ }
+
private void NotesCell_Tapped(object sender, EventArgs e)
{
Copy(Model.Notes, AppResources.Notes);
@@ -218,7 +235,7 @@ namespace Bit.App.Pages
private void Copy(string copyText, string alertLabel)
{
- _clipboardService.CopyToClipboard(copyText);
+ _deviceActionService.CopyToClipboard(copyText);
_userDialogs.Toast(string.Format(AppResources.ValueHasBeenCopied, alertLabel));
}
diff --git a/src/App/Resources/AppResources.Designer.cs b/src/App/Resources/AppResources.Designer.cs
index c7768822d..39e230a4d 100644
--- a/src/App/Resources/AppResources.Designer.cs
+++ b/src/App/Resources/AppResources.Designer.cs
@@ -1987,6 +1987,24 @@ namespace Bit.App.Resources {
}
}
+ ///
+ /// Looks up a localized string similar to Unable to download file..
+ ///
+ public static string UnableToDownloadFile {
+ get {
+ return ResourceManager.GetString("UnableToDownloadFile", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Unable to open this type of file on your device..
+ ///
+ public static string UnableToOpenFile {
+ get {
+ return ResourceManager.GetString("UnableToOpenFile", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Unlock with {0}.
///
diff --git a/src/App/Resources/AppResources.resx b/src/App/Resources/AppResources.resx
index fc3405956..39f64722d 100644
--- a/src/App/Resources/AppResources.resx
+++ b/src/App/Resources/AppResources.resx
@@ -908,4 +908,10 @@
Attachments
+
+ Unable to download file.
+
+
+ Unable to open this type of file on your device.
+
\ No newline at end of file
diff --git a/src/App/Services/CryptoService.cs b/src/App/Services/CryptoService.cs
index 044a98856..4483b481c 100644
--- a/src/App/Services/CryptoService.cs
+++ b/src/App/Services/CryptoService.cs
@@ -297,18 +297,61 @@ namespace Bit.App.Services
// Old encrypt-then-mac scheme, swap out the key
if(_legacyEtmKey == null)
{
- _legacyEtmKey = new SymmetricCryptoKey(key.Key, Enums.EncryptionType.AesCbc128_HmacSha256_B64);
+ _legacyEtmKey = new SymmetricCryptoKey(key.Key, EncryptionType.AesCbc128_HmacSha256_B64);
}
key = _legacyEtmKey;
}
- if(encyptedValue.EncryptionType != key.EncryptionType)
+ return Crypto.AesCbcDecrypt(encyptedValue, key);
+ }
+
+ public byte[] DecryptToBytes(byte[] encyptedValue, SymmetricCryptoKey key = null)
+ {
+ if(key == null)
{
- throw new ArgumentException("encType unavailable.");
+ key = EncKey ?? Key;
}
- return Crypto.AesCbcDecrypt(encyptedValue, key);
+ if(key == null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if(encyptedValue == null || encyptedValue.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(encyptedValue));
+ }
+
+ byte[] ct, iv, mac = null;
+ var encType = (EncryptionType)encyptedValue[0];
+ switch(encType)
+ {
+ case EncryptionType.AesCbc128_HmacSha256_B64:
+ case EncryptionType.AesCbc256_HmacSha256_B64:
+ if(encyptedValue.Length <= 49)
+ {
+ throw new InvalidOperationException("Invalid value length.");
+ }
+
+ iv = new ArraySegment(encyptedValue, 1, 16).ToArray();
+ mac = new ArraySegment(encyptedValue, 17, 32).ToArray();
+ ct = new ArraySegment(encyptedValue, 49, encyptedValue.Length - 49).ToArray();
+ break;
+ case EncryptionType.AesCbc256_B64:
+ if(encyptedValue.Length <= 17)
+ {
+ throw new InvalidOperationException("Invalid value length.");
+ }
+
+ iv = new ArraySegment(encyptedValue, 1, 16).ToArray();
+ ct = new ArraySegment(encyptedValue, 17, encyptedValue.Length - 17).ToArray();
+ break;
+ default:
+ throw new InvalidOperationException("Invalid encryption type.");
+ }
+
+ return Crypto.AesCbcDecrypt(encType, ct, iv, mac, key);
}
public byte[] RsaDecryptToBytes(CipherString encyptedValue, byte[] privateKey)
diff --git a/src/App/Services/LoginService.cs b/src/App/Services/LoginService.cs
index dc5ab2aa9..8f890d597 100644
--- a/src/App/Services/LoginService.cs
+++ b/src/App/Services/LoginService.cs
@@ -7,6 +7,7 @@ using Bit.App.Models;
using Bit.App.Models.Api;
using Bit.App.Models.Data;
using Xamarin.Forms;
+using System.Net.Http;
namespace Bit.App.Services
{
@@ -17,19 +18,22 @@ namespace Bit.App.Services
private readonly IAuthService _authService;
private readonly ILoginApiRepository _loginApiRepository;
private readonly ISettingsService _settingsService;
+ private readonly ICryptoService _cryptoService;
public LoginService(
ILoginRepository loginRepository,
IAttachmentRepository attachmentRepository,
IAuthService authService,
ILoginApiRepository loginApiRepository,
- ISettingsService settingsService)
+ ISettingsService settingsService,
+ ICryptoService cryptoService)
{
_loginRepository = loginRepository;
_attachmentRepository = attachmentRepository;
_authService = authService;
_loginApiRepository = loginApiRepository;
_settingsService = settingsService;
+ _cryptoService = cryptoService;
}
public async Task GetByIdAsync(string id)
@@ -217,6 +221,33 @@ namespace Bit.App.Services
return response;
}
+ public async Task DownloadAndDecryptAttachmentAsync(SymmetricCryptoKey key, string url)
+ {
+ using(var client = new HttpClient())
+ {
+ try
+ {
+ var response = await client.GetAsync(new Uri(url)).ConfigureAwait(false);
+ if(!response.IsSuccessStatusCode)
+ {
+ return null;
+ }
+
+ var data = await response.Content.ReadAsByteArrayAsync();
+ if(data == null)
+ {
+ return null;
+ }
+
+ return _cryptoService.DecryptToBytes(data, key);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+ }
+
private string WebUriFromAndroidAppUri(string androidAppUriString)
{
if(!UriIsAndroidApp(androidAppUriString))
diff --git a/src/App/Utilities/Crypto.cs b/src/App/Utilities/Crypto.cs
index 0d850ff35..ba6800352 100644
--- a/src/App/Utilities/Crypto.cs
+++ b/src/App/Utilities/Crypto.cs
@@ -1,4 +1,5 @@
-using Bit.App.Models;
+using Bit.App.Enums;
+using Bit.App.Models;
using PCLCrypto;
using System;
using System.Collections.Generic;
@@ -32,21 +33,41 @@ namespace Bit.App.Utilities
public static byte[] AesCbcDecrypt(CipherString encyptedValue, SymmetricCryptoKey key)
{
- if(key == null)
- {
- throw new ArgumentNullException(nameof(key));
- }
-
if(encyptedValue == null)
{
throw new ArgumentNullException(nameof(encyptedValue));
}
- if(key.MacKey != null && !string.IsNullOrWhiteSpace(encyptedValue.Mac))
+ return AesCbcDecrypt(encyptedValue.EncryptionType, encyptedValue.CipherTextBytes,
+ encyptedValue.InitializationVectorBytes, encyptedValue.MacBytes, key);
+ }
+
+ public static byte[] AesCbcDecrypt(EncryptionType type, byte[] ct, byte[] iv, byte[] mac, SymmetricCryptoKey key)
+ {
+ if(key == null)
{
- var computedMacBytes = ComputeMac(encyptedValue.CipherTextBytes,
- encyptedValue.InitializationVectorBytes, key.MacKey);
- if(!MacsEqual(key.MacKey, computedMacBytes, encyptedValue.MacBytes))
+ throw new ArgumentNullException(nameof(key));
+ }
+
+ if(ct == null)
+ {
+ throw new ArgumentNullException(nameof(ct));
+ }
+
+ if(iv == null)
+ {
+ throw new ArgumentNullException(nameof(iv));
+ }
+
+ if(key.EncryptionType != type)
+ {
+ throw new InvalidOperationException(nameof(type));
+ }
+
+ if(key.MacKey != null && mac != null)
+ {
+ var computedMacBytes = ComputeMac(ct, iv, key.MacKey);
+ if(!MacsEqual(key.MacKey, computedMacBytes, mac))
{
throw new InvalidOperationException("MAC failed.");
}
@@ -54,8 +75,7 @@ namespace Bit.App.Utilities
var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7);
var cryptoKey = provider.CreateSymmetricKey(key.EncKey);
- var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, encyptedValue.CipherTextBytes,
- encyptedValue.InitializationVectorBytes);
+ var decryptedBytes = WinRTCrypto.CryptographicEngine.Decrypt(cryptoKey, ct, iv);
return decryptedBytes;
}
diff --git a/src/iOS/AppDelegate.cs b/src/iOS/AppDelegate.cs
index 81189b4a9..cc290f34d 100644
--- a/src/iOS/AppDelegate.cs
+++ b/src/iOS/AppDelegate.cs
@@ -254,7 +254,7 @@ namespace Bit.iOS
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
- container.RegisterSingleton();
+ container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
container.RegisterSingleton();
diff --git a/src/iOS/Services/ClipboardService.cs b/src/iOS/Services/ClipboardService.cs
deleted file mode 100644
index 8ac1f4d3a..000000000
--- a/src/iOS/Services/ClipboardService.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Bit.App.Abstractions;
-using UIKit;
-
-namespace Bit.iOS.Services
-{
- public class ClipboardService : IClipboardService
- {
- public void CopyToClipboard(string text)
- {
- UIPasteboard clipboard = UIPasteboard.General;
- clipboard.String = text;
- }
- }
-}
diff --git a/src/iOS/Services/DeviceActionService.cs b/src/iOS/Services/DeviceActionService.cs
new file mode 100644
index 000000000..618edc608
--- /dev/null
+++ b/src/iOS/Services/DeviceActionService.cs
@@ -0,0 +1,20 @@
+using System;
+using Bit.App.Abstractions;
+using UIKit;
+
+namespace Bit.iOS.Services
+{
+ public class DeviceActionService : IDeviceActionService
+ {
+ public void CopyToClipboard(string text)
+ {
+ UIPasteboard clipboard = UIPasteboard.General;
+ clipboard.String = text;
+ }
+
+ public bool OpenFile(byte[] fileData, string id, string fileName)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/iOS/iOS.csproj b/src/iOS/iOS.csproj
index fbf076127..dd0e6dae7 100644
--- a/src/iOS/iOS.csproj
+++ b/src/iOS/iOS.csproj
@@ -232,7 +232,7 @@
-
+