2067 lines
77 KiB
C#
2067 lines
77 KiB
C#
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using YamlDotNet.RepresentationModel;
|
|
|
|
namespace MConnectSDK
|
|
{
|
|
/// <summary>
|
|
/// Client per connessione a MaestroConnect
|
|
/// <summary>
|
|
public class MConnectClient
|
|
{
|
|
#region Protected Fields
|
|
|
|
/// <summary>
|
|
/// Parametri di comunicazione attivi
|
|
/// <summary>
|
|
protected confParam _currParam;
|
|
|
|
/// <summary>
|
|
/// URL per test HEALT del cloud
|
|
/// <summary>
|
|
protected string AliveTarget = @"https://stg.api.maestroconnect.scmgroup.com/health";
|
|
|
|
/// <summary>
|
|
/// URL BASE per le chiamate
|
|
/// <summary>
|
|
protected string BaseUrl = @"https://stg.api.maestroconnect.scmgroup.com/";
|
|
|
|
/// <summary>
|
|
/// ID applicativo/client per piattaforma MaestroConnect (differente per UTE)
|
|
/// <summary>
|
|
protected string ClientID;
|
|
|
|
/// <summary>
|
|
/// Semaforo controllo CLOUD PRELIMINARE
|
|
/// </summary>
|
|
protected bool cloudOk = false;
|
|
|
|
/// <summary>
|
|
/// DataOra ultimo controllo stato server REDIS
|
|
/// <summary>
|
|
protected DateTime lastRedSrvCheck;
|
|
|
|
/// <summary>
|
|
/// TTL lungo in REDIS (10 min)
|
|
/// <summary>
|
|
protected int longTTL = 600;
|
|
|
|
/// <summary>
|
|
/// Oggetto accesso memoria...
|
|
/// <summary>
|
|
protected memLayer ML;
|
|
|
|
/// <summary>
|
|
/// Semaforo ping PRELIMINARE
|
|
/// </summary>
|
|
protected bool pingOk = false;
|
|
|
|
/// <summary>
|
|
/// URL BASE per le chiamate
|
|
/// <summary>
|
|
protected string PingTarget = @"stg.maestroconnect.scmgroup.com";
|
|
|
|
/// <summary>
|
|
/// URL BASE per le chiamate upload
|
|
/// <summary>
|
|
protected string UploadUrl = @"https://stg.maestroconnect.scmgroup.com/storage-api/backups";
|
|
|
|
/// <summary>
|
|
/// Intervallo di default x recall successive
|
|
/// <summary>
|
|
protected int waitRecall = 1000;
|
|
|
|
/// <summary>
|
|
/// URL x chiamata auth WebApp / QR-code da YAML...
|
|
/// <summary>
|
|
protected string WebAppUrl = @"https://smart.maestroconnect.com/deviceauth?user_code={0}&machineID={1}";
|
|
|
|
#endregion Protected Fields
|
|
|
|
#region Public Fields
|
|
|
|
/// <summary>
|
|
/// ID univoco dell'HMI per piattaforma MaestroConnect
|
|
/// <summary>
|
|
public string MachineID;
|
|
|
|
/// <summary>
|
|
/// Determina se il server redis sia attivo e connesso (check periodico...)
|
|
/// <summary>
|
|
public bool redServAlive;
|
|
|
|
#endregion Public Fields
|
|
|
|
#region Public Constructors
|
|
|
|
/// <summary>
|
|
/// Inizializzazione classe specificando il file di conf con i parametri
|
|
/// <summary>
|
|
public MConnectClient()
|
|
{
|
|
confParam param = new confParam();
|
|
ML = new memLayer(param);
|
|
}
|
|
|
|
#endregion Public Constructors
|
|
|
|
#region Protected Properties
|
|
|
|
/// <summary>
|
|
/// AccessToken
|
|
/// <summary>
|
|
protected string access_token
|
|
{
|
|
get
|
|
{
|
|
string answ = "";
|
|
answ = verifResp.access_token;
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Risposta della chiamata di clientInfo
|
|
/// <summary>
|
|
protected clientInfoResponse clientInfoResp
|
|
{
|
|
get
|
|
{
|
|
clientInfoResponse answ = new clientInfoResponse();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<clientInfoResponse>(ML.getRSV(ML.redHash("ClientInfoResponse")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("ClientInfoResponse"), serVal);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Utente corrente (SE login OK)
|
|
/// <summary>
|
|
protected UserData currUser
|
|
{
|
|
get
|
|
{
|
|
UserData answ = new UserData();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<UserData>(ML.getRSV(ML.redHash("CurrUser")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
// salvo MAX 2h
|
|
ML.setRSV(ML.redHash("CurrUser"), serVal, 60 * 60 * 2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// DateTime di enroll (se futuro = NON enrolled)
|
|
/// <summary>
|
|
protected DateTime enrollDate
|
|
{
|
|
get
|
|
{
|
|
DateTime answ = DateTime.Now.AddYears(100);
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<DateTime>(ML.getRSV(ML.redHash("EnrollDate")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("EnrollDate"), serVal);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ultima response controllo enroll
|
|
/// <summary>
|
|
protected enrollResponse enrollResp
|
|
{
|
|
get
|
|
{
|
|
enrollResponse answ = new enrollResponse();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<enrollResponse>(ML.getRSV(ML.redHash("enrollResp")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
if (value != null)
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("enrollResp"), serVal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dati Licenza (in cache locale)
|
|
/// <summary>
|
|
protected LicenseData LicenseDataCache
|
|
{
|
|
get
|
|
{
|
|
LicenseData answ = new LicenseData();
|
|
try
|
|
{
|
|
string rawResult = ML.getRSV(ML.redHash("LicenseDataCache"));
|
|
answ = JsonConvert.DeserializeObject<LicenseData>(rawResult);
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string license = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("LicenseDataCache"), license);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// URL pagina per check enroll (parametrico)
|
|
/// <summary>
|
|
protected string pageUrlCheckEnroll
|
|
{
|
|
get
|
|
{
|
|
string answ = "";
|
|
//string shortBaseUrl = BaseUrl.Replace("/api", "");
|
|
string shortBaseUrl = RemoveFromEnd(BaseUrl, "/api");
|
|
answ = $"{shortBaseUrl}/clients/{organizationCode}/machines/{MachineID}";
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// URL pagina per Download Immagini ( https://firebasestorage.googleapis.com/v0/b/scm-mconnect.appspot.com/o/ )
|
|
/// <summary>
|
|
protected string pageUrlImgDownload
|
|
{
|
|
get
|
|
{
|
|
return "https://firebasestorage.googleapis.com/v0/b/scm-mconnect.appspot.com/o/";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// RefreshToken
|
|
/// <summary>
|
|
protected string refresh_token
|
|
{
|
|
get
|
|
{
|
|
string answ = "";
|
|
answ = verifResp.refresh_token;
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stato della richiesta attuale tornato
|
|
/// <summary>
|
|
protected Result reqStatus
|
|
{
|
|
get
|
|
{
|
|
Result answ = new Result();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<Result>(ML.getRSV(ML.redHash("RequestStatus")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("RequestStatus"), serVal, longTTL);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se sia necessario rigenerare il token...
|
|
/// <summary>
|
|
protected bool tokenDone
|
|
{
|
|
get
|
|
{
|
|
bool answ = false;
|
|
if (redServAlive)
|
|
{
|
|
string currObj = ML.getRSV(ML.redHash("TokenResponse"));
|
|
answ = !string.IsNullOrEmpty(currObj);
|
|
}
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ultimo token response ottenuto (e salvato in REDIS)
|
|
/// <summary>
|
|
protected tokenResponse tokResp
|
|
{
|
|
get
|
|
{
|
|
tokenResponse answ = new tokenResponse();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<tokenResponse>(ML.getRSV(ML.redHash("TokenResponse")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
if (value != null)
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("TokenResponse"), serVal, value.expires_in);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Elenco utenti (in cache locale)
|
|
/// <summary>
|
|
protected List<UserData> userListCache
|
|
{
|
|
get
|
|
{
|
|
List<UserData> answ = new List<UserData>();
|
|
try
|
|
{
|
|
string rawResult = ML.getRSV(ML.redHash("UserListCache"));
|
|
answ = JsonConvert.DeserializeObject<List<UserData>>(rawResult);
|
|
}
|
|
catch
|
|
{ }
|
|
// bonifico eventuali null...
|
|
answ.RemoveAll(item => item == null);
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
value.RemoveAll(item => item == null);
|
|
value.Sort();
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("UserListCache"), serVal);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Elenco utenti (in cache locale)
|
|
/// <summary>
|
|
protected List<userPwdData> userPwdLocalCache
|
|
{
|
|
get
|
|
{
|
|
List<userPwdData> answ = new List<userPwdData>();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<List<userPwdData>>(ML.getRSV(ML.redHash("UserPwdLocalCache")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("UserPwdLocalCache"), serVal);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ultima response elenco utenti
|
|
/// <summary>
|
|
protected userListResponse usrListResp
|
|
{
|
|
get
|
|
{
|
|
userListResponse answ = new userListResponse();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<userListResponse>(ML.getRSV(ML.redHash("usrListResp")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
if (value != null)
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("usrListResp"), serVal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se sia necessario rifare la fase di verifica x i token di auth...
|
|
/// <summary>
|
|
protected bool verificationDone
|
|
{
|
|
get
|
|
{
|
|
bool answ = true;
|
|
if (redServAlive)
|
|
{
|
|
string currObj = ML.getRSV(ML.redHash("VerificationResponse"));
|
|
answ = !string.IsNullOrEmpty(currObj);
|
|
}
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Risposta della chiamata di verifica
|
|
/// <summary>
|
|
protected verificationSuccessResponse verifResp
|
|
{
|
|
get
|
|
{
|
|
verificationSuccessResponse answ = new verificationSuccessResponse();
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<verificationSuccessResponse>(ML.getRSV(ML.redHash("VerificationResponse")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("VerificationResponse"), serVal);
|
|
ML.forceDbDump();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// DateTime di watchdog
|
|
/// <summary>
|
|
protected DateTime watchDog
|
|
{
|
|
get
|
|
{
|
|
DateTime answ = DateTime.Now.AddDays(-1);
|
|
try
|
|
{
|
|
answ = JsonConvert.DeserializeObject<DateTime>(ML.getRSV(ML.redHash("WatchDog")));
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
set
|
|
{
|
|
string serVal = JsonConvert.SerializeObject(value);
|
|
ML.setRSV(ML.redHash("WatchDog"), serVal, longTTL);
|
|
}
|
|
}
|
|
|
|
#endregion Protected Properties
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// Restituisce codice organizzazione
|
|
/// <summary>
|
|
public string organizationCode
|
|
{
|
|
get
|
|
{
|
|
string answ = "";
|
|
try
|
|
{
|
|
answ = clientInfoResp.organization_code;
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo x ottenere update richiesta
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
public Result reqStatusUpd
|
|
{
|
|
get
|
|
{
|
|
// !!!TBD!!! Fare vera chaimata
|
|
return reqStatus;
|
|
}
|
|
}
|
|
|
|
#endregion Public Properties
|
|
|
|
#region Private Methods
|
|
|
|
/// <summary>
|
|
/// Fa un controllo sulla risposta, eventualmente rinnova il token di accesso restituendo SE HA FATTO refresh (quindi serve nuova chiamata)
|
|
/// <summary>
|
|
/// <param name="rawResult">{ext}<param>
|
|
private bool checkFixToken(string rawResult)
|
|
{
|
|
bool answ = false;
|
|
try
|
|
{
|
|
// verifico eventuali errori
|
|
var errResp = JsonConvert.DeserializeObject<errorResponse>(rawResult);
|
|
// se trovo errore aggiungo nella reqStatus...
|
|
if (errResp.error != "")
|
|
{
|
|
if (errResp.error == "Access Token not Valid")
|
|
{
|
|
// effettuo una chiamata al refresh e aggiorno!
|
|
refreshAuthToken();
|
|
answ = true;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Controlla stato server alive
|
|
/// <summary>
|
|
private bool checkRedisAlive()
|
|
{
|
|
bool newStatus = ML.connRedis.IsConnected;
|
|
// controllo se cambia lo stato...
|
|
string message = "";
|
|
if (redServAlive != newStatus)
|
|
{
|
|
message = newStatus ? "Server REDIS ONLINE!" : "Server REDIS offline";
|
|
Log.Instance.Info(message);
|
|
redServAlive = newStatus;
|
|
if (newStatus)
|
|
{
|
|
setWatchdog();
|
|
}
|
|
}
|
|
return newStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua lo step 1 di chiamata x ottenere il TOKEN iniziale
|
|
/// <summary>
|
|
private void doTokenStep()
|
|
{
|
|
// parametri chiamata
|
|
Uri callUri = new Uri(BaseUrl + "/oauth2/token");
|
|
// encapsulo clientID
|
|
var payload = "{\"client_id\": \"" + ClientID + "\"}";
|
|
HttpContent callCont = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// effettuo call effettiva
|
|
var taskRes = Task.Run(() => Utils.postUriAsync(callUri, callCont));
|
|
taskRes.Wait();
|
|
|
|
// decodifico risposta come tokResp...
|
|
var resp = JsonConvert.DeserializeObject<tokenResponse>(taskRes.Result);
|
|
waitRecall = 1000 * resp.interval;
|
|
tokResp = resp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effetuo step 3: /verification
|
|
/// <summary>
|
|
private bool doVerificationStep()
|
|
{
|
|
bool answ = false;
|
|
if (redServAlive)
|
|
{
|
|
// SOLO SE ho un device code...
|
|
if (tokResp != null && tokResp.device_code != "")
|
|
{
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// parametri chiamata
|
|
Uri callUri = new Uri(BaseUrl + "/oauth2/verification");
|
|
// !!! FARE fix hard coded !!!
|
|
var payload = "{\"client_id\": \"" + ClientID + "\", \"device_code\": \"" + tokResp.device_code + "\"}";
|
|
HttpContent callCont = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
// effettuo call effettiva
|
|
var taskRes = Task.Run(() => Utils.postUriAsync(callUri, callCont));
|
|
taskRes.Wait();
|
|
|
|
// leggo la rispsota in var temporanea...
|
|
var resp = JsonConvert.DeserializeObject<tokenResponse>(taskRes.Result);
|
|
// verifico se esista un errore come risposta...
|
|
if (resp != null)
|
|
{
|
|
// se è stato autorizzato...
|
|
if (resp.error == "")
|
|
{
|
|
var authResp = JsonConvert.DeserializeObject<verificationSuccessResponse>(taskRes.Result);
|
|
verifResp = authResp;
|
|
// blocco richiesta verifica
|
|
var currReq = reqStatus;
|
|
currReq.IsAuth = true;
|
|
answ = true;
|
|
reqStatus = currReq;
|
|
}
|
|
else
|
|
{
|
|
// resp.error == "slow_down"
|
|
// resp.error == "authorization_pending"
|
|
Thread.Sleep(waitRecall);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene i dati licenza da MaestroConnect
|
|
/// <summary>
|
|
/// <returns>Dati licenza<returns>
|
|
private LicenseData GetLicenseMConnect()
|
|
{
|
|
LicenseData result = null;
|
|
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
//controllo con la chiamata della clientInfo...
|
|
var taskCliInfo = Task.Run(() => GetClientStatusAsync());
|
|
taskCliInfo.Wait();
|
|
|
|
// parametri chiamata
|
|
string shortBaseUrl = BaseUrl.Replace(".com/api", ".com/clients");
|
|
|
|
string pageUrl = $"{shortBaseUrl}/{organizationCode}/machines/{MachineID}/license";
|
|
// effettuo call
|
|
Console.WriteLine("-> " + verifResp.access_token);
|
|
var taskRes = Task.Run(() => Utils.getPageAsync(pageUrl, verifResp.access_token));
|
|
taskRes.Wait();
|
|
try
|
|
{
|
|
var resp = JsonConvert.DeserializeObject<LicenseResponse>(taskRes.Result);
|
|
result = resp.result;
|
|
}
|
|
catch
|
|
{
|
|
result = null;
|
|
}
|
|
finally
|
|
{
|
|
Console.WriteLine(taskRes.Result.ToString());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parsing file di configurazione
|
|
/// <summary>
|
|
/// <param name="confFilePath">{ext}<param>
|
|
private void parseConfFile(string confFilePath)
|
|
{
|
|
confParam param = new confParam();
|
|
// deserializzazione brutale...
|
|
try
|
|
{
|
|
// Load the stream
|
|
using (var reader = new StreamReader(confFilePath))
|
|
{
|
|
// Load the stream
|
|
var yaml = new YamlStream();
|
|
yaml.Load(reader);
|
|
// Examine the stream
|
|
var mapping = (YamlMappingNode)yaml.Documents[0].RootNode;
|
|
var config = mapping.Children["conf"];
|
|
|
|
// imposto val da file YAML...
|
|
MachineID = config["MachineID"].ToString();
|
|
ClientID = config["Client_ID"].ToString();
|
|
// URL base x le chiamate REALI
|
|
BaseUrl = config["BaseUrl"].ToString();
|
|
WebAppUrl = config["WebAppUrl"].ToString();
|
|
UploadUrl = config["UploadUrl"].ToString();
|
|
PingTarget = config["PingTarget"].ToString();
|
|
AliveTarget = config["AliveTarget"].ToString();
|
|
|
|
var redisConf = config["MemoryLayer"]["Redis"];
|
|
|
|
int currDb = 0;
|
|
int.TryParse(redisConf["DbIndex"].ToString(), out currDb);
|
|
|
|
string baseConf = redisConf["ConnectionString"].ToString();
|
|
string _module = redisConf["Module"].ToString();
|
|
param = new confParam
|
|
{
|
|
redisConn = baseConf,
|
|
redisConnAdmin = baseConf + ",allowAdmin=true",
|
|
module = _module,
|
|
defaultDb = currDb
|
|
};
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
throw;
|
|
}
|
|
// registro parametri...
|
|
_currParam = param;
|
|
ML = new memLayer(_currParam);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua test preliminare stato...
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
private Result preCheckStatus()
|
|
{
|
|
Result answ = new Result()
|
|
{
|
|
// init in stato ND...
|
|
IsHmiEnrolled = false,
|
|
CloudStatusOk = false,
|
|
LocalStatusOk = false,
|
|
Source = SourceType.OFFLINE,
|
|
CallResultOk = false
|
|
};
|
|
// verifico REDIS in primis
|
|
answ.LocalStatusOk = checkRedisAlive();
|
|
if (answ.LocalStatusOk)
|
|
{
|
|
// cerco i TOKEN in REDIS...
|
|
answ.IsAuth = !string.IsNullOrEmpty(refresh_token);
|
|
answ.Source = SourceType.LOCAL;
|
|
}
|
|
|
|
// ORA verifica STATO sistema A VALLE...
|
|
if (checkConnStatus())
|
|
{
|
|
answ.CloudStatusOk = true;
|
|
answ.Source = SourceType.CLOUD;
|
|
}
|
|
answ.CallResultOk = (answ.CloudStatusOk || answ.LocalStatusOk);
|
|
// x capire SE sia enrolled --> faccio chiamata di verifica...
|
|
answ.IsHmiEnrolled = checkEnroll();
|
|
// rispondo!
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua una chaimata alla funzione di refresh del token di comunicazione
|
|
/// <summary>
|
|
private void refreshAuthToken()
|
|
{
|
|
// parametri chiamata
|
|
Uri callUri = new Uri(BaseUrl + "/oauth2/refresh");
|
|
var payload = "{\"refresh_token\": \"" + verifResp.refresh_token + "\"}";
|
|
HttpContent callCont = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// effettuo call effettiva
|
|
var taskRes = Task.Run(() => Utils.postUriAsync(callUri, callCont));
|
|
taskRes.Wait();
|
|
|
|
// decodifico risposta come authResp...
|
|
var authResp = JsonConvert.DeserializeObject<verificationSuccessResponse>(taskRes.Result);
|
|
// se OK salvo e sovrascrivo...
|
|
verifResp = authResp;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna stato redis server + invio dati pending
|
|
/// <summary>
|
|
private void setWatchdog()
|
|
{
|
|
// verifico eventuali invii pending... SE connesso...
|
|
if (redServAlive)
|
|
{
|
|
// salvo in REDIS che sto trasmettendo...
|
|
watchDog = DateTime.Now;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna i dati licenza dal cloud
|
|
///
|
|
/// <summary>
|
|
private void UpdateLicenseDataCacheFromCloud()
|
|
{
|
|
try
|
|
{
|
|
// se NON modalità testing LEGGO davvero da cloud i nuovi dati
|
|
LicenseData answ = GetLicenseMConnect();
|
|
if (answ != null)
|
|
{
|
|
// salvo in cache locale (comunque...)
|
|
LicenseDataCache = answ;
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua update userList
|
|
/// <param name="getImages">Indica se scaricare ANCHE TUTTE le immagini degli utenti (default = false){ext}<param>
|
|
/// <summary>
|
|
private void updateUserListCacheFromCloud(bool getImages = false)
|
|
{
|
|
try
|
|
{
|
|
// se NON modalità testing LEGGO davvero da cloud la NUOVA lista
|
|
List<UserData> answ = userListMConnect(getImages);
|
|
if (answ != null && answ.Count > 0)
|
|
{
|
|
// salvo in cache locale (comunque...)
|
|
userListCache = answ;
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Elenco utenti da MaestroConnect
|
|
/// <summary>
|
|
/// <param name="getImages">Indica se scaricare ANCHE TUTTE le immagini degli utenti (default = false){ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
private List<UserData> userListMConnect(bool getImages = false)
|
|
{
|
|
// continuo da remoto
|
|
List<UserData> answ = new List<UserData>();
|
|
List<UserData> resFilt = new List<UserData>();
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// salvo VECCHIO elenco utenti...
|
|
List<UserData> userListOld = userListCache;
|
|
// gestisco numero utenti precedentemente importati... e trovati nella nuova call da cloud
|
|
var elImported = userListOld.FindAll(X => X.LoginStatus == UserStatus.IMPORTED);
|
|
int numImp = elImported.Count;
|
|
int numFound = 0;
|
|
// controllo con la chiamata della clientInfo...
|
|
var taskCliInfo = Task.Run(() => GetClientStatusAsync());
|
|
taskCliInfo.Wait();
|
|
|
|
// parametri chiamata...SENZA /api...
|
|
string shortBaseUrl = BaseUrl.Replace("/api", "");
|
|
|
|
string pageUrl = $"{shortBaseUrl}/clients/{organizationCode}/users?skip=0&limit=5";
|
|
// effettuo call PRELIMINARE
|
|
var taskRes = Task.Run(() => Utils.getPageAsync(pageUrl, verifResp.access_token));
|
|
taskRes.Wait();
|
|
var rawResult = taskRes.Result;
|
|
// leggo la risposta in var temporanea...
|
|
var resp = JsonConvert.DeserializeObject<userListResponse>(rawResult);
|
|
|
|
// ora che so quanti utenti ho --> chiedo TUTTI!
|
|
pageUrl = $"{shortBaseUrl}/clients/{organizationCode}/users?skip=0&limit={resp.total}";
|
|
// effettuo call EFFETTIVA
|
|
taskRes = Task.Run(() => Utils.getPageAsync(pageUrl, verifResp.access_token));
|
|
taskRes.Wait();
|
|
rawResult = taskRes.Result;
|
|
// leggo la risposta in var temporanea...
|
|
resp = JsonConvert.DeserializeObject<userListResponse>(rawResult);
|
|
// salvo in cache REDIS
|
|
usrListResp = resp;
|
|
UserData _newUser;
|
|
Image userImg = null;
|
|
UserStatus currStatus = UserStatus.NOT_IMPORTED;
|
|
// recupero effettivi dati utente e restituisco...
|
|
foreach (var item in resp.result)
|
|
{
|
|
// verifica SE FOSSE GIA' esistente --> tengo STATUS...
|
|
resFilt = userListOld.FindAll(X => X.Username == item.Username);
|
|
if (resFilt.Count > 0)
|
|
{
|
|
currStatus = resFilt[0].LoginStatus;
|
|
if (currStatus == UserStatus.IMPORTED)
|
|
{
|
|
numFound++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currStatus = UserStatus.NOT_IMPORTED;
|
|
}
|
|
// creo utente
|
|
_newUser = new UserData
|
|
{
|
|
UserId = item.Id,
|
|
IsAdmin = item.IsAdmin,
|
|
Username = item.Username,
|
|
Email = item.Email,
|
|
Cognome = item.Lastname,
|
|
Nome = item.Firstname,
|
|
LoginStatus = currStatus,
|
|
UserImage = userImg
|
|
};
|
|
answ.Add(_newUser);
|
|
}
|
|
|
|
// se ne avessi ricevuti (da cloud) meno di quanti ne avevo --> cerco DELETED...
|
|
if (numFound != numImp)
|
|
{
|
|
// cerco nei "vecchi" imported chi sia stato eliminato...
|
|
foreach (var item in elImported)
|
|
{
|
|
resFilt = answ.FindAll(X => X.Username == item.Username);
|
|
if (resFilt.Count == 0)
|
|
{
|
|
// creo utente
|
|
_newUser = new UserData
|
|
{
|
|
UserId = item.UserId,
|
|
IsAdmin = item.IsAdmin,
|
|
Username = item.Username,
|
|
Email = item.Email,
|
|
Cognome = item.Cognome,
|
|
Nome = item.Nome,
|
|
LoginStatus = UserStatus.DELETED,
|
|
UserImage = userImg
|
|
};
|
|
answ.Add(_newUser);
|
|
}
|
|
}
|
|
}
|
|
|
|
// se richiesto aggiungo immagine...
|
|
if (getImages)
|
|
{
|
|
// in modalità parallela TENTO scaricamento imagini...
|
|
Parallel.ForEach(resp.result, item =>
|
|
{
|
|
userImg = null;
|
|
try
|
|
{
|
|
// creo obj userData... SE HO immagine...
|
|
if (item.Image_Url != "")
|
|
{
|
|
var imgRes = Task.Run(() => Utils.getImageAsync(pageUrlImgDownload, item.Image_Url));
|
|
imgRes.Wait();
|
|
if (imgRes.Result != null)
|
|
{
|
|
userImg = imgRes.Result;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
resFilt = answ.FindAll(X => X.Username == item.Username);
|
|
if (resFilt.Count == 1)
|
|
{
|
|
resFilt[0].UserImage = userImg;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
#endregion Private Methods
|
|
|
|
#region Protected Methods
|
|
|
|
/// <summary>
|
|
/// Verifica stato connessione (ping + cloudOK)
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected bool checkConnStatus()
|
|
{
|
|
// STEP 1: PING a server (locale/remoto)
|
|
pingOk = false;
|
|
cloudOk = false;
|
|
if (PingTarget != "")
|
|
{
|
|
pingOk = Utils.pingAddress(PingTarget);
|
|
}
|
|
// STEP 2: test HEALTH page...
|
|
if (pingOk)
|
|
{
|
|
// verifica chiamata pagina HEALT con reply = "HELLO" | https://stg.api.maestroconnect.scmgroup.com/health
|
|
var pageRes = Utils.getPageAsync(AliveTarget);
|
|
pageRes.Wait();
|
|
// 2019.09.26 versione x evitare deadlock sezionatura, by F.Sodano
|
|
//var pageRes = Task.Run(async () => await Utils.getPageAsync(AliveTarget));
|
|
if (pageRes.Result.IndexOf("HELLO") >= 0)
|
|
{
|
|
cloudOk = true;
|
|
}
|
|
else
|
|
{
|
|
var _cReqStatus = reqStatus;
|
|
_cReqStatus.CloudStatusOk = false;
|
|
_cReqStatus.Source = SourceType.LOCAL;
|
|
reqStatus = _cReqStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var _cReqStatus = reqStatus;
|
|
_cReqStatus.CloudStatusOk = false;
|
|
_cReqStatus.Source = SourceType.OFFLINE;
|
|
reqStatus = _cReqStatus;
|
|
}
|
|
return pingOk && cloudOk;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua verifica enroll (chiamando organization + machineID)
|
|
/// <summary>
|
|
protected bool checkEnroll()
|
|
{
|
|
bool answ = false;
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// controllo con la chiamata della clientInfo...
|
|
var taskCliInfo = Task.Run(() => GetClientStatusAsync());
|
|
taskCliInfo.Wait();
|
|
// effettuo lettura verifica...
|
|
var taskRes = Task.Run(() => Utils.getPageAsync(pageUrlCheckEnroll, verifResp.access_token));
|
|
taskRes.Wait();
|
|
var rawResult = taskRes.Result;
|
|
// leggo la risposta...
|
|
var resp = JsonConvert.DeserializeObject<enrollResponse>(taskRes.Result);
|
|
if (resp != null)
|
|
{
|
|
enrollResp = resp;
|
|
var currStatus = reqStatus;
|
|
if (resp.statusCode != 404)
|
|
{
|
|
if (resp.result != null && (resp.result.OrganizationCode.ToUpper() == organizationCode.ToUpper()))
|
|
{
|
|
// registro che è OK x AUTH
|
|
currStatus.IsHmiEnrolled = true;
|
|
answ = true;
|
|
reqStatus = currStatus;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log.Instance.Error($"Errore in fase di checkEnroll: {Environment.NewLine}{taskRes.Result}");
|
|
}
|
|
}
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generazione HASH password salted
|
|
/// <summary>
|
|
/// <param name="_plainText">{ext}<param>
|
|
/// <param name="_salt">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
protected string GenerateSaltedHash(string _plainText, string _salt)
|
|
{
|
|
byte[] plainText = Encoding.ASCII.GetBytes(_plainText);
|
|
byte[] salt = Encoding.ASCII.GetBytes(_salt);
|
|
|
|
HashAlgorithm algorithm = new SHA256Managed();
|
|
|
|
byte[] plainTextWithSaltBytes =
|
|
new byte[plainText.Length + salt.Length];
|
|
|
|
for (int i = 0; i < plainText.Length; i++)
|
|
{
|
|
plainTextWithSaltBytes[i] = plainText[i];
|
|
}
|
|
for (int i = 0; i < salt.Length; i++)
|
|
{
|
|
plainTextWithSaltBytes[plainText.Length + i] = salt[i];
|
|
}
|
|
string answ = ASCIIEncoding.ASCII.GetString(algorithm.ComputeHash(plainTextWithSaltBytes));
|
|
return answ;
|
|
}
|
|
|
|
#endregion Protected Methods
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Effettua vera chiamata enroll
|
|
/// <summary>
|
|
/// <param name="pageUrl">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public string doEnroll()
|
|
{
|
|
string answ = "";
|
|
// effettuo call DI ENROLL
|
|
Uri callUri = new Uri(pageUrlCheckEnroll);
|
|
var payload = "";
|
|
HttpContent callCont = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
var taskRes = Task.Run(() => Utils.putAsync(callUri, callCont, verifResp.access_token));
|
|
taskRes.Wait();
|
|
answ = taskRes.Result;
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Task x AUTH
|
|
/// <summary>
|
|
/// <param name="token">{ext}<param>
|
|
/// <param name="progress">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> EnrollMachineAsync(CancellationToken token, IProgress<Result> progress)
|
|
{
|
|
// predispongo il payload da allegare alla result...
|
|
ActivationPayload authData = new ActivationPayload();
|
|
StringBuilder sb = new StringBuilder();
|
|
bool processRequest = true;
|
|
var _currReq = reqStatus;
|
|
// fino a quanod NON è auth --> cicla...
|
|
while (!_currReq.IsHmiEnrolled && processRequest)
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
// SOLOS E precedentemente autorizzato...
|
|
if (_currReq.IsAuth)
|
|
{
|
|
authData = tryEnroll();
|
|
// recupero nuova reqStatus...
|
|
_currReq = reqStatusUpd;
|
|
|
|
// verifico SE sia stato registrato...
|
|
if (_currReq.IsHmiEnrolled || authData.IsEnrolled)
|
|
{
|
|
sb.AppendLine("");
|
|
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | ENROLL DONE", DateTime.Now));
|
|
authData.Message += sb.ToString();
|
|
processRequest = false;
|
|
_currReq.IsAuth = true;
|
|
_currReq.IsHmiEnrolled = true;
|
|
}
|
|
|
|
// verifico se richiesta cancellazione... CHIUDO!
|
|
if (token.IsCancellationRequested)
|
|
{
|
|
// riporto richiesta cancellazione... ed esco
|
|
authData = new ActivationPayload();
|
|
processRequest = false;
|
|
}
|
|
|
|
// aggiorno il payload...
|
|
_currReq.Payload = authData;
|
|
reqStatus = _currReq;
|
|
|
|
// riporto progress...
|
|
if (progress != null)
|
|
{
|
|
progress.Report(_currReq);
|
|
}
|
|
}
|
|
// attendo...
|
|
Thread.Sleep(500);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
// return finale dello status della chiamata...
|
|
return _currReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forza svuotamento di TUTTI i dati in REDIS
|
|
/// <summary>
|
|
public void forceRedisFlush()
|
|
{
|
|
// svuoto TUTTI i dati x INIT...
|
|
ML.redFlushKey(_currParam.module + "*", _currParam.defaultDb);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera status del client
|
|
/// <summary>
|
|
/// <returns><returns>
|
|
public async Task<Result> GetClientStatusAsync()
|
|
{
|
|
checkConnStatus();
|
|
var currReq = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
string rawResult = "";
|
|
bool tokenRefreshed = false;
|
|
// verifico il token della risposta...
|
|
if (verifResp != null)
|
|
{
|
|
// se ho il TOKEN di accesso procedo
|
|
if (verifResp.access_token != "")
|
|
{
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// provo a chiamare clientInfo...
|
|
string pageUrl = $"{BaseUrl}/oauth2/clientinfo?access_token={verifResp.access_token}";
|
|
|
|
var taskRes = Task.Run(() => Utils.getPageAsync(pageUrl));
|
|
taskRes.Wait();
|
|
rawResult = taskRes.Result;
|
|
tokenRefreshed = checkFixToken(rawResult);
|
|
if (tokenRefreshed)
|
|
{
|
|
taskRes = Task.Run(() => Utils.getPageAsync(pageUrl));
|
|
taskRes.Wait();
|
|
rawResult = taskRes.Result;
|
|
tokenRefreshed = checkFixToken(rawResult);
|
|
currReq.CloudStatusOk = true;
|
|
}
|
|
|
|
// ORA se NON HA + fatto refresh --> procedo!
|
|
if (!tokenRefreshed)
|
|
{
|
|
// salvo il client info...
|
|
var cliResp = JsonConvert.DeserializeObject<clientInfoResponse>(taskRes.Result);
|
|
if (cliResp != null)
|
|
{
|
|
clientInfoResp = cliResp;
|
|
currReq.CloudStatusOk = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
reqStatus = currReq;
|
|
}
|
|
}).ConfigureAwait(false);
|
|
return currReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera status del client
|
|
/// <summary>
|
|
/// <param name="token">Token, che può benissimo essere CancellationToken.None{ext}<param>
|
|
/// <param name="progress">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> GetClientStatusAsync(CancellationToken token, IProgress<Result> progress = null)
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
// iniziod a watchdog...
|
|
setWatchdog();
|
|
// faccio VERA chiamata x ClientInfo
|
|
checkConnStatus();
|
|
// verifico il token della risposta...
|
|
if (verifResp != null)
|
|
{
|
|
// se ho il TOKEN di accesso procedo
|
|
if (verifResp.access_token != "")
|
|
{
|
|
// controllo preliminare ping/cloud..
|
|
if (pingOk && cloudOk)
|
|
{
|
|
// provo a chiamare clientInfo...
|
|
string pageUrl = $"{BaseUrl}/oauth2/clientinfo?access_token={verifResp.access_token}";
|
|
|
|
var taskRes = Task.Run(() => Utils.getPageAsync(pageUrl));
|
|
taskRes.Wait();
|
|
|
|
string outVal = taskRes.Result;
|
|
}
|
|
}
|
|
}
|
|
}).ConfigureAwait(false);
|
|
return reqStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera i dati licenza per la macchina corrente
|
|
/// </summary>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> GetLicenseAsync()
|
|
{
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
var license = new LicensePayload();
|
|
UpdateLicenseDataCacheFromCloud();
|
|
LicenseData answ = LicenseDataCache;
|
|
// aggiungo nella risposta...
|
|
license.Data = answ;
|
|
if (answ != null && string.IsNullOrWhiteSpace(answ.error) && answ.currentPeriod != null)
|
|
{
|
|
string isActive = answ.IsActive ? "Active" : "Inactive";
|
|
license.Message = $"License {isActive}! \nStart date: {answ.currentPeriod.StartDate} \nEndDate: {answ.currentPeriod.EndDate}.";
|
|
}
|
|
else license.Message = $"License Not Found!";
|
|
|
|
_currStatus.Payload = license;
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera elenco user di Maestro connect registrati come ELIMINATI (erano importati ma NON SONO PIU' su MConnect)
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> getUserdeletedListAsync()
|
|
{
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
var userList = new UserPayload();
|
|
List<UserData> answ = userListCache;
|
|
// filtro SOLO per stato (improted)
|
|
var resFilt = answ.FindAll(X => X.LoginStatus == UserStatus.DELETED);
|
|
if (resFilt.Count > 0)
|
|
{
|
|
answ = resFilt;
|
|
}
|
|
else
|
|
{
|
|
answ = new List<UserData>();
|
|
}
|
|
// aggiungo nella risposta...
|
|
userList.UserList = answ;
|
|
userList.Message = $"{answ.Count} Users DELETED Found!";
|
|
_currStatus.Payload = userList;
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera elenco user di Maestro connect registrati come CREATI sul client/HMI x organizzazione corrente
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> getUserImportedListAsync()
|
|
{
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
var userList = new UserPayload();
|
|
List<UserData> answ = userListCache;
|
|
// filtro SOLO per stato (improted)
|
|
var resFilt = answ.FindAll(X => X.LoginStatus == UserStatus.IMPORTED);
|
|
if (resFilt.Count > 0)
|
|
{
|
|
answ = resFilt;
|
|
}
|
|
else
|
|
{
|
|
answ = new List<UserData>();
|
|
}
|
|
// aggiungo nella risposta...
|
|
userList.UserList = answ;
|
|
userList.Message = $"{answ.Count} Users Imported Found!";
|
|
_currStatus.Payload = userList;
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recupera elenco user x organizzazione corrente
|
|
/// <summary>
|
|
/// <param name="getImages">Indica se scaricare ANCHE TUTTE le immagini degli utenti (default = false){ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> getUserListAsync(bool getImages = false)
|
|
{
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
var userList = new UserPayload();
|
|
updateUserListCacheFromCloud(getImages);
|
|
List<UserData> answ = userListCache;
|
|
// aggiungo nella risposta...
|
|
userList.UserList = answ;
|
|
userList.Message = $"{answ.Count} Users Found!";
|
|
_currStatus.Payload = userList;
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Metodo di INIT della classe di comunicazione
|
|
/// <summary>
|
|
/// <param name="confFilePath">{ext}<param>
|
|
/// <param name="token">Token, che può benissimo essere CancellationToken.None{ext}<param>
|
|
/// <param name="progress">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> InitSDKAsync(string confFilePath, CancellationToken token, IProgress<Result> progress = null)
|
|
{
|
|
if (confFilePath != "")
|
|
{
|
|
// controlloare file di conf se valido... = "mconnect.conf.yaml"
|
|
parseConfFile(confFilePath);
|
|
Result _currStatus = new Result();
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio con invio watchdog...
|
|
setWatchdog();
|
|
// init output
|
|
_currStatus = preCheckStatus();
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentException(confFilePath, "File yaml (confFilePath) deve essere valorizzato");
|
|
}
|
|
}
|
|
|
|
public string RemoveFromEnd(string fullString, string suffix)
|
|
{
|
|
string answ = fullString;
|
|
if (fullString.EndsWith(suffix))
|
|
{
|
|
answ = fullString.Substring(0, fullString.Length - suffix.Length);
|
|
}
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Imposta UN SINGOLO utente come importato (<see langword="true"/>) o eliminato(false)
|
|
/// <summary>
|
|
/// <param name="sender">CHI invia la richeista (x decidere come impostare status), HMI = LOCAL{ext}<param>
|
|
/// <param name="username">{ext}<param>
|
|
/// <param name="imported">true=importato / false = cancellato/non + importato{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> setUserImported(SourceType sender, string username, bool imported = true)
|
|
{
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
var userList = new UserPayload();
|
|
List<UserData> answ = userListCache;
|
|
// CERCO utente x settare importato...
|
|
if (answ.Exists(x => x.Username == username))
|
|
{
|
|
// cerco ed aggiorno
|
|
var obj = answ.FirstOrDefault(x => x.Username == username);
|
|
if (obj != null)
|
|
{
|
|
// se mi chiede imported --> setto sempre...
|
|
if (imported)
|
|
{
|
|
obj.LoginStatus = UserStatus.IMPORTED;
|
|
}
|
|
// altrimenti controllo
|
|
else
|
|
{
|
|
// se chiede HMI/LOCAL --> NOT_IMPORTED
|
|
if (sender == SourceType.LOCAL)
|
|
{
|
|
obj.LoginStatus = UserStatus.NOT_IMPORTED;
|
|
}
|
|
else
|
|
{
|
|
// SE CHIEDE CLOUD e NON E' importato --> ELIMINO
|
|
if (obj.LoginStatus != UserStatus.IMPORTED)
|
|
{
|
|
// elimino ANCHE da cache pwd (se c'era)
|
|
if (userPwdLocalCache.Exists(x => x.Username == obj.Username))
|
|
{
|
|
var tmpData = userPwdLocalCache;
|
|
tmpData.RemoveAll(x => x.Username == obj.Username);
|
|
userPwdLocalCache = tmpData;
|
|
}
|
|
// elimino...
|
|
answ.Remove(obj);
|
|
}
|
|
// SE CHIEDE CLOUD ed E' importato --> imposto DELETED
|
|
else
|
|
{
|
|
obj.LoginStatus = UserStatus.DELETED;
|
|
}
|
|
}
|
|
}
|
|
//obj.LoginStatus = imported ? UserStatus.IMPORTED : UserStatus.DELETED;
|
|
}
|
|
// salvo
|
|
userListCache = answ;
|
|
}
|
|
// aggiungo nella risposta...
|
|
userList.UserList = answ;
|
|
userList.Message = "User Updated!";
|
|
_currStatus.Payload = userList;
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Task x AUTH
|
|
/// <summary>
|
|
/// <param name="token">{ext}<param>
|
|
/// <param name="progress">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> TryActivationAsync(CancellationToken token, IProgress<Result> progress)
|
|
{
|
|
// predispongo il payload da allegare alla result...
|
|
ActivationPayload authData = new ActivationPayload();
|
|
StringBuilder sb = new StringBuilder();
|
|
bool processRequest = true;
|
|
var _currReq = reqStatus;
|
|
// fino a quanod NON è auth --> cicla...
|
|
while (!reqStatus.IsAuth && processRequest)
|
|
{
|
|
await Task.Run(() =>
|
|
{
|
|
authData = tryAuthorize();
|
|
// aggiorno che ho finito...
|
|
sb = new StringBuilder();
|
|
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} >> keep trying", DateTime.Now));
|
|
sb.AppendLine("--------------------------------------------------");
|
|
authData.Message += sb.ToString();
|
|
|
|
// verifico SE sia stato x autorizzato...
|
|
if (reqStatus.IsAuth || authData.HasAuth)
|
|
{
|
|
sb.AppendLine("");
|
|
sb.AppendLine("SUCCESS 200: AUTH RECEIVED!");
|
|
authData.Message += sb.ToString();
|
|
processRequest = false;
|
|
}
|
|
|
|
// verifico se richiesta cancellazione... CHIUDO!
|
|
if (token.IsCancellationRequested)
|
|
{
|
|
// riporto richiesta cancellazione... ed esco
|
|
authData = new ActivationPayload();
|
|
processRequest = false;
|
|
}
|
|
|
|
_currReq = reqStatus;
|
|
// aggiorno il payload...
|
|
_currReq.Payload = authData;
|
|
reqStatus = _currReq;
|
|
|
|
// riporto progress...
|
|
if (progress != null)
|
|
{
|
|
progress.Report(_currReq);
|
|
}
|
|
|
|
// mi fermo...
|
|
Thread.Sleep(500);
|
|
}).ConfigureAwait(false);
|
|
}
|
|
// return finale dello status della chiamata...
|
|
return _currReq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tentativo AUTH
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
public ActivationPayload tryAuthorize()
|
|
{
|
|
// init output
|
|
ActivationPayload answ = new ActivationPayload();
|
|
StringBuilder sb = new StringBuilder();
|
|
bool hasAuth = false;
|
|
// verifico autorizzazione... se non c'è TENTO auth...
|
|
if (!reqStatus.IsAuth)
|
|
{
|
|
// attesa come richiesto da call precedente...
|
|
Thread.Sleep(tokResp.interval);
|
|
|
|
// verifico SE HO il valore in REDIS del primo token (quindi passo a /verification) oppure devo rifare step 1 (/token)...
|
|
if (redServAlive)
|
|
{
|
|
setWatchdog();
|
|
// verifico SE ho un token valido...
|
|
if (!tokenDone)
|
|
{
|
|
doTokenStep();
|
|
}
|
|
else
|
|
{
|
|
hasAuth = doVerificationStep();
|
|
}
|
|
}
|
|
// riporto in risposta i dati...
|
|
answ.DeviceCode = tokResp.device_code;
|
|
answ.UserCode = tokResp.user_code;
|
|
|
|
string userCode = (tokResp != null && tokResp.user_code != null) ? tokResp.user_code : "";
|
|
answ.QrCode = string.Format(WebAppUrl, userCode, MachineID);
|
|
sb.AppendLine("--------------------------------------------------");
|
|
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | Try OAuth process", DateTime.Now));
|
|
sb.AppendLine("--------------------------------------------------");
|
|
answ.Message = sb.ToString();
|
|
|
|
// controllo SE abbia avuto auth...
|
|
answ.HasAuth = hasAuth;
|
|
}
|
|
// ritorno!
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tentativo ENROLL
|
|
/// <summary>
|
|
/// <returns>{ext}<returns>
|
|
public ActivationPayload tryEnroll()
|
|
{
|
|
// init output
|
|
ActivationPayload answ = new ActivationPayload();
|
|
StringBuilder sb = new StringBuilder();
|
|
// verifico autorizzazione... se non c'è TENTO auth...
|
|
var currStatus = reqStatus;
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
// controllo con la chiamata della clientInfo...
|
|
var taskCliInfo = Task.Run(() => GetClientStatusAsync());
|
|
taskCliInfo.Wait();
|
|
if (!currStatus.IsHmiEnrolled && currStatus.IsAuth)
|
|
{
|
|
// verifico il token della risposta...
|
|
if (verifResp != null)
|
|
{
|
|
// se ho il TOKEN di accesso procedo
|
|
if (verifResp.access_token != "")
|
|
{
|
|
// provo a chiamare ENROLL...
|
|
string callEnroll = doEnroll();
|
|
checkEnroll();
|
|
}
|
|
}
|
|
|
|
// controllo SE sia stato autorizzato
|
|
if (enrollResp != null && enrollResp.statusCode == 200)
|
|
{
|
|
if (enrollResp.result.OrganizationCode.ToUpper() == organizationCode.ToUpper())
|
|
{
|
|
sb.AppendLine("--------------------------------------------------");
|
|
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | ENROLL DONE", DateTime.Now));
|
|
sb.AppendLine("--------------------------------------------------");
|
|
answ.Message = sb.ToString();
|
|
// riporto in risposta i dati VUOTI...
|
|
answ.DeviceCode = "";
|
|
answ.UserCode = "";
|
|
answ.QrCode = "";
|
|
// registro che è OK x AUTH
|
|
answ.HasAuth = true;
|
|
answ.IsEnrolled = true;
|
|
}
|
|
}
|
|
}
|
|
// ritorno!
|
|
return answ;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Classe x caricamento files
|
|
/// <summary>
|
|
/// <param name="token">{ext}<param>
|
|
/// <param name="progress">{ext}<param>
|
|
/// <param name="fileName">Nem file (PATH COMPLETO) da caricare{ext}<param>
|
|
/// <param name="uploadName">Nome da dare al file per il caricamento (con cui sarà nominato al download), se nullo/empty --> impostato BACKUP.{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> TryUpLoadFile(CancellationToken token, IProgress<Result> progress, string fileName, string uploadName = "")
|
|
{
|
|
Result result = new Result();
|
|
await Task.Run(() =>
|
|
{
|
|
// setup valori
|
|
result.Payload = new FilePayload();
|
|
result.CallResultOk = false;
|
|
GatewayPayload resp = new GatewayPayload();
|
|
bool forceChange = false;
|
|
if (uploadName == null || uploadName == "")
|
|
{
|
|
if (forceChange)
|
|
{
|
|
string fileExt = Path.GetExtension(fileName);
|
|
uploadName = string.Format("Backup{0}", fileExt);
|
|
}
|
|
else
|
|
{
|
|
uploadName = Path.GetFileName(fileName);
|
|
}
|
|
}
|
|
Stopwatch stopwatch = new Stopwatch();
|
|
try
|
|
{
|
|
stopwatch.Restart();
|
|
double fileSize = 0;
|
|
Task<string> taskRes;
|
|
//open file from the disk (file path is the path to the file to be opened)
|
|
using (FileStream fileStream = File.OpenRead(fileName))
|
|
{
|
|
fileSize = ((double)fileStream.Length) / 1024 / 1024;
|
|
Log.Instance.Info($"Inizio TryUpLoadFile, effettuata lettura del file {fileName} per {fileSize:N3}Mb");
|
|
|
|
// effettuo call EFFETTIVA
|
|
taskRes = Task.Run(() => Utils.uploadAsync(UploadUrl, uploadName, fileName));
|
|
}
|
|
//// test call FTP
|
|
//taskRes = Task.Run(() => Utils.ftpUploadAsync(MachineID, uploadName, fileName));
|
|
var rawResult = taskRes.Result;
|
|
if (rawResult != null && rawResult != "")
|
|
{
|
|
double velMbps = fileSize * 1000 / stopwatch.ElapsedMilliseconds;
|
|
Log.Instance.Info($"Completato invio del file: durata: {stopwatch.ElapsedMilliseconds} msec | vel media upload: {velMbps:N3} Mb/sec | risultato json: {rawResult}");
|
|
}
|
|
// leggo la risposta in var temporanea...
|
|
resp = JsonConvert.DeserializeObject<GatewayPayload>(rawResult);
|
|
}
|
|
catch (Exception exc)
|
|
{
|
|
// la loggo al posto di "embeddarla"
|
|
Log.Instance.Error(exc, $"Sollevata eccezione in TryUpLoadFile dopo {stopwatch.ElapsedMilliseconds} msec:");
|
|
// rilancio eccezione a monte...
|
|
throw;
|
|
}
|
|
|
|
if (resp != null)
|
|
{
|
|
// creo un nuovo result x embeddare il payload del risultato del caricamento file...
|
|
Result currReq = reqStatus;
|
|
// controllo se ho result...
|
|
string messaggio = resp.success ? "Upload Done!" : "Upload failed";
|
|
var risultato = new uploadResponse();
|
|
if (resp.success)
|
|
{
|
|
risultato.result = new FileUpLoadPayload()
|
|
{
|
|
key = resp.key,
|
|
link = resp.link,
|
|
expiry = resp.expiry,
|
|
filename = uploadName
|
|
};
|
|
}
|
|
else
|
|
{
|
|
risultato.error = new ErrorResult()
|
|
{
|
|
code = 000,
|
|
message = resp.error
|
|
};
|
|
}
|
|
FilePayload newPayload = new FilePayload()
|
|
{
|
|
Message = messaggio,
|
|
uploadResults = risultato
|
|
};
|
|
currReq.Payload = newPayload;
|
|
// salvo risultato!
|
|
reqStatus = currReq;
|
|
result = currReq;
|
|
}
|
|
else
|
|
{ }
|
|
}).ConfigureAwait(false);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effettua login e restituisce dati utente
|
|
/// <summary>
|
|
/// <param name="username">{ext}<param>
|
|
/// <param name="password">{ext}<param>
|
|
/// <returns>{ext}<returns>
|
|
public async Task<Result> TryUserLogin(string username, string password)
|
|
{
|
|
checkConnStatus();
|
|
var _currStatus = reqStatus;
|
|
await Task.Run(() =>
|
|
{
|
|
// inizio da watchdog...
|
|
setWatchdog();
|
|
// controllo con la chiamata della clientInfo...
|
|
var taskCliInfo = Task.Run(() => GetClientStatusAsync());
|
|
taskCliInfo.Wait();
|
|
_currStatus = taskCliInfo.Result;
|
|
// parto pessimista...
|
|
bool userOk = false;
|
|
// variabili accessorie
|
|
var userList = new UserPayload();
|
|
UserData answ = new UserData();
|
|
LoginErrorResult errore = new LoginErrorResult();
|
|
// controllo base: USER E PWD != ""
|
|
if (username != "" && password != "")
|
|
{
|
|
// recupero ed aggiorno userListcache
|
|
updateUserListCacheFromCloud();
|
|
UserData userFound = new UserData();
|
|
UserStatus currLoginStatus = UserStatus.NOT_IMPORTED;
|
|
string callResult = "";
|
|
// cerco in utenti LOCALI x trovare dati (di un precedente accesso/importazione) e quindi stato login...
|
|
userFound = userListCache.Find(x => x.Username == username);
|
|
if (userFound != null)
|
|
{
|
|
try
|
|
{
|
|
currLoginStatus = userFound.LoginStatus;
|
|
}
|
|
catch
|
|
{
|
|
currLoginStatus = UserStatus.NOT_IMPORTED;
|
|
}
|
|
}
|
|
|
|
// se sono online --> login
|
|
if (reqStatus.CloudStatusOk)
|
|
{
|
|
//Uri callUri = new Uri(BaseUrl + $"/FAKE/auth/login");
|
|
Uri callUri = new Uri(BaseUrl + $"/{organizationCode}/auth/login");
|
|
var payload = "{\"Username\": \"" + username + "\",\"Password\": \"" + password + "\"}";
|
|
HttpContent callCont = new StringContent(payload, Encoding.UTF8, "application/json");
|
|
// effettuo call effettiva
|
|
var taskRes = Task.Run(() => Utils.postUriAsync(callUri, callCont));
|
|
taskRes.Wait();
|
|
callResult = taskRes.Result;
|
|
if (callResult != "")
|
|
{
|
|
// deserializzo
|
|
var resp = JsonConvert.DeserializeObject<userLoginResponse>(callResult);
|
|
// controllo risultato: se 200 / status OK --> loggato!
|
|
if (resp.status == "Ok" || resp.statusCode == 200)
|
|
{
|
|
userData item = resp.result.user;
|
|
|
|
Image userImg = null;
|
|
try
|
|
{
|
|
// creo obj userData... SE HO immagine...
|
|
if (item.Image_Url != "")
|
|
{
|
|
var imgRes = Task.Run(() => Utils.getImageAsync(pageUrlImgDownload, item.Image_Url));
|
|
imgRes.Wait();
|
|
if (imgRes.Result != null)
|
|
{
|
|
userImg = imgRes.Result;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{ }
|
|
userFound = new UserData()
|
|
{
|
|
UserId = item.Id,
|
|
IsAdmin = item.IsAdmin,
|
|
Username = item.Username,
|
|
Email = item.Email,
|
|
Cognome = item.Lastname,
|
|
Nome = item.Firstname,
|
|
LoginStatus = currLoginStatus,
|
|
UserImage = userImg
|
|
};
|
|
}
|
|
else if (resp.statusCode == 400 && callResult.IndexOf("Invalid password") >= 0)
|
|
{
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.PwdMismatch,
|
|
message = "Username Ok, wrong Password"
|
|
};
|
|
_currStatus.Error = errore;
|
|
// elimino...
|
|
userFound = null;
|
|
}
|
|
else
|
|
{
|
|
userFound = null;
|
|
}
|
|
}
|
|
else
|
|
{ }
|
|
}
|
|
else
|
|
{
|
|
userFound = userListCache.Find(x => x.Username == username);
|
|
}
|
|
// se trovato user (locale/cloud) procedo
|
|
if (userFound != null)
|
|
{
|
|
// controllo SE deleted...
|
|
if (userFound.LoginStatus == UserStatus.DELETED)
|
|
{
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.UserDeleted,
|
|
message = "User deleted from CLOUD"
|
|
};
|
|
}
|
|
// SE VALIDO
|
|
else
|
|
{
|
|
var _usrPwdList = userPwdLocalCache;
|
|
// cifro la pwd locale... cifrata
|
|
byte[] salt;
|
|
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
|
|
string pwdCyph = GenerateSaltedHash(password, salt.ToString());
|
|
// cerco nelle pwd salvate: se lo trovo e NON HO una pwd --> accetto questa e salvo!
|
|
var usrId_Found = _usrPwdList.Find(x => x.Username == userFound.Username);
|
|
// --> a seconda che sia locale o meno decido come procedere...
|
|
if (reqStatus.CloudStatusOk)
|
|
{
|
|
if (usrId_Found != null)
|
|
{
|
|
// elimino vecchio record locale
|
|
_usrPwdList.Remove(usrId_Found);
|
|
}
|
|
|
|
// aggiungo nuova pwd
|
|
var _userPwd = new userPwdData()
|
|
{
|
|
Username = userFound.Username,
|
|
passwordCyph = pwdCyph
|
|
};
|
|
_usrPwdList.Add(_userPwd);
|
|
// salvo pwd
|
|
userPwdLocalCache = _usrPwdList;
|
|
// OK! trovato
|
|
answ = userFound;
|
|
userOk = true;
|
|
}
|
|
else
|
|
{
|
|
if (usrId_Found != null)
|
|
{
|
|
// verifico la pwd locale...
|
|
var pwdDataFound = _usrPwdList.Find(x => x.passwordCyph == pwdCyph);
|
|
if (pwdDataFound != null)
|
|
{
|
|
// OK! trovato
|
|
answ = userFound;
|
|
userOk = true;
|
|
}
|
|
else
|
|
{
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.PwdMismatch,
|
|
message = "Username Ok, wrong Password"
|
|
};
|
|
_currStatus.Error = errore;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var respErr = JsonConvert.DeserializeObject<userLoginFailedResponse>(callResult);
|
|
if (_currStatus.Error == null)
|
|
{
|
|
string msgErr = "Username not found";
|
|
if (respErr != null)
|
|
{
|
|
msgErr = "Username not found | " + respErr.message + " | " + respErr.result.error;
|
|
}
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.UserNotFound,
|
|
message = msgErr
|
|
};
|
|
_currStatus.Error = errore;
|
|
}
|
|
}
|
|
// controllo SE sia stato trovato
|
|
if (userOk)
|
|
{
|
|
// salvo in cache locale (comunque...)
|
|
currUser = answ;
|
|
|
|
// aggiungo nella risposta...
|
|
List<UserData> _answ = new List<UserData>();
|
|
_answ.Add(answ);
|
|
userList.UserList = _answ;
|
|
userList.Message = "User Found!";
|
|
_currStatus.Payload = userList;
|
|
_currStatus.Error = errore;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (username == "")
|
|
{
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.UserNotFound,
|
|
message = "Username is empty"
|
|
};
|
|
}
|
|
else
|
|
{
|
|
errore = new LoginErrorResult
|
|
{
|
|
Reason = UserReason.PwdMismatch,
|
|
message = "Password is empty"
|
|
};
|
|
}
|
|
_currStatus.Error = errore;
|
|
}
|
|
reqStatus = _currStatus;
|
|
}).ConfigureAwait(false);
|
|
return _currStatus;
|
|
}
|
|
|
|
#endregion Public Methods
|
|
}
|
|
} |