Files
MConnectSDK/MConnectSDK/MConnectClient.cs
T
Samuele E. Locatelli 64fe527c42 Fix cicli redirect inutili
2019-03-06 13:11:16 +01:00

782 lines
21 KiB
C#

using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MConnectSDK
{
/// <summary>
/// Client per connessione a MaestroConnect
/// </summary>
public class MConnectClient
{
#region oggetti base ed init
/// <summary>
/// wrapper di log
/// </summary>
public Logger lg;
/// <summary>
/// Oggetto accesso memoria...
/// </summary>
protected memLayer ML;
/// <summary>
/// Parametri di comunicazione attivi
/// </summary>
protected confParam _currParam;
/// <summary>
/// ID univoco dell'HMI per piattaforma MaestroConnect
/// </summary>
protected string _MConnectID;
/// <summary>
/// ID applicativo/client per piattaforma MaestroConnect (differente per UTE)
/// </summary>
protected string _ClientID;
/// <summary>
/// Determina se il server redis sia attivo e connesso (check periodico...)
/// </summary>
public bool redServAlive;
/// <summary>
/// DataOra ultimo controllo stato server REDIS
/// </summary>
protected DateTime lastRedSrvCheck;
/// <summary>
/// URL x chaimata auth QR-code
/// </summary>
protected string qrAuthBaseUrl = @"https://smart.maestroconnect.com/deviceauth/{0}";
/// <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);
}
}
/// <summary>
/// TTL lungo in REDIS (10 min)
/// </summary>
protected int longTTL = 600;
/// <summary>
/// Stato della richiesta attuale tornato
/// </summary>
protected requestStatus reqStatus
{
get
{
requestStatus answ = new requestStatus();
try
{
answ = JsonConvert.DeserializeObject<requestStatus>(ML.getRSV(ML.redHash("RequestStatus")));
}
catch
{ }
return answ;
}
set
{
string serVal = JsonConvert.SerializeObject(value);
ML.setRSV(ML.redHash("RequestStatus"), serVal, longTTL);
}
}
/// <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
{
string serVal = JsonConvert.SerializeObject(value);
ML.setRSV(ML.redHash("TokenResponse"), serVal, value.expires_in / 1000);
}
}
/// <summary>
/// Valore restituito dalla chiamata TOKEN (200/400/403/500)
/// </summary>
public int tokenRetVal
{
get
{
// default a -1...
int answ = -1;
int.TryParse(ML.getRSV(ML.redHash("TokenRetVal")), out answ);
return answ;
}
set
{
ML.setRSV(ML.redHash("TokenRetVal"), value.ToString(), 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>
/// 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);
}
}
/// <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>
/// AccessToken
/// </summary>
protected string access_token
{
get
{
string answ = "";
answ = verifResp.access_token;
return answ;
}
}
/// <summary>
/// RefreshToken
/// </summary>
protected string refresh_token
{
get
{
string answ = "";
answ = verifResp.refresh_token;
return answ;
}
}
/// <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);
}
}
/// <summary>
/// Elenco utenti (in cache locale)
/// </summary>
protected List<userData> userListCache
{
get
{
List<userData> answ = new List<userData>();
try
{
answ = JsonConvert.DeserializeObject<List<userData>>(ML.getRSV(ML.redHash("UserListCache")));
}
catch
{ }
return answ;
}
set
{
string serVal = JsonConvert.SerializeObject(value);
ML.setRSV(ML.redHash("UserListCache"), 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>
/// 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);
}
}
/// <summary>
/// Inizializzazione classe specificando tutti i parametri
/// </summary>
/// <param name="MConnectID">ID Univoco macchina</param>
/// <param name="ClientID">ID Applicativo UTE</param>
/// <param name="configurazione"></param>
public MConnectClient(string MConnectID, string ClientID, confParam configurazione)
{
_currParam = configurazione;
_MConnectID = MConnectID;
_ClientID = ClientID;
lg = LogManager.GetCurrentClassLogger();
ML = new memLayer(_currParam);
}
#endregion
#region metodi interni
/// <summary>
/// Effettua test preliminare stato...
/// </summary>
/// <returns></returns>
private requestStatus preCheckStatus()
{
requestStatus answ = new requestStatus()
{
// init in stato ND...
isHmiEnrolled = false,
cloudConnection = false,
localConnection = false,
source = comSource.offline,
callSuccess = false
};
// verifico REDIS in primis
answ.localConnection = checkRedisAlive();
if (answ.localConnection)
{
// cerco i TOKEN in REDIS...
answ.isAuth = !string.IsNullOrEmpty(refresh_token);
answ.isHmiEnrolled = enrollDate < DateTime.Now;
answ.source = comSource.local;
}
// verifico chiamata server...
if (_currParam.testMode)
{
// Fingo che tutto vada bene e di conseguenza setto risposta...
answ.cloudConnection = true;
answ.source = comSource.cloud;
answ.callSuccess = true;
// attesa random...
Random r = new Random();
Thread.Sleep(100 + r.Next(0, 100));
}
else
{
/// TBD !!!FARE!!! le vere chiamate di testing remoto...
}
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";
lg.Info(message);
redServAlive = newStatus;
if (newStatus)
{
setWatchdog();
}
}
return newStatus;
}
/// <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>
/// Effettua lo step 1 di chiamata x ottenere il TOKEN iniziale
/// </summary>
private void doTokenStep()
{
// verifico chiamata server...
if (_currParam.testMode)
{
int user_code = 0;
int device_code = 0;
int refrInt = 100;
int expIn = 10000;
if (redServAlive)
{
Random r = new Random();
user_code = r.Next(1, 999999);
device_code = r.Next(1, 999);
// salvo token
tokResp = new tokenResponse()
{
device_code = string.Format("TEST-DEV-{0:000}", device_code),
user_code = string.Format("{0:000000}", user_code),
verification_uri = "localhost:3000/client-auth/verification",
interval = refrInt,
expires_in = expIn
};
}
}
else
{
/// TBD !!!FARE!!! le vere chiamate di testing remoto...
}
}
/// <summary>
/// Effetuo step 3: /verification
/// </summary>
private void doVerificationStep()
{
if (redServAlive)
{
if (_currParam.testMode)
{
// controllo in REDIS, SE HO 200 --> AUTH DONE!!!
if (tokenRetVal == 200)
{
Random r = new Random();
int access = r.Next(1, 999999999);
int refresh = r.Next(1, 999999999);
int orgCod = r.Next(1, 999);
// salvo token
verifResp = new verificationSuccessResponse()
{
access_token = string.Format("AT-{0:000000000}", access),
refresh_token = string.Format("RT-{0:000000000}", refresh),
token_type = "default",
expires = 60000,
client_id = _ClientID,
organizationCode = string.Format("A{0:000}", orgCod)
};
}
else
{
if (!tokenDone)
{
doTokenStep();
}
}
}
else
{
/*-----------------------------------------
* Processo: eseguo le vere chiamate di testing remoto...
* - chiamo /verification, con client_id + device_code
* - salvo e verifico retVal
*-----------------------------------------*/
}
}
}
#endregion
#region metodi public
/// <summary>
/// Metodo di INIT della classe di comunicazione
/// </summary>
/// <returns></returns>
public requestStatus init()
{
// svuoto TUTTI i dati x INIT...
//ML.redFlushDB(_currParam.defaultDb);
ML.redFlushKey("MCC*", _currParam.defaultDb);
enrollDate = DateTime.Now.AddYears(100);
// iniziod a watchdog...
setWatchdog();
// init output
reqStatus = preCheckStatus();
return reqStatus;
}
/// <summary>
/// Recupera lo USER_CODE da msotrare x auth (anche con QR)
/// </summary>
/// <returns></returns>
public userAuthData tryAuthorize()
{
// init output
userAuthData answ = new userAuthData();
StringBuilder sb = new StringBuilder();
// init altro
reqStatus = preCheckStatus();
// verifico autorizzazione... se non c'è TENTO auth...
if (!reqStatus.isHmiEnrolled)
{
// 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
{
doVerificationStep();
}
}
// controllo SE sia stato autorizzato
if (tokenRetVal == 200)
{
sb.AppendLine("--------------------------------------------------");
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | OAuth process DONE", DateTime.Now));
sb.AppendLine("--------------------------------------------------");
answ.outMessage = sb.ToString();
// riporto in risposta i dati VUOTI...
answ.deviceCode = "";
answ.userCode = "";
answ.qrCode = "";
// registro che è OK x AUTH
answ.hasAuth = true;
reqStatus.isAuth = true;
answ.isEnrolled = false;
reqStatus.isHmiEnrolled = false;
// controllo se sia stato arruolato, altrimenti lo arruolo...
if (_currParam.testMode)
{
// fingo di chiamare associazione...per simularlo imposto una enroll date a + 5 secondi, se non ci fosse... altrimenti aspetto...
if (enrollDate < DateTime.Now.AddSeconds(10))
{
answ.isEnrolled = true;
reqStatus.isHmiEnrolled = true;
}
else
{
enrollDate = DateTime.Now.AddSeconds(5);
}
}
else
{
/*-----------------------------------------
* Processo VERA chiamata:
* - chiamo metodo x associazione
* - salvo dati orgCode
*-----------------------------------------*/
}
}
// non autorizzato...
else
{
// riporto in risposta i dati...
answ.deviceCode = tokResp.device_code;
answ.userCode = tokResp.user_code;
answ.qrCode = string.Format(qrAuthBaseUrl, tokResp.user_code);
sb.AppendLine("--------------------------------------------------");
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | Try OAuth process", DateTime.Now));
sb.AppendLine("--------------------------------------------------");
answ.outMessage = sb.ToString();
}
}
// ritorno!
return answ;
}
/// <summary>
/// Metodo x ottenere update richiesta
/// </summary>
/// <returns></returns>
public requestStatus reqStatusUpd
{
get
{
return reqStatus;
}
}
/// <summary>
/// Restituisce codice organizzazione
/// </summary>
public string organizationCode
{
get
{
string answ = "";
if (_currParam.testMode)
{
answ = verifResp.organizationCode;
}
else
{
/// TBD!!! qui la VERA CHIAMATA
}
return answ;
}
}
/// <summary>
/// Recupera elenco user x organizzazione corrente
/// </summary>
/// <returns></returns>
public List<userData> getUserList()
{
List<userData> answ = new List<userData>();
userData _newUser;
if (_currParam.testMode)
{
// cerco se li ho in cache... sennò genero ex-novo
if (userListCache.Count > 0)
{
answ = userListCache;
}
else
{
Random r = new Random();
int numUser = r.Next(5, 50);
// genero tanti utenti random...
for (int i = 0; i < numUser; i++)
{
_newUser = new userData
{
organizationCode = organizationCode,
user_id = string.Format("{0}.{1:000}", organizationCode, numUser + i),
isActive = true,
isAdmin = (i % 5) == 0, // uno ogni 5!
UserName = string.Format("USER_{0:000}", numUser + i),
Email = string.Format("USER_{0:000}@{1}.org", numUser + i, organizationCode),
Cognome = string.Format("COGNOME_{0:000}", numUser + i),
Nome = string.Format("NOME_{0:000}", numUser + i),
ImgUrl = string.Format(@"http://{{BASE_URL}}/{0}/user/{1}/image.png", organizationCode, numUser + i)
};
answ.Add(_newUser);
}
}
// attesa...
Thread.Sleep(500);
}
else
{
/// TBD !!!FARE!!! le vere chiamate di testing remoto...
}
// salvo in cache locale (comunque...)
userListCache = answ;
return answ;
}
/// <summary>
/// Effettua login e restituisce dati utente
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
public userData doUserLogin(string username, string password)
{
userData answ = new userData();
if (_currParam.testMode)
{
// cerco se li ho in cache... sennò genero ex-novo
if (currUser.organizationCode != "" && currUser.user_id != "" && currUser.UserName == username)
{
answ = currUser;
}
else
{
// cerco in utenti simulati che ci sia
userData userFound = userListCache.Find(x => x.UserName == username);
if (userFound != null)
{
// cerco nelle pwd salvate: se lo trovo e NON HO una pwd --> accetto questa e salvo!
var pwdDataFound = userPwdLocalCache.Find(x => x.user_id == userFound.user_id);
if (pwdDataFound != null)
{
}
else
{
}
}
}
// attesa...
Thread.Sleep(500);
}
else
{
/// TBD !!!FARE!!! le vere chiamate di testing remoto...
}
// salvo in cache locale (comunque...)
currUser = answ;
return answ;
}
/// <summary>
/// Task x AUTH
/// </summary>
/// <param name="token"></param>
/// <param name="progress"></param>
/// <returns></returns>
public async Task doOAuthCycleTask(CancellationToken token, IProgress<userAuthData> progress)
{
userAuthData authData = new userAuthData();
StringBuilder sb = new StringBuilder();
bool processRequest = true;
// fino a quanod NON è auth --> cicla...
while (!reqStatus.isHmiEnrolled && processRequest)
{
await Task.Run(() =>
{
authData = tryAuthorize();
// recupero nuova reqStatus...
reqStatus = reqStatusUpd;
// aggiorno che ho finito...
sb = new StringBuilder();
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} >> keep trying", DateTime.Now));
sb.AppendLine("--------------------------------------------------");
authData.outMessage += sb.ToString();
// verifico SE sia stato x autorizzato...
if (reqStatus.isAuth || authData.hasAuth)
{
sb.AppendLine("");
sb.AppendLine("SUCCESS 200: AUTH RECEIVED!");
authData.outMessage += sb.ToString();
}
// verifico SE sia stato registrato...
if (reqStatus.isHmiEnrolled || authData.isEnrolled)
{
sb.AppendLine("");
sb.AppendLine(string.Format("{0:HH.mm.ss.fff} | ENROLL DONE", DateTime.Now));
authData.outMessage += sb.ToString();
processRequest = false;
}
// verifico se richiesta cancellazione... CHIUDO!
if (token.IsCancellationRequested)
{
// riporto richiesta cancellazione... ed esco
authData = new userAuthData();
processRequest = false;
}
// riporto progress...
if (progress != null)
{
progress.Report(authData);
}
// mi fermo...
Thread.Sleep(500);
});
}
}
#endregion
}
}