Merge branch 'scripting_from_api'

This commit is contained in:
Jared Goodwin 2020-02-05 22:45:09 -08:00
commit 0b50826537
16 changed files with 313 additions and 16 deletions

View File

@ -40,7 +40,6 @@ namespace Remotely.Agent.Services
}
break;
}
case "winps":
if (OSUtils.IsWindows)
{
@ -99,6 +98,50 @@ namespace Remotely.Agent.Services
await hubConnection.InvokeAsync("DisplayMessage", "There was an error executing the command. It has been logged on the client device.", "Error executing command.", senderConnectionID);
}
}
public async Task ExecuteCommandFromApi(string mode, string requestID, string command, string commandID, string senderUserName, HubConnection hubConnection)
{
try
{
switch (mode.ToLower())
{
case "pscore":
var psCoreResult = PSCore.GetCurrent(senderUserName).WriteInput(command, commandID);
await SendResultsViaAjax("PSCore", psCoreResult);
break;
case "winps":
if (OSUtils.IsWindows)
{
var result = WindowsPS.GetCurrent(senderUserName).WriteInput(command, commandID);
await SendResultsViaAjax("WinPS", result);
}
break;
case "cmd":
if (OSUtils.IsWindows)
{
var result = CMD.GetCurrent(senderUserName).WriteInput(command, commandID);
await SendResultsViaAjax("CMD", result);
}
break;
case "bash":
if (OSUtils.IsLinux)
{
var result = Bash.GetCurrent(senderUserName).WriteInput(command, commandID);
await SendResultsViaAjax("Bash", result);
}
break;
default:
break;
}
await hubConnection.InvokeAsync("CommandResultViaApi", commandID, requestID);
}
catch (Exception ex)
{
Logger.Write(ex);
}
}
private async Task SendResultsViaAjax(string resultType, object result)
{
var targetURL = ConfigService.GetConnectionInfo().Host + $"/API/Commands/{resultType}";

View File

@ -114,6 +114,17 @@ namespace Remotely.Agent.Services
await CommandExecutor.ExecuteCommand(mode, command, commandID, senderConnectionID, HubConnection);
}));
HubConnection.On("ExecuteCommandFromApi", (async (string mode, string requestID, string command, string commandID, string senderUserName) =>
{
if (!IsServerVerified)
{
Logger.Write($"Command attempted before server was verified. Mode: {mode}. Command: {command}. Sender: {senderUserName}");
Uninstaller.UninstallAgent();
return;
}
await CommandExecutor.ExecuteCommandFromApi(mode, requestID, command, commandID, senderUserName, HubConnection);
}));
HubConnection.On("TransferFiles", async (string transferID, List<string> fileIDs, string requesterID) =>
{
Logger.Write($"File transfer started by {requesterID}.");

View File

@ -211,6 +211,16 @@ namespace Remotely.ScreenCast.Core.Communication
}
});
Connection.On("Disconnect", async (string reason) =>
{
Logger.Write($"Disconnecting caster socket. Reason: {reason}");
foreach (var viewer in conductor.Viewers.Values.ToList())
{
await Connection.InvokeAsync("ViewerDisconnected", viewer.ViewerConnectionID);
viewer.DisconnectRequested = true;
}
});
Connection.On("GetScreenCast", (string viewerID, string requesterName) =>
{
try
@ -302,11 +312,11 @@ namespace Remotely.ScreenCast.Core.Communication
Connection.On("ViewerDisconnected", async (string viewerID) =>
{
await Connection.InvokeAsync("ViewerDisconnected", viewerID);
if (conductor.Viewers.TryGetValue(viewerID, out var viewer))
{
viewer.DisconnectRequested = true;
}
await Connection.InvokeAsync("ViewerDisconnected", viewerID);
conductor.InvokeViewerRemoved(viewerID);
});

View File

@ -10,6 +10,7 @@ using Remotely.Shared.Models;
using Remotely.Server.Data;
using Remotely.Server.Models;
using Remotely.Server.Services;
using Microsoft.AspNetCore.SignalR;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
@ -19,16 +20,24 @@ namespace Remotely.Server.API
[ApiController]
public class LoginController : ControllerBase
{
public LoginController(SignInManager<RemotelyUser> signInManager, DataService dataService, ApplicationConfig appConfig)
public LoginController(SignInManager<RemotelyUser> signInManager,
DataService dataService,
ApplicationConfig appConfig,
IHubContext<RCDeviceSocketHub> rcDeviceHub,
IHubContext<RCBrowserSocketHub> rcBrowserHub)
{
SignInManager = signInManager;
DataService = dataService;
AppConfig = appConfig;
RCDeviceHub = rcDeviceHub;
RCBrowserHub = rcBrowserHub;
}
private SignInManager<RemotelyUser> SignInManager { get; }
private DataService DataService { get; }
public ApplicationConfig AppConfig { get; }
private IHubContext<RCDeviceSocketHub> RCDeviceHub { get; }
private IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
[HttpPost]
public async Task<IActionResult> Post([FromBody]ApiLogin login)
@ -68,6 +77,12 @@ namespace Remotely.Server.API
if (HttpContext?.User?.Identity?.IsAuthenticated == true)
{
orgId = DataService.GetUserByName(HttpContext.User.Identity.Name)?.OrganizationID;
var activeSessions = RCDeviceSocketHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions.ToList())
{
await RCDeviceHub.Clients.Client(session.Value.RCDeviceSocketID).SendAsync("Disconnect", "User logged out.");
await RCBrowserHub.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
}
}
await SignInManager.SignOutAsync();
DataService.WriteEvent($"API logout successful for {HttpContext?.User?.Identity?.Name}.", orgId);

View File

@ -94,19 +94,19 @@ namespace Remotely.Server.API
var stopWatch = Stopwatch.StartNew();
while (!RCDeviceSocketHub.SessionInfoList.Values.Any(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCSocketID)) && stopWatch.Elapsed.TotalSeconds < 5)
while (!RCDeviceSocketHub.SessionInfoList.Values.Any(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCDeviceSocketID)) && stopWatch.Elapsed.TotalSeconds < 5)
{
await Task.Delay(10);
}
if (!RCDeviceSocketHub.SessionInfoList.Values.Any(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y => y.Key != x.RCSocketID)))
if (!RCDeviceSocketHub.SessionInfoList.Values.Any(x => x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y => y.Key != x.RCDeviceSocketID)))
{
return StatusCode(408, "The remote control process failed to start in time on the remote device.");
}
else
{
var rcSession = RCDeviceSocketHub.SessionInfoList.Values.FirstOrDefault(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCSocketID));
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.RCSocketID}&serviceID={targetDevice.Key}&fromApi=true");
var rcSession = RCDeviceSocketHub.SessionInfoList.Values.FirstOrDefault(x=>x.DeviceID == targetDevice.Value.ID && !existingSessions.Any(y=>y.Key != x.RCDeviceSocketID));
return Ok($"{HttpContext.Request.Scheme}://{Request.Host}/RemoteControl?clientID={rcSession.RCDeviceSocketID}&serviceID={targetDevice.Key}&fromApi=true");
}
}
else

