mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Refactor DataService for nullability. Need to refactor calling services next.
This commit is contained in:
parent
e5470f9d5f
commit
b500f7e9e0
@ -107,7 +107,30 @@ public class ScriptExecutor : IScriptExecutor
|
||||
var url = $"{connectionInfo.Host}/API/SavedScripts/{savedScriptId}";
|
||||
using var hc = new HttpClient();
|
||||
hc.DefaultRequestHeaders.Add("Authorization", authToken);
|
||||
var savedScript = await hc.GetFromJsonAsync<SavedScript>(url);
|
||||
var response = await hc.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_logger.LogWarning("Failed to get saved script. Status Code: {responseStatusCode}", response.StatusCode);
|
||||
return;
|
||||
}
|
||||
|
||||
var savedScript = await response.Content.ReadFromJsonAsync<SavedScript>();
|
||||
|
||||
if (savedScript is null)
|
||||
{
|
||||
_logger.LogWarning("Failed to deserialize saved script.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(savedScript.Content))
|
||||
{
|
||||
_logger.LogWarning("Script content is empty. Aborting script run.");
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await ExecuteScriptContent(savedScript.Shell,
|
||||
Guid.NewGuid().ToString(),
|
||||
|
||||
6
Directory.Build.props
Normal file
6
Directory.Build.props
Normal file
@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@ -39,6 +39,7 @@ EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2176596E-12DA-4766-96E1-4D23EA7DBEC8}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
Directory.Build.props = Directory.Build.props
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
EndProjectSection
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
@ -40,7 +41,10 @@ public class AlertsController : ControllerBase
|
||||
[HttpPost("Create")]
|
||||
public async Task<IActionResult> Create(AlertOptions alertOptions)
|
||||
{
|
||||
_ = Request.Headers.TryGetValue("OrganizationID", out var orgId);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Alert created. Alert Options: {options}", JsonSerializer.Serialize(alertOptions));
|
||||
|
||||
@ -48,7 +52,7 @@ public class AlertsController : ControllerBase
|
||||
{
|
||||
try
|
||||
{
|
||||
await _dataService.AddAlert(alertOptions.AlertDeviceID, orgId.ToString(), alertOptions.AlertMessage);
|
||||
await _dataService.AddAlert(alertOptions.AlertDeviceID, orgId, alertOptions.AlertMessage);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -105,11 +109,14 @@ public class AlertsController : ControllerBase
|
||||
[HttpDelete("Delete/{alertID}")]
|
||||
public async Task<IActionResult> Delete(string alertID)
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
var alert = await _dataService.GetAlert(alertID);
|
||||
|
||||
if (alert?.OrganizationID == orgID)
|
||||
if (alert?.OrganizationID == orgId)
|
||||
{
|
||||
await _dataService.DeleteAlert(alert);
|
||||
|
||||
@ -122,7 +129,10 @@ public class AlertsController : ControllerBase
|
||||
[HttpDelete("DeleteAll")]
|
||||
public async Task<IActionResult> DeleteAll()
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgId);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
|
||||
@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Models;
|
||||
@ -117,8 +118,11 @@ public class ClientDownloadsController : ControllerBase
|
||||
[HttpGet("{platformID}")]
|
||||
public async Task<IActionResult> GetInstaller(string platformID)
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
return await GetInstallFile(orgID, platformID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
return await GetInstallFile(orgId, platformID);
|
||||
}
|
||||
|
||||
[HttpGet("{organizationID}/{platformID}")]
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -26,28 +28,37 @@ public class DevicesController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IEnumerable<Device> Get()
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Array.Empty<Device>();
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated)
|
||||
if (User.Identity?.IsAuthenticated == true &&
|
||||
!string.IsNullOrWhiteSpace(User.Identity.Name))
|
||||
{
|
||||
return DataService.GetDevicesForUser(User.Identity.Name);
|
||||
}
|
||||
|
||||
return DataService.GetAllDevices(orgID);
|
||||
// Authorized with API key. Return all.
|
||||
return DataService.GetAllDevices(orgId);
|
||||
}
|
||||
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
[HttpGet("{id}")]
|
||||
public Device Get(string id)
|
||||
public ActionResult<Device> Get(string id)
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
var device = DataService.GetDevice(orgID, id);
|
||||
var device = DataService.GetDevice(orgId, id);
|
||||
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
if (User.Identity?.IsAuthenticated == true &&
|
||||
!string.IsNullOrWhiteSpace(User.Identity.Name) &&
|
||||
!DataService.DoesUserHaveAccessToDevice(id, DataService.GetUserByNameWithOrg(User.Identity.Name)))
|
||||
{
|
||||
return null;
|
||||
return Unauthorized();
|
||||
}
|
||||
return device;
|
||||
}
|
||||
@ -58,20 +69,31 @@ public class DevicesController : ControllerBase
|
||||
[FromBody] DeviceSetupOptions deviceOptions,
|
||||
[FromHeader] string organizationId)
|
||||
{
|
||||
if (deviceOptions == null ||
|
||||
if (string.IsNullOrWhiteSpace(deviceOptions?.DeviceID) ||
|
||||
string.IsNullOrWhiteSpace(organizationId))
|
||||
{
|
||||
return BadRequest("DeviceOptions and OrganizationId are required.");
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.DoesUserHaveAccessToDevice(deviceOptions.DeviceID, DataService.GetUserByNameWithOrg(User.Identity.Name)))
|
||||
if (string.IsNullOrWhiteSpace(User.Identity?.Name))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var user = DataService.GetUserByNameWithOrg(User.Identity.Name);
|
||||
if (user is null)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (User.Identity?.IsAuthenticated == true &&
|
||||
!DataService.DoesUserHaveAccessToDevice(deviceOptions.DeviceID, user))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var device = await DataService.UpdateDevice(deviceOptions, organizationId);
|
||||
if (device == null)
|
||||
if (device is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Org.BouncyCastle.Crypto.Agreement;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.ViewModels;
|
||||
@ -37,7 +38,14 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult ChangeIsAdmin(string userID, [FromBody] bool isAdmin)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
if (User.Identity is null ||
|
||||
User.Identity.IsAuthenticated == false ||
|
||||
string.IsNullOrEmpty(User.Identity.Name))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated == true &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
{
|
||||
return Unauthorized();
|
||||
@ -49,25 +57,33 @@ public class OrganizationManagementController : ControllerBase
|
||||
return BadRequest("You can't remove administrator rights from yourself.");
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
DataService.ChangeUserIsAdmin(orgID, userID, isAdmin);
|
||||
return Ok("ok");
|
||||
DataService.ChangeUserIsAdmin(orgId, userID, isAdmin);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteInvite/{inviteID}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult DeleteInvite(string inviteID)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (User.Identity is null ||
|
||||
User.Identity.IsAuthenticated == false ||
|
||||
string.IsNullOrEmpty(User.Identity.Name))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.DeleteInvite(orgID, inviteID);
|
||||
return Ok("ok");
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
}
|
||||
|
||||
DataService.DeleteInvite(orgId, inviteID);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteUser/{userID}")]
|
||||
@ -88,7 +104,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
await DataService.DeleteUser(orgID, userID);
|
||||
return Ok("ok");
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("DeviceGroup")]
|
||||
@ -117,7 +133,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.DeleteDeviceGroup(orgID, deviceGroupID.Trim());
|
||||
return Ok("ok");
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("DeviceGroup")]
|
||||
@ -240,7 +256,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.UpdateOrganizationName(orgID, organizationName.Trim());
|
||||
return Ok("ok");
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut("SetDefault")]
|
||||
@ -255,7 +271,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.SetIsDefaultOrganization(orgID, isDefault);
|
||||
return Ok("ok");
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPost("SendInvite")]
|
||||
@ -272,30 +288,41 @@ public class OrganizationManagementController : ControllerBase
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("Organization ID is required.");
|
||||
}
|
||||
|
||||
|
||||
if (!DataService.DoesUserExist(invite.InvitedUser))
|
||||
{
|
||||
var result = await DataService.CreateUser(invite.InvitedUser, invite.IsAdmin, orgID);
|
||||
if (result)
|
||||
{
|
||||
var user = await UserManager.FindByEmailAsync(invite.InvitedUser);
|
||||
|
||||
await UserManager.ConfirmEmailAsync(user, await UserManager.GenerateEmailConfirmationTokenAsync(user));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
var result = await DataService.CreateUser(invite.InvitedUser, invite.IsAdmin, orgId);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest("There was an issue creating the new account.");
|
||||
}
|
||||
|
||||
var user = await UserManager.FindByEmailAsync(invite.InvitedUser);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return BadRequest("User not found.");
|
||||
}
|
||||
|
||||
await UserManager.ConfirmEmailAsync(user, await UserManager.GenerateEmailConfirmationTokenAsync(user));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newInvite = DataService.AddInvite(orgID, invite);
|
||||
var newInvite = await DataService.AddInvite(orgId, invite);
|
||||
|
||||
var inviteURL = $"{Request.Scheme}://{Request.Host}/Invite?id={newInvite.ID}";
|
||||
if (!newInvite.IsSuccess)
|
||||
{
|
||||
return BadRequest(newInvite.Reason);
|
||||
}
|
||||
|
||||
var inviteURL = $"{Request.Scheme}://{Request.Host}/Invite?id={newInvite.Value.ID}";
|
||||
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
|
||||
$@"<img src='{Request.Scheme}://{Request.Host}/images/Remotely_Logo.png'/>
|
||||
<br><br>
|
||||
@ -304,7 +331,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
You've been invited to join an organization in Remotely.
|
||||
<br><br>
|
||||
You can join the organization by <a href='{HtmlEncoder.Default.Encode(inviteURL)}'>clicking here</a>.",
|
||||
orgID);
|
||||
orgId);
|
||||
|
||||
if (!emailResult)
|
||||
{
|
||||
|
||||
@ -45,39 +45,40 @@ public class RegisterModel : PageModel
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
public int OrganizationCount { get; set; }
|
||||
public string ReturnUrl { get; set; }
|
||||
public InputModel Input { get; set; } = null!;
|
||||
|
||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
||||
public int OrganizationCount { get; set; }
|
||||
public string? ReturnUrl { get; set; }
|
||||
|
||||
public IList<AuthenticationScheme>? ExternalLogins { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
public required string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
public required string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
public string? ConfirmPassword { get; set; }
|
||||
}
|
||||
|
||||
public async Task OnGetAsync(string returnUrl = null)
|
||||
public async Task OnGetAsync(string? returnUrl = null)
|
||||
{
|
||||
OrganizationCount = _dataService.GetOrganizationCount();
|
||||
ReturnUrl = returnUrl;
|
||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
|
||||
{
|
||||
var organizationCount = _dataService.GetOrganizationCount();
|
||||
if (_appConfig.MaxOrganizationCount > 0 && organizationCount >= _appConfig.MaxOrganizationCount)
|
||||
@ -110,15 +111,20 @@ public class RegisterModel : PageModel
|
||||
var callbackUrl = Url.Page(
|
||||
"/Account/ConfirmEmail",
|
||||
pageHandler: null,
|
||||
values: new { area = "Identity", userId = user.Id, code = code, returnUrl = returnUrl },
|
||||
values: new { area = "Identity", userId = user.Id, code, returnUrl },
|
||||
protocol: Request.Scheme);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(callbackUrl))
|
||||
{
|
||||
return BadRequest($"{nameof(callbackUrl)} cannot be empty.");
|
||||
}
|
||||
|
||||
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
|
||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
||||
|
||||
if (_userManager.Options.SignIn.RequireConfirmedAccount)
|
||||
{
|
||||
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
|
||||
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl });
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@ -29,7 +29,11 @@ public partial class ScriptSchedules : AuthComponentBase
|
||||
|
||||
private SavedScript _selectedScript;
|
||||
|
||||
private ScriptSchedule _selectedSchedule = new() { StartAt = Time.Now };
|
||||
private ScriptSchedule _selectedSchedule = new()
|
||||
{
|
||||
Name = string.Empty,
|
||||
StartAt = Time.Now
|
||||
};
|
||||
|
||||
[CascadingParameter]
|
||||
private ScriptsPage ParentPage { get; set; }
|
||||
@ -67,9 +71,13 @@ public partial class ScriptSchedules : AuthComponentBase
|
||||
{
|
||||
_selectedScript = new()
|
||||
{
|
||||
Name = "Test Script"
|
||||
Name = string.Empty
|
||||
};
|
||||
_selectedSchedule = new()
|
||||
{
|
||||
Name = string.Empty,
|
||||
StartAt = Time.Now
|
||||
};
|
||||
_selectedSchedule = new() { StartAt = Time.Now };
|
||||
_selectedDeviceGroups.Clear();
|
||||
_selectedDevices.Clear();
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ namespace Remotely.Server.Data;
|
||||
public class AppDb : IdentityDbContext
|
||||
{
|
||||
private static readonly ValueComparer<string[]> _stringArrayComparer = new(
|
||||
(a, b) => a.SequenceEqual(b),
|
||||
(a, b) => (a ?? Array.Empty<string>()).SequenceEqual(b ?? Array.Empty<string>()),
|
||||
c => c.Aggregate(0, (a, b) => HashCode.Combine(a, b.GetHashCode())),
|
||||
c => c.ToArray());
|
||||
|
||||
@ -131,9 +131,6 @@ public class AppDb : IdentityDbContext
|
||||
builder.Entity<Device>()
|
||||
.HasMany(x => x.ScriptRuns)
|
||||
.WithMany(x => x.Devices);
|
||||
builder.Entity<Device>()
|
||||
.HasMany(x => x.ScriptRunsCompleted)
|
||||
.WithMany(x => x.DevicesCompleted);
|
||||
builder.Entity<Device>()
|
||||
.HasMany(x => x.ScriptSchedules)
|
||||
.WithMany(x => x.Devices);
|
||||
@ -153,9 +150,6 @@ public class AppDb : IdentityDbContext
|
||||
builder.Entity<ScriptRun>()
|
||||
.HasMany(x => x.Devices)
|
||||
.WithMany(x => x.ScriptRuns);
|
||||
builder.Entity<ScriptRun>()
|
||||
.HasMany(x => x.DevicesCompleted)
|
||||
.WithMany(x => x.ScriptRunsCompleted);
|
||||
|
||||
builder.Entity<ScriptResult>()
|
||||
.Property(x => x.ErrorOutput)
|
||||
|
||||
28
Server/Extensions/IHeaderDictionaryExtensions.cs
Normal file
28
Server/Extensions/IHeaderDictionaryExtensions.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Remotely.Server.Extensions;
|
||||
|
||||
public static class IHeaderDictionaryExtensions
|
||||
{
|
||||
public static bool TryGetOrganizationId(
|
||||
this IHeaderDictionary headers,
|
||||
[NotNullWhen(true)] out string? organizationId)
|
||||
{
|
||||
organizationId = null;
|
||||
|
||||
if (!headers.TryGetValue("OrganizationId", out var orgId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
organizationId = $"{orgId}";
|
||||
|
||||
if (string.IsNullOrWhiteSpace(organizationId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -24,30 +24,30 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
private readonly List<InviteLink> _invites = new();
|
||||
private readonly List<RemotelyUser> _orgUsers = new();
|
||||
private bool _inviteAsAdmin;
|
||||
private string _inviteEmail;
|
||||
private string _newDeviceGroupName;
|
||||
private Organization _organization;
|
||||
private string _selectedDeviceGroupId;
|
||||
private string _inviteEmail = string.Empty;
|
||||
private string _newDeviceGroupName = string.Empty;
|
||||
private Organization? _organization;
|
||||
private string _selectedDeviceGroupId = string.Empty;
|
||||
|
||||
[Inject]
|
||||
private IDataService DataService { get; set; }
|
||||
private IDataService DataService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IEmailSenderEx EmailSender { get; set; }
|
||||
private IEmailSenderEx EmailSender { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IJsInterop JsInterop { get; set; }
|
||||
private IJsInterop JsInterop { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IModalService ModalService { get; set; }
|
||||
private IModalService ModalService { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavManager { get; set; }
|
||||
private NavigationManager NavManager { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private IToastService ToastService { get; set; }
|
||||
private IToastService ToastService { get; set; } = null!;
|
||||
[Inject]
|
||||
private UserManager<RemotelyUser> UserManager { get; set; }
|
||||
private UserManager<RemotelyUser> UserManager { get; set; } = null!;
|
||||
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
@ -88,12 +88,21 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
|
||||
private void DefaultOrgCheckChanged(ChangeEventArgs args)
|
||||
{
|
||||
if (_organization is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!User.IsServerAdmin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isDefault = (bool)args.Value;
|
||||
if (args.Value is not bool isDefault)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DataService.SetIsDefaultOrganization(_organization.ID, isDefault);
|
||||
ToastService.ShowToast("Default organization set.");
|
||||
}
|
||||
@ -194,6 +203,11 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
}
|
||||
private void OrganizationNameChanged(ChangeEventArgs args)
|
||||
{
|
||||
if (_organization is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!User.IsAdministrator)
|
||||
{
|
||||
return;
|
||||
@ -290,9 +304,15 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
InvitedUser = _inviteEmail,
|
||||
IsAdmin = _inviteAsAdmin
|
||||
};
|
||||
var newInvite = DataService.AddInvite(User.OrganizationID, invite);
|
||||
var newInvite = await DataService.AddInvite(User.OrganizationID, invite);
|
||||
|
||||
var inviteURL = $"{NavManager.BaseUri}Invite?id={newInvite.ID}";
|
||||
if (!newInvite.IsSuccess)
|
||||
{
|
||||
ToastService.ShowToast($"Failed to create invite. {newInvite.Reason}", classString: "bg-danger");
|
||||
return;
|
||||
}
|
||||
|
||||
var inviteURL = $"{NavManager.BaseUri}Invite?id={newInvite.Value.ID}";
|
||||
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
|
||||
$@"<img src='{NavManager.BaseUri}images/Remotely_Logo.png'/>
|
||||
<br><br>
|
||||
@ -308,7 +328,7 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
|
||||
_inviteAsAdmin = false;
|
||||
_inviteEmail = string.Empty;
|
||||
_invites.Add(newInvite);
|
||||
_invites.Add(newInvite.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -324,7 +344,11 @@ public partial class ManageOrganization : AuthComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
var isAdmin = (bool)args.Value;
|
||||
if (args.Value is not bool isAdmin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DataService.ChangeUserIsAdmin(User.OrganizationID, orgUser.Id, isAdmin);
|
||||
ToastService.ShowToast("Administrator value set.");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -76,17 +76,15 @@ public class Device
|
||||
public int ProcessorCount { get; set; }
|
||||
|
||||
public string? PublicIP { get; set; }
|
||||
[JsonIgnore]
|
||||
public List<ScriptResult>? ScriptResults { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptRun>? ScriptRuns { get; set; }
|
||||
public List<ScriptResult> ScriptResults { get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptRun>? ScriptRunsCompleted { get; set; }
|
||||
public List<ScriptRun> ScriptRuns { get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptSchedule>? ScriptSchedules { get; set; }
|
||||
public List<ScriptSchedule> ScriptSchedules { get; set; } = new();
|
||||
|
||||
public string? ServerVerificationToken { get; set; }
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ public class DeviceGroup
|
||||
public string ID { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Device>? Devices { get; set; }
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public Organization? Organization { get; set; }
|
||||
@ -24,7 +24,7 @@ public class DeviceGroup
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public List<RemotelyUser>? Users { get; set; }
|
||||
public List<RemotelyUser> Users { get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptSchedule>? ScriptSchedules { get; set; }
|
||||
|
||||
@ -9,34 +9,34 @@ namespace Remotely.Shared.Models;
|
||||
|
||||
public class Organization
|
||||
{
|
||||
public ICollection<Alert>? Alerts { get; set; }
|
||||
public ICollection<Alert> Alerts { get; set; } = new List<Alert>();
|
||||
|
||||
public ICollection<ApiToken>? ApiTokens { get; set; }
|
||||
public ICollection<ApiToken> ApiTokens { get; set; } = new List<ApiToken>();
|
||||
|
||||
public BrandingInfo? BrandingInfo { get; set; }
|
||||
|
||||
public ICollection<ScriptResult>? ScriptResults { get; set; }
|
||||
public ICollection<ScriptResult> ScriptResults { get; set; } = new List<ScriptResult>();
|
||||
|
||||
public ICollection<ScriptRun>? ScriptRuns { get; set; }
|
||||
public ICollection<SavedScript>? SavedScripts { get; set; }
|
||||
public ICollection<ScriptRun> ScriptRuns { get; set; } = new List<ScriptRun>();
|
||||
public ICollection<SavedScript> SavedScripts { get; set; } = new List<SavedScript>();
|
||||
|
||||
public ICollection<ScriptSchedule>? ScriptSchedules { get; set; }
|
||||
public ICollection<ScriptSchedule> ScriptSchedules { get; set; } = new List<ScriptSchedule>();
|
||||
|
||||
public ICollection<DeviceGroup>? DeviceGroups { get; set; }
|
||||
public ICollection<DeviceGroup> DeviceGroups { get; set; } = new List<DeviceGroup>();
|
||||
|
||||
public ICollection<Device>? Devices { get; set; }
|
||||
public ICollection<Device> Devices { get; set; } = new List<Device>();
|
||||
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string ID { get; set; } = null!;
|
||||
|
||||
public ICollection<InviteLink>? InviteLinks { get; set; }
|
||||
public ICollection<InviteLink> InviteLinks { get; set; } = new List<InviteLink>();
|
||||
|
||||
public bool IsDefaultOrganization { get; set; }
|
||||
|
||||
[StringLength(25)]
|
||||
public required string OrganizationName { get; set; }
|
||||
|
||||
public ICollection<RemotelyUser>? RemotelyUsers { get; set; }
|
||||
public ICollection<SharedFile>? SharedFiles { get; set; }
|
||||
public ICollection<RemotelyUser> RemotelyUsers { get; set; } = new List<RemotelyUser>();
|
||||
public ICollection<SharedFile> SharedFiles { get; set; } = new List<SharedFile>();
|
||||
}
|
||||
@ -7,9 +7,9 @@ namespace Remotely.Shared.Models;
|
||||
|
||||
public class RemotelyUser : IdentityUser
|
||||
{
|
||||
public ICollection<Alert>? Alerts { get; set; }
|
||||
public ICollection<Alert> Alerts { get; set; } = new List<Alert>();
|
||||
|
||||
public List<DeviceGroup>? DeviceGroups { get; set; }
|
||||
public List<DeviceGroup> DeviceGroups { get; set; } = new();
|
||||
public bool IsAdministrator { get; set; }
|
||||
public bool IsServerAdmin { get; set; }
|
||||
|
||||
@ -18,8 +18,8 @@ public class RemotelyUser : IdentityUser
|
||||
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
|
||||
public List<SavedScript>? SavedScripts { get; set; }
|
||||
public List<ScriptSchedule>? ScriptSchedules { get; set; }
|
||||
public List<SavedScript> SavedScripts { get; set; } = new();
|
||||
public List<ScriptSchedule> ScriptSchedules { get; set; } = new();
|
||||
|
||||
public string? TempPassword { get; set; }
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ public class ScriptResult
|
||||
[IgnoreDataMember]
|
||||
public Organization? Organization { get; set; }
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
|
||||
[JsonConverter(typeof(TimeSpanJsonConverter))]
|
||||
public TimeSpan RunTime { get; set; }
|
||||
|
||||
|
||||
@ -13,23 +13,22 @@ namespace Remotely.Shared.Models;
|
||||
public class ScriptRun
|
||||
{
|
||||
[JsonIgnore]
|
||||
public List<Device> Devices { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Device> DevicesCompleted { get; set; }
|
||||
public List<Device>? Devices { get; set; }
|
||||
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
public string Initiator { get; set; }
|
||||
|
||||
public string? Initiator { get; set; }
|
||||
|
||||
public ScriptInputType InputType { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Organization Organization { get; set; }
|
||||
public Organization? Organization { get; set; }
|
||||
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
|
||||
public string OrganizationID { get; set; }
|
||||
[JsonIgnore]
|
||||
public List<ScriptResult> Results { get; set; }
|
||||
public List<ScriptResult>? Results { get; set; }
|
||||
|
||||
public DateTimeOffset RunAt { get; set; }
|
||||
public bool RunOnNextConnect { get; set; }
|
||||
|
||||
@ -16,15 +16,15 @@ public class ScriptSchedule
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public RemotelyUser Creator { get; set; }
|
||||
public RemotelyUser? Creator { get; set; }
|
||||
|
||||
public string CreatorId { get; set; }
|
||||
public string CreatorId { get; set; } = null!;
|
||||
|
||||
[JsonIgnore]
|
||||
public List<DeviceGroup> DeviceGroups { get; set; }
|
||||
public List<DeviceGroup> DeviceGroups { get; set; } = new();
|
||||
|
||||
[JsonIgnore]
|
||||
public List<Device> Devices { get; set; }
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
@ -33,19 +33,19 @@ public class ScriptSchedule
|
||||
|
||||
public DateTimeOffset? LastRun { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public required string Name { get; set; }
|
||||
|
||||
public DateTimeOffset NextRun { get; set; }
|
||||
|
||||
public DateTimeOffset StartAt { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public Organization Organization { get; set; }
|
||||
public string OrganizationID { get; set; }
|
||||
public Organization? Organization { get; set; }
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
|
||||
public bool RunOnNextConnect { get; set; } = true;
|
||||
public Guid SavedScriptId { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<ScriptRun> ScriptRuns { get; set; }
|
||||
public List<ScriptRun> ScriptRuns { get; set; } = new();
|
||||
}
|
||||
|
||||
@ -8,11 +8,11 @@ public class SharedFile
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string ID { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
public byte[] FileContents { get; set; }
|
||||
public string ID { get; set; } = null!;
|
||||
public string? FileName { get; set; }
|
||||
public string? ContentType { get; set; }
|
||||
public byte[] FileContents { get; set; } = Array.Empty<byte>();
|
||||
public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.Now;
|
||||
public Organization Organization { get; set; }
|
||||
public string OrganizationID { get; set; }
|
||||
public Organization? Organization { get; set; }
|
||||
public string OrganizationID { get; set; } = null!;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
|
||||
<AssemblyName>Remotely_Shared</AssemblyName>
|
||||
<AssemblyName></AssemblyName>
|
||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||
<RootNamespace>Remotely.Shared</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@ -159,8 +159,8 @@ public class DataServiceTests
|
||||
|
||||
var pendingRuns = await _dataService.GetPendingScriptRuns(_testData.Org1Device1.ID);
|
||||
|
||||
Assert.AreEqual(1, pendingRuns.Count);
|
||||
Assert.AreEqual(2, pendingRuns[0].Id);
|
||||
Assert.AreEqual(1, pendingRuns.Count());
|
||||
Assert.AreEqual(2, pendingRuns.First().Id);
|
||||
|
||||
var scriptResult = new ScriptResult()
|
||||
{
|
||||
@ -179,7 +179,7 @@ public class DataServiceTests
|
||||
|
||||
pendingRuns = await _dataService.GetPendingScriptRuns(_testData.Org1Device1.ID);
|
||||
|
||||
Assert.AreEqual(0, pendingRuns.Count);
|
||||
Assert.AreEqual(0, pendingRuns.Count());
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user