mirror of
https://github.com/immense/Remotely.git
synced 2025-10-26 11:27:15 +00:00
Refactor OrganizationManagementController, ApiAuthorizationFilter, and ExpiringTokenFilter.
This commit is contained in:
parent
b500f7e9e0
commit
8b341562bc
@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Agent.Extensions;
|
||||
using Remotely.Agent.Interfaces;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Enums;
|
||||
using Remotely.Shared.Models;
|
||||
using Remotely.Shared.Services;
|
||||
@ -449,7 +450,7 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
}
|
||||
});
|
||||
|
||||
_hubConnection.On("TransferFileFromBrowserToAgent", async (string transferID, List<string> fileIDs, string requesterID, string authToken) =>
|
||||
_hubConnection.On("TransferFileFromBrowserToAgent", async (string transferID, List<string> fileIDs, string requesterID, string expiringToken) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -467,7 +468,7 @@ public class AgentHubConnection : IAgentHubConnection, IDisposable
|
||||
{
|
||||
var url = $"{_connectionInfo.Host}/API/FileSharing/{fileID}";
|
||||
using var client = _httpFactory.CreateClient();
|
||||
client.DefaultRequestHeaders.Add("Authorization", authToken);
|
||||
client.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
|
||||
using var response = await client.GetAsync(url);
|
||||
|
||||
var filename = response.Content.Headers.ContentDisposition.FileName;
|
||||
|
||||
@ -93,7 +93,7 @@ public class ScriptExecutor : IScriptExecutor
|
||||
int scriptRunId,
|
||||
string initiator,
|
||||
ScriptInputType scriptInputType,
|
||||
string authToken)
|
||||
string expiringToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -106,7 +106,7 @@ public class ScriptExecutor : IScriptExecutor
|
||||
var connectionInfo = _configService.GetConnectionInfo();
|
||||
var url = $"{connectionInfo.Host}/API/SavedScripts/{savedScriptId}";
|
||||
using var hc = new HttpClient();
|
||||
hc.DefaultRequestHeaders.Add("Authorization", authToken);
|
||||
hc.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
|
||||
var response = await hc.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@ -142,7 +142,7 @@ public class ScriptExecutor : IScriptExecutor
|
||||
result.InputType = scriptInputType;
|
||||
result.SavedScriptId = savedScriptId;
|
||||
|
||||
var responseResult = await SendResultsToApi(result, authToken);
|
||||
var responseResult = await SendResultsToApi(result, expiringToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -195,12 +195,12 @@ public class ScriptExecutor : IScriptExecutor
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private async Task<ScriptResult> SendResultsToApi(object result, string authToken)
|
||||
private async Task<ScriptResult> SendResultsToApi(object result, string expiringToken)
|
||||
{
|
||||
var targetURL = _configService.GetConnectionInfo().Host + $"/API/ScriptResults";
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", authToken);
|
||||
httpClient.DefaultRequestHeaders.Add(AppConstants.ExpiringTokenHeaderName, expiringToken);
|
||||
|
||||
using var response = await httpClient.PostAsJsonAsync(targetURL, result);
|
||||
|
||||
|
||||
@ -207,13 +207,13 @@ Remotely has a basic API, which can be browsed at https://remotely.lucency.co/sw
|
||||
|
||||
When accessing the API from the browser on another website, you'll need to set up CORS in appsettings by adding the website origin URL to the TrustedCorsOrigins array. If you're not familiar with how CORS works, I recommend reading up on it before proceeding. For example, if I wanted to create a login form on https://lucency.co that logged into the Remotely API, I'd need to add "https://lucency.co" to the TrustedCorsOrigins.
|
||||
|
||||
The API key and secret must first be combined [ApiKey]:[ApiSecret] and then encoded with Base64 as [EncodedAuhorization]. After that you can add the encoded string to the request's Authorization header in the form "Basic [EncodedAuhorization]"
|
||||
Each request to the API must have a header named "X-Api-Key". The value should be the API key's ID and secret, separated by a colon (i.e. [ApiKey]:[ApiSecret]).
|
||||
|
||||
Below is an example API request:
|
||||
|
||||
POST https://localhost:5001/API/Scripting/ExecuteCommand/PSCore/f2b0a595-5ea8-471b-975f-12e70e0f3497 HTTP/1.1
|
||||
Content-Type: application/json
|
||||
Authorization: 31fb288d-af97-4ce1-ae7b-ceebb98281ac:HLkrKaZGExYvozSPvcACZw9awKkhHnNK
|
||||
X-Api-Key: 31fb288d-af97-4ce1-ae7b-ceebb98281ac:HLkrKaZGExYvozSPvcACZw9awKkhHnNK
|
||||
User-Agent: PostmanRuntime/7.22.0
|
||||
Accept: */*
|
||||
Cache-Control: no-cache
|
||||
@ -284,7 +284,7 @@ Register-ScheduledJob -ScriptBlock {
|
||||
$FreeSpace = $OsDrive.Free / ($OsDrive.Used + $OsDrive.Free)
|
||||
if ($FreeSpace -lt .1) {
|
||||
Invoke-WebRequest -Uri "https://localhost:5001/api/Alerts/Create/" -Method Post -Headers @{
|
||||
Authorization="3e9d8273-1dc1-4303-bd50-7a133e36b9b7:S+82XKZdvg278pSFHWtUklqHENuO5IhH"
|
||||
X-Api-Key="3e9d8273-1dc1-4303-bd50-7a133e36b9b7:S+82XKZdvg278pSFHWtUklqHENuO5IhH"
|
||||
} -Body @"
|
||||
{
|
||||
"AlertDeviceID": "f2b0a595-5ea8-471b-975f-12e70e0f3497",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Immense.RemoteControl.Shared.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Auth;
|
||||
@ -43,7 +44,7 @@ public class AlertsController : ControllerBase
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Alert created. Alert Options: {options}", JsonSerializer.Serialize(alertOptions));
|
||||
@ -111,19 +112,23 @@ public class AlertsController : ControllerBase
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var alert = await _dataService.GetAlert(alertID);
|
||||
|
||||
if (alert?.OrganizationID == orgId)
|
||||
var alertResult = await _dataService.GetAlert(alertID);
|
||||
_logger.LogResult(alertResult);
|
||||
if (!alertResult.IsSuccess)
|
||||
{
|
||||
await _dataService.DeleteAlert(alert);
|
||||
|
||||
return Ok();
|
||||
return BadRequest(alertResult.Reason);
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
if (alertResult.Value.OrganizationID != orgId)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
await _dataService.DeleteAlert(alertResult.Value);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteAll")]
|
||||
@ -131,7 +136,7 @@ public class AlertsController : ControllerBase
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
|
||||
@ -120,7 +120,7 @@ public class ClientDownloadsController : ControllerBase
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
return await GetInstallFile(orgId, platformID);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ public class DevicesController : ControllerBase
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var device = DataService.GetDevice(orgId, id);
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
using MailKit;
|
||||
using Immense.RemoteControl.Desktop.Native.Windows;
|
||||
using Immense.RemoteControl.Shared;
|
||||
using Immense.RemoteControl.Shared.Extensions;
|
||||
using MailKit;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
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;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
@ -20,90 +26,91 @@ namespace Remotely.Server.API;
|
||||
[ApiController]
|
||||
public class OrganizationManagementController : ControllerBase
|
||||
{
|
||||
public OrganizationManagementController(IDataService dataService,
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IEmailSenderEx _emailSender;
|
||||
private readonly ILogger<OrganizationManagementController> _logger;
|
||||
private readonly UserManager<RemotelyUser> _userManager;
|
||||
|
||||
public OrganizationManagementController(
|
||||
UserManager<RemotelyUser> userManager,
|
||||
IEmailSenderEx emailSender)
|
||||
IDataService dataService,
|
||||
IEmailSenderEx emailSender,
|
||||
ILogger<OrganizationManagementController> logger)
|
||||
{
|
||||
DataService = dataService;
|
||||
UserManager = userManager;
|
||||
EmailSender = emailSender;
|
||||
_dataService = dataService;
|
||||
_userManager = userManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private IDataService DataService { get; }
|
||||
private IEmailSenderEx EmailSender { get; }
|
||||
private UserManager<RemotelyUser> UserManager { get; }
|
||||
|
||||
|
||||
[HttpPost("ChangeIsAdmin/{userID}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult ChangeIsAdmin(string userID, [FromBody] bool isAdmin)
|
||||
public async Task<IActionResult> ChangeIsAdmin(string userId, [FromBody] bool isAdmin)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
DataService.GetUserByNameWithOrg(User.Identity.Name).Id == userID)
|
||||
{
|
||||
return BadRequest("You can't remove administrator rights from yourself.");
|
||||
}
|
||||
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
DataService.ChangeUserIsAdmin(orgId, userID, isAdmin);
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
var userResult = await _dataService.GetUserByNameWithOrg($"{User.Identity.Name}");
|
||||
if (userResult.IsSuccess && userResult.Value.Id == userId)
|
||||
{
|
||||
return BadRequest("You can't remove administrator rights from yourself.");
|
||||
}
|
||||
}
|
||||
|
||||
_dataService.ChangeUserIsAdmin(orgId, userId, isAdmin);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteInvite/{inviteID}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult DeleteInvite(string inviteID)
|
||||
public async Task<IActionResult> DeleteInvite(string inviteID)
|
||||
{
|
||||
if (User.Identity is null ||
|
||||
User.Identity.IsAuthenticated == false ||
|
||||
string.IsNullOrEmpty(User.Identity.Name))
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
var result = await _dataService.DeleteInvite(orgId, inviteID);
|
||||
_logger.LogResult(result);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest("OrganizationID is required.");
|
||||
return BadRequest(result.Reason);
|
||||
}
|
||||
|
||||
DataService.DeleteInvite(orgId, inviteID);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteUser/{userID}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> DeleteUser(string userID)
|
||||
public async Task<IActionResult> DeleteUser(string userId)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
DataService.GetUserByNameWithOrg(User.Identity.Name).Id == userID)
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return BadRequest("You can't delete yourself here. You must go to the Personal Data page to delete your own account.");
|
||||
var userResult = await _dataService.GetUserByNameWithOrg($"{User.Identity.Name}");
|
||||
if (userResult.IsSuccess && userResult.Value.Id == userId)
|
||||
{
|
||||
return BadRequest("You can't delete yourself here. You must go to the Personal Data page to delete your own account.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var result = await _dataService.DeleteUser(orgId, userId);
|
||||
_logger.LogResult(result);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest(result.Reason);
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
await DataService.DeleteUser(orgID, userID);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@ -111,28 +118,29 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult DeviceGroup()
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
{
|
||||
return Ok(DataService.GetDeviceGroups(User.Identity.Name));
|
||||
}
|
||||
if (Request.Headers.TryGetValue("OrganizationID", out var orgID))
|
||||
return Ok(DataService.GetDeviceGroupsForOrganization(orgID));
|
||||
return NotFound("Unable to find User or organizationID for device group");
|
||||
}
|
||||
|
||||
[HttpDelete("DeviceGroup")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult DeviceGroup([FromBody] string deviceGroupID)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.DeleteDeviceGroup(orgID, deviceGroupID.Trim());
|
||||
return Ok(_dataService.GetDeviceGroupsForOrganization(orgId));
|
||||
}
|
||||
|
||||
[HttpDelete("DeviceGroup")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> DeviceGroup([FromBody] string deviceGroupId)
|
||||
{
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var result = await _dataService.DeleteDeviceGroup(orgId, deviceGroupId.Trim());
|
||||
_logger.LogResult(result);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest(result.Reason);
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@ -140,8 +148,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> DeviceGroup([FromBody] DeviceGroup deviceGroup)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
@ -151,9 +158,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgId);
|
||||
|
||||
var result = await DataService.AddDeviceGroup($"{orgId}", deviceGroup);
|
||||
var result = await _dataService.AddDeviceGroup(orgId, deviceGroup);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest(result.Reason);
|
||||
@ -165,8 +170,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> DeviceGroupRemoveUser([FromBody] string userID, string groupID)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
@ -176,8 +180,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!await DataService.RemoveUserFromDeviceGroup(orgID, groupID, userID))
|
||||
if (!await _dataService.RemoveUserFromDeviceGroup(orgId, groupID, userID))
|
||||
{
|
||||
return BadRequest("Failed to remove user from group.");
|
||||
}
|
||||
@ -188,8 +191,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult DeviceGroupAddUser([FromBody] string userID, string groupID)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
@ -199,8 +201,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
var result = DataService.AddUserToDeviceGroup(orgID, groupID, userID, out var resultMessage);
|
||||
var result = _dataService.AddUserToDeviceGroup(orgId, groupID, userID, out var resultMessage);
|
||||
if (!result)
|
||||
{
|
||||
return BadRequest(resultMessage);
|
||||
@ -211,24 +212,26 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
[HttpGet("GenerateResetUrl/{userID}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> GenerateResetUrl(string userID)
|
||||
public async Task<IActionResult> GenerateResetUrl(string userId)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
|
||||
var user = await UserManager.FindByIdAsync(userID);
|
||||
if (user is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (user.OrganizationID != orgID)
|
||||
if (user.OrganizationID != orgId)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var code = await UserManager.GeneratePasswordResetTokenAsync(user);
|
||||
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
|
||||
var callbackUrl = Url.Page(
|
||||
"/Account/ResetPassword",
|
||||
@ -242,35 +245,37 @@ public class OrganizationManagementController : ControllerBase
|
||||
|
||||
[HttpPut("Name")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult Name([FromBody] string organizationName)
|
||||
public async Task<IActionResult> Name([FromBody] string organizationName)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (organizationName.Length > 25)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.UpdateOrganizationName(orgID, organizationName.Trim());
|
||||
var result = await _dataService.UpdateOrganizationName(orgId, organizationName.Trim());
|
||||
_logger.LogResult(result);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return BadRequest(result.Reason);
|
||||
}
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpPut("SetDefault")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public IActionResult SetDefault([FromBody] bool isDefault)
|
||||
public async Task<IActionResult> SetDefault([FromBody] bool isDefault)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsServerAdmin)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
DataService.SetIsDefaultOrganization(orgID, isDefault);
|
||||
await _dataService.SetIsDefaultOrganization(orgId, isDefault);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
@ -278,44 +283,39 @@ public class OrganizationManagementController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> SendInvite([FromBody] InviteViewModel invite)
|
||||
{
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!DataService.GetUserByNameWithOrg(User.Identity.Name).IsAdministrator)
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return BadRequest("Organization ID is required.");
|
||||
}
|
||||
|
||||
|
||||
if (!DataService.DoesUserExist(invite.InvitedUser))
|
||||
if (!_dataService.DoesUserExist(invite.InvitedUser))
|
||||
{
|
||||
var result = await DataService.CreateUser(invite.InvitedUser, invite.IsAdmin, orgId);
|
||||
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);
|
||||
var user = await _userManager.FindByEmailAsync(invite.InvitedUser);
|
||||
|
||||
if (user is null)
|
||||
{
|
||||
return BadRequest("User not found.");
|
||||
}
|
||||
|
||||
await UserManager.ConfirmEmailAsync(user, await UserManager.GenerateEmailConfirmationTokenAsync(user));
|
||||
await _userManager.ConfirmEmailAsync(user, await _userManager.GenerateEmailConfirmationTokenAsync(user));
|
||||
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newInvite = await DataService.AddInvite(orgId, invite);
|
||||
var newInvite = await _dataService.AddInvite(orgId, invite);
|
||||
|
||||
if (!newInvite.IsSuccess)
|
||||
{
|
||||
@ -323,7 +323,7 @@ public class OrganizationManagementController : ControllerBase
|
||||
}
|
||||
|
||||
var inviteURL = $"{Request.Scheme}://{Request.Host}/Invite?id={newInvite.Value.ID}";
|
||||
var emailResult = await EmailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
|
||||
var emailResult = await _emailSender.SendEmailAsync(invite.InvitedUser, "Invitation to Organization in Remotely",
|
||||
$@"<img src='{Request.Scheme}://{Request.Host}/images/Remotely_Logo.png'/>
|
||||
<br><br>
|
||||
Hello!
|
||||
|
||||
@ -17,6 +17,7 @@ using Immense.RemoteControl.Server.Abstractions;
|
||||
using Immense.RemoteControl.Shared.Helpers;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Extensions;
|
||||
|
||||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
|
||||
@ -62,8 +63,12 @@ public class RemoteControlController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public async Task<IActionResult> Get(string deviceID)
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
return await InitiateRemoteControl(deviceID, orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return await InitiateRemoteControl(deviceID, orgId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@ -74,11 +79,17 @@ public class RemoteControlController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var orgId = _dataService.GetUserByNameWithOrg(rcRequest.Email)?.OrganizationID;
|
||||
var userResult = await _dataService.GetUserByNameWithOrg(rcRequest.Email);
|
||||
if (!userResult.IsSuccess)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var orgId = userResult.Value.OrganizationID;
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(rcRequest.Email, rcRequest.Password, false, true);
|
||||
if (result.Succeeded &&
|
||||
_dataService.DoesUserHaveAccessToDevice(rcRequest.DeviceID, _dataService.GetUserByNameWithOrg(rcRequest.Email)))
|
||||
_dataService.DoesUserHaveAccessToDevice(rcRequest.DeviceID, userResult.Value))
|
||||
{
|
||||
_logger.LogInformation("API login successful for {rcRequestEmail}.", rcRequest.Email);
|
||||
return await InitiateRemoteControl(rcRequest.DeviceID, orgId);
|
||||
@ -110,12 +121,20 @@ public class RemoteControlController : ControllerBase
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (User.Identity.IsAuthenticated &&
|
||||
!_dataService.DoesUserHaveAccessToDevice(targetDevice.ID, _dataService.GetUserByNameWithOrg(User.Identity.Name)))
|
||||
if (User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
var userResult = await _dataService.GetUserByNameWithOrg($"{User.Identity.Name}");
|
||||
|
||||
if (!userResult.IsSuccess)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
if (!_dataService.DoesUserHaveAccessToDevice(targetDevice.ID, userResult.Value))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
var sessionCount = _remoteControlSessionCache.Sessions
|
||||
.OfType<RemoteControlSessionEx>()
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Remotely.Server.Auth;
|
||||
using Remotely.Server.Extensions;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -28,20 +31,26 @@ public class ScriptResultsController : ControllerBase
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public ActionResult DownloadAll()
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var commandResults = _dataService.GetAllCommandResults(orgID);
|
||||
var commandResults = _dataService.GetAllCommandResults(orgId);
|
||||
var content = System.Text.Json.JsonSerializer.Serialize(commandResults);
|
||||
return File(Encoding.UTF8.GetBytes(content), "application/octet-stream", "ScriptHistory.json");
|
||||
}
|
||||
|
||||
[HttpGet("{scriptId}")]
|
||||
[ServiceFilter(typeof(ApiAuthorizationFilter))]
|
||||
public FileResult DownloadResults(string scriptId)
|
||||
public ActionResult<FileResult> DownloadResults(string scriptId)
|
||||
{
|
||||
Request.Headers.TryGetValue("OrganizationID", out var orgID);
|
||||
if (!Request.Headers.TryGetOrganizationId(out var orgId))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var commandResult = _dataService.GetScriptResult(scriptId, orgID);
|
||||
var commandResult = _dataService.GetScriptResult(scriptId, orgId);
|
||||
var content = System.Text.Json.JsonSerializer.Serialize(commandResult);
|
||||
return File(Encoding.UTF8.GetBytes(content), "application/octet-stream", "ScriptResults.json");
|
||||
}
|
||||
@ -49,40 +58,55 @@ public class ScriptResultsController : ControllerBase
|
||||
|
||||
[HttpPost]
|
||||
[ServiceFilter(typeof(ExpiringTokenFilter))]
|
||||
public async Task<ScriptResult> Post([FromBody] ScriptResult result)
|
||||
public async Task<ActionResult<ScriptResult>> Post([FromBody] ScriptResult result)
|
||||
{
|
||||
_dataService.AddOrUpdateScriptResult(result);
|
||||
|
||||
var errorOut = result.ErrorOutput ?? Array.Empty<string>();
|
||||
|
||||
if (result.HadErrors && result.SavedScriptId.HasValue)
|
||||
{
|
||||
var savedScript = await _dataService.GetSavedScript(result.SavedScriptId.Value);
|
||||
var savedScriptResult = await _dataService.GetSavedScript(result.SavedScriptId.Value);
|
||||
if (!savedScriptResult.IsSuccess)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var savedScript = savedScriptResult.Value;
|
||||
if (savedScript.GenerateAlertOnError)
|
||||
{
|
||||
await _dataService.AddAlert(result.DeviceID,
|
||||
result.OrganizationID,
|
||||
$"Alert triggered while running script {savedScript.Name}.",
|
||||
string.Join("\n", result.ErrorOutput));
|
||||
string.Join("\n", errorOut));
|
||||
}
|
||||
|
||||
if (savedScript.SendEmailOnError)
|
||||
{
|
||||
var device = _dataService.GetDevice(result.DeviceID);
|
||||
var deviceResult = await _dataService.GetDevice(
|
||||
result.DeviceID,
|
||||
query => query.Include(x => x.DeviceGroup));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(device.DeviceGroupID))
|
||||
if (!deviceResult.IsSuccess)
|
||||
{
|
||||
device.DeviceGroup = await _dataService.GetDeviceGroup(device.DeviceGroupID);
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
await _emailSender.SendEmailAsync(savedScript.SendErrorEmailTo,
|
||||
"Script Run Alert",
|
||||
$"An alert was triggered while running script {savedScript.Name} on device {device.DeviceName}. <br /><br />" +
|
||||
$"Device ID: {device.ID} <br /> " +
|
||||
$"Device Name: {device.DeviceName} <br /> " +
|
||||
$"Device Alias: {device.Alias} <br /> " +
|
||||
$"Device Group (if any): {device.DeviceGroup?.Name} <br /> " +
|
||||
var device = deviceResult.Value;
|
||||
|
||||
$"Error Output: <br /> <br /> " +
|
||||
$"{string.Join("<br /> <br />", result.ErrorOutput)}");
|
||||
if (!string.IsNullOrWhiteSpace(savedScript.SendErrorEmailTo))
|
||||
{
|
||||
await _emailSender.SendEmailAsync(savedScript.SendErrorEmailTo,
|
||||
"Script Run Alert",
|
||||
$"An alert was triggered while running script {savedScript.Name} on device {device.DeviceName}. <br /><br />" +
|
||||
$"Device ID: {device.ID} <br /> " +
|
||||
$"Device Name: {device.DeviceName} <br /> " +
|
||||
$"Device Alias: {device.Alias} <br /> " +
|
||||
$"Device Group (if any): {device.DeviceGroup?.Name} <br /> " +
|
||||
|
||||
$"Error Output: <br /> <br /> " +
|
||||
$"{string.Join("<br /> <br />", errorOut)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,72 +1,90 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Immense.RemoteControl.Shared.Extensions;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Auth;
|
||||
|
||||
public class ApiAuthorizationFilter : IAuthorizationFilter
|
||||
public class ApiAuthorizationFilter : IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly ILogger<ApiAuthorizationFilter> _logger;
|
||||
|
||||
public ApiAuthorizationFilter(IDataService dataService)
|
||||
public ApiAuthorizationFilter(
|
||||
IDataService dataService,
|
||||
ILogger<ApiAuthorizationFilter> logger)
|
||||
{
|
||||
_dataService = dataService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
|
||||
if (context.HttpContext.User.Identity.IsAuthenticated)
|
||||
try
|
||||
{
|
||||
var orgID = _dataService.GetUserByNameWithOrg(context.HttpContext.User.Identity.Name)?.OrganizationID;
|
||||
context.HttpContext.Request.Headers["OrganizationID"] = orgID;
|
||||
return;
|
||||
await Authorize(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while authorizing API key.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Authorize(AuthorizationFilterContext context)
|
||||
{
|
||||
var http = context.HttpContext;
|
||||
http.Request.Headers["OrganizationID"] = string.Empty;
|
||||
|
||||
if (http.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
var userResult = await _dataService.GetUserByNameWithOrg($"{http.User.Identity.Name}");
|
||||
if (userResult.IsSuccess && userResult.Value.IsAdministrator)
|
||||
{
|
||||
http.Request.Headers["OrganizationID"] = userResult.Value.OrganizationID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.HttpContext.Request.Headers.TryGetValue("Authorization", out var result))
|
||||
if (http.Request.Headers.TryGetValue(AppConstants.ApiKeyHeaderName, out var apiHeaderValue))
|
||||
{
|
||||
|
||||
var headerComponents = result.ToString().Split(" ");
|
||||
var headerComponents = apiHeaderValue.ToString().Split(":");
|
||||
if (headerComponents.Length < 2)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
http.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
};
|
||||
|
||||
var tokenType = headerComponents[0].Trim();
|
||||
var encodedToken = headerComponents[1].Trim();
|
||||
var keyId = headerComponents[0].Trim();
|
||||
var secret = headerComponents[1].Trim();
|
||||
|
||||
switch (tokenType)
|
||||
var isValid = await _dataService.ValidateApiKey(
|
||||
keyId,
|
||||
secret,
|
||||
http.Request.Path,
|
||||
$"{http.Connection.RemoteIpAddress}");
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
case "Basic":
|
||||
byte[] data = Convert.FromBase64String(encodedToken);
|
||||
string decodedString = Encoding.UTF8.GetString(data);
|
||||
var keyResult = await _dataService.GetApiKey(keyId);
|
||||
|
||||
var authComponents = decodedString.ToString().Split(":");
|
||||
if (authComponents.Length < 2)
|
||||
{
|
||||
context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
};
|
||||
|
||||
var keyId = authComponents[0]?.Trim();
|
||||
var apiSecret = authComponents[1]?.Trim();
|
||||
if (_dataService.ValidateApiKey(keyId, apiSecret, context.HttpContext.Request.Path, context.HttpContext.Connection.RemoteIpAddress.ToString()))
|
||||
{
|
||||
var orgID = _dataService.GetApiKey(keyId)?.OrganizationID;
|
||||
context.HttpContext.Request.Headers["OrganizationID"] = orgID;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
if (keyResult.IsSuccess)
|
||||
{
|
||||
http.Request.Headers["OrganizationID"] = keyResult.Value.OrganizationID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
http.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Remotely.Server.Models;
|
||||
using Remotely.Server.Services;
|
||||
using Remotely.Shared;
|
||||
using Remotely.Shared.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Remotely.Server.Auth;
|
||||
|
||||
public class ExpiringTokenFilter : ActionFilterAttribute, IAuthorizationFilter
|
||||
public class ExpiringTokenFilter : ActionFilterAttribute, IAsyncAuthorizationFilter
|
||||
{
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IExpiringTokenService _expiringTokenService;
|
||||
private readonly ILogger<ExpiringTokenFilter> _logger;
|
||||
|
||||
public ExpiringTokenFilter(IExpiringTokenService expiringTokenService,
|
||||
public ExpiringTokenFilter(
|
||||
IExpiringTokenService expiringTokenService,
|
||||
IDataService dataService,
|
||||
ILogger<ExpiringTokenFilter> logger)
|
||||
{
|
||||
@ -27,41 +26,70 @@ public class ExpiringTokenFilter : ActionFilterAttribute, IAuthorizationFilter
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnAuthorization(AuthorizationFilterContext context)
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (context.HttpContext.User.Identity.IsAuthenticated)
|
||||
try
|
||||
{
|
||||
await Authorize(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error while authorizing expiring token.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Authorize(AuthorizationFilterContext context)
|
||||
{
|
||||
var http = context.HttpContext;
|
||||
|
||||
if (http.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("Authorization", out var authorization))
|
||||
if (http.Request.Headers.TryGetValue(AppConstants.ApiKeyHeaderName, out var apiHeaderValue))
|
||||
{
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
}
|
||||
|
||||
if (authorization.ToString().Contains(":"))
|
||||
{
|
||||
var keyId = authorization.ToString().Split(":")[0]?.Trim();
|
||||
var apiSecret = authorization.ToString().Split(":")[1]?.Trim();
|
||||
|
||||
if (_dataService.ValidateApiKey(keyId, apiSecret, context.HttpContext.Request.Path, context.HttpContext.Connection.RemoteIpAddress.ToString()))
|
||||
var headerComponents = apiHeaderValue.ToString().Split(":");
|
||||
if (headerComponents.Length < 2)
|
||||
{
|
||||
var orgID = _dataService.GetApiKey(keyId)?.OrganizationID;
|
||||
context.HttpContext.Request.Headers["OrganizationID"] = orgID;
|
||||
http.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
context.Result = new UnauthorizedResult();
|
||||
return;
|
||||
};
|
||||
|
||||
var keyId = headerComponents[0].Trim();
|
||||
var secret = headerComponents[1].Trim();
|
||||
|
||||
var isValid = await _dataService.ValidateApiKey(
|
||||
keyId,
|
||||
secret,
|
||||
http.Request.Path,
|
||||
$"{http.Connection.RemoteIpAddress}");
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
var keyResult = await _dataService.GetApiKey(keyId);
|
||||
|
||||
if (keyResult.IsSuccess)
|
||||
{
|
||||
_logger.LogDebug("Expiring token authorized via API key. Key ID: {keyId}.", keyId);
|
||||
http.Request.Headers["OrganizationID"] = keyResult.Value.OrganizationID;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (http.Request.Headers.TryGetValue(AppConstants.ExpiringTokenHeaderName, out var expiringToken))
|
||||
{
|
||||
if (_expiringTokenService.TryGetExpiration(expiringToken.ToString(), out var expiration) &&
|
||||
expiration > Time.Now)
|
||||
{
|
||||
_logger.LogDebug("Expiring token authorized. Token: {token}. Expiration: {expiration}", expiringToken, expiration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_expiringTokenService.TryGetExpiration(authorization.ToString(), out var expiration) &&
|
||||
expiration > DateTimeOffset.Now)
|
||||
{
|
||||
_logger.LogDebug("Expiring token authorized. Token: {token}. Expiration: {expiration}", authorization, expiration);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Expiring token not authorized. Token: {token}.", authorization);
|
||||
_logger.LogDebug("Expiring token not authorization failed.");
|
||||
context.Result = new UnauthorizedResult();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,18 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Remotely.Server.Auth;
|
||||
|
||||
namespace Remotely.Server.Extensions;
|
||||
|
||||
public static class IHeaderDictionaryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, ensures that the user was authorized via <see cref="ApiAuthorizationFilter"/>,
|
||||
/// and is either an Administrator or is using a valid API access token.
|
||||
/// </summary>
|
||||
/// <param name="headers"></param>
|
||||
/// <param name="organizationId"></param>
|
||||
/// <returns></returns>
|
||||
public static bool TryGetOrganizationId(
|
||||
this IHeaderDictionary headers,
|
||||
[NotNullWhen(true)] out string? organizationId)
|
||||
|
||||
@ -93,7 +93,12 @@ else
|
||||
var secret = RandomGenerator.GenerateString(36);
|
||||
var secretHash = new PasswordHasher<string>().HashPassword(string.Empty, secret);
|
||||
|
||||
await DataService.CreateApiToken(Username, _createKeyName, secretHash);
|
||||
var result = await DataService.CreateApiToken(Username, _createKeyName, secretHash);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
ToastService.ShowToast2(result.Reason, ToastType.Error);
|
||||
return;
|
||||
}
|
||||
RefreshData();
|
||||
_alertMessage = "Key created.";
|
||||
_newKeySecret = secret;
|
||||
@ -121,9 +126,9 @@ else
|
||||
{
|
||||
_apiTokens.Clear();
|
||||
_apiTokens.AddRange(DataService.GetAllApiTokens(User.Id));
|
||||
_createKeyName = null;
|
||||
_alertMessage = null;
|
||||
_newKeySecret = null;
|
||||
_createKeyName = string.Empty;
|
||||
_alertMessage = string.Empty;
|
||||
_newKeySecret = string.Empty;
|
||||
}
|
||||
|
||||
|
||||
@ -142,8 +147,8 @@ else
|
||||
{
|
||||
ModalService.ShowModal("Using API Keys", new[]
|
||||
{
|
||||
"API keys should be added to the request header when making API calls. The key should be \"Authorization\", and value should be \"{key-id}:{key-secret}\". Note the colon in between.",
|
||||
"Example: Authorization=e5da1c09-e851-4bd4-a8c1-532144b3f894:7uY6h5zBYm4+90pZVek4lD6ewbQ83nKcDpghBfG00hhZu6Ew"
|
||||
"API keys should be added to the request header when making API calls. The key should be \"X-Api-Key\", and value should be \"{key-id}:{key-secret}\". Note the colon in between.",
|
||||
"Example: X-Api-Key=e5da1c09-e851-4bd4-a8c1-532144b3f894:7uY6h5zBYm4+90pZVek4lD6ewbQ83nKcDpghBfG00hhZu6Ew"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Immense.RemoteControl.Shared;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Remotely.Shared.Models;
|
||||
using System;
|
||||
@ -13,7 +14,7 @@ namespace Remotely.Server.Services;
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<bool> IsAuthenticated();
|
||||
Task<RemotelyUser> GetUser();
|
||||
Task<Result<RemotelyUser>> GetUser();
|
||||
}
|
||||
|
||||
public class AuthService : IAuthService
|
||||
@ -35,15 +36,15 @@ public class AuthService : IAuthService
|
||||
return principal?.User?.Identity?.IsAuthenticated ?? false;
|
||||
}
|
||||
|
||||
public async Task<RemotelyUser> GetUser()
|
||||
public async Task<Result<RemotelyUser>> GetUser()
|
||||
{
|
||||
var principal = await _authProvider.GetAuthenticationStateAsync();
|
||||
|
||||
if (principal?.User?.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
return await _dataService.GetUserAsync(principal.User.Identity.Name);
|
||||
return await _dataService.GetUserAsync($"{principal.User.Identity.Name}");
|
||||
}
|
||||
|
||||
return null;
|
||||
return Result.Fail<RemotelyUser>("Not authenticated.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,7 +116,9 @@ public interface IDataService
|
||||
|
||||
Task<Result<Organization>> GetDefaultOrganization();
|
||||
|
||||
Task<Result<Device>> GetDevice(string deviceId);
|
||||
Task<Result<Device>> GetDevice(
|
||||
string deviceId,
|
||||
Action<IQueryable<Device>>? includesBuilder = null);
|
||||
|
||||
Task<Result<Device>> GetDevice(string orgId, string deviceId);
|
||||
|
||||
@ -1268,11 +1270,18 @@ public class DataService : IDataService
|
||||
return Result.Ok(device);
|
||||
}
|
||||
|
||||
public async Task<Result<Device>> GetDevice(string deviceId)
|
||||
public async Task<Result<Device>> GetDevice(
|
||||
string deviceId,
|
||||
Action<IQueryable<Device>>? includesBuilder = null)
|
||||
{
|
||||
using var dbContext = _appDbFactory.GetContext();
|
||||
|
||||
var device = await dbContext.Devices.FirstOrDefaultAsync(x => x.ID == deviceId);
|
||||
|
||||
var query = dbContext.Devices.AsQueryable();
|
||||
if (includesBuilder is not null)
|
||||
{
|
||||
includesBuilder(query);
|
||||
}
|
||||
var device = await query.FirstOrDefaultAsync(x => x.ID == deviceId);
|
||||
|
||||
if (device is null)
|
||||
{
|
||||
@ -1710,7 +1719,7 @@ public class DataService : IDataService
|
||||
|
||||
public async Task<Result<RemotelyUser>> GetUserByNameWithOrg(string userName)
|
||||
{
|
||||
if (userName == null)
|
||||
if (string.IsNullOrWhiteSpace(userName))
|
||||
{
|
||||
return Result.Fail<RemotelyUser>("Username cannot be empty.");
|
||||
}
|
||||
|
||||
@ -131,29 +131,44 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
|
||||
private RemotelyUser _user;
|
||||
private Organization _organization;
|
||||
private RemotelyUser? _user;
|
||||
private Organization? _organization;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
_user = await AuthService.GetUser();
|
||||
var userResult = await AuthService.GetUser();
|
||||
|
||||
if (!userResult.IsSuccess)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_user = userResult.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_user?.OrganizationID))
|
||||
{
|
||||
_organization = DataService.GetOrganizationById(_user.OrganizationID);
|
||||
var orgResult = await DataService.GetOrganizationById(_user.OrganizationID);
|
||||
if (orgResult.IsSuccess)
|
||||
{
|
||||
_organization = orgResult.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_organization = await DataService.GetDefaultOrganization();
|
||||
var orgResult = await DataService.GetDefaultOrganization();
|
||||
if (orgResult.IsSuccess)
|
||||
{
|
||||
_organization = orgResult.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
private string NavMenuCssClass => collapseNavMenu ? "collapse" : "";
|
||||
|
||||
|
||||
private void ToggleNavMenu()
|
||||
|
||||
@ -13,6 +13,8 @@ public class AppConstants
|
||||
public const int EmbeddedDataBlockLength = 256;
|
||||
public const long MaxUploadFileSize = 100_000_000;
|
||||
public const double ScriptRunExpirationMinutes = 30;
|
||||
public const string ApiKeyHeaderName = "X-Api-Key";
|
||||
public const string ExpiringTokenHeaderName = "X-Expiring-Token";
|
||||
|
||||
#pragma warning disable IDE0230 // Use UTF-8 string literal
|
||||
public static byte[] EmbeddedImmySignature { get; } = new byte[] { 73, 109, 109, 121, 66, 111, 116, 32, 114, 111, 99, 107, 115, 32, 116, 104, 101, 32, 115, 111, 99, 107, 115, 32, 117, 110, 116, 105, 108, 32, 116, 104, 101, 32, 101, 113, 117, 105, 110, 111, 120, 33 };
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit d82d3572b7ae9d1b5091a0b83b0af9ffef663119
|
||||
Subproject commit 4de148bc4f37bf625186155373dd176e6c492207
|
||||
Loading…
Reference in New Issue
Block a user