View File

@ -0,0 +1,84 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using Remotely.Server.Services;
using Remotely.Shared.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Remotely.Shared.Helpers;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Remotely.Server.API
{
[ApiController]
[Route("api/[controller]")]
public class ScriptingController : ControllerBase
{
public ScriptingController(DataService dataService,
UserManager<RemotelyUser> userManager,
IHubContext<DeviceSocketHub> deviceHub)
{
DataService = dataService;
UserManager = userManager;
DeviceHub = deviceHub;
}
private DataService DataService { get; }
private IHubContext<DeviceSocketHub> DeviceHub { get; }
private UserManager<RemotelyUser> UserManager { get; }
[Authorize]
[HttpPost("[action]/{mode}/{deviceID}")]
public async Task<ActionResult<CommandContext>> ExecuteCommand(string mode, string deviceID)
{
var command = string.Empty;
using (var sr = new StreamReader(Request.Body))
{
command = await sr.ReadToEndAsync();
}
var username = Request.HttpContext.User.Identity.Name;
var user = await UserManager.FindByNameAsync(username);
if (!DataService.DoesUserHaveAccessToDevice(deviceID, user))
{
return Unauthorized();
}
KeyValuePair<string, Device> connection = DeviceSocketHub.ServiceConnections.FirstOrDefault(x =>
x.Value.OrganizationID == user.OrganizationID &&
x.Value.ID == deviceID);
if (string.IsNullOrWhiteSpace(connection.Key))
{
return NotFound();
}
var commandContext = new CommandContext()
{
CommandMode = "PSCore",
CommandText = command,
SenderConnectionID = string.Empty,
SenderUserID = user.Id,
TargetDeviceIDs = new string[] { deviceID },
OrganizationID = user.OrganizationID
};
DataService.AddOrUpdateCommandContext(commandContext);
var requestID = Guid.NewGuid().ToString();
await DeviceHub.Clients.Client(connection.Key).SendAsync("ExecuteCommandFromApi", mode, requestID, command, commandContext.ID, username);
var success = await TaskHelper.DelayUntil(() => DeviceSocketHub.ApiScriptResults.TryGetValue(requestID, out _), TimeSpan.FromSeconds(30));
if (!success)
{
return commandContext;
}
DeviceSocketHub.ApiScriptResults.TryGetValue(requestID, out var commandID);
DeviceSocketHub.ApiScriptResults.Remove(requestID);
DataService.DetachEntity(commandContext);
var result = DataService.GetCommandContext(commandID.ToString(), username);
return result;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Remotely.Server.Data;
using Remotely.Shared.Models;
[assembly: HostingStartup(typeof(Remotely.Server.Areas.Identity.IdentityHostingStartup))]
namespace Remotely.Server.Areas.Identity
{
public class IdentityHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.ConfigureServices((context, services) => {
});
}
}
}

View File

@ -0,0 +1,10 @@
@page
@model LogoutModel
@{
ViewData["Title"] = "Log out";
}
<header>
<h1>@ViewData["Title"]</h1>
<p>You have successfully logged out of the application.</p>
</header>

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Remotely.Server.Services;
using Remotely.Shared.Models;
namespace Remotely.Server.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class LogoutModel : PageModel
{
private readonly ILogger<LogoutModel> _logger;
private readonly SignInManager<RemotelyUser> _signInManager;
public LogoutModel(SignInManager<RemotelyUser> signInManager,
ILogger<LogoutModel> logger,
IHubContext<RCDeviceSocketHub> rcDeviceHub,
IHubContext<RCBrowserSocketHub> rcBrowserHub)
{
_signInManager = signInManager;
_logger = logger;
RCDeviceHub = rcDeviceHub;
RCBrowserHub = rcBrowserHub;
}
private IHubContext<RCDeviceSocketHub> RCDeviceHub { get; }
private IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
public void OnGet()
{
}
public async Task<IActionResult> OnPost(string returnUrl = null)
{
if (HttpContext.User.Identity.IsAuthenticated)
{
var activeSessions = RCDeviceSocketHub.SessionInfoList.Where(x => x.Value.RequesterUserName == HttpContext.User.Identity.Name);
foreach (var session in activeSessions.ToList())
{
await RCDeviceHub.Clients.Client(session.Value.RCDeviceSocketID).SendAsync("Disconnect", "User logged out.");
await RCBrowserHub.Clients.Client(session.Value.RequesterSocketID).SendAsync("ConnectionFailed");
}
}
await _signInManager.SignOutAsync();
_logger.LogInformation("User logged out.");
if (returnUrl != null)
{
return LocalRedirect(returnUrl);
}
else
{
return RedirectToPage();
}
}
}
}

