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; namespace Tango.MachineService.Controllers { public class PPCController : TangoController { private static List _pendingUploads; private ActiveDirectoryManager _ad_manager; 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(); } public PPCController() { _ad_manager = new ActiveDirectoryManager(); } #endregion #region Setup & Update [HttpPost] [JwtTokenFilter] public MachineSetupResponse MachineSetup(MachineSetupRequest request) { MachineSetupResponse response = new MachineSetupResponse(); LogManager.Log("Setup request received: " + request.ToString()); using (ObservablesContext db = ObservablesContextHelper.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."); } 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; db.SaveChanges(); var machine_version = db.MachineVersions.SingleOrDefault(x => x.Guid == machine.MachineVersionGuid); var latest_machine_version = db.TangoVersions.Where(x => x.MachineVersionGuid == machine_version.Guid).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); response.Version = latest_machine_version.Version; 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)); DbCredentials credentials = new DbCredentials(); using (SmoManager smo = new SmoManager()) { credentials = smo.CreateRandomLoginAndUser(); Task.Delay(TimeSpan.FromMinutes(10)).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.IsDemo = machine.IsDemo; } return response; } [HttpPost] [JwtTokenFilter] public DownloadUpdateResponse MachineUpdate(DownloadUpdateRequest request) { DownloadUpdateResponse response = new DownloadUpdateResponse(); using (ObservablesContext db = ObservablesContextHelper.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).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); response.Version = latest_machine_version.Version; 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)); DbCredentials credentials = new DbCredentials(); using (SmoManager smo = new SmoManager()) { credentials = smo.CreateRandomLoginAndUser(); Task.Delay(TimeSpan.FromMinutes(10)).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, }; } return response; } [HttpPost] [JwtTokenFilter] public CheckForUpdateResponse CheckForUpdates(CheckForUpdateRequest request) { CheckForUpdateResponse response = new CheckForUpdateResponse(); using (ObservablesContext db = ObservablesContextHelper.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).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); if (Version.Parse(latest_machine_version.Version) > Version.Parse(request.Version)) { response.IsUpdateAvailable = true; } response.Version = latest_machine_version.Version; if (machine.ForceVersionUpdate) { response.IsUpdateAvailable = true; } } } return response; } [HttpPost] [JwtTokenFilter] public UpdateDBResponse UpdateDB(UpdateDBRequest request) { UpdateDBResponse response = new UpdateDBResponse(); using (ObservablesContext db = ObservablesContextHelper.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(10)).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, }; } return response; } #endregion #region Version Upload [HttpPost] public LatestVersionResponse GetLatestVersion(LatestVersionRequest request) { LatestVersionResponse response = new LatestVersionResponse(); using (ObservablesContext db = ObservablesContextHelper.CreateContext()) { var versions = db.TangoVersions.Where(x => x.MachineVersionGuid == request.MachineVersionGuid).ToList(); if (versions.Count == 0) { return new LatestVersionResponse() { Version = "0.0.0.0", }; } response.Version = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault().Version; } return response; } [HttpPost] [JwtTokenFilter] public UploadVersionResponse UploadVersion(UploadVersionRequest request) { UploadVersionResponse response = new UploadVersionResponse(); using (ObservablesContext db = ObservablesContextHelper.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)) { var versions = db.TangoVersions.ToList().Where(x => x.MachineVersionGuid == request.MachineVersionGuid).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} - Tango Version {local_version.ToString()}.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, }; 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 = ObservablesContextHelper.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, }); db.SaveChanges(); } return new UploadCompletedResponse(); } else { throw new AuthenticationException("Invalid upload token."); } } [HttpPost] public MachineVersionsResponse GetMachineVersions(MachineVersionsRequest request) { using (var db = ObservablesContextHelper.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 = ObservablesContextHelper.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); if (machine == null) { throw new AuthenticationException("Invalid serial number."); } response.AccessToken = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() { Mode = LoginMode.Machine, MachineGuid = machine.Guid, }).AccessToken; } } return response; } #endregion } }