Skip to content

Commit

Permalink
added terminal support to cloud apps
Browse files Browse the repository at this point in the history
  • Loading branch information
reven committed May 19, 2023
1 parent 163ee33 commit f8ee2db
Show file tree
Hide file tree
Showing 17 changed files with 557 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Components/DriveComponent/DriveComponent.razor
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<div class="apps">
@foreach (var app in grp.Items)
{
<div class="drive-app" data-app-type="@app.Type" data-src="@app.Address">
<div class="drive-app" data-app-type="@app.Type" data-src="@GetAddress(app)" x-uid="@app.Uid">
<div class="drive-app-inner">
<span class="icon">
@if (string.IsNullOrEmpty(app.Icon))
Expand Down
22 changes: 22 additions & 0 deletions Components/DriveComponent/DriveComponent.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,26 @@ private string GetIcon(GroupItem item)
return $"/apps/{Uri.EscapeDataString(app.Name)}/{app.Icon}?version=" + Globals.Version;
return $"/favicon?color={AccentColor?.Replace("#", string.Empty)}&version={Globals.Version}";
}

/// <summary>
/// Gets the address for a app
/// </summary>
/// <param name="app">the app</param>
/// <returns>the address to put into the HTML</returns>
private string GetAddress(CloudApp app)
{
if (app.Type != CloudAppType.Ssh)
return app.Address;
string address = app.Address;
int atIndex = address.IndexOf("@");
if (atIndex < 0)
return address;
string user = address[0..atIndex];
address = address.Substring(atIndex + 1);
int colonIndex = user.IndexOf(":");
if (colonIndex < 0)
return app.Uid + "@" + address;
// so we never ever every not in a million years send the password to the client
return app.Uid + ":" + app.Uid + "@" + address;
}
}
4 changes: 4 additions & 0 deletions Components/SideEditors/CloudAppEditor/CloudAppEditor.razor
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
{
<InputText Page="CloudAppEditor" Label="AddressVnc" @bind-Value="AddressVnc" Required="true" Pattern="^([\w.-]+|\[[0-9a-fA-F:]+\])(:\d+)?$"></InputText>
}
else if(AppType == CloudAppType.Ssh)
{
<InputText Page="CloudAppEditor" Label="AddressSsh" @bind-Value="AddressSsh" Required="true"></InputText>
}
else if(AppType == CloudAppType.IFrame)
{
<InputText Page="CloudAppEditor" Label="Address" @bind-Value="Address" Required="true" Pattern="^[^:]+://."></InputText>
Expand Down
12 changes: 11 additions & 1 deletion Components/SideEditors/CloudAppEditor/CloudAppEditor.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public partial class CloudAppEditor : SideEditorBase
/// Gets or sets the address for a VNC app
/// </summary>
private string AddressVnc { get; set; }

/// <summary>
/// Gets or sets the address for a SSH app
/// </summary>
private string AddressSsh { get; set; }


/// <summary>
Expand All @@ -73,6 +78,8 @@ private string GetAddress(CloudAppType type)
{
if(AppType == CloudAppType.Vnc)
return AddressVnc;
if (AppType == CloudAppType.Ssh)
return AddressSsh;
return Address;
}

Expand All @@ -89,6 +96,8 @@ protected override void OnInitialized()
AppType = Item?.Type ?? CloudAppType.IFrame;
if (AppType == CloudAppType.Vnc)
AddressVnc = Item?.Address ?? "http://";
else if (AppType == CloudAppType.Ssh)
AddressSsh = Item?.Address ?? string.Empty;
else
Address = Item?.Address ?? "https://";
Icon = Item?.Icon ?? string.Empty;
Expand All @@ -99,6 +108,7 @@ protected override void OnInitialized()
new() { Label = Translator.Instant($"Enums.{nameof(CloudAppType)}.{nameof(CloudAppType.External)}"), Value = CloudAppType.External },
new() { Label = Translator.Instant($"Enums.{nameof(CloudAppType)}.{nameof(CloudAppType.ExternalSame)}"), Value = CloudAppType.ExternalSame },
new() { Label = Translator.Instant($"Enums.{nameof(CloudAppType)}.{nameof(CloudAppType.Vnc)}"), Value = CloudAppType.Vnc },
new() { Label = Translator.Instant($"Enums.{nameof(CloudAppType)}.{nameof(CloudAppType.Ssh)}"), Value = CloudAppType.Ssh },
};
}