View File

@ -13,8 +13,10 @@ namespace Remotely.Server.Models
public string MachineName { get; set; }
public RemoteControlMode Mode { get; set; }
public string OrganizationID { get; set; }
public string RCSocketID { get; set; }
public string RCDeviceSocketID { get; set; }
public string RequesterName { get; set; }
public string RequesterSocketID { get; set; }
public string RequesterUserName { get; set; }
public string ServiceID { get; set; }
public DateTime StartTime { get; set; }
}

View File

@ -102,8 +102,7 @@ namespace Remotely.Server.Services
return Task.CompletedTask;
}
public override async Task OnConnectedAsync()
public override async Task OnConnectedAsync()
{
RemotelyUser = DataService.GetUserByID(Context.UserIdentifier);
if (await IsConnectionValid() == false)

View File

@ -227,6 +227,12 @@ namespace Remotely.Server.Services
RemotelyContext.SaveChanges();
}
public void DetachEntity(object entity)
{
RemotelyContext.Entry(entity).State = EntityState.Detached;
}
public void DeviceDisconnected(string deviceID)
{
var device = RemotelyContext.Devices.Find(deviceID);

View File

@ -9,13 +9,15 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Internal;
namespace Remotely.Server.Services
{
public class DeviceSocketHub : Hub
{
public DeviceSocketHub(DataService dataService,
IHubContext<BrowserSocketHub> browserHub,
public DeviceSocketHub(DataService dataService,
IHubContext<BrowserSocketHub> browserHub,
IHubContext<RCBrowserSocketHub> rcBrowserHub)
{
DataService = dataService;
@ -23,7 +25,8 @@ namespace Remotely.Server.Services
RCBrowserHub = rcBrowserHub;
}
public static ConcurrentDictionary<string, Device> ServiceConnections { get; } = new ConcurrentDictionary<string, Device>();
public static ConcurrentDictionary<string, Device> ServiceConnections { get; } = new ConcurrentDictionary<string, Device>();
public static IMemoryCache ApiScriptResults { get; } = new MemoryCache(new MemoryCacheOptions());
public IHubContext<RCBrowserSocketHub> RCBrowserHub { get; }
private IHubContext<BrowserSocketHub> BrowserHub { get; }
private DataService DataService { get; }
@ -44,6 +47,7 @@ namespace Remotely.Server.Services
var commandContext = DataService.GetCommandContext(commandID);
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("BashResultViaAjax", commandID, Device.ID);
}
public Task Chat(string message, string senderConnectionID)
{
return BrowserHub.Clients.Client(senderConnectionID).SendAsync("Chat", Device.DeviceName, message);
@ -55,7 +59,7 @@ namespace Remotely.Server.Services
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("CMDResultViaAjax", commandID, Device.ID);
}
public Task CommandResult(GenericCommandResult result)
public Task CommandResult(GenericCommandResult result)
{
result.DeviceID = Device.ID;
var commandContext = DataService.GetCommandContext(result.CommandContextID);
@ -64,6 +68,11 @@ namespace Remotely.Server.Services
return BrowserHub.Clients.Client(commandContext.SenderConnectionID).SendAsync("CommandResult", result);
}
public void CommandResultViaApi(string commandID, string requestID)
{
ApiScriptResults.Set(requestID, commandID, DateTimeOffset.Now.AddHours(1));
}
public Task DeviceCameOnline(Device device)
{
try

View File

@ -166,7 +166,7 @@ namespace Remotely.Server.Services
return Clients.Caller.SendAsync("SessionIDNotFound");
}
screenCasterID = RCDeviceSocketHub.SessionInfoList.First(x => x.Value.AttendedSessionID == screenCasterID).Value.RCSocketID;
screenCasterID = RCDeviceSocketHub.SessionInfoList.First(x => x.Value.AttendedSessionID == screenCasterID).Value.RCDeviceSocketID;
}
RCDeviceSocketHub.SessionInfoList.TryGetValue(screenCasterID, out var sessionInfo);
@ -188,6 +188,8 @@ namespace Remotely.Server.Services
return Task.CompletedTask;
}
sessionInfo.OrganizationID = orgId;
sessionInfo.RequesterUserName = Context.User.Identity.Name;
sessionInfo.RequesterSocketID = Context.ConnectionId;
}
DataService.WriteEvent(new EventLog()

View File

@ -121,7 +121,7 @@ namespace Remotely.Server.Services
{
SessionInfo = new RCSessionInfo()
{
RCSocketID = Context.ConnectionId,
RCDeviceSocketID = Context.ConnectionId,
StartTime = DateTime.Now
};
SessionInfoList.AddOrUpdate(Context.ConnectionId, SessionInfo, (id, si) => SessionInfo);

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
namespace Remotely.Shared.Helpers
{
public static class TaskHelper
{
public static async Task<bool> DelayUntil(Func<bool> condition, TimeSpan timeout, int pollingMs = 10)
{
var sw = Stopwatch.StartNew();
while (!condition() && sw.Elapsed < timeout)
{
await Task.Delay(pollingMs);
}
return condition();
}
}
}