Create windows and viewmodels to prompt for remote control access.

This commit is contained in:
Jared 2020-10-02 13:37:18 -07:00 committed by Jared Goodwin
parent af9fe6df81
commit ec36c87be5
15 changed files with 424 additions and 21 deletions

View File

@ -7,6 +7,6 @@ namespace Remotely.Desktop.Core.Interfaces
{
public interface IRemoteControlAccessService
{
Task<bool> PromptForAccess();
Task<bool> PromptForAccess(string requesterName, string organizationName);
}
}

View File

@ -159,13 +159,18 @@ namespace Remotely.Desktop.Core.Services
await DisconnectAllViewers();
});
Connection.On("GetScreenCast", async (string viewerID, string requesterName, bool notifyUser, bool enforceAttendedAccess) =>
Connection.On("GetScreenCast", async (
string viewerID,
string requesterName,
bool notifyUser,
bool enforceAttendedAccess,
string organizationName) =>
{
try
{
if (enforceAttendedAccess)
{
var result = await RemoteControlAccessService.PromptForAccess();
var result = await RemoteControlAccessService.PromptForAccess(requesterName, organizationName);
if (!result)
{
await SendConnectionRequestDenied(viewerID);

View File

@ -1,6 +1,11 @@
using Remotely.Desktop.Core.Interfaces;
using Avalonia.Threading;
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Linux.ViewModels;
using Remotely.Desktop.Linux.Views;
using Remotely.Shared.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -8,9 +13,29 @@ namespace Remotely.Desktop.Linux.Services
{
public class RemoteControlAccessServiceLinux : IRemoteControlAccessService
{
public Task<bool> PromptForAccess()
public async Task<bool> PromptForAccess(string requesterName, string organizationName)
{
throw new NotImplementedException();
var result = await Dispatcher.UIThread.InvokeAsync(async () =>
{
var promptWindow = new PromptForAccessWindow();
var viewModel = promptWindow.DataContext as PromptForAccessWindowViewModel;
if (!string.IsNullOrWhiteSpace(requesterName))
{
viewModel.RequesterName = requesterName;
}
if (!string.IsNullOrWhiteSpace(organizationName))
{
viewModel.OrganizationName = organizationName;
}
promptWindow.Show();
await TaskHelper.DelayUntilAsync(() => !promptWindow.IsVisible, TimeSpan.MaxValue);
return viewModel.PromptResult;
});
return result;
}
}
}

View File

@ -12,9 +12,9 @@ namespace Remotely.Desktop.Linux.ViewModels
{
public class ChatWindowViewModel : ReactiveViewModel
{
private string inputText;
private string organizationName = "your IT provider";
private string senderName = "a technician";
private string _inputText;
private string _organizationName = "your IT provider";
private string _senderName = "a technician";
public ObservableCollection<ChatMessage> ChatMessages { get; } = new ObservableCollection<ChatMessage>();
public string ChatSessionHeader => $"Chat session with {OrganizationName}";
@ -25,8 +25,8 @@ namespace Remotely.Desktop.Linux.ViewModels
});
public string InputText
{
get => inputText;
set => this.RaiseAndSetIfChanged(ref inputText, value);
get => _inputText;
set => this.RaiseAndSetIfChanged(ref _inputText, value);
}
public ICommand MinimizeCommand => new Executor((param) =>
@ -36,10 +36,10 @@ namespace Remotely.Desktop.Linux.ViewModels
public string OrganizationName
{
get => organizationName;
get => _organizationName;
set
{
this.RaiseAndSetIfChanged(ref organizationName, value);
this.RaiseAndSetIfChanged(ref _organizationName, value);
this.RaisePropertyChanged(nameof(ChatSessionHeader));
}
}
@ -48,8 +48,8 @@ namespace Remotely.Desktop.Linux.ViewModels
public string SenderName
{
get => senderName;
set => this.RaiseAndSetIfChanged(ref senderName, value);
get => _senderName;
set => this.RaiseAndSetIfChanged(ref _senderName, value);
}
public async Task SendChatMessage()

View File

@ -0,0 +1,56 @@
using Avalonia.Controls;
using ReactiveUI;
using Remotely.Desktop.Linux.Services;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
namespace Remotely.Desktop.Linux.ViewModels
{
public class PromptForAccessWindowViewModel : ReactiveViewModel
{
private string _organizationName = "your IT provider";
private string _requesterName = "a technician";
public ICommand CloseCommand => new Executor((param) =>
{
(param as Window)?.Close();
});
public ICommand MinimizeCommand => new Executor((param) =>
{
(param as Window).WindowState = WindowState.Minimized;
});
public string OrganizationName
{
get => _organizationName;
set => this.RaiseAndSetIfChanged(ref _organizationName, value);
}
public bool PromptResult { get; set; }
public string RequesterName
{
get => _requesterName;
set => this.RaiseAndSetIfChanged(ref _requesterName, value);
}
public ICommand SetResultNo => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = false;
promptWindow.Close();
}
});
public ICommand SetResultYes => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = true;
promptWindow.Close();
}
});
}
}

View File

@ -38,7 +38,7 @@
<Button Classes="TitlebarButton" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="X" />
<Button Classes="TitlebarButton" Command="{Binding MinimizeCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="_"/>
</DockPanel>
</Border>
</Border>
<TextBlock Grid.Row="1"
Text="{Binding ChatSessionHeader}"

View File

@ -0,0 +1,81 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Remotely.Desktop.Linux.ViewModels;assembly=Remotely_Desktop"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Remotely.Desktop.Linux.Views.PromptForAccessWindow"
HasSystemDecorations="False"
BorderBrush="DimGray"
BorderThickness="1"
Title="Remote Control Request"
MinHeight="200"
MinWidth="250"
Height="275"
Width="450"
Icon="/Assets/favicon.ico">
<Window.DataContext>
<vm:PromptForAccessWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Name="TitleBanner" Height="50" Background="#FF464646">
<DockPanel Margin="10,4,0,0">
<DockPanel>
<Image Height="50" Width="50" Source="/Assets/Remotely_Icon.png" Margin="0,0,10,0"></Image>
<TextBlock Foreground="#FF1D90F1" FontWeight="Bold" FontSize="20" VerticalAlignment="Center">
Remotely Chat
</TextBlock>
</DockPanel>
<Button Classes="TitlebarButton" Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="X" />
<Button Classes="TitlebarButton" Command="{Binding MinimizeCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Content="_"/>
</DockPanel>
</Border>
<StackPanel Grid.Row="1">
<TextBlock Classes="SectionHeader" FontWeight="Bold" FontSize="18" Foreground="DimGray" Margin="10" TextWrapping="Wrap">
A remote control session has been requested.
</TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Classes="SectionHeader"
FontWeight="Bold"
FontSize="18"
Foreground="DimGray"
Margin="10"
TextWrapping="Wrap"
Text="{Binding RequesterName, StringFormat=Would you like to allow {0} }"></TextBlock>
<TextBlock Classes="SectionHeader"
FontWeight="Bold"
FontSize="18"
Foreground="DimGray"
Margin="10"
TextWrapping="Wrap"
Text="{Binding OrganizationName, StringFormat=from {0} to control your computer?}"></TextBlock>
</StackPanel>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Width="60" Height="40" Content="Yes" Margin="10"
Classes="NormalButton"
Command="{Binding SetResultYes}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"></Button>
<Button Width="60" Height="40" Content="No" Margin="10"
Classes="NormalButton"
Command="{Binding SetResultNo}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"></Button>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,39 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
namespace Remotely.Desktop.Linux.Views
{
public class PromptForAccessWindow : Window
{
public PromptForAccessWindow()
{
this.InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
Opened += Window_Opened;
this.FindControl<Border>("TitleBanner").PointerPressed += TitleBanner_PointerPressed;
}
private void Window_Opened(object sender, EventArgs e)
{
Topmost = false;
}
private void TitleBanner_PointerPressed(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == Avalonia.Input.PointerUpdateKind.LeftButtonPressed)
{
BeginMoveDrag(e);
}
}
}
}

