using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Authentication; using System.Threading.Tasks; using System.Web.Http; using Tango.BL; using Tango.BL.Builders; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core; using Tango.Core.DB; using Tango.Logging; using Tango.MachineService.Models; using Tango.PPC.Common.Web; using Tango.Web.Controllers; using Tango.Web.Helpers; using Tango.Web.SMO; using Tango.Web.Storage; using System.Data.Entity; using Tango.Web.Security; using Tango.Web.ActiveDirectory; using Tango.Core.Cryptography; using Tango.MachineService.Filters; using Tango.BL.DTO; using Z.EntityFramework.Plus; using Twilio; using Twilio.Rest.Api.V2010.Account; using Twilio.Types; using Microsoft.WindowsAzure.Storage; using Tango.Web; using Microsoft.WindowsAzure.Storage.Table; using Tango.MachineService.Telemetry; namespace Tango.MachineService.Controllers { public class PPCController : TangoController { private static List _pendingUploads; private static List _pendingUpdates; private ActiveDirectoryManager _ad_manager; public const int SQL_TEMP_CREDENTIALS_EXP_MINUTS = 30; public class TokenObject { public LoginMode Mode { get; set; } public String UserGuid { get; set; } public String MachineGuid { get; set; } } #region Constructors static PPCController() { _pendingUploads = new List(); _pendingUpdates = new List(); TwilioClient.Init(MachineServiceConfig.TWILIO_ACCOUNT_SID, MachineServiceConfig.TWILIO_AUTH_TOKEN); } public PPCController() { _ad_manager = new ActiveDirectoryManager(); } #endregion #region Setup & Update [HttpPost] [JwtTokenFilter] public MachineSetupResponse MachineSetup(MachineSetupRequest request) { MachineSetupResponse response = new MachineSetupResponse(); response.NotifyCompletedToken = Guid.NewGuid().ToString(); LogManager.Log("Setup request received: " + request.ToString()); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { String machine_guid = RequestToken.Object.MachineGuid; var machine = db.Machines.Include(x => x.Organization).SingleOrDefault(x => x.Guid == machine_guid); if (machine == null) { throw new AuthenticationException("The specified serial number could not be found."); } if (machine.SetupActivation && String.IsNullOrWhiteSpace(machine.OsKey)) { throw new InvalidDataException("The specified machine is configured to perform an OS activation but is not associated with an OS activation key."); } if (String.IsNullOrWhiteSpace(request.DeviceID)) { throw new InvalidDataException("Device id not set."); } if (String.IsNullOrWhiteSpace(request.DeviceName)) { throw new InvalidDataException("Device name not set."); } if (machine.IsDeviceRegistered && machine.DeviceId != request.DeviceID) { throw new InvalidDataException("The specified machine is already registered and device id is invalid."); } machine.DeviceName = request.DeviceName; machine.DeviceId = request.DeviceID; machine.IsDeviceRegistered = true; var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid); var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); response.Version = latest_machine_version.Version; response.FirmwareVersion = latest_machine_version.FirmwareVersion; response.Tag = latest_machine_version.Tag; var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(latest_machine_version.BlobName); response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) { response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; } DbCredentials credentials = new DbCredentials(); using (SmoManager smo = new SmoManager()) { credentials = smo.CreateRandomLoginAndUser(); Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) => { using (SmoManager m = new SmoManager()) { m.DeleteLoginAndUser(credentials.UserName); } }); } response.DataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, UserName = credentials.UserName, Password = credentials.Password, IntegratedSecurity = false, Type = DataSourceType.SQLServer, }; response.OSKey = machine.OsKey; response.SetupActivation = machine.SetupActivation; response.SetupRemoteAssistance = machine.SetupRemoteAssistance; response.SetupUWF = machine.SetupUwf; response.SetupFirmware = machine.SetupFirmware; response.SetupFPGA = machine.SetupFpga; response.IsDemo = machine.IsDemo; response.Organization = machine.Organization.Name; TangoUpdate tangoUpdate = new TangoUpdate(); tangoUpdate.ApplicationVersion = latest_machine_version.Version; tangoUpdate.FirmwareVersion = latest_machine_version.FirmwareVersion; tangoUpdate.ApplicationVersionTag = latest_machine_version.Tag; tangoUpdate.MachineGuid = machine.Guid; tangoUpdate.StartDate = DateTime.UtcNow; tangoUpdate.UpdateStatus = TangoUpdateStatuses.SetupStarted; db.TangoUpdates.Add(tangoUpdate); machine.ProductionDate = DateTime.UtcNow; db.SaveChanges(); _pendingUpdates.Add(new PPCPendingUpdate() { Token = response.NotifyCompletedToken, TangoUpdateGuid = tangoUpdate.Guid, }); Task.Factory.StartNew(() => { using (ObservablesContext b = ObservablesWebContext.CreateContext()) { //Reset Job Runs. try { b.JobRuns.Where(x => x.MachineGuid == machine.Guid && x.IsSynchronized).Update(x => new JobRun() { IsSynchronized = false }); } catch (Exception ex) { LogManager.Log(ex, $"Error resetting synchronized job runs for machine '{machine.SerialNumber}'."); } try { b.DataStoreItems.Where(x => x.MachineGuid == machine.Guid).Update(x => new DataStoreItem() { IsSynchronized = false }); } catch (Exception ex) { LogManager.Log(ex, $"Error resetting synchronized data store items for machine '{machine.SerialNumber}'."); } //Reset Events. //b.MachinesEvents.Where(x => x.MachineGuid == machine.Guid).Update(x => new MachinesEvent() { IsSynchronized = false }); //Reset Jobs. //b.Jobs.Where(x => x.MachineGuid == machine.Guid).Update(x => new Job() { IsSynchronized = false }); } }); } return response; } [HttpPost] [JwtTokenFilter] public DownloadUpdateResponse MachineUpdate(DownloadUpdateRequest request) { DownloadUpdateResponse response = new DownloadUpdateResponse(); response.NotifyCompletedToken = Guid.NewGuid().ToString(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { db.Configuration.LazyLoadingEnabled = false; String machine_guid = RequestToken.Object.MachineGuid; var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid); if (machine == null) { throw new AuthenticationException("The specified serial number could not be found."); } var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid); var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); response.Version = latest_machine_version.Version; response.FirmwareVersion = latest_machine_version.FirmwareVersion; response.Tag = latest_machine_version.Tag; var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(latest_machine_version.BlobName); response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) { response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; } DbCredentials credentials = new DbCredentials(); using (SmoManager smo = new SmoManager()) { credentials = smo.CreateRandomLoginAndUser(); Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) => { using (SmoManager m = new SmoManager()) { m.DeleteLoginAndUser(credentials.UserName); } }); } response.DataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, UserName = credentials.UserName, Password = credentials.Password, IntegratedSecurity = false, Type = DataSourceType.SQLServer, }; TangoUpdate tangoUpdate = new TangoUpdate(); tangoUpdate.ApplicationVersion = latest_machine_version.Version; tangoUpdate.FirmwareVersion = latest_machine_version.FirmwareVersion; tangoUpdate.ApplicationVersionTag = latest_machine_version.Tag; tangoUpdate.MachineGuid = machine.Guid; tangoUpdate.StartDate = DateTime.UtcNow; tangoUpdate.UpdateStatus = TangoUpdateStatuses.UpdateStarted; db.TangoUpdates.Add(tangoUpdate); db.SaveChanges(); _pendingUpdates.Add(new PPCPendingUpdate() { Token = response.NotifyCompletedToken, TangoUpdateGuid = tangoUpdate.Guid, }); } return response; } [HttpPost] [JwtTokenFilter] public MachineUpdateCompletedResponse NotifyUpdateCompleted(MachineUpdateCompletedRequest request) { var pendingUpdate = _pendingUpdates.SingleOrDefault(x => x.Token == request.Token); if (pendingUpdate != null) { _pendingUpdates.Remove(pendingUpdate); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var tangoUpdate = db.TangoUpdates.SingleOrDefault(x => x.Guid == pendingUpdate.TangoUpdateGuid); if (tangoUpdate != null) { tangoUpdate.LastUpdated = DateTime.UtcNow; tangoUpdate.UpdateStatus = request.Status; tangoUpdate.EndDate = DateTime.UtcNow; tangoUpdate.FailedReason = request.FailedReason; tangoUpdate.FailedLog = request.FailedLog; if (request.ReportsAboutDbCheckNoDifferences) { db.TangoUpdates.Remove(tangoUpdate); } db.SaveChanges(); } } } return new MachineUpdateCompletedResponse(); } [HttpPost] [JwtTokenFilter] public CheckForUpdateResponse CheckForUpdates(CheckForUpdateRequest request) { CheckForUpdateResponse response = new CheckForUpdateResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { String machine_guid = RequestToken.Object.MachineGuid; var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid); if (machine == null) { throw new AuthenticationException("The specified serial number could not be found."); } response.SetupFirmware = machine.SetupFirmware; response.SetupFPGA = machine.SetupFpga; if (!machine.SuspendVersionUpdate) { var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid); var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid && x.Tag == machine.VersionTag && !x.Disabled).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); if (Version.Parse(latest_machine_version.Version) > Version.Parse(request.Version)) { response.IsUpdateAvailable = true; } if (request.Tag != machine.VersionTag) { response.IsUpdateAvailable = true; } if (!response.IsUpdateAvailable && !machine.IsDemo && machine.SetupFirmware) { if (!String.IsNullOrWhiteSpace(request.FirmwareVersion)) { if (Version.Parse(latest_machine_version.FirmwareVersion) > Version.Parse(request.FirmwareVersion)) { response.IsUpdateAvailable = true; } } } response.Version = latest_machine_version.Version; response.FirmwareVersion = latest_machine_version.FirmwareVersion; //Compare database var rmls = db.Rmls.Select(x => new { x.Guid, x.LastUpdated }).ToList(); var hardwareVersions = db.HardwareVersions.Select(x => new { x.Guid, x.LastUpdated }).ToList(); var catalogs = db.ColorCatalogs.Select(x => new { x.Guid, x.LastUpdated }).ToList(); var arr = request.UsedRmlsGuids.ToArray(); var existingRml = db.Rmls.Where(x => arr.Contains(x.Guid)).Select(x => x.Guid).Distinct().ToList(); response.UsedNotExistingRmlsGuids = arr.Where(x => !existingRml.Exists(y => y == x)).ToList(); bool hasDatabaseUpdates = false; hasDatabaseUpdates = machine.LastUpdated > request.MachineLastUpdated; if (!hasDatabaseUpdates) { hasDatabaseUpdates = rmls.Exists(x => request.Rmls.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.Rmls.Exists(y => x.Guid == y.Guid)); } if (!hasDatabaseUpdates) { hasDatabaseUpdates = hardwareVersions.Exists(x => request.HardwareVersions.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.HardwareVersions.Exists(y => x.Guid == y.Guid)); } if (!hasDatabaseUpdates) { hasDatabaseUpdates = catalogs.Exists(x => request.Catalogs.Exists(y => x.Guid == y.Guid && x.LastUpdated > y.LastUpdated) || !request.Catalogs.Exists(y => x.Guid == y.Guid)); } if (hasDatabaseUpdates) { response.IsDatabaseUpdateAvailable = true; response.UpdateDBResponse = UpdateDB(new UpdateDBRequest() { ApplicationVersion = request.Version, FirmwareVersion = request.FirmwareVersion }); } //Compare database if (machine.ForceVersionUpdate) { response.IsUpdateAvailable = true; } } } return response; } [HttpPost] [JwtTokenFilter] public UpdateDBResponse UpdateDB(UpdateDBRequest request) { UpdateDBResponse response = new UpdateDBResponse(); response.NotifyCompletedToken = Guid.NewGuid().ToString(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { String machine_guid = RequestToken.Object.MachineGuid; var machine = db.Machines.SingleOrDefault(x => x.Guid == machine_guid); if (machine == null) { throw new AuthenticationException("The specified serial number could not be found."); } response.PerformSchemaUpdate = machine.PerformSchemaUpdateOnDataUpdate; DbCredentials credentials = new DbCredentials(); using (SmoManager manager = new SmoManager()) { credentials = manager.CreateRandomLoginAndUser(); Task.Delay(TimeSpan.FromMinutes(SQL_TEMP_CREDENTIALS_EXP_MINUTS)).ContinueWith((x) => { using (SmoManager m = new SmoManager()) { m.DeleteLoginAndUser(credentials.UserName); } }); } response.DataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, UserName = credentials.UserName, Password = credentials.Password, IntegratedSecurity = false, Type = DataSourceType.SQLServer, }; TangoUpdate tangoUpdate = new TangoUpdate(); tangoUpdate.ApplicationVersion = request.ApplicationVersion; tangoUpdate.FirmwareVersion = request.FirmwareVersion; tangoUpdate.ApplicationVersionTag = machine.VersionTag; tangoUpdate.MachineGuid = machine.Guid; tangoUpdate.StartDate = DateTime.UtcNow; tangoUpdate.UpdateStatus = TangoUpdateStatuses.DatabaseStarted; db.TangoUpdates.Add(tangoUpdate); db.SaveChanges(); _pendingUpdates.Add(new PPCPendingUpdate() { Token = response.NotifyCompletedToken, TangoUpdateGuid = tangoUpdate.Guid, }); } return response; } #endregion #region Synchronization [HttpPost] [JwtTokenFilter] public UploadMachineDataResponse UploadMachineData(UploadMachineDataRequest request) { UploadMachineDataResponse response = new UploadMachineDataResponse(); response.NotifyCompletedToken = Guid.NewGuid().ToString(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) { throw new AuthenticationException("The specified machine could not be found."); } TangoUpdate tangoUpdate = new TangoUpdate(); tangoUpdate.ApplicationVersion = request.ApplicationVersion; tangoUpdate.FirmwareVersion = request.FirmwareVersion; tangoUpdate.ApplicationVersionTag = machine.VersionTag; tangoUpdate.MachineGuid = machine.Guid; tangoUpdate.StartDate = DateTime.UtcNow; tangoUpdate.UpdateStatus = TangoUpdateStatuses.SynchronizationStarted; db.TangoUpdates.Add(tangoUpdate); db.SaveChanges(); _pendingUpdates.Add(new PPCPendingUpdate() { Token = response.NotifyCompletedToken, TangoUpdateGuid = tangoUpdate.Guid, }); } try { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) { throw new AuthenticationException("The specified machine could not be found."); } } //Insert/Replace Jobs. foreach (var dto in request.Jobs) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { try { var job = dto.ToObservable(); job.ID = 0; job.CustomerGuid = null; job.IsSynchronized = true; var existingJob = db.Jobs.SingleOrDefault(x => x.Guid == job.Guid); if (existingJob == null) { db.Jobs.Add(job); db.SaveChanges(); } else if (job.LastUpdated > existingJob.LastUpdated) { job.UserGuid = existingJob.UserGuid; existingJob.Delete(db); db.Jobs.Add(job); db.SaveChanges(); } } catch (Exception ex) { response.FailedJobs.Add(new SynchronizationFailedEntity() { Guid = dto.Guid, Reason = ex.FlattenMessage(), }); } } } //Insert Update DataStore Items foreach (var dto in request.DataStoreItems) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { try { var dataStoreItem = dto.ToObservable(); dataStoreItem.MachineGuid = RequestToken.Object.MachineGuid; dataStoreItem.ID = 0; dataStoreItem.IsSynchronized = true; var existingItem = db.DataStoreItems.SingleOrDefault(x => x.Guid == dataStoreItem.Guid); if (existingItem == null) { db.DataStoreItems.Add(dataStoreItem); db.SaveChanges(); } else if (dataStoreItem.LastUpdated >= existingItem.LastUpdated) { existingItem.DataType = dataStoreItem.DataType; existingItem.Value = dataStoreItem.Value; existingItem.IsSynchronized = true; existingItem.LastUpdated = dataStoreItem.LastUpdated; db.SaveChanges(); } } catch (Exception ex) { response.FailedDataStoreItems.Add(new SynchronizationFailedEntity() { Guid = dto.Guid, Reason = ex.FlattenMessage(), }); } } } //Insert JobRuns. foreach (var dto in request.JobRuns) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { try { var run = dto.ToObservable(); run.ID = 0; run.IsSynchronized = true; if (db.JobRuns.SingleOrDefault(x => x.Guid == run.Guid) == null) { db.JobRuns.Add(run); db.SaveChanges(); } } catch (Exception ex) { response.FailedJobRuns.Add(new SynchronizationFailedEntity() { Guid = dto.Guid, Reason = ex.FlattenMessage(), }); } } } //Insert TangoUpdates. foreach (var dto in request.OfflineUpdates) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { try { var update = dto.ToObservable(); update.ID = 0; update.IsSynchronized = true; if (db.TangoUpdates.SingleOrDefault(x => x.Guid == update.Guid) == null) { db.TangoUpdates.Add(update); db.SaveChanges(); } } catch (Exception ex) { response.FailedOfflineUpdates.Add(new SynchronizationFailedEntity() { Guid = dto.Guid, Reason = ex.FlattenMessage(), }); } } } //Insert MachineEvents. foreach (var dto in request.MachineEvents) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { try { var ev = dto.ToObservable(); ev.ID = 0; ev.IsSynchronized = true; if (db.MachinesEvents.SingleOrDefault(x => x.Guid == ev.Guid) == null) { db.MachinesEvents.Add(ev); db.SaveChanges(); } } catch (Exception ex) { response.FailedMachineEvents.Add(new SynchronizationFailedEntity() { Guid = dto.Guid, Reason = ex.FlattenMessage(), }); } } } } catch (Exception ex) { NotifyUpdateCompleted(new MachineUpdateCompletedRequest() { Status = TangoUpdateStatuses.SynchronizationFailed, FailedReason = ex.FlattenMessage(), FailedLog = null, Token = response.NotifyCompletedToken, }); throw ex; } return response; } [HttpPost] [JwtTokenFilter] public DownloadMachineDataResponse DownloadMachineData(DownloadMachineDataRequest request) { DownloadMachineDataResponse response = new DownloadMachineDataResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) { throw new AuthenticationException("The specified machine could not be found."); } //Send Jobs //if (request.RequestJobs) //{ // var jobs = new JobsCollectionBuilder(db).Set(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).WithSegments().WithBrushStops().Query(x => x.Take(request.MaxJobs).OrderByDescending(z => z.LastUpdated)).BuildList(); // foreach (var job in jobs) // { // JobDTO dto = JobDTO.FromObservable(job); // response.Jobs.Add(dto); // } //} //Send Job Runs //if (request.RequestJobRuns) //{ // var jobRuns = db.JobRuns.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).Take(request.MaxJobRuns).OrderByDescending(x => x.LastUpdated).ToList(); // foreach (var jobRun in jobRuns) // { // JobRunDTO dto = JobRunDTO.FromObservable(jobRun); // response.JobRuns.Add(dto); // } //} //Send Machine Events //if (request.RequestMachineEvents) //{ // var machineEvents = db.MachinesEvents.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized).Take(request.MaxMachinesEvents).OrderByDescending(x => x.LastUpdated).ToList(); // foreach (var machineEvent in machineEvents) // { // MachinesEventDTO dto = MachinesEventDTO.FromObservable(machineEvent); // response.MachineEvents.Add(dto); // } //} //Send DataStore Items if (request.RequestDataStoreItems) { var dataStoreItems = db.DataStoreItems.Where(x => x.MachineGuid == machine.Guid && !x.IsSynchronized & !x.IsDeleted).Take(request.MaxDataStoreItems).OrderByDescending(x => x.LastUpdated).ToList(); foreach (var item in dataStoreItems) { DataStoreItemDTO dto = DataStoreItemDTO.FromObservable(item); response.DataStoreItems.Add(dto); } } } return response; } [HttpPost] [JwtTokenFilter] public NotifyMachineDataDownloadCompletedResponse NotifyMachineDataDownloadCompleted(NotifyMachineDataDownloadCompletedRequest request) { var response = new NotifyMachineDataDownloadCompletedResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) { throw new AuthenticationException("The specified machine could not be found."); } if (request.SynchronizedJobs.Count > 0) { db.Database.ExecuteSqlCommand($"UPDATE JOBS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedJobs.Select(x => "'" + x + "'"))});"); } if (request.SynchronizedJobRuns.Count > 0) { db.Database.ExecuteSqlCommand($"UPDATE JOB_RUNS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedJobRuns.Select(x => "'" + x + "'"))});"); } if (request.SynchronizedMachineEvents.Count > 0) { db.Database.ExecuteSqlCommand($"UPDATE MACHINES_EVENTS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedMachineEvents.Select(x => "'" + x + "'"))});"); } if (request.SynchronizedDataStoreItems.Count > 0) { db.Database.ExecuteSqlCommand($"UPDATE DATA_STORE_ITEMS SET IS_SYNCHRONIZED = 1 WHERE GUID IN ({String.Join(",", request.SynchronizedDataStoreItems.Select(x => "'" + x + "'"))});"); } } return response; } #endregion #region SMS [HttpPost] [JwtTokenFilter] public SendSMSResponse SendSMS(SendSMSRequest request) { var response = new SendSMSResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) { throw new AuthenticationException("The specified machine could not be found."); } if (!machine.AllowSmsNotifications) { throw new Exception("This machine is not allowed to send text messages. Please contact your administrator."); } } if (MachineServiceConfig.TWILIO_ENABLE_SMS) { if (MachineServiceConfig.TWILIO_ENABLE_ALPHA_SENDER) { foreach (var phone in request.PhoneNumbers) { MessageResource.Create(messagingServiceSid: MachineServiceConfig.TWILIO_MESSAGING_SERVICE_SID, to: new PhoneNumber(phone), body: request.Message); } } else { foreach (var phone in request.PhoneNumbers) { MessageResource.Create(from: MachineServiceConfig.TWILIO_FROM_NUMBER, to: new PhoneNumber(phone), body: request.Message); } } } return response; } #endregion #region Version Upload [HttpPost] public LatestVersionResponse GetLatestVersion(LatestVersionRequest request) { LatestVersionResponse response = new LatestVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var versions = db.TangoVersions.Where(x => x.MachineVersionGuid == request.MachineVersionGuid && request.Tag == x.Tag).ToList(); if (versions.Count == 0) { return new LatestVersionResponse() { Version = "0.0.0.0", FirmwareVersion = "0.0.0.0" }; } var latestTangoVersion = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); response.Version = latestTangoVersion.Version; response.FirmwareVersion = latestTangoVersion.FirmwareVersion; } return response; } [HttpPost] public GetTagsResponse GetTags(GetTagsRequest request) { GetTagsResponse response = new GetTagsResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var versions = db.TangoVersions.Where(x => x.MachineVersionGuid == request.MachineVersionGuid).ToList(); if (versions.Count == 0) { return new GetTagsResponse(); } response.Tags = versions.Select(x => x.Tag).Where(x => x != null).Distinct().ToList(); } return response; } [HttpPost] [JwtTokenFilter] public UploadVersionResponse UploadVersion(UploadVersionRequest request) { UploadVersionResponse response = new UploadVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { //Load relations first... db.Roles.ToList(); db.Permissions.ToList(); db.UsersRoles.ToList(); db.RolesPermissions.ToList(); var user_guid = RequestToken.Object.UserGuid; var user = new UserBuilder(db).Set(user_guid).WithRolesAndPermissions().Build(); if (user != null && user.HasPermission(Permissions.PublishPPCVersions)) { if (!request.Tag.IsNotNullOrEmpty()) { request.Tag = null; } var versions = db.TangoVersions.ToList().Where(x => x.MachineVersionGuid == request.MachineVersionGuid && x.Tag == request.Tag).OrderByDescending(x => Version.Parse(x.Version)).ToList(); var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == request.MachineVersionGuid); TangoVersion latestVersion = new TangoVersion(); latestVersion.Version = "0.0.0.0"; if (versions.Count > 0) { latestVersion = versions.FirstOrDefault(); } Version local_version = Version.Parse(request.Version); if (local_version > Version.Parse(latestVersion.Version)) { String newVersionFileName = $"{machine_version.Name} v{local_version.ToString(3) + (request.Tag.IsNotNullOrEmpty() ? $" [{request.Tag}]" : String.Empty)}.tup"; var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER); var blob = container.CreateEmptyBlob(newVersionFileName); response.Token = Guid.NewGuid().ToString(); response.BlobAddress = blob.GenerateWriteSignature(TimeSpan.FromMinutes(30)); PPCPendingUpload pending_upload = new PPCPendingUpload() { UserGuid = user.Guid, Comments = request.Comments, Token = response.Token, Version = request.Version, BlobName = blob.Name, MachineVersionGuid = request.MachineVersionGuid, FirmwareVersion = request.FirmwareVersion, Tag = request.Tag, }; if (request.WithInstaller) { String installerVersionFileName = Path.ChangeExtension(newVersionFileName, ".exe"); var installerBlob = container.CreateEmptyBlob(installerVersionFileName); response.InstallerBlobAddress = installerBlob.GenerateWriteSignature(TimeSpan.FromMinutes(30)); pending_upload.InstallerBlobName = installerBlob.Name; } _pendingUploads.Add(pending_upload); } else { throw new ArgumentException("New version must be greater than latest version."); } } else { throw new AuthenticationException("Invalid user credentials."); } } return response; } [HttpPost] [JwtTokenFilter] public UploadCompletedResponse NotifyVersionUploadCompleted(UploadCompletedRequest request) { PPCPendingUpload upload = _pendingUploads.FirstOrDefault(x => x.Token == request.Token); if (upload != null) { _pendingUploads.RemoveAll(x => x.Token == upload.Token); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { db.TangoVersions.Add(new TangoVersion() { Comments = upload.Comments, BlobName = upload.BlobName, InstallerBlobName = upload.InstallerBlobName, UserGuid = upload.UserGuid, Version = upload.Version, MachineVersionGuid = upload.MachineVersionGuid, FirmwareVersion = upload.FirmwareVersion, Tag = upload.Tag, }); db.SaveChanges(); } return new UploadCompletedResponse(); } else { throw new AuthenticationException("Invalid upload token."); } } [HttpPost] public MachineVersionsResponse GetMachineVersions(MachineVersionsRequest request) { using (var db = ObservablesWebContext.CreateContext()) { return new MachineVersionsResponse() { MachineVersions = db.MachineVersions.ToList().Select(x => MachineVersionDTO.FromObservable(x)).ToList(), }; } } [HttpPost] public LoginResponse Login(LoginRequest request) { LoginResponse response = new LoginResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { if (request.Mode == LoginMode.User) { var authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); //if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email)) //{ // throw new AuthenticationException($"You do not have permissions to access the {MachineServiceConfig.DEPLOYMENT_SLOT.ToDescription()} environment."); //} BasicHashGenerator hash = new BasicHashGenerator(); String pass = hash.Encrypt(request.Password); var user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && x.Password == pass).Build(); if (user == null) { throw new AuthenticationException("Domain user found but the database entry validation failed."); } response.AccessToken = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() { Mode = LoginMode.User, UserGuid = user.Guid, }).AccessToken; } else if (request.Mode == LoginMode.Machine) { var machine = db.Machines.SingleOrDefault(x => x.SerialNumber == request.SerialNumber || x.Guid == request.MachineGuid); if (machine == null) { throw new AuthenticationException("Invalid machine serial number or id."); } response.AccessToken = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() { Mode = LoginMode.Machine, MachineGuid = machine.Guid, }).AccessToken; } } return response; } #endregion #region Telemetry [HttpPost] [JwtTokenFilter] public TelemetrySetCheckPointsResponse SetTelemetryCheckPoints(TelemetrySetCheckPointsRequest request) { TelemetryCheckpointStore store = new TelemetryCheckpointStore(MachineServiceConfig.STORAGE_ACCOUNT); store.SaveMany(RequestToken.Object.MachineGuid, request.Checkpoints); return new TelemetrySetCheckPointsResponse(); } [HttpPost] [JwtTokenFilter] public TelemetryGetCheckPointsResponse GetTelemetryCheckPoints(TelemetryGetCheckPointsRequest request) { TelemetryCheckpointStore store = new TelemetryCheckpointStore(MachineServiceConfig.STORAGE_ACCOUNT); var checkPoints = store.GetAllForMachine(RequestToken.Object.MachineGuid).ToList(); return new TelemetryGetCheckPointsResponse() { Checkpoints = checkPoints }; } [HttpPost] [JwtTokenFilter] public TelemetryDeviceRegistrationResponse GetTelemetryDeviceConnection(TelemetryDeviceRegistrationRequest request) { var response = new TelemetryDeviceRegistrationResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid); if (machine == null) throw new AuthenticationException("The specified machine could not be found."); string serialNumber = machine.SerialNumber; string storageAccount = MachineServiceConfig.STORAGE_ACCOUNT; // Azure Storage CS string iotHubService = MachineServiceConfig.IOT_HUB_CONNECTION_STRING; // iothubowner SAS (service) CS var mgr = new TelemetryDeviceRegistrationManager(storageAccount, iotHubService); string iotHubDeviceConnectionString = mgr.GetOrCreateDeviceConnectionString(machine.Guid, serialNumber); response.ConnectionString = iotHubDeviceConnectionString; } return response; } #endregion } }