using System; using System.Collections.Generic; using System.Text; using IndianHealthService.BMXNet.WinForm.Configuration; using IndianHealthService.BMXNet.Net; using System.Security.Principal; using IndianHealthService.BMXNet.Forms; using IndianHealthService.BMXNet.WinForm.Forms; using System.Windows.Forms; using IndianHealthService.BMXNet.WinForm.Model; namespace IndianHealthService.BMXNet.WinForm { /// /// This class models the workflow of the LoginProcess. It correographs UI and non-UI /// login methods, login cancelling, management dialogs, and provides hooks (events) to /// customize the login workflow. /// /// /// See the SDK for other examples. /// /// this.Framework = WinFramework.CreateWithNetworkBroker(true); /// this.Framework.LoadSettings(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "settings"); /// this.Framework.LoadConnectionSpecs(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "connectiosn"); /// LoginProcess login = this.Framework.CreateLoginProcess(); /// /// //Attempt a non-UI WindowsAuth if and only if there is a default connection with WindowsAuth /// //Of course, an application can set its own policy of when to AttemptWindowsAuthLogin() /// /// if (login.HasDefaultConnectionWithUseWindowsAuth) /// { /// login.AttemptWindowsAuthLogin(); /// } /// /// //If not attempted yet, i.e. skipped the AttemptWindowsAuthLogin(), or was unsuccessul, try and UI login /// if (!login.WasLoginAttempted || !login.WasSuccessful) /// { /// login.AttemptUserInputLogin(IndianHealthService.BMXNet.Util.EntryAssemblyInfo.AssemblyTitle+" Login", 3,!this.Framework.BootStrapSettings.Get("lockedit",false), this); /// } /// /// //If the login process was unable to login after the max tries (or fow other configuration reasons), exit the application /// if (!login.WasSuccessful) /// { /// this.Close(); /// return; /// } /// /// // Making sure that the user's division is set. Can use AttemptUserInputSetDivision() or the application can devise another solution /// if ((this.Framework.User.Division == null) && !this.Framework.AttemptUserInputSetDivision("Set Initial Division", this)) /// { /// this.Close(); /// return; /// } /// /// // Logged in with valid user and division /// this.RemoteSession = this.Framework.PrimaryRemoteSession; /// public class LoginProcess { /// /// Triggered before every login attempt. See for details . /// public event EventHandler AttemptingLogin; /// /// Triggered after every login attempt. See for details . /// public event EventHandler LoginAttempted; private bool _cancel = false; /// /// During a LoginAttempted the LoginProcess can be Cancelled by setting Cancel to True. /// public bool Cancel { get { return _cancel; } set { _cancel = value; } } private bool _IsSwitchServerModeEnabled = false; /// /// If set to True, the Connection combo box on the Login screen with be dropped down when the /// dialog is displayed. This is useful for applications that have an option to change Connections. /// The default is False. /// public bool IsSwitchServerModeEnabled { get { return _IsSwitchServerModeEnabled; } set { _IsSwitchServerModeEnabled = value; } } private bool _autoSetDivisionToLastLookup = true; /// /// If set to True, RPMS is checked and if there was a previously set division for the user it will be used, otherwise /// MustResolveDivision will be set to True and the division will need to be set for the user. /// The default is True /// public bool AutoSetDivisionToLastLookup { get { return _autoSetDivisionToLastLookup; } set { _autoSetDivisionToLastLookup = value; } } private bool _mustResolveDivision = false; /// /// If the division for the user has not been determine after the LoginProcess, MustResolveDivision will be set to True. /// The default if False. /// public bool MustResolveDivision { get { return _mustResolveDivision; } set { _mustResolveDivision = value; } } private int _loginAttempts = 0; /// /// The number of login attempts so far. This value can be modified during the AttemptingLogin and LoginAttempted events. /// public int LoginAttempts { get { return _loginAttempts; } set { _loginAttempts = value; } } private int _maxAttempts = 3; /// /// The number of login attempts before cancelling the LoginProcess. The default value is 3. This value can be modified during the AttemptingLogin and LoginAttempted events. /// public int MaxAttempts { get { return _maxAttempts; } set { _maxAttempts = value; } } private bool _wasLoginAttempted = false; /// /// True if a login was attempted. False if the user is presented with a LoginDialog and "Cancel" is selected. /// The value is changed during every login attempt cycle. /// public bool WasLoginAttempted { get { return _wasLoginAttempted; } set { _wasLoginAttempted = value; } } private bool _wasSuccessful = false; /// /// True if the most recent login attempt was successful. /// public bool WasSuccessful { get { return _wasSuccessful; } set { _wasSuccessful = value; } } private String _failureMessage = null; /// /// A reasonable message to display to the user if the last login attempt failed. /// public String FailureMessage { get { return _failureMessage; } set { _failureMessage = value; } } private Exception _failureException = null; /// /// If an exception occured during the last login attempt, FailureException will be set to it. /// public Exception FailureException { get { return _failureException; } set { _failureException = value; } } private RpmsConnectionSpec _connectionSpec = null; /// /// The active ConnectionSpec being used to login. With care, the property can be changed or the instance can be modified during the AttemptingLogin and LoginAttempted events /// with care. /// public RpmsConnectionSpec ConnectionSpec { get { return _connectionSpec; } set { _connectionSpec = value; } } internal LoginProcess(WinFramework aFramework) { this.Framework = aFramework; } internal RpmsConnectionSettings Settings { get { return this.Framework.ConnectionSettings; } } private WinFramework _framework = null; internal protected WinFramework Framework { get { return _framework; } set { _framework = value; } } /// /// Answer True if there is a default managed connection that uses WindowsAuthenication /// public bool HasDefaultConnectionWithUseWindowsAuth { get { RpmsConnectionSpec spec = this.Settings.DefaultConnectionSpec; return spec == null ? false : spec.UseWindowsAuthentication; } } /// /// Attempt a WindowsAuthentication Login and answer true if it was successful. /// /// True if login was successful public bool AttemptWindowsAuthLogin(RpmsConnectionSpec aConnectionSpec) { RpmsConnectionSpec spec = aConnectionSpec; if (spec == null) { return this.Failed("A default connection spec must be set for automatic Windows Authenication"); } if (spec.UseWindowsAuthentication) { return this.PrimitiveAttemptWindowsAuthLogin(spec); } else { return this.Failed("Connection spec must be set for automatic Windows Authenication for auto-login"); } } /// /// Using the current default connection spec, attempt a WindowsAuthentication Login and answer true if it was successful. /// /// /// Most common approach when using the DefaultConnection feature the RpmsConnection spec class. /// /// True if login was successful public bool AttemptWindowsAuthLogin() { return this.AttemptWindowsAuthLogin(this.Settings.DefaultConnectionSpec); } private bool Failed(string aMessage) { this.WasLoginAttempted = true; this.WasSuccessful = false; this.FailureMessage = aMessage; return this.WasSuccessful; } /// /// Attempt an interactive UI login. There are several useful arguments to control the process and appearance of the this process. /// /// The title of the login. Customize to be something like "MyApplication Login" /// The number of attempts before cancelling /// If false, the user will not be able to change the connections. This is useful for lockdown situations /// Provide your main application as the window owner so the LoginDialog box is correctly managed. /// True if login was successful public bool AttemptUserInputLogin(string aDialogTitle, int maxAttempts, bool enableConnectionManagement,IWin32Window aUiOwnerForPositioningDialog) { this.MaxAttempts = maxAttempts; RpmsLoginView view = (RpmsLoginView)new RpmsLoginDialog(); view.Title = aDialogTitle; RpmsLoginPresenter presenter = new RpmsLoginPresenter(); presenter.EnableConnectionManagement = enableConnectionManagement; presenter.View = view; presenter.Model = this; presenter.UiOwner = aUiOwnerForPositioningDialog; presenter.Open(); return this.WasSuccessful; } /// /// If the application is managing the actual login, send Succeeded() to indicate success. /// /// /// Do not set WasSuccessful to true. /// /// True if successful public bool Succeeded() { this.WasLoginAttempted = true; this.WasSuccessful = true; this.FailureMessage = ""; this.Framework.Login = this; return this.WasSuccessful; } internal bool PrimitiveAttemptWindowsAuthLogin(RpmsConnectionSpec aSpec) { BMXNetBroker broker = this.Framework.SocketBroker; this.ConnectionSpec = aSpec; if (this.AttemptingLogin != null) { AttemptingLoginEventArgs args = new AttemptingLoginEventArgs(); args.Process = this; this.AttemptingLogin(this, args); if (args.Handled) { return this.WasSuccessful; } if (args.Cancel) { this.Cancel = true; return false; } } try { if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, WindowsIdentity.GetCurrent(),aSpec.SendTimeout,aSpec.ReceiveTimeout)) { this.Succeeded(); this.ResolveDivision(broker); return this.WasSuccessful; } else { return this.Failed("Unable to authenicate. Use may need to login"); } } catch (BMXNetException problem) { this.FailureException = problem; return this.Failed(problem.Message); } catch (Exception anException) { this.FailureException = anException; return this.Failed("Critical issue: " + anException.Message); } } public VerifyCodeUpdateResult AttemptVerifyCodeChange(WinFramework bmxFramework, RpmsConnectionSpec bmxConnectionSpec, string strAccessCode) { // Open the verify code upate dialog VerifyCodeUpdateDialog vcd = new VerifyCodeUpdateDialog(bmxFramework, bmxConnectionSpec, strAccessCode); DialogResult dr = new DialogResult(); VerifyCodeUpdateResult vr = new VerifyCodeUpdateResult(); while (vr.DialogResult != DialogResult.Cancel && !(vr.WasVerifyCodeUpdatedSuccessfully )) { vr = vcd.ShowVerifyChangeDialog(null); } return vr; } /// /// Attempt a headless non-interactive UI login. This would be useful for an ASP.NET or NT-service type application /// /// The RpmsConnectionSpec to use during the login process /// The clear text access code /// The clear text verify code /// True if the login was successful public bool AttemptAccessVerifyLogin(RpmsConnectionSpec aSpec, String anAccessCode, String aVerifyCode) { BMXNetBroker broker = this.Framework.SocketBroker; this.ConnectionSpec = aSpec; if (this.AttemptingLogin != null) { AttemptingLoginEventArgs args = new AttemptingLoginEventArgs(); args.Process = this; this.AttemptingLogin(this, args); if (args.Handled) { return this.WasSuccessful; } if (args.Cancel) { this.Cancel = true; return false; } } try { this.LoginAttempts++; if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, anAccessCode, aVerifyCode,aSpec.SendTimeout,aSpec.ReceiveTimeout)) { this.Succeeded(); if (aSpec.UseWindowsAuthentication) { String result = broker.RegisterWindowsIdentityForWindowsAuthenication(WindowsIdentity.GetCurrent()); } this.ResolveDivision(broker); return this.WasSuccessful; } else { return this.Failed("Unable to authenicate. Use may need to login"); } } catch (BMXNetException problem) { this.FailureException = problem; if ( problem.Message.Contains("VERIFY CODE MUST be changed before continued use.")) { string message = "Your Verify Code has expired. Do you want to submit a new Verify Code now? Click Yes to continue, Click No to exit the sign in process."; string caption = "Verify Code Expired"; MessageBoxButtons buttons = MessageBoxButtons.YesNo; DialogResult result; // Displays the MessageBox. result = MessageBox.Show(message, caption, buttons); if (result == System.Windows.Forms.DialogResult.Yes) { // Open the verify code upate dialog VerifyCodeUpdateDialog vcd =new VerifyCodeUpdateDialog(this.Framework,this.ConnectionSpec,anAccessCode); DialogResult dr = new DialogResult(); while ( (dr != DialogResult.Cancel) && (!vcd.IsUpdateSuccessful)) { dr = vcd.ShowDialog(); } if (dr == DialogResult.OK) { bool bLogin = AttemptAccessVerifyLogin(this.ConnectionSpec,vcd.AccessCode, vcd.NewVerifyCode); return bLogin; } else { this.FailureMessage = "VERIFY CODE MUST be changed before continued use."; return this.WasSuccessful; } } else { return this.Failed(problem.Message); } } else { return this.Failed(problem.Message); } } catch (Exception anException) { this.FailureException = anException; return this.Failed("Critical issue: " + anException.Message); } } private void TriggerAttemptingLogin() { throw new NotImplementedException(); } private void ResolveDivision(BMXNetBroker aBroker) { List divisions = this.Framework.Divisions(aBroker.Duz); if (divisions.Count == 1) { this.Framework.SetDivision(divisions[0]); } else { if (this.AutoSetDivisionToLastLookup) { WinDivision last = null; foreach (WinDivision each in divisions) { if (each.MostRecentLookup) { last = each; break; } } if (last == null) { this.MustResolveDivision = true; } else { this.Framework.SetDivision(last); } } else { this.MustResolveDivision = true; } } } /// /// Will trigger the LoginAttempted event /// /// Answer true if the LoginProcess has been Canceled or handled public bool HandleLoginAttemptedFailed() { if (this.LoginAttempted != null) { LoginAttemptedEventArgs args = new LoginAttemptedEventArgs(); args.Process = this; args.Handled = false; args.WasSuccessful = this.WasLoginAttempted; this.LoginAttempted(this, args); if (args.Cancel) { this.Cancel = true; } return args.Handled || this.Cancel; } else { return false; } } } }