View File

@ -1,4 +1,6 @@
using Remotely.Desktop.Core.Interfaces;
using Remotely.Desktop.Win.ViewModels;
using Remotely.Desktop.Win.Views;
using System;
using System.Collections.Generic;
using System.Text;
@ -8,9 +10,26 @@ namespace Remotely.Desktop.Win.Services
{
public class RemoteControlAccessServiceWin : IRemoteControlAccessService
{
public Task<bool> PromptForAccess()
public Task<bool> PromptForAccess(string requesterName, string organizationName)
{
throw new NotImplementedException();
var result = App.Current.Dispatcher.Invoke(() =>
{
var promptWindow = new PromptForAccessWindow();
var viewModel = promptWindow.DataContext as PromptForAccessWindowViewModel;
if (!string.IsNullOrWhiteSpace(requesterName))
{
viewModel.RequesterName = requesterName;
}
if (!string.IsNullOrWhiteSpace(organizationName))
{
viewModel.OrganizationName = organizationName;
}
promptWindow.ShowDialog();
return viewModel.PromptResult;
});
return Task.FromResult(result);
}
}
}

View File

@ -0,0 +1,55 @@
using Remotely.Desktop.Core.ViewModels;
using Remotely.Desktop.Win.Services;
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace Remotely.Desktop.Win.ViewModels
{
public class PromptForAccessWindowViewModel : ViewModelBase
{
private string _organizationName = "your IT provider";
private string _requesterName = "a technician";
public string OrganizationName
{
get => _organizationName;
set
{
_organizationName = value;
FirePropertyChanged(nameof(OrganizationName));
}
}
public bool PromptResult { get; set; }
public string RequesterName
{
get => _requesterName;
set
{
_requesterName = value;
FirePropertyChanged(nameof(RequesterName));
}
}
public ICommand SetResultNo => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = false;
promptWindow.Close();
}
});
public ICommand SetResultYes => new Executor(param =>
{
if (param is Window promptWindow)
{
PromptResult = true;
promptWindow.Close();
}
});
}
}