Expand All @@ -113,7 +123,7 @@ async Task Save()
return;
var item = new CloudApp();
item.Name = Name;
item.Address = GetAddress(AppType);
item.Address = (EncryptedString)GetAddress(AppType);
item.Icon = Icon;
item.Type = AppType;
await OnSaved.InvokeAsync(item);
Expand Down
13 changes: 13 additions & 0 deletions Controllers/BaseController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ public class BaseController : Controller
var settings = new UserSettingsService().Load(uid.Value);
return settings;
}
/// <summary>
/// Gets the users profile for currently logged in user
/// </summary>
/// <returns>the user profile, or null if user not logged in</returns>
protected UserProfile? GetUserProfile()
{
var uid = User.GetUserUid();
if (uid == null)
return null;

var profile = new UserService().GetProfileByUid(uid.Value);
return profile;
}

/// <summary>
/// Gets the user uid
Expand Down
65 changes: 64 additions & 1 deletion Controllers/TerminalController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Fenrus.Models;
using Fenrus.Services;
using Fenrus.Terminals;
using Microsoft.AspNetCore.Mvc;

Expand Down Expand Up @@ -74,6 +73,63 @@ public async Task Get([FromRoute] Guid uid, [FromQuery] int rows = 24, [FromQuer
}


/// <summary>
/// Opens a terminal connection
/// </summary>
/// <param name="rows">the number of rows for the terminal</param>
/// <param name="cols">the number of columns for the terminal</param>
[HttpGet("ssh")]
public async Task Ssh([FromQuery] string info, [FromQuery] int rows = 24, [FromQuery] int cols = 24)
{
var profile = GetUserProfile();
if (profile == null)
throw new UnauthorizedAccessException();

string decrypted = EncryptionHelper.DecryptAes(info);
var serverInfo = JsonSerializer.Deserialize<SshInfo>(decrypted, new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = true
});

if (Guid.TryParse(serverInfo.User, out Guid uid))
{
var app = profile.AppGroups.SelectMany(x => x.Items).FirstOrDefault(x => x.Uid == uid);
if (app != null)
{
(serverInfo.User, serverInfo.Password) = ParseAddress(app.Address, serverInfo.User, serverInfo.Password);
}
}



using var ws = await HttpContext.WebSockets.AcceptWebSocketAsync();
var terminal = new SshTerminal(ws, rows, cols, serverInfo.Server, 0, serverInfo.User, serverInfo.Password);
await terminal.Connect();
}

/// <summary>
/// Parses an address and gets the username/password from it if set
/// </summary>
/// <param name="address">the address</param>
/// <param name="currentUser">the current username to return if not set here</param>
/// <param name="currentPassword">the current password to return if not set here</param>
/// <returns>the username and password if available</returns>
private (string User, string Password) ParseAddress(string address, string currentUser, string currentPassword)
{
if (string.IsNullOrEmpty(address))
return (currentUser, currentPassword);
int atIndex = address.IndexOf("@");
if (atIndex < 0)
return (currentUser, currentPassword);
string user = address[..atIndex];
int caretIndex = user.IndexOf(":");
if (caretIndex < 0)
return (user, currentPassword);
string pwd = user.Substring(caretIndex + 1);
user = user[..caretIndex];
return (user, pwd);
}

/// <summary>
/// Opens a terminal log
/// </summary>
Expand Down Expand Up @@ -129,4 +185,11 @@ public async Task GetLog([FromRoute] Guid uid, [FromQuery] int rows = 24, [FromQ

await terminal.Log();
}

private class SshInfo
{
public string Server { get; set; }
public string User { get; set; }
public string Password { get; set; }
}
}
61 changes: 51 additions & 10 deletions Helpers/EncryptionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ public class EncryptionHelper
private static byte[] IV =
{
0x13, 0x14, 0x15, 0x16,
0x01, 0x02, 0x03, 0x04,
0x09, 0x10, 0x11, 0x12,
0x01, 0x02, 0x03, 0x04,
0x09, 0x10, 0x11, 0x12,
0x05, 0x06, 0x07, 0x08
};

private static byte[] Key;

/// <summary>
/// Tests if a string is likely encrypted
/// </summary>
Expand All @@ -26,9 +26,9 @@ public class EncryptionHelper
public static bool IsEncrypted(string input)
{
Span<byte> buffer = new Span<byte>(new byte[input.Length]);
return Convert.TryFromBase64String(input, buffer , out int bytesParsed);
return Convert.TryFromBase64String(input, buffer, out int bytesParsed);
}

