using Microsoft.IdentityModel.Clients.ActiveDirectory; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Authentication; using System.Web.Http; using Tango.BL; using Tango.BL.Builders; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core.Cryptography; using Tango.MachineService.Models; using Tango.MachineStudio.Common.Authentication; using System.Data.Entity; using Tango.MachineStudio.Common.Web; using Tango.Web.Controllers; using Tango.Web.Helpers; using Tango.Web.Storage; using Tango.Web.Security; using Tango.Web.ActiveDirectory; using Tango.MachineService.Filters; using Tango.MachineService.Security; using Tango.Web.SQLServer; using Tango.Core; using Tango.Web.SMO; using Tango.Core.DB; using System.Threading.Tasks; namespace Tango.MachineService.Controllers { public class MachineStudioController : TangoController { private static List _pendingUploads; private ActiveDirectoryManager _ad_manager; public class TokenObject { public String UserGuid { get; set; } } #region Constructors /// /// Initializes the class. /// static MachineStudioController() { _pendingUploads = new List(); } /// /// Initializes a new instance of the class. /// public MachineStudioController() : base() { _ad_manager = new ActiveDirectoryManager(); } #endregion #region Actions /// /// Checks for updates. /// /// The request. /// [HttpPost] [JwtTokenFilter] public CheckForUpdatesResponse CheckForUpdates(CheckForUpdatesRequest request) { LogManager.Log("Request received..."); var userID = RequestToken.Object.UserGuid; CheckForUpdatesResponse response = new CheckForUpdatesResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var versions = db.MachineStudioVersions.ToList(); MachineStudioVersion 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.MACHINE_STUDIO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(latestVersion.BlobName); response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) { response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; } response.IsUpdateAvailable = true; response.Version = latestVersion.Version; response.Comments = latestVersion.Comments; } } return response; } /// /// Downloads the latest version. /// /// The request. /// [HttpPost] [JwtTokenFilter] public DownloadLatestVersionResponse DownloadLatestVersion(DownloadLatestVersionRequest request) { DownloadLatestVersionResponse response = new DownloadLatestVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var versions = db.MachineStudioVersions.ToList(); MachineStudioVersion latestVersion = versions.OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); if (latestVersion != null) { var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.MACHINE_STUDIO_VERSIONS_CONTAINER); var blob = container.GetBlockBlobReference(latestVersion.BlobName); if (!String.IsNullOrWhiteSpace(MachineServiceConfig.CDN_ENDPOINT)) { response.CdnAddress = MachineServiceConfig.CDN_ENDPOINT + blob.Uri.AbsolutePath; } response.BlobAddress = blob.GenerateReadSignature(TimeSpan.FromMinutes(60)); response.Version = latestVersion.Version; } } return response; } /// /// Uploads a version. /// /// The request. /// /// New version must be greater than latest version. /// Invalid user credentials. [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.MachineStudioVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); Version local_version = Version.Parse(request.Version); if (latestVersion == null || local_version > Version.Parse(latestVersion.Version)) { String newVersionFileName = "Machine Studio v" + local_version.ToString() + ".zip"; var manager = new BlobStorageManager(); var container = manager.GetContainer(MachineServiceConfig.MACHINE_STUDIO_VERSIONS_CONTAINER); var blob = container.CreateEmptyBlob(newVersionFileName); response.Token = Guid.NewGuid().ToString(); response.BlobAddress = blob.GenerateWriteSignature(TimeSpan.FromMinutes(30)); MachineStudioPendingUpload pending_upload = new MachineStudioPendingUpload() { UserGuid = user.Guid, Comments = request.Comments, Token = response.Token, Version = request.Version, BlobName = blob.Name, }; if (request.WithInstaller) { String installerVersionFileName = "Machine Studio v" + local_version.ToString() + ".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; } /// /// Notifies about a version upload completion. /// /// The request. /// /// Invalid Token. [HttpPost] [JwtTokenFilter] public UploadCompletedResponse NotifyVersionUploadCompleted(UploadCompletedRequest request) { MachineStudioPendingUpload upload = _pendingUploads.FirstOrDefault(x => x.Token == request.Token); if (upload != null) { _pendingUploads.RemoveAll(x => x.Token == upload.Token); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { db.MachineStudioVersions.Add(new MachineStudioVersion() { Comments = upload.Comments, BlobName = upload.BlobName, InstallerBlobName = upload.InstallerBlobName, UserGuid = upload.UserGuid, Version = upload.Version, }); db.SaveChanges(); } return new UploadCompletedResponse(); } else { throw new ArgumentException("Invalid Token."); } } /// /// Gets the latest version. /// /// The request. /// [HttpPost] public LatestVersionResponse GetLatestVersion(LatestVersionRequest request) { using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var version = db.MachineStudioVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); return new LatestVersionResponse() { Version = version != null ? version.Version : "0.0.0.0" }; } } /// /// Login to the service. /// /// The request. /// /// [HttpPost] public LoginResponse Login(LoginRequest request) { AuthenticationResult authResult = null; 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"); } bool versionChangeRequired = false; String requiredVersion = null; bool isPasswordOK = false; try { authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); isPasswordOK = true; } catch { } //Login via Active Directory if (request.Method == LoginMethod.ActiveDirectory) { try { authResult = _ad_manager.ValidateUserCredentials(request.Email, request.Password); } catch (Exception ex) { throw new AuthenticationException(ex.FlattenMessage()); } if (!_ad_manager.CanUserAccessCurrentEnvironment(request.Email)) { throw new AuthenticationException($"You do not have permissions to access the {MachineServiceConfig.DEPLOYMENT_SLOT.ToDescription()} environment."); } using (ObservablesContext db = ObservablesWebContext.CreateContext()) { db.Roles.ToList(); db.Permissions.ToList(); db.UsersRoles.ToList(); db.RolesPermissions.ToList(); user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower()).WithRolesAndPermissions().WithDeleted().Build(); if (user == null) { user = new User(); user.Email = request.Email; user.Password = hash.Encrypt(request.Password); user.Organization = db.Organizations.Include(x => x.Address).Single(x => x.Name == "Twine"); user.Address = user.Organization.Address.Clone(); user.Contact = new Contact() { FirstName = authResult.UserInfo.GivenName, LastName = authResult.UserInfo.FamilyName, FullName = authResult.UserInfo.GivenName + " " + authResult.UserInfo.FamilyName, Email = request.Email, }; db.UsersRoles.Add(new UsersRole() { User = user, Role = db.Roles.Single(x => (Roles)x.Code == Roles.User), }); db.UsersRoles.Add(new UsersRole() { User = user, Role = db.Roles.Single(x => (Roles)x.Code == Roles.MachineStudioUser), }); user.Password = hash.Encrypt(request.Password); db.Users.Add(user); } else { if (user.Deleted) { throw new AuthenticationException("Your account has been disabled. Please contact your administrator."); } } user.LastLogin = DateTime.UtcNow; db.SaveChanges(); } dataSource = new DataSource() { Address = MachineServiceConfig.DB_ADDRESS, Catalog = MachineServiceConfig.DB_CATALOG, Type = Core.DataSourceType.Azure, IntegratedSecurity = false, UserName = request.Email, Password = request.Password, }; } //Login via Database standard user else { var password = hash.Encrypt(request.Password); using (var db = ObservablesWebContext.CreateContext()) { user = new UserBuilder(db).Set(x => x.Email.ToLower() == request.Email.ToLower() && (isPasswordOK || 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."); } 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 = Core.DataSourceType.AccessToken, IntegratedSecurity = false, AccessToken = accessToken.AccessToken, AccessTokenExpiration = accessToken.ExpiresOn.UtcDateTime }; } //Enforce Machine Studio Version ? if (MachineServiceConfig.ENFORCE_MACHINE_STUDIO_VERSION) { using (var db = ObservablesWebContext.CreateContext()) { var latest_version = db.MachineStudioVersions.ToList().OrderByDescending(x => Version.Parse(x.Version)).FirstOrDefault(); if (latest_version != null && Version.Parse(latest_version.Version) != client_version) { versionChangeRequired = true; requiredVersion = latest_version.Version; } } } //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, VersionChangeRequired = versionChangeRequired, RequiredVersion = requiredVersion, PasswordChangeRequired = request.Method == LoginMethod.StandardUser && user.PasswordChangeRequired }; } [JwtTokenFilter] public RefreshTokenResponse RefreshToken(RefreshTokenRequest request) { SQLServerManager sqlServer = new SQLServerManager(); var accessToken = sqlServer.GetAccessToken(); //TokenManager tokenManager = new TokenManager(); //tokenManager.UpdateToken(request.AccessToken, accessToken.AccessToken, accessToken.ExpiresOn.UtcDateTime); return new RefreshTokenResponse() { AccessToken = accessToken.AccessToken, Expiration = accessToken.ExpiresOn.UtcDateTime, }; } [HttpPost] [JwtTokenFilter] public DownloadLatestPPCVersionResponse DownloadLatestPPCVersion(DownloadLatestPPCVersionRequest request) { DownloadLatestPPCVersionResponse response = new DownloadLatestPPCVersionResponse(); using (ObservablesContext db = ObservablesWebContext.CreateContext()) { var machine = db.Machines.SingleOrDefault(x => x.SerialNumber == request.SerialNumber); 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)); 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; } #endregion } }