source: BMXNET_RPMS_dotNET_UTILITIES-BMX/branch/IHS BMX Framework/IndianHealthService.BMXNet.WinForm/LoginProcess.cs@ 1166

Last change on this file since 1166 was 1146, checked in by Sam Habiel, 14 years ago

Initial Import of BMX4

File size: 21.0 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Text;
4using IndianHealthService.BMXNet.WinForm.Configuration;
5using IndianHealthService.BMXNet.Net;
6using System.Security.Principal;
7using IndianHealthService.BMXNet.Forms;
8using IndianHealthService.BMXNet.WinForm.Forms;
9using System.Windows.Forms;
10using IndianHealthService.BMXNet.WinForm.Model;
11
12namespace IndianHealthService.BMXNet.WinForm
13{
14 /// <summary>
15 /// This class models the workflow of the LoginProcess. It correographs UI and non-UI
16 /// login methods, login cancelling, management dialogs, and provides hooks (events) to
17 /// customize the login workflow.
18 /// </summary>
19 /// <example>
20 /// See the SDK for other examples.
21 /// <code>
22 /// this.Framework = WinFramework.CreateWithNetworkBroker(true);
23 /// this.Framework.LoadSettings(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "settings");
24 /// this.Framework.LoadConnectionSpecs(LocalPersistentStore.CreateIn(Environment.SpecialFolder.LocalApplicationData, EntryAssemblyInfo.AssemblyCompany + "/" + EntryAssemblyInfo.AssemblyProduct, false), "connectiosn");
25 /// LoginProcess login = this.Framework.CreateLoginProcess();
26 ///
27 /// //Attempt a non-UI WindowsAuth if and only if there is a default connection with WindowsAuth
28 /// //Of course, an application can set its own policy of when to AttemptWindowsAuthLogin()
29 ///
30 /// if (login.HasDefaultConnectionWithUseWindowsAuth)
31 /// {
32 /// login.AttemptWindowsAuthLogin();
33 /// }
34 ///
35 /// //If not attempted yet, i.e. skipped the AttemptWindowsAuthLogin(), or was unsuccessul, try and UI login
36 /// if (!login.WasLoginAttempted || !login.WasSuccessful)
37 /// {
38 /// login.AttemptUserInputLogin(IndianHealthService.BMXNet.Util.EntryAssemblyInfo.AssemblyTitle+" Login", 3,!this.Framework.BootStrapSettings.Get("lockedit",false), this);
39 /// }
40 ///
41 /// //If the login process was unable to login after the max tries (or fow other configuration reasons), exit the application
42 /// if (!login.WasSuccessful)
43 /// {
44 /// this.Close();
45 /// return;
46 /// }
47 ///
48 /// // Making sure that the user's division is set. Can use AttemptUserInputSetDivision() or the application can devise another solution
49 /// if ((this.Framework.User.Division == null) &amp;&amp; !this.Framework.AttemptUserInputSetDivision("Set Initial Division", this))
50 /// {
51 /// this.Close();
52 /// return;
53 /// }
54 ///
55 /// // Logged in with valid user and division
56 /// this.RemoteSession = this.Framework.PrimaryRemoteSession;
57 /// </code></example>
58 public class LoginProcess
59 {
60 /// <summary>
61 /// Triggered before every login attempt. See <see cref="AttemptingLoginEventArgs"/> for details .
62 /// </summary>
63 public event EventHandler<AttemptingLoginEventArgs> AttemptingLogin;
64
65 /// <summary>
66 /// Triggered after every login attempt. See <see cref="LoginAttemptedEventArgs"/> for details .
67 /// </summary>
68 public event EventHandler<LoginAttemptedEventArgs> LoginAttempted;
69
70 private bool _cancel = false;
71
72 /// <summary>
73 /// During a LoginAttempted the LoginProcess can be Cancelled by setting Cancel to True.
74 /// </summary>
75 public bool Cancel
76 {
77 get { return _cancel; }
78 set { _cancel = value; }
79 }
80
81 private bool _IsSwitchServerModeEnabled = false;
82
83 /// <summary>
84 /// If set to True, the Connection combo box on the Login screen with be dropped down when the
85 /// dialog is displayed. This is useful for applications that have an option to change Connections.
86 /// The default is False.
87 /// </summary>
88 public bool IsSwitchServerModeEnabled
89 {
90 get { return _IsSwitchServerModeEnabled; }
91 set { _IsSwitchServerModeEnabled = value; }
92 }
93
94 private bool _autoSetDivisionToLastLookup = true;
95
96 /// <summary>
97 /// If set to True, RPMS is checked and if there was a previously set division for the user it will be used, otherwise
98 /// MustResolveDivision will be set to True and the division will need to be set for the user.
99 /// The default is True
100 /// </summary>
101 public bool AutoSetDivisionToLastLookup
102 {
103 get { return _autoSetDivisionToLastLookup; }
104 set { _autoSetDivisionToLastLookup = value; }
105 }
106
107 private bool _mustResolveDivision = false;
108
109 /// <summary>
110 /// If the division for the user has not been determine after the LoginProcess, MustResolveDivision will be set to True.
111 /// The default if False.
112 /// </summary>
113 public bool MustResolveDivision
114 {
115 get { return _mustResolveDivision; }
116 set { _mustResolveDivision = value; }
117 }
118
119 private int _loginAttempts = 0;
120
121 /// <summary>
122 /// The number of login attempts so far. This value can be modified during the AttemptingLogin and LoginAttempted events.
123 /// </summary>
124 public int LoginAttempts
125 {
126 get { return _loginAttempts; }
127 set { _loginAttempts = value; }
128 }
129
130 private int _maxAttempts = 3;
131
132 /// <summary>
133 /// 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.
134 /// </summary>
135 public int MaxAttempts
136 {
137 get { return _maxAttempts; }
138 set { _maxAttempts = value; }
139 }
140
141 private bool _wasLoginAttempted = false;
142
143 /// <summary>
144 /// True if a login was attempted. False if the user is presented with a LoginDialog and "Cancel" is selected.
145 /// The value is changed during every login attempt cycle.
146 /// </summary>
147 public bool WasLoginAttempted
148 {
149 get { return _wasLoginAttempted; }
150 set { _wasLoginAttempted = value; }
151 }
152
153 private bool _wasSuccessful = false;
154
155 /// <summary>
156 /// True if the most recent login attempt was successful.
157 /// </summary>
158 public bool WasSuccessful
159 {
160 get { return _wasSuccessful; }
161 set { _wasSuccessful = value; }
162 }
163 private String _failureMessage = null;
164
165 /// <summary>
166 /// A reasonable message to display to the user if the last login attempt failed.
167 /// </summary>
168 public String FailureMessage
169 {
170 get { return _failureMessage; }
171 set { _failureMessage = value; }
172 }
173
174 private Exception _failureException = null;
175
176 /// <summary>
177 /// If an exception occured during the last login attempt, FailureException will be set to it.
178 /// </summary>
179 public Exception FailureException
180 {
181 get { return _failureException; }
182 set { _failureException = value; }
183 }
184
185 private RpmsConnectionSpec _connectionSpec = null;
186
187 /// <summary>
188 /// 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
189 /// with care.
190 /// </summary>
191 public RpmsConnectionSpec ConnectionSpec
192 {
193 get { return _connectionSpec; }
194 set { _connectionSpec = value; }
195 }
196
197 internal LoginProcess(WinFramework aFramework)
198 {
199 this.Framework = aFramework;
200 }
201
202 internal RpmsConnectionSettings Settings
203 {
204 get { return this.Framework.ConnectionSettings; }
205 }
206
207 private WinFramework _framework = null;
208
209 internal protected WinFramework Framework
210 {
211 get { return _framework; }
212 set { _framework = value; }
213 }
214
215 /// <summary>
216 /// Answer True if there is a default managed connection that uses WindowsAuthenication
217 /// </summary>
218 public bool HasDefaultConnectionWithUseWindowsAuth
219 {
220 get
221 {
222 RpmsConnectionSpec spec = this.Settings.DefaultConnectionSpec;
223
224 return spec == null ? false : spec.UseWindowsAuthentication;
225 }
226
227
228 }
229
230
231 /// <summary>
232 /// Attempt a WindowsAuthentication Login and answer true if it was successful.
233 /// </summary>
234 /// <returns>True if login was successful</returns>
235 public bool AttemptWindowsAuthLogin(RpmsConnectionSpec aConnectionSpec)
236 {
237 RpmsConnectionSpec spec = aConnectionSpec;
238 if (spec == null)
239 {
240 return this.Failed("A default connection spec must be set for automatic Windows Authenication");
241 }
242
243 if (spec.UseWindowsAuthentication)
244 {
245 return this.PrimitiveAttemptWindowsAuthLogin(spec);
246 }
247 else
248 {
249 return this.Failed("Connection spec must be set for automatic Windows Authenication for auto-login");
250 }
251 }
252
253 /// <summary>
254 /// Using the current default connection spec, attempt a WindowsAuthentication Login and answer true if it was successful.
255 /// </summary>
256 /// <remarks>
257 /// Most common approach when using the DefaultConnection feature the RpmsConnection spec class.
258 /// </remarks>
259 /// <returns>True if login was successful</returns>
260 public bool AttemptWindowsAuthLogin()
261 {
262 return this.AttemptWindowsAuthLogin(this.Settings.DefaultConnectionSpec);
263 }
264
265 private bool Failed(string aMessage)
266 {
267 this.WasLoginAttempted = true;
268 this.WasSuccessful = false;
269 this.FailureMessage = aMessage;
270
271 return this.WasSuccessful;
272 }
273
274 /// <summary>
275 /// Attempt an interactive UI login. There are several useful arguments to control the process and appearance of the this process.
276 /// </summary>
277 /// <param name="aDialogTitle">The title of the login. Customize to be something like "MyApplication Login"</param>
278 /// <param name="maxAttempts">The number of attempts before cancelling</param>
279 /// <param name="enableConnectionManagement">If false, the user will not be able to change the connections. This is useful for lockdown situations</param>
280 /// <param name="aUiOwnerForPositioningDialog">Provide your main application as the window owner so the LoginDialog box is correctly managed.</param>
281 /// <returns>True if login was successful</returns>
282 public bool AttemptUserInputLogin(string aDialogTitle, int maxAttempts, bool enableConnectionManagement,IWin32Window aUiOwnerForPositioningDialog)
283 {
284 this.MaxAttempts = maxAttempts;
285 RpmsLoginView view = (RpmsLoginView)new RpmsLoginDialog();
286
287 view.Title = aDialogTitle;
288
289 RpmsLoginPresenter presenter = new RpmsLoginPresenter();
290 presenter.EnableConnectionManagement = enableConnectionManagement;
291 presenter.View = view;
292 presenter.Model = this;
293 presenter.UiOwner = aUiOwnerForPositioningDialog;
294
295 presenter.Open();
296
297
298 return this.WasSuccessful;
299 }
300
301 /// <summary>
302 /// If the application is managing the actual login, send Succeeded() to indicate success.
303 /// </summary>
304 /// <remarks>
305 /// Do not set WasSuccessful to true.
306 /// </remarks>
307 /// <returns>True if successful</returns>
308 public bool Succeeded()
309 {
310 this.WasLoginAttempted = true;
311 this.WasSuccessful = true;
312 this.FailureMessage = "";
313 this.Framework.Login = this;
314
315 return this.WasSuccessful;
316 }
317
318
319
320 internal bool PrimitiveAttemptWindowsAuthLogin(RpmsConnectionSpec aSpec)
321 {
322 BMXNetBroker broker = this.Framework.SocketBroker;
323 this.ConnectionSpec = aSpec;
324
325 if (this.AttemptingLogin != null)
326 {
327 AttemptingLoginEventArgs args = new AttemptingLoginEventArgs();
328 args.Process = this;
329 this.AttemptingLogin(this, args);
330 if (args.Handled)
331 {
332 return this.WasSuccessful;
333 }
334
335 if (args.Cancel)
336 {
337 this.Cancel = true;
338 return false;
339 }
340 }
341
342 try
343 {
344 if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, WindowsIdentity.GetCurrent(),aSpec.SendTimeout,aSpec.ReceiveTimeout))
345 {
346 this.Succeeded();
347 this.ResolveDivision(broker);
348 return this.WasSuccessful;
349 }
350 else
351 {
352 return this.Failed("Unable to authenicate. Use may need to login");
353 }
354 }
355 catch (BMXNetException problem)
356 {
357 this.FailureException = problem;
358 return this.Failed(problem.Message);
359 }
360 catch (Exception anException)
361 {
362 this.FailureException = anException;
363 return this.Failed("Critical issue: " + anException.Message);
364 }
365 }
366
367
368 public VerifyCodeUpdateResult AttemptVerifyCodeChange(WinFramework bmxFramework, RpmsConnectionSpec bmxConnectionSpec, string strAccessCode)
369 {
370
371 // Open the verify code upate dialog
372 VerifyCodeUpdateDialog vcd = new VerifyCodeUpdateDialog(bmxFramework, bmxConnectionSpec, strAccessCode);
373 DialogResult dr = new DialogResult();
374 VerifyCodeUpdateResult vr = new VerifyCodeUpdateResult();
375
376 while (vr.DialogResult != DialogResult.Cancel && !(vr.WasVerifyCodeUpdatedSuccessfully ))
377 {
378 vr = vcd.ShowVerifyChangeDialog(null);
379 }
380
381 return vr;
382
383 }
384
385 /// <summary>
386 /// Attempt a headless non-interactive UI login. This would be useful for an ASP.NET or NT-service type application
387 /// </summary>
388 /// <param name="aSpec">The RpmsConnectionSpec to use during the login process</param>
389 /// <param name="anAccessCode">The clear text access code</param>
390 /// <param name="aVerifyCode">The clear text verify code</param>
391 /// <returns>True if the login was successful</returns>
392 public bool AttemptAccessVerifyLogin(RpmsConnectionSpec aSpec, String anAccessCode, String aVerifyCode)
393 {
394 BMXNetBroker broker = this.Framework.SocketBroker;
395
396 this.ConnectionSpec = aSpec;
397
398
399 if (this.AttemptingLogin != null)
400 {
401 AttemptingLoginEventArgs args = new AttemptingLoginEventArgs();
402 args.Process = this;
403 this.AttemptingLogin(this, args);
404 if (args.Handled)
405 {
406 return this.WasSuccessful;
407 }
408
409 if (args.Cancel)
410 {
411 this.Cancel = true;
412 return false;
413 }
414 }
415
416
417 try
418 {
419 this.LoginAttempts++;
420
421 if (broker.Open(aSpec.Server, aSpec.Port, aSpec.NameSpace, anAccessCode, aVerifyCode,aSpec.SendTimeout,aSpec.ReceiveTimeout))
422 {
423 this.Succeeded();
424 if (aSpec.UseWindowsAuthentication)
425 {
426 String result = broker.RegisterWindowsIdentityForWindowsAuthenication(WindowsIdentity.GetCurrent());
427 }
428 this.ResolveDivision(broker);
429
430 return this.WasSuccessful;
431 }
432 else
433 {
434 return this.Failed("Unable to authenicate. Use may need to login");
435 }
436 }
437
438 catch (BMXNetException problem)
439 {
440 this.FailureException = problem;
441
442 if ( problem.Message.Contains("VERIFY CODE MUST be changed before continued use."))
443 {
444 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.";
445 string caption = "Verify Code Expired";
446 MessageBoxButtons buttons = MessageBoxButtons.YesNo;
447
448 DialogResult result;
449
450 // Displays the MessageBox.
451
452 result = MessageBox.Show(message, caption, buttons);
453
454 if (result == System.Windows.Forms.DialogResult.Yes)
455 {
456 // Open the verify code upate dialog
457 VerifyCodeUpdateDialog vcd =new VerifyCodeUpdateDialog(this.Framework,this.ConnectionSpec,anAccessCode);
458 DialogResult dr = new DialogResult();
459
460 while ( (dr != DialogResult.Cancel) && (!vcd.IsUpdateSuccessful))
461 {
462 dr = vcd.ShowDialog();
463 }
464
465
466 if (dr == DialogResult.OK)
467 {
468 bool bLogin = AttemptAccessVerifyLogin(this.ConnectionSpec,vcd.AccessCode, vcd.NewVerifyCode);
469 return bLogin;
470 }
471 else
472 {
473 this.FailureMessage = "VERIFY CODE MUST be changed before continued use.";
474 return this.WasSuccessful;
475 }
476 }
477 else
478 {
479 return this.Failed(problem.Message);
480 }
481 }
482 else
483 {
484 return this.Failed(problem.Message);
485 }
486
487 }
488 catch (Exception anException)
489 {
490 this.FailureException = anException;
491 return this.Failed("Critical issue: " + anException.Message);
492 }
493 }
494
495 private void TriggerAttemptingLogin()
496 {
497 throw new NotImplementedException();
498 }
499
500 private void ResolveDivision(BMXNetBroker aBroker)
501 {
502 List<SelectableDivision> divisions = this.Framework.Divisions(aBroker.Duz);
503 if (divisions.Count == 1)
504 {
505 this.Framework.SetDivision(divisions[0]);
506 }
507 else
508 {
509 if (this.AutoSetDivisionToLastLookup)
510 {
511 WinDivision last = null;
512 foreach (WinDivision each in divisions)
513 {
514 if (each.MostRecentLookup)
515 {
516 last = each;
517 break;
518 }
519 }
520 if (last == null)
521 {
522 this.MustResolveDivision = true;
523 }
524 else
525 {
526 this.Framework.SetDivision(last);
527 }
528 }
529 else
530 {
531 this.MustResolveDivision = true;
532 }
533 }
534 }
535
536 /// <summary>
537 /// Will trigger the LoginAttempted event
538 /// </summary>
539 /// <returns>Answer true if the LoginProcess has been Canceled or handled</returns>
540 public bool HandleLoginAttemptedFailed()
541 {
542 if (this.LoginAttempted != null)
543 {
544 LoginAttemptedEventArgs args = new LoginAttemptedEventArgs();
545 args.Process = this;
546 args.Handled = false;
547 args.WasSuccessful = this.WasLoginAttempted;
548 this.LoginAttempted(this, args);
549 if (args.Cancel)
550 {
551 this.Cancel = true;
552 }
553 return args.Handled || this.Cancel;
554
555 }
556 else
557 {
558 return false;
559 }
560 }
561 }
562}
Note: See TracBrowser for help on using the repository browser.