Added option to not store the password
This commit is contained in:
parent
4c6089a991
commit
4b730b2a1a
@ -6,12 +6,12 @@ namespace PettingZoo.Core.Settings
|
||||
{
|
||||
public interface IConnectionSettingsRepository
|
||||
{
|
||||
Task<ConnectionSettings> GetLastUsed();
|
||||
Task StoreLastUsed(ConnectionSettings connectionSettings);
|
||||
Task<StoredConnectionSettings> GetLastUsed();
|
||||
Task StoreLastUsed(bool storePassword, ConnectionSettings connectionSettings);
|
||||
|
||||
Task<IEnumerable<StoredConnectionSettings>> GetStored();
|
||||
Task<StoredConnectionSettings> Add(string displayName, ConnectionSettings connectionSettings);
|
||||
Task<StoredConnectionSettings> Update(Guid id, string displayName, ConnectionSettings connectionSettings);
|
||||
Task<StoredConnectionSettings> Add(string displayName, bool storePassword, ConnectionSettings connectionSettings);
|
||||
Task<StoredConnectionSettings> Update(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings);
|
||||
Task Delete(Guid id);
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ namespace PettingZoo.Core.Settings
|
||||
public string VirtualHost { get; }
|
||||
public int Port { get; }
|
||||
public string Username { get; }
|
||||
public string? Password { get; }
|
||||
public string Password { get; }
|
||||
|
||||
public bool Subscribe { get; }
|
||||
public string Exchange { get; }
|
||||
@ -32,7 +32,7 @@ namespace PettingZoo.Core.Settings
|
||||
public static readonly ConnectionSettings Default = new("localhost", "/", 5672, "guest", "guest", false, "", "#");
|
||||
|
||||
|
||||
public ConnectionSettings(string host, string virtualHost, int port, string username, string? password,
|
||||
public ConnectionSettings(string host, string virtualHost, int port, string username, string password,
|
||||
bool subscribe, string exchange, string routingKey)
|
||||
{
|
||||
Host = host;
|
||||
@ -65,22 +65,26 @@ namespace PettingZoo.Core.Settings
|
||||
{
|
||||
public Guid Id { get; }
|
||||
public string DisplayName { get; }
|
||||
public bool StorePassword { get; }
|
||||
|
||||
public StoredConnectionSettings(Guid id, string displayName, string host, string virtualHost, int port, string username,
|
||||
string? password, bool subscribe, string exchange, string routingKey)
|
||||
|
||||
public StoredConnectionSettings(Guid id, string displayName, bool storePassword, string host, string virtualHost, int port, string username,
|
||||
string password, bool subscribe, string exchange, string routingKey)
|
||||
: base(host, virtualHost, port, username, password, subscribe, exchange, routingKey)
|
||||
{
|
||||
Id = id;
|
||||
DisplayName = displayName;
|
||||
StorePassword = storePassword;
|
||||
}
|
||||
|
||||
|
||||
public StoredConnectionSettings(Guid id, string displayName, ConnectionSettings connectionSettings)
|
||||
: base(connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port, connectionSettings.Username, connectionSettings.Password,
|
||||
connectionSettings.Subscribe, connectionSettings.Exchange, connectionSettings.RoutingKey)
|
||||
public StoredConnectionSettings(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings)
|
||||
: base(connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port, connectionSettings.Username,
|
||||
connectionSettings.Password, connectionSettings.Subscribe, connectionSettings.Exchange, connectionSettings.RoutingKey)
|
||||
{
|
||||
Id = id;
|
||||
DisplayName = displayName;
|
||||
StorePassword = storePassword;
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,10 @@ namespace PettingZoo.Settings.LiteDB
|
||||
|
||||
protected ILiteDatabaseAsync GetDatabase()
|
||||
{
|
||||
return new LiteDatabaseAsync(databaseFilename);
|
||||
return new LiteDatabaseAsync(databaseFilename, new BsonMapper
|
||||
{
|
||||
EmptyStringToNull = false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,33 +15,36 @@ namespace PettingZoo.Settings.LiteDB
|
||||
}
|
||||
|
||||
|
||||
public async Task<ConnectionSettings> GetLastUsed()
|
||||
public async Task<StoredConnectionSettings> GetLastUsed()
|
||||
{
|
||||
using var database = GetDatabase();
|
||||
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionLastUsed);
|
||||
|
||||
var lastUsed = await collection.FindOneAsync(r => true);
|
||||
if (lastUsed == null)
|
||||
return ConnectionSettings.Default;
|
||||
return new StoredConnectionSettings(Guid.Empty, "", false, ConnectionSettings.Default);
|
||||
|
||||
return new ConnectionSettings(
|
||||
return new StoredConnectionSettings(
|
||||
Guid.Empty,
|
||||
"",
|
||||
lastUsed.Password != null,
|
||||
lastUsed.Host,
|
||||
lastUsed.VirtualHost,
|
||||
lastUsed.Port,
|
||||
lastUsed.Username,
|
||||
lastUsed.Password,
|
||||
lastUsed.Password ?? "",
|
||||
lastUsed.Subscribe,
|
||||
lastUsed.Exchange,
|
||||
lastUsed.RoutingKey);
|
||||
}
|
||||
|
||||
|
||||
public async Task StoreLastUsed(ConnectionSettings connectionSettings)
|
||||
public async Task StoreLastUsed(bool storePassword, ConnectionSettings connectionSettings)
|
||||
{
|
||||
using var database = GetDatabase();
|
||||
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionLastUsed);
|
||||
|
||||
await collection.UpsertAsync(ConnectionSettingsRecord.FromConnectionSettings(LastUsedId, connectionSettings, ""));
|
||||
await collection.UpsertAsync(ConnectionSettingsRecord.FromConnectionSettings(LastUsedId, connectionSettings, "", storePassword));
|
||||
}
|
||||
|
||||
|
||||
@ -51,30 +54,30 @@ namespace PettingZoo.Settings.LiteDB
|
||||
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
|
||||
|
||||
return (await collection.FindAllAsync())
|
||||
.Select(r => new StoredConnectionSettings(r.Id, r.DisplayName, r.Host, r.VirtualHost, r.Port, r.Username, r.Password, r.Subscribe, r.Exchange, r.RoutingKey))
|
||||
.Select(r => new StoredConnectionSettings(r.Id, r.DisplayName, r.Password != null, r.Host, r.VirtualHost, r.Port, r.Username, r.Password ?? "", r.Subscribe, r.Exchange, r.RoutingKey))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public async Task<StoredConnectionSettings> Add(string displayName, ConnectionSettings connectionSettings)
|
||||
public async Task<StoredConnectionSettings> Add(string displayName, bool storePassword, ConnectionSettings connectionSettings)
|
||||
{
|
||||
using var database = GetDatabase();
|
||||
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
|
||||
|
||||
var id = Guid.NewGuid();
|
||||
await collection.InsertAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName));
|
||||
await collection.InsertAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName, storePassword));
|
||||
|
||||
return new StoredConnectionSettings(id, displayName, connectionSettings);
|
||||
return new StoredConnectionSettings(id, displayName, storePassword, connectionSettings);
|
||||
}
|
||||
|
||||
|
||||
public async Task<StoredConnectionSettings> Update(Guid id, string displayName, ConnectionSettings connectionSettings)
|
||||
public async Task<StoredConnectionSettings> Update(Guid id, string displayName, bool storePassword, ConnectionSettings connectionSettings)
|
||||
{
|
||||
using var database = GetDatabase();
|
||||
var collection = database.GetCollection<ConnectionSettingsRecord>(CollectionStored);
|
||||
|
||||
await collection.UpdateAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName));
|
||||
return new StoredConnectionSettings(id, displayName, connectionSettings);
|
||||
await collection.UpdateAsync(ConnectionSettingsRecord.FromConnectionSettings(id, connectionSettings, displayName, storePassword));
|
||||
return new StoredConnectionSettings(id, displayName, storePassword, connectionSettings);
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +108,7 @@ namespace PettingZoo.Settings.LiteDB
|
||||
public string RoutingKey { get; set; } = null!;
|
||||
|
||||
|
||||
public static ConnectionSettingsRecord FromConnectionSettings(Guid id, ConnectionSettings connectionSettings, string displayName)
|
||||
public static ConnectionSettingsRecord FromConnectionSettings(Guid id, ConnectionSettings connectionSettings, string displayName, bool storePassword)
|
||||
{
|
||||
return new ConnectionSettingsRecord
|
||||
{
|
||||
@ -116,7 +119,7 @@ namespace PettingZoo.Settings.LiteDB
|
||||
VirtualHost = connectionSettings.VirtualHost,
|
||||
Port = connectionSettings.Port,
|
||||
Username = connectionSettings.Username,
|
||||
Password = connectionSettings.Password,
|
||||
Password = storePassword ? connectionSettings.Password : null,
|
||||
|
||||
Subscribe = connectionSettings.Subscribe,
|
||||
Exchange = connectionSettings.Exchange,
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@ -6,6 +7,8 @@ namespace PettingZoo.UI
|
||||
{
|
||||
public class BaseViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private int commandsChangedDisabled;
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
@ -30,6 +33,7 @@ namespace PettingZoo.UI
|
||||
RaisePropertyChanged(otherProperty);
|
||||
}
|
||||
|
||||
// ReSharper disable once InvertIf
|
||||
if (delegateCommandsChanged != null)
|
||||
{
|
||||
foreach (var delegateCommand in delegateCommandsChanged)
|
||||
@ -38,5 +42,24 @@ namespace PettingZoo.UI
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected void DisableCommandsChanged(Action updateFields, params DelegateCommand[] delegateCommandsChangedAfter)
|
||||
{
|
||||
commandsChangedDisabled++;
|
||||
try
|
||||
{
|
||||
updateFields();
|
||||
}
|
||||
finally
|
||||
{
|
||||
commandsChangedDisabled--;
|
||||
if (commandsChangedDisabled == 0)
|
||||
{
|
||||
foreach (var delegateCommand in delegateCommandsChangedAfter)
|
||||
delegateCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,14 +5,12 @@ using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using PettingZoo.Core.Settings;
|
||||
|
||||
// TODO "save password" checkbox
|
||||
|
||||
namespace PettingZoo.UI.Connection
|
||||
{
|
||||
public class ConnectionViewModel : BaseViewModel
|
||||
{
|
||||
private readonly IConnectionSettingsRepository connectionSettingsRepository;
|
||||
private readonly ConnectionSettings defaultSettings;
|
||||
private readonly StoredConnectionSettings defaultSettings;
|
||||
private string host = null!;
|
||||
private string virtualHost = null!;
|
||||
private int port;
|
||||
@ -23,6 +21,8 @@ namespace PettingZoo.UI.Connection
|
||||
private string exchange = null!;
|
||||
private string routingKey = null!;
|
||||
|
||||
private bool storePassword;
|
||||
|
||||
private StoredConnectionSettings? selectedStoredConnection;
|
||||
|
||||
private readonly DelegateCommand okCommand;
|
||||
@ -60,7 +60,7 @@ namespace PettingZoo.UI.Connection
|
||||
public string Password
|
||||
{
|
||||
get => password;
|
||||
set => SetField(ref password, value);
|
||||
set => SetField(ref password, value, delegateCommandsChanged: connectionChangedCommands);
|
||||
}
|
||||
|
||||
|
||||
@ -91,6 +91,13 @@ namespace PettingZoo.UI.Connection
|
||||
}
|
||||
|
||||
|
||||
public bool StorePassword
|
||||
{
|
||||
get => storePassword;
|
||||
set => SetField(ref storePassword, value, delegateCommandsChanged: connectionChangedCommands);
|
||||
}
|
||||
|
||||
|
||||
public ObservableCollection<StoredConnectionSettings> StoredConnections { get; } = new();
|
||||
|
||||
public StoredConnectionSettings? SelectedStoredConnection
|
||||
@ -104,15 +111,21 @@ namespace PettingZoo.UI.Connection
|
||||
if (!SetField(ref selectedStoredConnection, value, delegateCommandsChanged: new [] { deleteCommand }))
|
||||
return;
|
||||
|
||||
DisableCommandsChanged(
|
||||
() =>
|
||||
{
|
||||
Host = value.Host;
|
||||
VirtualHost = value.VirtualHost;
|
||||
Port = value.Port;
|
||||
Username = value.Username;
|
||||
Password = value.Password ?? "";
|
||||
Password = value.Password;
|
||||
StorePassword = value.StorePassword;
|
||||
|
||||
Exchange = value.Exchange;
|
||||
RoutingKey = value.RoutingKey;
|
||||
Subscribe = value.Subscribe;
|
||||
},
|
||||
connectionChangedCommands);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +138,7 @@ namespace PettingZoo.UI.Connection
|
||||
public event EventHandler? OkClick;
|
||||
|
||||
|
||||
public ConnectionViewModel(IConnectionSettingsRepository connectionSettingsRepository, ConnectionSettings defaultSettings)
|
||||
public ConnectionViewModel(IConnectionSettingsRepository connectionSettingsRepository, StoredConnectionSettings defaultSettings)
|
||||
{
|
||||
this.connectionSettingsRepository = connectionSettingsRepository;
|
||||
this.defaultSettings = defaultSettings;
|
||||
@ -144,6 +157,7 @@ namespace PettingZoo.UI.Connection
|
||||
var defaultConnection = new StoredConnectionSettings(
|
||||
Guid.Empty,
|
||||
ConnectionWindowStrings.LastUsedDisplayName,
|
||||
defaultSettings.StorePassword,
|
||||
defaultSettings.Host,
|
||||
defaultSettings.VirtualHost,
|
||||
defaultSettings.Port,
|
||||
@ -157,7 +171,7 @@ namespace PettingZoo.UI.Connection
|
||||
|
||||
foreach (var storedConnectionSettings in await connectionSettingsRepository.GetStored())
|
||||
{
|
||||
if (!isStored && storedConnectionSettings.SameParameters(defaultConnection))
|
||||
if (!isStored && storedConnectionSettings.SameParameters(defaultConnection, false))
|
||||
{
|
||||
SelectedStoredConnection = storedConnectionSettings;
|
||||
isStored = true;
|
||||
@ -169,7 +183,7 @@ namespace PettingZoo.UI.Connection
|
||||
if (isStored)
|
||||
{
|
||||
// The last used parameters match a stored connection, insert the "New connection" item with default parameters
|
||||
StoredConnections.Insert(0, new StoredConnectionSettings(Guid.Empty, ConnectionWindowStrings.LastUsedDisplayName, ConnectionSettings.Default));
|
||||
StoredConnections.Insert(0, new StoredConnectionSettings(Guid.Empty, ConnectionWindowStrings.LastUsedDisplayName, true, ConnectionSettings.Default));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -225,7 +239,7 @@ namespace PettingZoo.UI.Connection
|
||||
|
||||
var selectedIndex = StoredConnections.IndexOf(SelectedStoredConnection);
|
||||
|
||||
var updatedStoredConnection = await connectionSettingsRepository.Update(SelectedStoredConnection.Id, SelectedStoredConnection.DisplayName, ToModel());
|
||||
var updatedStoredConnection = await connectionSettingsRepository.Update(SelectedStoredConnection.Id, SelectedStoredConnection.DisplayName, StorePassword, ToModel());
|
||||
|
||||
|
||||
StoredConnections[selectedIndex] = updatedStoredConnection;
|
||||
@ -238,7 +252,10 @@ namespace PettingZoo.UI.Connection
|
||||
return SelectedStoredConnection != null &&
|
||||
SelectedStoredConnection.Id != Guid.Empty &&
|
||||
ValidConnection(false) &&
|
||||
!ToModel().SameParameters(SelectedStoredConnection, false);
|
||||
(
|
||||
!ToModel().SameParameters(SelectedStoredConnection, StorePassword) ||
|
||||
SelectedStoredConnection.StorePassword != StorePassword
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -250,7 +267,7 @@ namespace PettingZoo.UI.Connection
|
||||
if (!ConnectionDisplayNameDialog.Execute(ref displayName))
|
||||
return;
|
||||
|
||||
var storedConnectionSettings = await connectionSettingsRepository.Add(displayName, ToModel());
|
||||
var storedConnectionSettings = await connectionSettingsRepository.Add(displayName, StorePassword, ToModel());
|
||||
|
||||
StoredConnections.Add(storedConnectionSettings);
|
||||
SelectedStoredConnection = storedConnectionSettings;
|
||||
@ -298,7 +315,7 @@ namespace PettingZoo.UI.Connection
|
||||
{
|
||||
public DesignTimeConnectionViewModel() : base(null!, null!)
|
||||
{
|
||||
StoredConnections.Add(new StoredConnectionSettings(Guid.Empty, "Dummy", ConnectionSettings.Default));
|
||||
StoredConnections.Add(new StoredConnectionSettings(Guid.Empty, "Dummy", true, ConnectionSettings.Default));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,18 @@
|
||||
</UniformGrid>
|
||||
|
||||
<ListBox Grid.Row="0" Grid.Column="0" Margin="0 0 8 0" Width="250" ItemsSource="{Binding StoredConnections}" SelectedValue="{Binding SelectedStoredConnection}">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding DisplayName}" />
|
||||
<TextBlock Text="{Binding DisplayName}">
|
||||
<TextBlock.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.OkCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}" />
|
||||
</TextBlock.InputBindings>
|
||||
</TextBlock>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
@ -60,7 +69,8 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="8"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="24"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
@ -79,14 +89,15 @@
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="4" Content="{x:Static connection:ConnectionWindowStrings.LabelPassword}"/>
|
||||
<PasswordBox Grid.Column="1" Grid.Row="4" ui:PasswordBoxAssistant.BindPassword="true" ui:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
|
||||
<CheckBox Grid.Column="1" Grid.Row="5" Content="{x:Static connection:ConnectionWindowStrings.LabelStorePassword}" IsChecked="{Binding StorePassword}"/>
|
||||
|
||||
<CheckBox Grid.Column="1" Grid.Row="6" Content="{x:Static connection:ConnectionWindowStrings.LabelSubscribe}" IsChecked="{Binding Subscribe}"/>
|
||||
<CheckBox Grid.Column="1" Grid.Row="7" Content="{x:Static connection:ConnectionWindowStrings.LabelSubscribe}" IsChecked="{Binding Subscribe}"/>
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="7" Content="{x:Static connection:ConnectionWindowStrings.LabelExchange}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="7" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
|
||||
<Label Grid.Column="0" Grid.Row="8" Content="{x:Static connection:ConnectionWindowStrings.LabelExchange}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="8" Text="{Binding Exchange, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
|
||||
|
||||
<Label Grid.Column="0" Grid.Row="8" Content="{x:Static connection:ConnectionWindowStrings.LabelRoutingKey}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="8" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
|
||||
<Label Grid.Column="0" Grid.Row="9" Content="{x:Static connection:ConnectionWindowStrings.LabelRoutingKey}"/>
|
||||
<TextBox Grid.Column="1" Grid.Row="9" Text="{Binding RoutingKey, UpdateSourceTrigger=PropertyChanged}" GotFocus="CaretToEnd"/>
|
||||
</ui:GridLayout>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
@ -38,7 +38,7 @@ namespace PettingZoo.UI.Connection
|
||||
return null;
|
||||
|
||||
var newSettings = viewModel.ToModel();
|
||||
await connectionSettingsRepository.StoreLastUsed(newSettings);
|
||||
await connectionSettingsRepository.StoreLastUsed(viewModel.StorePassword, newSettings);
|
||||
|
||||
return newSettings;
|
||||
}
|
||||
|
@ -168,6 +168,15 @@ namespace PettingZoo.UI.Connection {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Store password.
|
||||
/// </summary>
|
||||
public static string LabelStorePassword {
|
||||
get {
|
||||
return ResourceManager.GetString("LabelStorePassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Subscribe.
|
||||
/// </summary>
|
||||
|
@ -153,6 +153,9 @@
|
||||
<data name="LabelRoutingKey" xml:space="preserve">
|
||||
<value>Routing key</value>
|
||||
</data>
|
||||
<data name="LabelStorePassword" xml:space="preserve">
|
||||
<value>Store password</value>
|
||||
</data>
|
||||
<data name="LabelSubscribe" xml:space="preserve">
|
||||
<value>Subscribe</value>
|
||||
</data>
|
||||
|
Loading…
Reference in New Issue
Block a user