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)
{
//test sam
//Framework.SocketBroker.Open(
//Framework.SocketBroker.PrimaryRemoteSession.TransmitRPC("XUS INTRO TEXT","","XUS SIGNON");
//test sam
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;
}
}
}
}