add ramdisk, load AI model to ramdisk and start recognition from it

rewrite zmq to DEALER and ROUTER
add GET_USER command to get CurrentUser from Python
all auth is on the python side
inference run and validate annotations on python
This commit is contained in:
Alex Bezdieniezhnykh
2025-01-29 17:45:26 +02:00
parent 82b3b526a7
commit 62623b7123
55 changed files with 945 additions and 895 deletions
-9
View File
@@ -1,9 +0,0 @@
namespace Azaion.CommonSecurity.DTO;
public class ApiConfig
{
public string Url { get; set; } = null!;
public int RetryCount {get;set;}
public double TimeoutSeconds { get; set; }
public string ResourcesFolder { get; set; } = "";
}
@@ -1,24 +0,0 @@
using MessagePack;
namespace Azaion.CommonSecurity.DTO.Commands;
[MessagePackObject]
public class FileCommand
{
[Key("CommandType")]
public CommandType CommandType { get; set; }
[Key("Filename")]
public string Filename { get; set; }
[Key("Data")]
public byte[] Data { get; set; }
}
public enum CommandType
{
None = 0,
Inference = 1,
Load = 2
}
@@ -0,0 +1,24 @@
using MessagePack;
namespace Azaion.CommonSecurity.DTO.Commands;
[MessagePackObject]
public class RemoteCommand(CommandType commandType, string? filename = null, byte[]? data = null)
{
[Key("CommandType")]
public CommandType CommandType { get; set; } = commandType;
[Key("Filename")]
public string? Filename { get; set; } = filename;
[Key("Data")]
public byte[]? Data { get; set; } = data;
}
public enum CommandType
{
None = 0,
Inference = 1,
Load = 2,
GetUser = 3
}
@@ -1,6 +0,0 @@
namespace Azaion.CommonSecurity.DTO;
public class SecureAppConfig
{
public ApiConfig ApiConfig { get; set; } = null!;
}
+5 -15
View File
@@ -1,21 +1,11 @@
using System.Security.Claims;
using MessagePack;
namespace Azaion.CommonSecurity.DTO;
[MessagePackObject]
public class User
{
public Guid Id { get; set; }
public string Email { get; set; }
public RoleEnum Role { get; set; }
public User(IEnumerable<Claim> claims)
{
var claimDict = claims.ToDictionary(x => x.Type, x => x.Value);
Id = Guid.Parse(claimDict[SecurityConstants.CLAIM_NAME_ID]);
Email = claimDict[SecurityConstants.CLAIM_EMAIL];
if (!Enum.TryParse(claimDict[SecurityConstants.CLAIM_ROLE], out RoleEnum role))
role = RoleEnum.None;
Role = role;
}
[Key("i")]public string Id { get; set; }
[Key("e")]public string Email { get; set; }
[Key("r")]public RoleEnum Role { get; set; }
}
+2 -3
View File
@@ -19,9 +19,8 @@ public class SecurityConstants
#endregion ApiConfig
#region SocketClient
public const string SOCKET_HOST = "127.0.0.1";
public const int SOCKET_SEND_PORT = 5127;
public const int SOCKET_RECEIVE_PORT = 5128;
public const string ZMQ_HOST = "127.0.0.1";
public const int ZMQ_PORT = 5127;
#endregion SocketClient
}
@@ -1,127 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using System.Net;
using System.Net.Http.Headers;
using System.Security;
using System.Text;
using Azaion.CommonSecurity.DTO;
using Newtonsoft.Json;
namespace Azaion.CommonSecurity.Services;
public class AzaionApiClient(HttpClient httpClient) : IDisposable
{
const string JSON_MEDIA = "application/json";
private static ApiConfig _apiConfig = null!;
private string Email { get; set; } = null!;
private SecureString Password { get; set; } = new();
private string JwtToken { get; set; } = null!;
public User User { get; set; } = null!;
public static AzaionApiClient Create(ApiCredentials credentials)
{
try
{
if (!File.Exists(SecurityConstants.CONFIG_PATH))
throw new FileNotFoundException(SecurityConstants.CONFIG_PATH);
var configStr = File.ReadAllText(SecurityConstants.CONFIG_PATH);
_apiConfig = JsonConvert.DeserializeObject<SecureAppConfig>(configStr)!.ApiConfig;
}
catch (Exception e)
{
Console.WriteLine(e);
_apiConfig = new ApiConfig
{
Url = SecurityConstants.DEFAULT_API_URL,
RetryCount = SecurityConstants.DEFAULT_API_RETRY_COUNT ,
TimeoutSeconds = SecurityConstants.DEFAULT_API_TIMEOUT_SECONDS
};
}
var api = new AzaionApiClient(new HttpClient
{
BaseAddress = new Uri(_apiConfig.Url),
Timeout = TimeSpan.FromSeconds(_apiConfig.TimeoutSeconds)
});
api.EnterCredentials(credentials);
return api;
}
public void EnterCredentials(ApiCredentials credentials)
{
if (string.IsNullOrWhiteSpace(credentials.Email) || string.IsNullOrWhiteSpace(credentials.Password))
throw new Exception("Email or password is empty!");
Email = credentials.Email;
Password = credentials.Password.ToSecureString();
}
public async Task<Stream> GetResource(string fileName, string password, HardwareInfo hardware)
{
var response = await Send(httpClient, new HttpRequestMessage(HttpMethod.Post, $"/resources/get/{_apiConfig.ResourcesFolder}")
{
Content = new StringContent(JsonConvert.SerializeObject(new { fileName, password, hardware }), Encoding.UTF8, JSON_MEDIA)
});
return await response.Content.ReadAsStreamAsync();
}
private async Task Authorize()
{
if (string.IsNullOrEmpty(Email) || Password.Length == 0)
throw new Exception("Email or password is empty! Please do EnterCredentials first!");
var payload = new
{
email = Email,
password = Password.ToRealString()
};
var response = await httpClient.PostAsync(
"login",
new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, JSON_MEDIA));
if (!response.IsSuccessStatusCode)
throw new Exception($"EnterCredentials failed: {response.StatusCode}");
var responseData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<LoginResponse>(responseData);
if (string.IsNullOrEmpty(result?.Token))
throw new Exception("JWT Token not found in response");
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(result.Token);
User = new User(token.Claims);
JwtToken = result.Token;
}
private async Task<HttpResponseMessage> Send(HttpClient client, HttpRequestMessage request)
{
if (string.IsNullOrEmpty(JwtToken))
await Authorize();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
var response = await client.SendAsync(request);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
await Authorize();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", JwtToken);
response = await client.SendAsync(request);
}
if (response.IsSuccessStatusCode)
return response;
var result = await response.Content.ReadAsStringAsync();
throw new Exception($"Failed: {response.StatusCode}! Result: {result}");
}
public void Dispose()
{
httpClient.Dispose();
Password.Dispose();
}
}
@@ -0,0 +1,60 @@
using System.Text;
using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.DTO.Commands;
using MessagePack;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.CommonSecurity.Services;
public interface IResourceLoader
{
Task<MemoryStream> LoadFile(string fileName, CancellationToken ct = default);
}
public interface IAuthProvider
{
User CurrentUser { get; }
}
public class PythonResourceLoader : IResourceLoader, IAuthProvider
{
private readonly DealerSocket _dealer = new();
private readonly Guid _clientId = Guid.NewGuid();
public User CurrentUser { get; }
public PythonResourceLoader(ApiCredentials credentials)
{
//Run python by credentials
_dealer.Options.Identity = Encoding.UTF8.GetBytes(_clientId.ToString("N"));
_dealer.Connect($"tcp://{SecurityConstants.ZMQ_HOST}:{SecurityConstants.ZMQ_PORT}");
_dealer.SendFrame(MessagePackSerializer.Serialize(new RemoteCommand(CommandType.GetUser)));
var user = _dealer.Get<User>(out _);
if (user == null)
throw new Exception("Can't get user from Auth provider");
CurrentUser = user;
}
public async Task<MemoryStream> LoadFile(string fileName, CancellationToken ct = default)
{
try
{
_dealer.SendFrame(MessagePackSerializer.Serialize(new RemoteCommand(CommandType.Load, fileName)));
if (!_dealer.TryReceiveFrameBytes(TimeSpan.FromMilliseconds(1000), out var bytes))
throw new Exception($"Unable to receive {fileName}");
return await Task.FromResult(new MemoryStream(bytes));
}
catch (Exception ex)
{
throw new Exception($"Failed to load fil0e '{fileName}': {ex.Message}", ex);
}
}
}
@@ -1,63 +0,0 @@
using Azaion.CommonSecurity.DTO;
using Azaion.CommonSecurity.DTO.Commands;
using MessagePack;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.CommonSecurity.Services;
public interface IResourceLoader
{
Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default);
}
public class PythonResourceLoader : IResourceLoader
{
private readonly PushSocket _pushSocket = new();
private readonly PullSocket _pullSocket = new();
public PythonResourceLoader(ApiCredentials credentials)
{
//Run python by credentials
_pushSocket.Connect($"tcp://{SecurityConstants.SOCKET_HOST}:{SecurityConstants.SOCKET_SEND_PORT}");
_pullSocket.Connect($"tcp://{SecurityConstants.SOCKET_HOST}:{SecurityConstants.SOCKET_RECEIVE_PORT}");
}
public async Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default)
{
try
{
var b = MessagePackSerializer.Serialize(new FileCommand
{
CommandType = CommandType.Load,
Filename = fileName
});
_pushSocket.SendFrame(b);
var bytes = _pullSocket.ReceiveFrameBytes(out bool more);
return new MemoryStream(bytes);
}
catch (Exception ex)
{
throw new Exception($"Failed to load fil0e '{fileName}': {ex.Message}", ex);
}
}
}
public class ResourceLoader(AzaionApiClient api, ApiCredentials credentials) : IResourceLoader
{
public async Task<MemoryStream> Load(string fileName, CancellationToken cancellationToken = default)
{
var hardwareService = new HardwareService();
var hardwareInfo = hardwareService.GetHardware();
var encryptedStream = Task.Run(() => api.GetResource(fileName, credentials.Password, hardwareInfo), cancellationToken).Result;
var key = Security.MakeEncryptionKey(credentials.Email, credentials.Password, hardwareInfo.Hash);
var stream = new MemoryStream();
await encryptedStream.DecryptTo(stream, key, cancellationToken);
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
+16
View File
@@ -0,0 +1,16 @@
using MessagePack;
using NetMQ;
using NetMQ.Sockets;
namespace Azaion.CommonSecurity;
public static class ZeroMqExtensions
{
public static T? Get<T>(this DealerSocket dealer, out byte[] message)
{
if (!dealer.TryReceiveFrameBytes(TimeSpan.FromMinutes(2), out var bytes))
throw new Exception($"Unable to get {typeof(T).Name}");
message = bytes;
return MessagePackSerializer.Deserialize<T>(bytes);
}
}