/// <summary>
/// Encrypts a string
/// </summary>
Expand All @@ -38,7 +38,7 @@ public static string Encrypt(string text)
{
if (string.IsNullOrEmpty(text))
return text;

using Aes aes = Aes.Create();
aes.Key = Key;
aes.IV = IV;
Expand Down Expand Up @@ -71,7 +71,7 @@ public static bool TryDecrypt(string text, out string decrypted)
return false;
}
}

/// <summary>
/// Decrypts a string
/// </summary>
Expand All @@ -81,13 +81,13 @@ public static string Decrypt(string text)
{
if (string.IsNullOrEmpty(text))
return text;

Span<byte> buffer = new Span<byte>(new byte[text.Length]);
if (Convert.TryFromBase64String(text, buffer, out int bytesParsed) == false)
return text;

var data = buffer.Slice(0, bytesParsed).ToArray();

//byte[] encrypted = Convert.FromBase64String(text);
using Aes aes = Aes.Create();
aes.Key = Key;
Expand All @@ -98,7 +98,7 @@ public static string Decrypt(string text)
cryptoStream.CopyTo(output);
return Encoding.Unicode.GetString(output.ToArray());
}

/// <summary>
/// Gets a key from the key password
/// </summary>
Expand Down Expand Up @@ -146,4 +146,45 @@ public static void Init(string dataDir)
if (decrypted != teststring)
throw new Exception("Decryption test failed");
}

/// <summary>
/// Decrypts an AES encrypted string
/// </summary>
/// <param name="encryptedString">the AES encrypted string</param>
/// <returns>the decrpted string</returns>
public static string DecryptAes(string encryptedString)
{
try
{

// byte[] Key = Encoding.ASCII.GetBytes("E2C1B1E9C0812A8F2D52C0C7A1E90B8F"); // 32-byte key in ASCII format
//
// byte[] IV = Encoding.ASCII.GetBytes("65F1D7AC3903D2E964E3A2C1B1E9C081"); // 16-byte IV in ASCII format
byte[] Key = new byte[]
{
0xE2, 0xC1, 0xB1, 0xE9, 0xC0, 0x81, 0x2A, 0x8F,
0x2D, 0x52, 0xC0, 0xC7, 0xA1, 0xE9, 0x0B, 0x8F
};

byte[] IV = new byte[]
{
0x65, 0xF1, 0xD7, 0xAC, 0x39, 0x03, 0xD2, 0xE9,
0x64, 0xE3, 0xA2, 0xC1, 0xB1, 0xE9, 0xC0, 0x81
};
byte[] encryptedBytes = Convert.FromBase64String(encryptedString);

using Aes aes = Aes.Create();
aes.Key = Key;
aes.IV = IV;

using ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] decryptedBytes = decryptor.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
string decryptedString = Encoding.UTF8.GetString(decryptedBytes);
return decryptedString;
}
catch (Exception ex)
{
throw;
}
}
}
16 changes: 14 additions & 2 deletions Models/UserProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ public class UserProfile:IModalUid
/// </summary>
public class CloudAppGroup
{
/// <summary>
/// Gets or sets a UID to identify this app group
/// </summary>
public Guid Uid { get; set; }
/// <summary>
/// Gets or sets the name of the cloud app group
/// </summary>
Expand All @@ -129,6 +133,10 @@ public class CloudAppGroup
/// </summary>
public class CloudApp
{
/// <summary>
/// Gets or sets a UID to identify this app
/// </summary>
public Guid Uid { get; set; }
/// <summary>
/// Gets or sets the name of the app
/// </summary>
Expand All @@ -140,7 +148,7 @@ public class CloudApp
/// <summary>
/// Gets or sets the address of the app
/// </summary>
public string Address { get; set; }
public EncryptedString Address { get; set; }
/// <summary>
/// Gets or sets the type of the app
/// </summary>
Expand Down Expand Up @@ -171,5 +179,9 @@ public enum CloudAppType
/// <summary>
/// VNC app
/// </summary>
Vnc = 4
Vnc = 4,
/// <summary>
/// SSH App
/// </summary>
Ssh = 5
}
Loading

0 comments on commit f8ee2db

Please sign in to comment.