mirror of
https://github.com/bitwarden/mobile.git
synced 2024-12-24 16:38:22 +01:00
view/add/edit login uris
This commit is contained in:
parent
83fd19784a
commit
4c8204f29a
@ -3,7 +3,7 @@ using System.ComponentModel;
|
|||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Bit.App.Enums;
|
using Bit.App.Enums;
|
||||||
using System.Linq;
|
using Bit.App.Resources;
|
||||||
|
|
||||||
namespace Bit.App.Models.Page
|
namespace Bit.App.Models.Page
|
||||||
{
|
{
|
||||||
@ -12,6 +12,7 @@ namespace Bit.App.Models.Page
|
|||||||
private string _name, _notes;
|
private string _name, _notes;
|
||||||
private List<Attachment> _attachments;
|
private List<Attachment> _attachments;
|
||||||
private List<Field> _fields;
|
private List<Field> _fields;
|
||||||
|
private List<LoginUri> _loginUris;
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
private string _loginUsername, _loginPassword, _loginUri, _loginTotpCode;
|
private string _loginUsername, _loginPassword, _loginUri, _loginTotpCode;
|
||||||
@ -117,69 +118,17 @@ namespace Bit.App.Models.Page
|
|||||||
public ImageSource LoginShowHideImage => RevealLoginPassword ?
|
public ImageSource LoginShowHideImage => RevealLoginPassword ?
|
||||||
ImageSource.FromFile("eye_slash.png") : ImageSource.FromFile("eye.png");
|
ImageSource.FromFile("eye_slash.png") : ImageSource.FromFile("eye.png");
|
||||||
|
|
||||||
public string LoginUri
|
public List<LoginUri> LoginUris
|
||||||
{
|
{
|
||||||
get => _loginUri;
|
get => _loginUris;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_loginUri = value;
|
_loginUris = value;
|
||||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUri)));
|
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUris)));
|
||||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(LoginUriHost)));
|
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginUris)));
|
||||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginUri)));
|
|
||||||
PropertyChanged(this, new PropertyChangedEventArgs(nameof(ShowLoginLaunch)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public bool ShowLoginUri => !string.IsNullOrWhiteSpace(LoginUri);
|
|
||||||
public bool ShowLoginLaunch
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(!ShowLoginUri)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Device.RuntimePlatform == Device.Android && !LoginUri.StartsWith("http") &&
|
|
||||||
!LoginUri.StartsWith("androidapp://"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Device.RuntimePlatform != Device.Android && !LoginUri.StartsWith("http"))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Uri.TryCreate(LoginUri, UriKind.Absolute, out Uri uri))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string LoginUriHost
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if(!ShowLoginUri)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Uri.TryCreate(LoginUri, UriKind.Absolute, out Uri uri))
|
|
||||||
{
|
|
||||||
return LoginUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(DomainName.TryParseBaseDomain(uri.Host, out string domain))
|
|
||||||
{
|
|
||||||
return domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
return uri.Host;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public bool ShowLoginUris => (LoginUris?.Count ?? 0) > 0;
|
||||||
|
|
||||||
public string LoginTotpCode
|
public string LoginTotpCode
|
||||||
{
|
{
|
||||||
@ -601,7 +550,23 @@ namespace Bit.App.Models.Page
|
|||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
LoginUsername = cipher.Login.Username?.Decrypt(cipher.OrganizationId);
|
LoginUsername = cipher.Login.Username?.Decrypt(cipher.OrganizationId);
|
||||||
LoginPassword = cipher.Login.Password?.Decrypt(cipher.OrganizationId);
|
LoginPassword = cipher.Login.Password?.Decrypt(cipher.OrganizationId);
|
||||||
LoginUri = cipher.Login.Uris?.FirstOrDefault()?.Uri?.Decrypt(cipher.OrganizationId);
|
|
||||||
|
if(cipher.Login.Uris != null)
|
||||||
|
{
|
||||||
|
var uris = new List<LoginUri>();
|
||||||
|
foreach(var uri in cipher.Login.Uris)
|
||||||
|
{
|
||||||
|
uris.Add(new LoginUri
|
||||||
|
{
|
||||||
|
Value = uri.Uri?.Decrypt(cipher.OrganizationId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
LoginUris = uris;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LoginUris = null;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CipherType.Card:
|
case CipherType.Card:
|
||||||
CardName = cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId);
|
CardName = cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId);
|
||||||
@ -666,5 +631,62 @@ namespace Bit.App.Models.Page
|
|||||||
public FieldType Type { get; set; }
|
public FieldType Type { get; set; }
|
||||||
public bool Revealed { get; set; }
|
public bool Revealed { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class LoginUri
|
||||||
|
{
|
||||||
|
public string Value { get; set; }
|
||||||
|
public bool ShowLaunch
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(Value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Device.RuntimePlatform == Device.Android && !IsWebsite && !IsApp)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Device.RuntimePlatform != Device.Android && !IsWebsite)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Uri.TryCreate(Value, UriKind.Absolute, out Uri uri))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Host
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(Value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Uri.TryCreate(Value, UriKind.Absolute, out Uri uri))
|
||||||
|
{
|
||||||
|
return Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(DomainName.TryParseBaseDomain(uri.Host, out string domain))
|
||||||
|
{
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri.Host;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Label => IsWebsite ? AppResources.Website : AppResources.URI;
|
||||||
|
public bool IsWebsite => Value.StartsWith("http://") || Value.StartsWith("https://");
|
||||||
|
public bool IsApp => Value.StartsWith(Constants.AndroidAppProtocol);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ namespace Bit.App.Pages
|
|||||||
public List<Folder> Folders { get; set; }
|
public List<Folder> Folders { get; set; }
|
||||||
public TableRoot TableRoot { get; set; }
|
public TableRoot TableRoot { get; set; }
|
||||||
public TableSection TopSection { get; set; }
|
public TableSection TopSection { get; set; }
|
||||||
|
public TableSection UrisSection { get; set; }
|
||||||
public TableSection MiddleSection { get; set; }
|
public TableSection MiddleSection { get; set; }
|
||||||
public TableSection FieldsSection { get; set; }
|
public TableSection FieldsSection { get; set; }
|
||||||
public ExtendedTableView Table { get; set; }
|
public ExtendedTableView Table { get; set; }
|
||||||
@ -94,11 +95,11 @@ namespace Bit.App.Pages
|
|||||||
public FormPickerCell FolderCell { get; private set; }
|
public FormPickerCell FolderCell { get; private set; }
|
||||||
public ExtendedSwitchCell FavoriteCell { get; set; }
|
public ExtendedSwitchCell FavoriteCell { get; set; }
|
||||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||||
|
public ExtendedTextCell AddUriCell { get; private set; }
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||||
public FormEntryCell LoginUsernameCell { get; private set; }
|
public FormEntryCell LoginUsernameCell { get; private set; }
|
||||||
public FormEntryCell LoginUriCell { get; private set; }
|
|
||||||
public FormEntryCell LoginTotpCell { get; private set; }
|
public FormEntryCell LoginTotpCell { get; private set; }
|
||||||
public ExtendedTextCell LoginGenerateCell { get; private set; }
|
public ExtendedTextCell LoginGenerateCell { get; private set; }
|
||||||
|
|
||||||
@ -190,13 +191,16 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||||
}
|
}
|
||||||
|
if(AddUriCell != null)
|
||||||
|
{
|
||||||
|
AddUriCell.Tapped += AddUriCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(_type)
|
switch(_type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
LoginPasswordCell.InitEvents();
|
LoginPasswordCell.InitEvents();
|
||||||
LoginUsernameCell.InitEvents();
|
LoginUsernameCell.InitEvents();
|
||||||
LoginUriCell.InitEvents();
|
|
||||||
LoginTotpCell.InitEvents();
|
LoginTotpCell.InitEvents();
|
||||||
LoginPasswordCell.Button.Clicked += PasswordButton_Clicked;
|
LoginPasswordCell.Button.Clicked += PasswordButton_Clicked;
|
||||||
LoginGenerateCell.Tapped += GenerateCell_Tapped;
|
LoginGenerateCell.Tapped += GenerateCell_Tapped;
|
||||||
@ -237,6 +241,9 @@ namespace Bit.App.Pages
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Helpers.InitSectionEvents(FieldsSection);
|
||||||
|
Helpers.InitSectionEvents(UrisSection);
|
||||||
|
|
||||||
if(_type == CipherType.Login && !_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
|
if(_type == CipherType.Login && !_fromAutofill && !_settings.GetValueOrDefault(AddedLoginAlertKey, false))
|
||||||
{
|
{
|
||||||
_settings.AddOrUpdateValue(AddedLoginAlertKey, true);
|
_settings.AddOrUpdateValue(AddedLoginAlertKey, true);
|
||||||
@ -267,6 +274,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||||
}
|
}
|
||||||
|
if(AddUriCell != null)
|
||||||
|
{
|
||||||
|
AddUriCell.Tapped -= AddUriCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(_type)
|
switch(_type)
|
||||||
{
|
{
|
||||||
@ -274,7 +285,6 @@ namespace Bit.App.Pages
|
|||||||
LoginTotpCell.Dispose();
|
LoginTotpCell.Dispose();
|
||||||
LoginPasswordCell.Dispose();
|
LoginPasswordCell.Dispose();
|
||||||
LoginUsernameCell.Dispose();
|
LoginUsernameCell.Dispose();
|
||||||
LoginUriCell.Dispose();
|
|
||||||
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
||||||
LoginGenerateCell.Tapped -= GenerateCell_Tapped;
|
LoginGenerateCell.Tapped -= GenerateCell_Tapped;
|
||||||
if(LoginTotpCell?.Button != null)
|
if(LoginTotpCell?.Button != null)
|
||||||
@ -314,16 +324,8 @@ namespace Bit.App.Pages
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
Helpers.DisposeSectionEvents(FieldsSection);
|
||||||
{
|
Helpers.DisposeSectionEvents(UrisSection);
|
||||||
foreach(var cell in FieldsSection)
|
|
||||||
{
|
|
||||||
if(cell is FormEntryCell entrycell)
|
|
||||||
{
|
|
||||||
entrycell.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
@ -398,8 +400,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if(_type == CipherType.Login)
|
if(_type == CipherType.Login)
|
||||||
{
|
{
|
||||||
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
|
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, useButton: _deviceInfo.HasCamera);
|
||||||
useButton: _deviceInfo.HasCamera);
|
|
||||||
if(_deviceInfo.HasCamera)
|
if(_deviceInfo.HasCamera)
|
||||||
{
|
{
|
||||||
LoginTotpCell.Button.Image = "camera.png";
|
LoginTotpCell.Button.Image = "camera.png";
|
||||||
@ -435,20 +436,23 @@ namespace Bit.App.Pages
|
|||||||
LoginUsernameCell.Entry.Text = _defaultUsername;
|
LoginUsernameCell.Entry.Text = _defaultUsername;
|
||||||
}
|
}
|
||||||
|
|
||||||
LoginUriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: LoginUsernameCell.Entry);
|
NameCell.NextElement = LoginUsernameCell.Entry;
|
||||||
if(!string.IsNullOrWhiteSpace(_defaultUri))
|
|
||||||
{
|
|
||||||
LoginUriCell.Entry.Text = _defaultUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
NameCell.NextElement = LoginUriCell.Entry;
|
|
||||||
|
|
||||||
// Build sections
|
// Build sections
|
||||||
TopSection.Add(LoginUriCell);
|
|
||||||
TopSection.Add(LoginUsernameCell);
|
TopSection.Add(LoginUsernameCell);
|
||||||
TopSection.Add(LoginPasswordCell);
|
TopSection.Add(LoginPasswordCell);
|
||||||
TopSection.Add(LoginGenerateCell);
|
TopSection.Add(LoginGenerateCell);
|
||||||
MiddleSection.Insert(0, LoginTotpCell);
|
TopSection.Add(LoginTotpCell);
|
||||||
|
|
||||||
|
// Uris
|
||||||
|
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||||
|
AddUriCell = new ExtendedTextCell
|
||||||
|
{
|
||||||
|
Text = AppResources.NewUri,
|
||||||
|
TextColor = Colors.Primary
|
||||||
|
};
|
||||||
|
UrisSection.Add(AddUriCell);
|
||||||
|
UrisSection.Insert(0, Helpers.MakeUriCell(string.Empty, UrisSection));
|
||||||
}
|
}
|
||||||
else if(_type == CipherType.Card)
|
else if(_type == CipherType.Card)
|
||||||
{
|
{
|
||||||
@ -583,6 +587,11 @@ namespace Bit.App.Pages
|
|||||||
FieldsSection
|
FieldsSection
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(UrisSection != null)
|
||||||
|
{
|
||||||
|
TableRoot.Insert(1, UrisSection);
|
||||||
|
}
|
||||||
|
|
||||||
Table = new ExtendedTableView
|
Table = new ExtendedTableView
|
||||||
{
|
{
|
||||||
Intent = TableIntent.Settings,
|
Intent = TableIntent.Settings,
|
||||||
@ -638,8 +647,6 @@ namespace Bit.App.Pages
|
|||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
cipher.Login = new Login
|
cipher.Login = new Login
|
||||||
{
|
{
|
||||||
Uri = string.IsNullOrWhiteSpace(LoginUriCell.Entry.Text) ? null :
|
|
||||||
LoginUriCell.Entry.Text.Encrypt(),
|
|
||||||
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
||||||
LoginUsernameCell.Entry.Text.Encrypt(),
|
LoginUsernameCell.Entry.Text.Encrypt(),
|
||||||
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
||||||
@ -647,6 +654,8 @@ namespace Bit.App.Pages
|
|||||||
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
||||||
LoginTotpCell.Entry.Text.Encrypt(),
|
LoginTotpCell.Entry.Text.Encrypt(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Helpers.ProcessUrisSectionForSave(UrisSection, cipher);
|
||||||
break;
|
break;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
cipher.SecureNote = new SecureNote
|
cipher.SecureNote = new SecureNote
|
||||||
@ -821,5 +830,15 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
await Helpers.AddField(this, FieldsSection);
|
await Helpers.AddField(this, FieldsSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddUriCell_Tapped(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cell = Helpers.MakeUriCell(string.Empty, UrisSection);
|
||||||
|
if(cell != null)
|
||||||
|
{
|
||||||
|
UrisSection.Insert(UrisSection.Count - 1, cell);
|
||||||
|
cell.InitEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ namespace Bit.App.Pages
|
|||||||
public List<Folder> Folders { get; set; }
|
public List<Folder> Folders { get; set; }
|
||||||
public TableRoot TableRoot { get; set; }
|
public TableRoot TableRoot { get; set; }
|
||||||
public TableSection TopSection { get; set; }
|
public TableSection TopSection { get; set; }
|
||||||
|
public TableSection UrisSection { get; set; }
|
||||||
public TableSection MiddleSection { get; set; }
|
public TableSection MiddleSection { get; set; }
|
||||||
public TableSection FieldsSection { get; set; }
|
public TableSection FieldsSection { get; set; }
|
||||||
public ExtendedTableView Table { get; set; }
|
public ExtendedTableView Table { get; set; }
|
||||||
@ -52,11 +53,11 @@ namespace Bit.App.Pages
|
|||||||
public ExtendedTextCell AttachmentsCell { get; private set; }
|
public ExtendedTextCell AttachmentsCell { get; private set; }
|
||||||
public ExtendedTextCell DeleteCell { get; private set; }
|
public ExtendedTextCell DeleteCell { get; private set; }
|
||||||
public ExtendedTextCell AddFieldCell { get; private set; }
|
public ExtendedTextCell AddFieldCell { get; private set; }
|
||||||
|
public ExtendedTextCell AddUriCell { get; private set; }
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
public FormEntryCell LoginPasswordCell { get; private set; }
|
public FormEntryCell LoginPasswordCell { get; private set; }
|
||||||
public FormEntryCell LoginUsernameCell { get; private set; }
|
public FormEntryCell LoginUsernameCell { get; private set; }
|
||||||
public FormEntryCell LoginUriCell { get; private set; }
|
|
||||||
public FormEntryCell LoginTotpCell { get; private set; }
|
public FormEntryCell LoginTotpCell { get; private set; }
|
||||||
public ExtendedTextCell LoginGenerateCell { get; private set; }
|
public ExtendedTextCell LoginGenerateCell { get; private set; }
|
||||||
|
|
||||||
@ -169,8 +170,7 @@ namespace Bit.App.Pages
|
|||||||
// Types
|
// Types
|
||||||
if(Cipher.Type == CipherType.Login)
|
if(Cipher.Type == CipherType.Login)
|
||||||
{
|
{
|
||||||
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, nextElement: NotesCell.Editor,
|
LoginTotpCell = new FormEntryCell(AppResources.AuthenticatorKey, useButton: _deviceInfo.HasCamera);
|
||||||
useButton: _deviceInfo.HasCamera);
|
|
||||||
if(_deviceInfo.HasCamera)
|
if(_deviceInfo.HasCamera)
|
||||||
{
|
{
|
||||||
LoginTotpCell.Button.Image = "camera.png";
|
LoginTotpCell.Button.Image = "camera.png";
|
||||||
@ -201,18 +201,31 @@ namespace Bit.App.Pages
|
|||||||
LoginUsernameCell.Entry.DisableAutocapitalize = true;
|
LoginUsernameCell.Entry.DisableAutocapitalize = true;
|
||||||
LoginUsernameCell.Entry.Autocorrect = false;
|
LoginUsernameCell.Entry.Autocorrect = false;
|
||||||
|
|
||||||
LoginUriCell = new FormEntryCell(AppResources.URI, Keyboard.Url, nextElement: LoginUsernameCell.Entry);
|
|
||||||
LoginUriCell.Entry.Text = Cipher.Login?.Uri?.Decrypt(Cipher.OrganizationId);
|
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
NameCell.NextElement = LoginUriCell.Entry;
|
NameCell.NextElement = LoginUsernameCell.Entry;
|
||||||
|
|
||||||
// Build sections
|
// Build sections
|
||||||
TopSection.Add(LoginUriCell);
|
|
||||||
TopSection.Add(LoginUsernameCell);
|
TopSection.Add(LoginUsernameCell);
|
||||||
TopSection.Add(LoginPasswordCell);
|
TopSection.Add(LoginPasswordCell);
|
||||||
TopSection.Add(LoginGenerateCell);
|
TopSection.Add(LoginGenerateCell);
|
||||||
MiddleSection.Insert(0, LoginTotpCell);
|
TopSection.Add(LoginTotpCell);
|
||||||
|
|
||||||
|
// Uris
|
||||||
|
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||||
|
AddUriCell = new ExtendedTextCell
|
||||||
|
{
|
||||||
|
Text = AppResources.NewUri,
|
||||||
|
TextColor = Colors.Primary
|
||||||
|
};
|
||||||
|
UrisSection.Add(AddUriCell);
|
||||||
|
if(Cipher.Login?.Uris != null)
|
||||||
|
{
|
||||||
|
foreach(var uri in Cipher.Login.Uris)
|
||||||
|
{
|
||||||
|
var value = uri.Uri?.Decrypt(Cipher.OrganizationId);
|
||||||
|
UrisSection.Insert(UrisSection.Count - 1, Helpers.MakeUriCell(value, UrisSection));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(Cipher.Type == CipherType.Card)
|
else if(Cipher.Type == CipherType.Card)
|
||||||
{
|
{
|
||||||
@ -436,6 +449,11 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(UrisSection != null)
|
||||||
|
{
|
||||||
|
TableRoot.Insert(1, UrisSection);
|
||||||
|
}
|
||||||
|
|
||||||
Table = new ExtendedTableView
|
Table = new ExtendedTableView
|
||||||
{
|
{
|
||||||
Intent = TableIntent.Settings,
|
Intent = TableIntent.Settings,
|
||||||
@ -488,8 +506,6 @@ namespace Bit.App.Pages
|
|||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
Cipher.Login = new Login
|
Cipher.Login = new Login
|
||||||
{
|
{
|
||||||
Uri = string.IsNullOrWhiteSpace(LoginUriCell.Entry.Text) ? null :
|
|
||||||
LoginUriCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
|
||||||
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
Username = string.IsNullOrWhiteSpace(LoginUsernameCell.Entry.Text) ? null :
|
||||||
LoginUsernameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
LoginUsernameCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||||
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
Password = string.IsNullOrWhiteSpace(LoginPasswordCell.Entry.Text) ? null :
|
||||||
@ -497,6 +513,8 @@ namespace Bit.App.Pages
|
|||||||
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
Totp = string.IsNullOrWhiteSpace(LoginTotpCell.Entry.Text) ? null :
|
||||||
LoginTotpCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
LoginTotpCell.Entry.Text.Encrypt(Cipher.OrganizationId),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Helpers.ProcessUrisSectionForSave(UrisSection, Cipher);
|
||||||
break;
|
break;
|
||||||
case CipherType.SecureNote:
|
case CipherType.SecureNote:
|
||||||
Cipher.SecureNote = new SecureNote
|
Cipher.SecureNote = new SecureNote
|
||||||
@ -679,13 +697,16 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
AddFieldCell.Tapped += AddFieldCell_Tapped;
|
||||||
}
|
}
|
||||||
|
if(AddUriCell != null)
|
||||||
|
{
|
||||||
|
AddUriCell.Tapped += AddUriCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(Cipher.Type)
|
switch(Cipher.Type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
LoginPasswordCell?.InitEvents();
|
LoginPasswordCell?.InitEvents();
|
||||||
LoginUsernameCell?.InitEvents();
|
LoginUsernameCell?.InitEvents();
|
||||||
LoginUriCell?.InitEvents();
|
|
||||||
LoginTotpCell?.InitEvents();
|
LoginTotpCell?.InitEvents();
|
||||||
if(LoginPasswordCell?.Button != null)
|
if(LoginPasswordCell?.Button != null)
|
||||||
{
|
{
|
||||||
@ -732,16 +753,8 @@ namespace Bit.App.Pages
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
Helpers.InitSectionEvents(FieldsSection);
|
||||||
{
|
Helpers.InitSectionEvents(UrisSection);
|
||||||
foreach(var cell in FieldsSection)
|
|
||||||
{
|
|
||||||
if(cell is FormEntryCell entrycell)
|
|
||||||
{
|
|
||||||
entrycell.InitEvents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDisappearing()
|
protected override void OnDisappearing()
|
||||||
@ -764,6 +777,10 @@ namespace Bit.App.Pages
|
|||||||
{
|
{
|
||||||
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
AddFieldCell.Tapped -= AddFieldCell_Tapped;
|
||||||
}
|
}
|
||||||
|
if(AddUriCell != null)
|
||||||
|
{
|
||||||
|
AddUriCell.Tapped -= AddUriCell_Tapped;
|
||||||
|
}
|
||||||
|
|
||||||
switch(Cipher.Type)
|
switch(Cipher.Type)
|
||||||
{
|
{
|
||||||
@ -771,7 +788,6 @@ namespace Bit.App.Pages
|
|||||||
LoginTotpCell?.Dispose();
|
LoginTotpCell?.Dispose();
|
||||||
LoginPasswordCell?.Dispose();
|
LoginPasswordCell?.Dispose();
|
||||||
LoginUsernameCell?.Dispose();
|
LoginUsernameCell?.Dispose();
|
||||||
LoginUriCell?.Dispose();
|
|
||||||
if(LoginPasswordCell?.Button != null)
|
if(LoginPasswordCell?.Button != null)
|
||||||
{
|
{
|
||||||
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
LoginPasswordCell.Button.Clicked -= PasswordButton_Clicked;
|
||||||
@ -816,17 +832,9 @@ namespace Bit.App.Pages
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(FieldsSection != null && FieldsSection.Count > 0)
|
Helpers.DisposeSectionEvents(FieldsSection);
|
||||||
{
|
Helpers.DisposeSectionEvents(UrisSection);
|
||||||
foreach(var cell in FieldsSection)
|
|
||||||
{
|
|
||||||
if(cell is FormEntryCell entrycell)
|
|
||||||
{
|
|
||||||
entrycell.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PasswordButton_Clicked(object sender, EventArgs e)
|
private void PasswordButton_Clicked(object sender, EventArgs e)
|
||||||
@ -920,6 +928,16 @@ namespace Bit.App.Pages
|
|||||||
await Helpers.AddField(this, FieldsSection);
|
await Helpers.AddField(this, FieldsSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddUriCell_Tapped(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cell = Helpers.MakeUriCell(string.Empty, UrisSection);
|
||||||
|
if(cell != null)
|
||||||
|
{
|
||||||
|
UrisSection.Insert(UrisSection.Count - 1, cell);
|
||||||
|
cell.InitEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AlertNoConnection()
|
private void AlertNoConnection()
|
||||||
{
|
{
|
||||||
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
DisplayAlert(AppResources.InternetConnectionRequiredTitle, AppResources.InternetConnectionRequiredMessage,
|
||||||
|
@ -39,6 +39,7 @@ namespace Bit.App.Pages
|
|||||||
private VaultViewCipherPageModel Model { get; set; } = new VaultViewCipherPageModel();
|
private VaultViewCipherPageModel Model { get; set; } = new VaultViewCipherPageModel();
|
||||||
private ExtendedTableView Table { get; set; }
|
private ExtendedTableView Table { get; set; }
|
||||||
private TableSection ItemInformationSection { get; set; }
|
private TableSection ItemInformationSection { get; set; }
|
||||||
|
public TableSection UrisSection { get; set; }
|
||||||
private TableSection NotesSection { get; set; }
|
private TableSection NotesSection { get; set; }
|
||||||
private TableSection AttachmentsSection { get; set; }
|
private TableSection AttachmentsSection { get; set; }
|
||||||
private TableSection FieldsSection { get; set; }
|
private TableSection FieldsSection { get; set; }
|
||||||
@ -50,7 +51,6 @@ namespace Bit.App.Pages
|
|||||||
// Login
|
// Login
|
||||||
public LabeledValueCell LoginUsernameCell { get; set; }
|
public LabeledValueCell LoginUsernameCell { get; set; }
|
||||||
public LabeledValueCell LoginPasswordCell { get; set; }
|
public LabeledValueCell LoginPasswordCell { get; set; }
|
||||||
public LabeledValueCell LoginUriCell { get; set; }
|
|
||||||
public LabeledValueCell LoginTotpCodeCell { get; set; }
|
public LabeledValueCell LoginTotpCodeCell { get; set; }
|
||||||
|
|
||||||
// Card
|
// Card
|
||||||
@ -141,22 +141,6 @@ namespace Bit.App.Pages
|
|||||||
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||||
LoginPasswordCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
LoginPasswordCell.Value.LineBreakMode = LineBreakMode.WordWrap;
|
||||||
|
|
||||||
// URI
|
|
||||||
LoginUriCell = new LabeledValueCell(AppResources.Website, button1Image: "launch.png");
|
|
||||||
LoginUriCell.Value.SetBinding(Label.TextProperty, nameof(VaultViewCipherPageModel.LoginUriHost));
|
|
||||||
LoginUriCell.Button1.SetBinding(IsVisibleProperty, nameof(VaultViewCipherPageModel.ShowLoginLaunch));
|
|
||||||
LoginUriCell.Button1.Command = new Command(async () =>
|
|
||||||
{
|
|
||||||
if(Device.RuntimePlatform == Device.Android && Model.LoginUri.StartsWith("androidapp://"))
|
|
||||||
{
|
|
||||||
await _deviceActionService.LaunchAppAsync(Model.LoginUri, this);
|
|
||||||
}
|
|
||||||
else if(Model.LoginUri.StartsWith("http://") || Model.LoginUri.StartsWith("https://"))
|
|
||||||
{
|
|
||||||
Device.OpenUri(new Uri(Model.LoginUri));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Totp
|
// Totp
|
||||||
LoginTotpCodeCell = new LabeledValueCell(
|
LoginTotpCodeCell = new LabeledValueCell(
|
||||||
AppResources.VerificationCodeTotp, button1Image: "clipboard.png", subText: "--");
|
AppResources.VerificationCodeTotp, button1Image: "clipboard.png", subText: "--");
|
||||||
@ -299,6 +283,22 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private void BuildTable(Cipher cipher)
|
private void BuildTable(Cipher cipher)
|
||||||
{
|
{
|
||||||
|
// URIs
|
||||||
|
if(UrisSection != null && Table.Root.Contains(UrisSection))
|
||||||
|
{
|
||||||
|
Table.Root.Remove(UrisSection);
|
||||||
|
}
|
||||||
|
if(Model.ShowLoginUris)
|
||||||
|
{
|
||||||
|
UrisSection = new TableSection(Helpers.GetEmptyTableSectionTitle());
|
||||||
|
foreach(var uri in Model.LoginUris)
|
||||||
|
{
|
||||||
|
UrisSection.Add(new UriViewCell(this, uri));
|
||||||
|
}
|
||||||
|
Table.Root.Add(UrisSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notes
|
||||||
if(Table.Root.Contains(NotesSection))
|
if(Table.Root.Contains(NotesSection))
|
||||||
{
|
{
|
||||||
Table.Root.Remove(NotesSection);
|
Table.Root.Remove(NotesSection);
|
||||||
@ -365,7 +365,6 @@ namespace Bit.App.Pages
|
|||||||
switch(cipher.Type)
|
switch(cipher.Type)
|
||||||
{
|
{
|
||||||
case CipherType.Login:
|
case CipherType.Login:
|
||||||
AddSectionCell(LoginUriCell, Model.ShowLoginUri);
|
|
||||||
AddSectionCell(LoginUsernameCell, Model.ShowLoginUsername);
|
AddSectionCell(LoginUsernameCell, Model.ShowLoginUsername);
|
||||||
AddSectionCell(LoginPasswordCell, Model.ShowLoginPassword);
|
AddSectionCell(LoginPasswordCell, Model.ShowLoginPassword);
|
||||||
|
|
||||||
@ -575,8 +574,7 @@ namespace Bit.App.Pages
|
|||||||
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a, bool? b)
|
public FieldViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.Field field, bool? a, bool? b)
|
||||||
: base(field.Name, field.MaskedValue, string.Empty, "clipboard.png")
|
: base(field.Name, field.MaskedValue, string.Empty, "clipboard.png")
|
||||||
{
|
{
|
||||||
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular",
|
Value.FontFamily = Helpers.OnPlatform(iOS: "Menlo-Regular", Android: "monospace", Windows: "Courier");
|
||||||
Android: "monospace", Windows: "Courier");
|
|
||||||
if(Device.RuntimePlatform == Device.iOS)
|
if(Device.RuntimePlatform == Device.iOS)
|
||||||
{
|
{
|
||||||
Button1.Margin = new Thickness(10, 0);
|
Button1.Margin = new Thickness(10, 0);
|
||||||
@ -610,5 +608,29 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class UriViewCell : LabeledValueCell
|
||||||
|
{
|
||||||
|
public UriViewCell(VaultViewCipherPage page, VaultViewCipherPageModel.LoginUri uri)
|
||||||
|
: base(uri.Label, uri.Host, uri.ShowLaunch ? "launch.png" : null, "clipboard.png")
|
||||||
|
{
|
||||||
|
Value.LineBreakMode = LineBreakMode.TailTruncation;
|
||||||
|
if(Button1 != null)
|
||||||
|
{
|
||||||
|
Button1.Command = new Command(async () =>
|
||||||
|
{
|
||||||
|
if(Device.RuntimePlatform == Device.Android && uri.IsApp)
|
||||||
|
{
|
||||||
|
await page._deviceActionService.LaunchAppAsync(uri.Value, page);
|
||||||
|
}
|
||||||
|
else if(uri.IsWebsite)
|
||||||
|
{
|
||||||
|
Device.OpenUri(new Uri(uri.Value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Button2.Command = new Command(() => page.Copy(uri.Value, AppResources.URI));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
src/App/Resources/AppResources.Designer.cs
generated
18
src/App/Resources/AppResources.Designer.cs
generated
@ -2130,6 +2130,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to New URI.
|
||||||
|
/// </summary>
|
||||||
|
public static string NewUri {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NewUri", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to No.
|
/// Looks up a localized string similar to No.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -2940,6 +2949,15 @@ namespace Bit.App.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to URI {0}.
|
||||||
|
/// </summary>
|
||||||
|
public static string URIPosition {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("URIPosition", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Use another two-step login method.
|
/// Looks up a localized string similar to Use another two-step login method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1249,4 +1249,11 @@
|
|||||||
<data name="Remove" xml:space="preserve">
|
<data name="Remove" xml:space="preserve">
|
||||||
<value>Remove</value>
|
<value>Remove</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="NewUri" xml:space="preserve">
|
||||||
|
<value>New URI</value>
|
||||||
|
</data>
|
||||||
|
<data name="URIPosition" xml:space="preserve">
|
||||||
|
<value>URI {0}</value>
|
||||||
|
<comment>Label for a uri/url with position. i.e. URI 1, URI 2, etc</comment>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -373,5 +373,99 @@ namespace Bit.App.Utilities
|
|||||||
cipher.Fields = null;
|
cipher.Fields = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FormEntryCell MakeUriCell(string value, TableSection urisSection)
|
||||||
|
{
|
||||||
|
var label = string.Format(AppResources.URIPosition, urisSection.Count);
|
||||||
|
var cell = new FormEntryCell(label, entryKeyboard: Keyboard.Url);
|
||||||
|
cell.Entry.Text = value;
|
||||||
|
cell.Entry.DisableAutocapitalize = true;
|
||||||
|
cell.Entry.Autocorrect = false;
|
||||||
|
|
||||||
|
var deleteAction = new MenuItem { Text = AppResources.Remove, IsDestructive = true };
|
||||||
|
deleteAction.Clicked += (sender, e) =>
|
||||||
|
{
|
||||||
|
if(urisSection.Contains(cell))
|
||||||
|
{
|
||||||
|
urisSection.Remove(cell);
|
||||||
|
if(cell is FormEntryCell feCell)
|
||||||
|
{
|
||||||
|
feCell.Dispose();
|
||||||
|
}
|
||||||
|
cell = null;
|
||||||
|
|
||||||
|
for(int i = 0; i < urisSection.Count; i++)
|
||||||
|
{
|
||||||
|
if(urisSection[i] is FormEntryCell uriCell)
|
||||||
|
{
|
||||||
|
uriCell.Label.Text = string.Format(AppResources.URIPosition, i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var optionsAction = new MenuItem { Text = AppResources.Options };
|
||||||
|
optionsAction.Clicked += async (sender, e) =>
|
||||||
|
{
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
cell.ContextActions.Add(optionsAction);
|
||||||
|
cell.ContextActions.Add(deleteAction);
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ProcessUrisSectionForSave(TableSection urisSection, Cipher cipher)
|
||||||
|
{
|
||||||
|
if(urisSection != null && urisSection.Count > 0)
|
||||||
|
{
|
||||||
|
var uris = new List<LoginUri>();
|
||||||
|
foreach(var cell in urisSection)
|
||||||
|
{
|
||||||
|
if(cell is FormEntryCell entryCell && !string.IsNullOrWhiteSpace(entryCell.Entry.Text))
|
||||||
|
{
|
||||||
|
uris.Add(new LoginUri
|
||||||
|
{
|
||||||
|
Uri = entryCell.Entry.Text.Encrypt(cipher.OrganizationId),
|
||||||
|
Match = null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cipher.Login.Uris = uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cipher.Login.Uris?.Any() ?? true)
|
||||||
|
{
|
||||||
|
cipher.Login.Uris = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InitSectionEvents(TableSection section)
|
||||||
|
{
|
||||||
|
if(section != null && section.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(var cell in section)
|
||||||
|
{
|
||||||
|
if(cell is FormEntryCell entrycell)
|
||||||
|
{
|
||||||
|
entrycell.InitEvents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DisposeSectionEvents(TableSection section)
|
||||||
|
{
|
||||||
|
if(section != null && section.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(var cell in section)
|
||||||
|
{
|
||||||
|
if(cell is FormEntryCell entrycell)
|
||||||
|
{
|
||||||
|
entrycell.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user