View File

@ -0,0 +1,76 @@
<Window x:Class="Remotely.Desktop.Win.Views.PromptForAccessWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Remotely.Desktop.Win.Views"
mc:Ignorable="d"
WindowStyle="None"
xmlns:ViewModels="clr-namespace:Remotely.Desktop.Win.ViewModels"
ResizeMode="CanResizeWithGrip"
MouseLeftButtonDown="Window_MouseLeftButtonDown"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
BorderBrush="DimGray"
BorderThickness="1"
Topmost="True"
Title="Remote Control Request"
MinHeight="200"
MinWidth="250"
Height="275"
Width="450"
Icon="/Assets/favicon.ico"
ContentRendered="Window_ContentRendered">
<Window.DataContext>
<ViewModels:PromptForAccessWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border Height="50" Background="#FF464646">
<DockPanel Margin="10,0,0,0">
<DockPanel>
<Image Height="50" Width="50" Source="/Assets/Remotely_Icon.png" Margin="0,0,10,0"></Image>
<TextBlock Foreground="#FF1D90F1" FontWeight="Bold" FontSize="20" VerticalAlignment="Center">
Remote Control Request
</TextBlock>
</DockPanel>
<Button Style="{StaticResource TitlebarButton}" Click="CloseButton_Click" Content="X" />
<Button Style="{StaticResource TitlebarButton}" Click="MinimizeButton_Click" Content="_"/>
</DockPanel>
</Border>
<StackPanel Grid.Row="1">
<TextBlock Style="{StaticResource SectionHeader}" FontWeight="Bold" FontSize="18" Foreground="DimGray" Margin="10" TextWrapping="Wrap">
A remote control session has been requested.
</TextBlock>
<TextBlock Style="{StaticResource SectionHeader}" FontWeight="Bold" FontSize="18" Foreground="DimGray" Margin="10" TextWrapping="Wrap">
<Run>Would you like to allow </Run>
<Run Text="{Binding RequesterName}"></Run>
<Run> from </Run>
<Run Text="{Binding OrganizationName}"></Run>
<Run> to control your computer?</Run>
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Width="60" Height="40" Content="Yes" Margin="10"
Style="{StaticResource NormalButton}"
Command="{Binding SetResultYes}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PromptForAccessWindow}}}"></Button>
<Button Width="60" Height="40" Content="No" Margin="10"
Style="{StaticResource NormalButton}"
Command="{Binding SetResultNo}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PromptForAccessWindow}}}"></Button>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Remotely.Desktop.Win.Views
{
/// <summary>
/// Interaction logic for PromptForAccessWindow.xaml
/// </summary>
public partial class PromptForAccessWindow : Window
{
public PromptForAccessWindow()
{
InitializeComponent();
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Close();
}
private void MinimizeButton_Click(object sender, RoutedEventArgs e)
{
this.WindowState = WindowState.Minimized;
}
private void Window_ContentRendered(object sender, EventArgs e)
{
Topmost = false;
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
}
}

View File

@ -199,11 +199,13 @@ namespace Remotely.Server.Hubs
(Context.User.Identity.IsAuthenticated &&
DataService.DoesUserHaveAccessToDevice(deviceID, Context.UserIdentifier)))
{
var orgName = DataService.GetOrganizationNameById(orgId);
return CasterHubContext.Clients.Client(screenCasterID).SendAsync("GetScreenCast",
Context.ConnectionId,
RequesterName,
AppConfig.RemoteControlNotifyUser,
AppConfig.EnforceAttendedAccess);
AppConfig.EnforceAttendedAccess,
orgName);
}
else
{

View File

@ -21,7 +21,7 @@ namespace Remotely.Server.Services
public string DBProvider => Config["ApplicationOptions:DBProvider"] ?? "SQLite";
public string DefaultPrompt => Config["ApplicationOptions:DefaultPrompt"] ?? "~>";
public bool EnableWindowsEventLog => bool.Parse(Config["ApplicationOptions:EnableWindowsEventLog"]);
public bool EnforceAttendedAccess => bool.Parse(Config["EnforceAttendedAccess"] ?? "false");
public bool EnforceAttendedAccess => bool.Parse(Config["ApplicationOptions:EnforceAttendedAccess"] ?? "false");
public IceServerModel[] IceServers => Config.GetSection("ApplicationOptions:IceServers").Get<IceServerModel[]>() ?? fallbackIceServers;
public string[] KnownProxies => Config.GetSection("ApplicationOptions:KnownProxies").Get<string[]>();
public int MaxConcurrentUpdates => int.Parse(Config["ApplicationOptions:MaxConcurrentUpdates"] ?? "10");

View File

@ -15,7 +15,7 @@
"DBProvider": "SQLite",
"DefaultPrompt": "~>",
"EnableWindowsEventLog": false,
"EnforceAttendedAccess": false,
"EnforceAttendedAccess": false,
"IceServers": [
{
"Url": "stun: stun.l.google.com:19302",