using SendGrid; using SendGrid.Helpers.Mail; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Authentication; using System.Threading.Tasks; using System.Web; using System.Web.Http; using Tango.BL; using Tango.BL.Builders; using Tango.BL.Entities; using Tango.Core; using Tango.Core.Cryptography; using Tango.Core.DB; using Tango.FSE.Web.Messages; using Tango.MachineService.Filters; using Tango.Web.Controllers; using Tango.Web.Helpers; using Tango.Web.Security; using Tango.Web.SMO; using Tango.Web.SQLServer; using Tango.Web.Storage; using System.Data.Entity; using static Tango.MachineService.Controllers.FSEController; using Tango.MachineService.Models; using Tango.BL.Enumerations; using Tango.BL.DTO; namespace Tango.MachineService.Controllers { public class FSEController : TangoController { private static List _pendingUploads; public class TokenObject { public String UserGuid { get; set; } } public class PasswordReset { public String ID { get; set; } public String UserGuid { get; set; } public String FullName { get; set; } } public static List PendingPasswordResets { get; set; } static FSEController() { _pendingUploads = new List(); PendingPasswordResets = new List(); } [HttpPost] public LoginResponse Login(LoginRequest request) { User user = null; DataSource dataSource = null; IHashGenerator hash = new BasicHashGenerator(); Version client_version; if (!Version.TryParse(request.Version, out client_version)) { client_version = new Version("1.0.0.0"); } var password = hash.Encrypt(request.Password); using (var db = ObservablesWebContext.CreateContext()) { user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && x.Password == password).WithRolesAndPermissions().WithDeleted().Build(); if (user == null) { throw new AuthenticationException("Invalid email or password."); } if (user.Deleted) { throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); } if (request.Build == BuildVariants.FSE && !user.HasPermission(Permissions.FSE_RunFSE)) { throw new AuthenticationException("You do not have permission to access Tango FSE. Please contact your administrator."); } if (request.Build == BuildVariants.TwineRSM && !user.HasPermission(Permissions.TwineRSMAccess)) { throw new AuthenticationException("You do not have permission to access Twine Studio. Please contact your administrator."); } user.LastLogin = DateTime.UtcNow; db.SaveChanges(); } SQLServerManager sqlServer = new SQLServerManager(); var accessToken = sqlServer.GetAccessToken(); dataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, Type = DataSourceType.AccessToken, IntegratedSecurity = false, AccessToken = accessToken.AccessToken, AccessTokenExpiration = accessToken.ExpiresOn.UtcDateTime }; //Return data source return new LoginResponse() { DataSource = dataSource, AccessToken = WebToken.CreateNew(MachineServiceConfig.JWT_TOKEN_SECRET, new TokenObject() { UserGuid = user.Guid, }, DateTime.UtcNow.AddDays(1)).AccessToken, PasswordChangeRequired = user.PasswordChangeRequired }; } [HttpPost] public GetUserResponse GetUser(GetUserRequest request) { using (var db = ObservablesWebContext.CreateContext()) { var user = new UserBuilder(db).Set(x => !x.Deleted && x.Email.ToLower() == request.Email.ToLower()) .WithOrganization() .WithRolesAndPermissions() .Build(); if (user == null) throw new AuthenticationException("User not found."); var userDTO = UserDTO.FromObservable(user); return new GetUserResponse() { User = userDTO }; } } [HttpPost] public GetMachineResponse GetMachine(GetMachineRequest request) { using (var db = ObservablesWebContext.CreateContext()) { Machine machine = null; if (request.GetExtendedInfo) { machine = new MachineBuilder(db) .Set(x => (request.AllowAllMachines || x.OrganizationGuid == request.OrganizationGuid) && x.SerialNumber == request.SerialNumber) .WithOrganization() .WithVersion() .WithSpools() .WithConfiguration().Build(); } else { machine = new MachineBuilder(db) .Set(x => (request.AllowAllMachines || x.OrganizationGuid == request.OrganizationGuid) && x.SerialNumber == request.SerialNumber) .WithOrganization() .Build(); } if (machine != null) { return new GetMachineResponse() { Machine = MachineDTO.FromObservable(machine) }; } return new GetMachineResponse(); } } [HttpPost] public GetAllMachinesResponse GetAllMachines(GetAllMachinesRequest request) { using (var db = ObservablesWebContext.CreateContext()) { var machines = db.Machines.Where(x => request.AllowAllMachines || x.OrganizationGuid == request.OrganizationGuid).Include(x => x.Organization).ToList(); var machinesDTO = machines.Select(x => MachineDTO.FromObservable(x)).ToList(); return new GetAllMachinesResponse() { Machines = machinesDTO }; } } [HttpPost] public GetEventTypesResponse GetEventTypes(GetEventTypesRequest request) { GetEventTypesResponse response = new GetEventTypesResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var eventTypes = db.EventTypes.ToList(); response.EventTypes = eventTypes.Select(x => EventTypeDTO.FromObservable(x)).ToList(); } return response; } [HttpPost] public GetProceduresResponse GetProcedures(GetProceduresRequest request) { GetProceduresResponse response = new GetProceduresResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var select = db.PublishedProcedureProjects .Where(x => x.IsVisible || !request.GetOnlyVisible) .Include(x => x.PublishedProcedureProjectsVersions) .Select(x => new { Project = x, LatestVersion = x.PublishedProcedureProjectsVersions.OrderByDescending(v => v.Version).FirstOrDefault() }).ToList(); List projects = new List(); foreach (var p in select) { PublishedProcedureProject project = p.Project; projects.Add(project); } response.Projects = projects.Select(x => PublishedProcedureProjectDTO.FromObservable(x)).ToList(); } return response; } [HttpPost] public GetTangoVersionsResponse GetTangoVersions(GetTangoVersionsRequest request) { GetTangoVersionsResponse response = new GetTangoVersionsResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var tangoVersions = db.TangoVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).ToList(); response.Versions = tangoVersions.Select(x => TangoVersionDTO.FromObservable(x)).ToList(); } return response; } [HttpPost] public GetTechComponentsResponse GetTechComponents(GetTechComponentsRequest request) { GetTechComponentsResponse response = new GetTechComponentsResponse(); using (var db = ObservablesWebContext.CreateContext()) { if (request.Blowers) { response.Blowers = db.HardwareBlowerTypes.ToList().Select(x => HardwareBlowerTypeDTO.FromObservable(x)).ToList(); } if (request.Controllers) { response.Controllers = db.TechControllers.ToList().Select(x => TechControllerDTO.FromObservable(x)).ToList(); } if (request.Dispensers) { response.Dispensers = db.TechDispensers.ToList().Select(x => TechDispenserDTO.FromObservable(x)).ToList(); } if (request.Heaters) { response.Heaters = db.TechHeaters.ToList().Select(x => TechHeaterDTO.FromObservable(x)).ToList(); } if (request.IOs) { response.IOs = db.TechIos.ToList().Select(x => TechIoDTO.FromObservable(x)).ToList(); } if (request.Monitors) { response.Monitors = db.TechMonitors.ToList().Select(x => TechMonitorDTO.FromObservable(x)).ToList(); } if (request.Motors) { response.Motors = db.HardwareMotorTypes.ToList().Select(x => HardwareMotorTypeDTO.FromObservable(x)).ToList(); } if (request.Valves) { response.Valves = db.TechValves.ToList().Select(x => TechValveDTO.FromObservable(x)).ToList(); } } return response; } [HttpPost] [JwtTokenFilter] public BugReportingInfoResponse GetBugReportInfo(BugReportingInfoRequest request) { return new BugReportingInfoResponse() { CollectionUrl = MachineServiceConfig.TFS_COLLECTION_URL, PersonalToken = MachineServiceConfig.TFS_PERSONAL_TOKEN, UserEmail = MachineServiceConfig.FSE_TFS_USER_EMAIL }; } [HttpPost] [JwtTokenFilter] public DownloadTangoVersionResponse DownloadTangoVersion(DownloadTangoVersionRequest request) { DownloadTangoVersionResponse response = new DownloadTangoVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var tangoVersion = db.TangoVersions.SingleOrDefault(x => x.Guid == request.TangoVersionGuid); if (tangoVersion == null) { throw new ArgumentException("Could not locate the specified Tango version."); } response.Version = tangoVersion.Version; var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.TANGO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(tangoVersion.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(PPCController.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, }; } return response; } [HttpPost] [JwtTokenFilter] public CheckForUpdatesResponse CheckForUpdates(CheckForUpdatesRequest request) { CheckForUpdatesResponse response = new CheckForUpdatesResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var versions = db.FseVersions.ToList().Where(x => x.BuildVariant == request.Build.ToInt32()).ToList(); FseVersion latestVersion = null; latestVersion = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); Version currentVersion = Version.Parse(request.Version); String comments = String.Join(Environment.NewLine, versions.OrderBy(x => Version.Parse(x.Version)).Where(x => Version.Parse(x.Version) > currentVersion).Select(x => x.Comments)); if (latestVersion != null && Version.Parse(latestVersion.Version) > currentVersion) { var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.FSE_VERSIONS_CONTAINER); //var blob = container.GetBlockBlobReference(latestVersion.BlobName); var installerBlob = container.GetBlockBlobReference(latestVersion.InstallerBlobName); //response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) { //response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; response.InstallerCdnAddress = MachineServiceConfig.CDN_ENDPOINT + installerBlob.Uri.AbsolutePath; } response.IsUpdateAvailable = true; response.Version = latestVersion.Version; response.Comments = latestVersion.Comments; } } return response; } [HttpPost] [JwtTokenFilter] public RefreshTokenResponse RefreshToken(RefreshTokenRequest request) { SQLServerManager sqlServer = new SQLServerManager(); var accessToken = sqlServer.GetAccessToken(); return new RefreshTokenResponse() { AccessToken = accessToken.AccessToken, Expiration = accessToken.ExpiresOn.UtcDateTime, }; } [HttpPost] [JwtTokenFilter] public UserInvitationEmailResponse SendUserInvitationEmail(UserInvitationEmailRequest request) { User user; using (ObservablesContext db = ObservablesWebContext.CreateContext()) { user = db.Users.Include(x => x.Contact).SingleOrDefault(x => x.Guid == request.UserGuid); if (user == null) { throw new InvalidOperationException("User not found."); } } var client = new SendGridClient(MachineServiceConfig.SEND_GRID_API_KEY); SendGridMessage msg = new SendGridMessage(); msg.SetFrom("info@twine-s.com", "Twine Solutions LTD"); msg.AddTo(user.Email); msg.Subject = "Welcome To Tango FSE"; msg.SetTemplateId("d-2af42ed0ea3c44b3abaa61016223555a"); var dynamicTemplateData = new { DownloadUrl = $"{request.MachineServiceAddress}/fse", FullName = user.Contact.FirstName, Password = request.Password, }; msg.SetTemplateData(dynamicTemplateData); var result = client.SendEmailAsync(msg).GetAwaiter().GetResult(); if (result.StatusCode != HttpStatusCode.Accepted) { throw new HttpException(result.StatusCode.ToString()); } return new UserInvitationEmailResponse(); } [HttpPost] public ForgotPasswordResponse SendForgotPasswordEmail(ForgotPasswordRequest request) { User user; using (ObservablesContext db = ObservablesWebContext.CreateContext()) { user = db.Users.Include(x => x.Contact).SingleOrDefault(x => x.Email.ToLower() == request.Email.ToLower()); if (user == null) { throw new InvalidOperationException("User not found."); } } String resetId = Guid.NewGuid().ToString(); var client = new SendGridClient(MachineServiceConfig.SEND_GRID_API_KEY); SendGridMessage msg = new SendGridMessage(); msg.SetFrom("info@twine-s.com", "Twine Solutions LTD"); msg.AddTo(request.Email); msg.Subject = "Tango FSE Password Reset"; msg.SetTemplateId("d-18065487dae4456b8684d4b47a91e4a6"); var dynamicTemplateData = new { ResetPasswordUrl = $"{request.MachineServiceAddress}/FSEAccount/ResetPassword?id={resetId}", FullName = user.Contact.FirstName, }; msg.SetTemplateData(dynamicTemplateData); var result = client.SendEmailAsync(msg).GetAwaiter().GetResult(); if (result.StatusCode != HttpStatusCode.Accepted) { throw new HttpException(result.StatusCode.ToString()); } PendingPasswordResets.Add(new PasswordReset() { ID = resetId, UserGuid = user.Guid, FullName = user.Contact.FirstName, }); return new ForgotPasswordResponse(); } #region Version Upload [HttpPost] public LatestVersionResponse GetLatestVersion(LatestVersionRequest request) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var version = db.FseVersions.ToList().Where(x => x.BuildVariant == request.Build.ToInt32()).OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); return new LatestVersionResponse() { Version = version != null ? version.Version : "0.0.0.0" }; } } [HttpPost] [JwtTokenFilter] public UploadVersionResponse UploadVersion(UploadVersionRequest request) { UploadVersionResponse response = new UploadVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { String userID = RequestToken.Object.UserGuid; var user = new UserBuilder(db).Set(userID).WithRolesAndPermissions().Build(); if (user != null && user.HasPermission(Permissions.PublishMachineStudioVersions)) { var latestVersion = db.FseVersions.ToList().Where(x => x.BuildVariant == request.Build.ToInt32()).ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); Version local_version = Version.Parse(request.Version); if (latestVersion == null || local_version > Version.Parse(latestVersion.Version)) { var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.FSE_VERSIONS_CONTAINER); var installerBlob = container.CreateEmptyBlob(request.InstallerBlobName); response.Token = Guid.NewGuid().ToString(); response.InstallerBlobAddress = installerBlob.GenerateWriteSignature(TimeSpan.FromMinutes(30)); FSEPendingUpload pending_upload = new FSEPendingUpload() { UserGuid = user.Guid, Comments = request.Comments, Token = response.Token, Version = request.Version, BlobName = "BLOB", InstallerBlobName = installerBlob.Name, BuildVariant = request.Build.ToInt32() }; _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) { FSEPendingUpload upload = _pendingUploads.FirstOrDefault(x => x.Token == request.Token); if (upload != null) { _pendingUploads.RemoveAll(x => x.Token == upload.Token); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { db.FseVersions.Add(new FseVersion() { Comments = upload.Comments, BlobName = upload.BlobName, InstallerBlobName = upload.InstallerBlobName, UserGuid = upload.UserGuid, Version = upload.Version, BuildVariant = upload.BuildVariant }); db.SaveChanges(); } return new UploadCompletedResponse(); } else { throw new ArgumentException("Invalid Token."); } } #endregion } }