AcUtils
A high performance abstraction layer for AccuRev
AcUsers.cs
Go to the documentation of this file.
1 
16 using System;
17 using System.Collections.Generic;
18 using System.ComponentModel;
19 using System.DirectoryServices;
20 using System.DirectoryServices.AccountManagement;
21 using System.Linq;
22 using System.Threading;
23 using System.Threading.Tasks;
24 using System.Xml.Linq;
25 
26 namespace AcUtils
27 {
34 
35  [Serializable]
36  [TypeDescriptionProvider(typeof(PrncplDescriptionProvider))]
37  public sealed class AcUser : IFormattable, IEquatable<AcUser>, IComparable<AcUser>, IComparable
38  {
39  #region class variables
40  private AcPrincipal _principal = new AcPrincipal();
41  private string _givenName; // John
42  private string _middleName;
43  private string _surname; // Doe
44  private string _displayName; // Doe, John
45  private string _business; // business phone number
46  private string _emailAddress; // John.Doe@VerizonWireless.com
47  private string _description;
48  private string _distinguishedName;
49  // [title,value] pairs for user properties outside the default set
50  private Dictionary<string, object> _other = new Dictionary<string, object>();
51  #endregion
52 
60 
62  internal AcUser(int id, string name, PrinStatus status)
63  {
64  _principal.ID = id;
65  _principal.Name = name;
66  _principal.Status = status;
67  }
68 
69  #region Equality comparison
70 
72  public bool Equals(AcUser other)
80  {
81  if (ReferenceEquals(other, null)) return false;
82  if (ReferenceEquals(this, other)) return true;
83  return Principal.ID == other.Principal.ID;
84  }
85 
90  public override bool Equals(object other)
91  {
92  if (ReferenceEquals(other, null)) return false;
93  if (ReferenceEquals(this, other)) return true;
94  if (GetType() != other.GetType()) return false;
95  return this.Equals(other as AcUser);
96  }
97 
102  public override int GetHashCode()
103  {
104  return Principal.ID;
105  }
107  #endregion
108 
109  #region Order comparison
110 
112 
119  public int CompareTo(AcUser other)
120  {
121  int result;
122  if (AcUser.ReferenceEquals(this, other))
123  result = 0;
124  else
125  {
126  if (!String.IsNullOrEmpty(DisplayName) && !String.IsNullOrEmpty(other.DisplayName))
127  result = String.Compare(DisplayName, other.DisplayName);
128  else
129  result = String.Compare(Principal.Name, other.Principal.Name);
130  }
131 
132  return result;
133  }
134 
141  int IComparable.CompareTo(object other)
142  {
143  if (!(other is AcUser))
144  throw new ArgumentException("Argument is not an AcUser", "other");
145  AcUser o = (AcUser)other;
146  return this.CompareTo(o);
147  }
149  #endregion
150 
156  public AcPrincipal Principal
157  {
158  get { return _principal; }
159  }
160 
163  public string GivenName
167  {
168  get { return _givenName ?? String.Empty; }
169  }
170 
174  public string MiddleName
175  {
176  get { return _middleName ?? String.Empty; }
177  }
178 
182  public string Surname
183  {
184  get { return _surname ?? String.Empty; }
185  }
186 
190  public string DisplayName
191  {
192  get { return _displayName ?? String.Empty; }
193  }
194 
198  public string Business
199  {
200  get { return _business ?? String.Empty; }
201  }
202 
206  public string EmailAddress
207  {
208  get { return _emailAddress ?? String.Empty; }
209  }
210 
214  public string Description
215  {
216  get { return _description ?? String.Empty; }
217  }
218 
222  public string DistinguishedName
223  {
224  get { return _distinguishedName ?? String.Empty; }
225  }
228 
262  public IDictionary<string, object> Other
263  {
264  get { return _other; }
265  }
266 
267  #region ToString
268  public string ToString(string format, IFormatProvider provider)
290  {
291  if (provider != null)
292  {
293  ICustomFormatter fmt = provider.GetFormat(this.GetType()) as ICustomFormatter;
294  if (fmt != null)
295  return fmt.Format(format, this, provider);
296  }
297 
298  if (String.IsNullOrEmpty(format))
299  format = "G";
300 
301  switch (format.ToUpperInvariant())
302  {
303  case "G": // user's display name from Active Directory if available, otherwise their AccuRev principal name
304  return _displayName ?? Principal.Name;
305  case "LV": // long version (verbose)
306  return $"{DisplayName} ({Principal.Name}), Business: {(String.IsNullOrEmpty(Business) ? "N/A" : Business)}, {EmailAddress}";
307  case "I": // user's AccuRev principal ID
308  return Principal.ID.ToString();
309  case "N": // user's AccuRev principal name
310  return Principal.Name;
311  case "S": // principal's status in AccuRev: Active or Inactive
312  return Principal.Status.ToString();
313  case "F": // user's given name from Active Directory
314  return GivenName;
315  case "M": // user's middle name from Active Directory
316  return MiddleName;
317  case "L": // user's surname from Active Directory
318  return Surname;
319  case "DN": // user's display name from Active Directory
320  return DisplayName;
321  case "B": // business phone number from Active Directory
322  return Business;
323  case "E": // user's email address from Active Directory
324  return EmailAddress;
325  case "D": // description from Active Directory
326  return Description;
327  case "DG": // distinguished name from Active Directory
328  return DistinguishedName;
329  default:
330  throw new FormatException($"The {format} format string is not supported.");
331  }
332  }
333 
334  // Calls ToString(string, IFormatProvider) version with a null IFormatProvider argument.
335  public string ToString(string format)
336  {
337  return ToString(format, null);
338  }
339 
340  // Calls ToString(string, IFormatProvider) version with the general format and a null IFormatProvider argument.
341  public override string ToString()
342  {
343  return ToString("G", null);
344  }
345  #endregion ToString
346 
359 
361  internal async Task<bool> initFromADAsync(DomainCollection dc, PropCollection pc = null)
362  {
363  return await Task.Run(() =>
364  {
365  bool ret = true; // assume success
366  foreach (DomainElement de in dc)
367  {
368  PrincipalContext ad = null;
369  try
370  {
371  ad = new PrincipalContext(ContextType.Domain, de.Host.Trim(), de.Path.Trim());
372  UserPrincipal up = new UserPrincipal(ad);
373  up.SamAccountName = Principal.Name;
374  using (PrincipalSearcher ps = new PrincipalSearcher(up))
375  {
376  UserPrincipal rs = (UserPrincipal)ps.FindOne();
377  if (rs != null)
378  {
379  _givenName = rs.GivenName;
380  _middleName = rs.MiddleName;
381  _surname = rs.Surname;
382  _displayName = rs.DisplayName;
383  _business = rs.VoiceTelephoneNumber;
384  _emailAddress = rs.EmailAddress;
385  _description = rs.Description;
386  _distinguishedName = rs.DistinguishedName;
387 
388  if (pc != null)
389  {
390  DirectoryEntry lowerLdap = (DirectoryEntry)rs.GetUnderlyingObject();
391  foreach (PropElement pe in pc)
392  {
393  PropertyValueCollection pvc = lowerLdap.Properties[pe.Field];
394  if (pvc != null)
395  _other.Add(pe.Title.Trim(), pvc.Value);
396  }
397  }
398 
399  break;
400  }
401  }
402  }
403 
404  catch (Exception ecx)
405  {
406  ret = false;
407  AcDebug.Log($"Exception caught and logged in AcUser.initFromADAsync{Environment.NewLine}{ecx.Message}");
408  }
409 
410  // avoid CA2202: Do not dispose objects multiple times
411  finally { if (ad != null) ad.Dispose(); }
412  }
413 
414  return ret;
415  }).ConfigureAwait(false);
416  }
417 
430 
434  internal async Task<bool> initGroupsListAsync()
435  {
436  bool ret = false; // assume failure
437  try
438  {
439  // works for inactive users too
440  AcResult r = await AcCommand.runAsync($@"show -fx -u ""{Principal.Name}"" groups")
441  .ConfigureAwait(false);
442  if (r != null && r.RetVal == 0) // if command succeeded
443  {
444  SortedSet<string> members = new SortedSet<string>();
445  XElement xml = XElement.Parse(r.CmdResult);
446  foreach (XElement e in xml.Elements("Element"))
447  {
448  string name = (string)e.Attribute("Name");
449  members.Add(name);
450  }
451 
452  Principal.Members = members;
453  ret = true; // operation succeeded
454  }
455  }
456 
457  catch (AcUtilsException ecx)
458  {
459  AcDebug.Log($"AcUtilsException caught and logged in AcUser.initGroupsListAsync{Environment.NewLine}{ecx.Message}");
460  }
461 
462  catch (Exception ecx)
463  {
464  AcDebug.Log($"Exception caught and logged in AcUser.initGroupsListAsync{Environment.NewLine}{ecx.Message}");
465  }
466 
467  return ret;
468  }
469 
475 
476  public string getGroups()
477  {
478  string list = null;
479  if (Principal.Members != null)
480  list = String.Join(", ", Principal.Members);
481 
482  return list;
483  }
484  }
485 
495 
496  [Serializable]
497  public sealed class AcUsers : List<AcUser>
498  {
499  #region class variables
500  private DomainCollection _dc;
501  private PropCollection _pc; // user properties from Active Directory beyond the default set
502  private bool _includeGroupsList;
503  private bool _includeDeactivated;
504  [NonSerialized] private readonly object _locker = new object(); // token for lock keyword scope
505  [NonSerialized] private int _counter; // used to report initialization progress back to the caller
506  #endregion
507 
508  #region object construction:
509 
511 
548  public AcUsers(DomainCollection dc = null, PropCollection pc = null, bool includeGroupsList = false, bool includeDeactivated = false)
549  {
550  _dc = dc;
551  _pc = pc;
552  _includeGroupsList = includeGroupsList;
553  _includeDeactivated = includeDeactivated;
554  }
555 
564 
565  public async Task<bool> initAsync(IProgress<int> progress = null)
566  {
567  bool ret = false; // assume failure
568  try
569  {
570  AcResult r = await AcCommand.runAsync($"show {(_includeDeactivated ? "-fix" : "-fx")} users")
571  .ConfigureAwait(false);
572  if (r != null && r.RetVal == 0) // if command succeeded
573  {
574  XElement xml = XElement.Parse(r.CmdResult);
575  IEnumerable<XElement> query = from element in xml.Elements("Element") select element;
576  List<Task<bool>> tasks = new List<Task<bool>>(query.Count());
577  Func<Task<bool>, bool> cf = t =>
578  {
579  bool res = t.Result;
580  if (res && progress != null) progress.Report(Interlocked.Increment(ref _counter));
581  return res;
582  };
583 
584  foreach (XElement e in query)
585  {
586  string name = (string)e.Attribute("Name");
587  int id = (int)e.Attribute("Number");
588  // XML attribute isActive exists only if the user is inactive, otherwise it isn't there
589  PrinStatus status = (e.Attribute("isActive") == null) ? PrinStatus.Active : PrinStatus.Inactive;
590  AcUser user = new AcUser(id, name, status);
591  lock (_locker) { Add(user); }
592  Task<bool> t = initUserPropsAsync(user).ContinueWith(cf);
593  tasks.Add(t);
594  }
595 
596  bool[] arr = await Task.WhenAll(tasks).ConfigureAwait(false);
597  ret = (arr != null && arr.All(n => n == true));
598  }
599  }
600 
601  catch (AcUtilsException ecx)
602  {
603  AcDebug.Log($"AcUtilsException caught and logged in AcUsers.initAsync{Environment.NewLine}{ecx.Message}");
604  }
605 
606  catch (Exception ecx)
607  {
608  AcDebug.Log($"Exception caught and logged in AcUsers.initAsync{Environment.NewLine}{ecx.Message}");
609  }
610 
611  return ret;
612  }
614  #endregion
615 
624  private async Task<bool> initUserPropsAsync(AcUser user)
625  {
626  bool ret = false; // assume failure
627  try
628  {
629  if (_dc != null && _includeGroupsList)
630  {
631  Task<bool>[] tasks = new Task<bool>[2];
632  // initialize default set and other user properties from Active Directory
633  tasks[0] = user.initFromADAsync(_dc, _pc);
634  // include group membership list initialization
635  tasks[1] = user.initGroupsListAsync();
636  bool[] arr = await Task.WhenAll(tasks).ConfigureAwait(false);
637  ret = (arr != null && arr.All(n => n == true));
638  }
639  else if (_dc != null && !_includeGroupsList)
640  ret = await user.initFromADAsync(_dc, _pc).ConfigureAwait(false);
641  else if (_dc == null && _includeGroupsList)
642  ret = await user.initGroupsListAsync().ConfigureAwait(false);
643  else // _dc == null && !_includeGroupsList
644  ret = true; // nothing to do
645  }
646 
647  catch (Exception ecx)
648  {
649  AcDebug.Log($"Exception caught and logged in AcUsers.initUserPropsAsync{Environment.NewLine}{ecx.Message}");
650  }
651 
652  return ret;
653  }
654 
660  public AcUser getUser(string name)
661  {
662  return this.SingleOrDefault(n => n.Principal.Name == name);
663  }
664 
670  public AcUser getUser(int ID)
671  {
672  return this.SingleOrDefault(n => n.Principal.ID == ID);
673  }
674 
680  public AcUser getWorkspaceOwner(string name)
681  {
682  int ii = name.LastIndexOf('_');
683  string prncpl = name.Substring(++ii);
684  return getUser(prncpl);
685  }
686  }
687 }
string Surname
User's surname from Active Directory, e.g. Doe
Definition: AcUsers.cs:183
AcUser getWorkspaceOwner(string name)
Get the AcUser object owner of AccuRev workspace name.
Definition: AcUsers.cs:680
int ID
AccuRev principal ID number for the user or group.
Definition: AcPrincipal.cs:145
string Business
User's business phone number from Active Directory.
Definition: AcUsers.cs:199
AcUser getUser(int ID)
Get the AcUser object for AccuRev principal ID number.
Definition: AcUsers.cs:670
AcPrincipal Principal
AccuRev principal attributes name, ID, and status (active or inactive), and optionally their group me...
Definition: AcUsers.cs:157
AccuRev program return value and command result.
Definition: AcCommand.cs:29
An Active Directory domain element host-path pair from .exe.config.
An Active Directory user property element field-title pair from .exe.config. These are user properties not in the regular default set.
Definition: PropElement.cs:29
string Description
User's description from Active Directory.
Definition: AcUsers.cs:215
The list of Active Directory user property field-title pairs from .exe.config. These are user properties not in the regular default set.
async Task< bool > initGroupsListAsync()
Optionally called during list construction to initialize the list of groups this user is a member of ...
Definition: AcUsers.cs:434
string Field
The field from a field-title pair in .exe.config.
Definition: PropElement.cs:53
int CompareTo(AcUser other)
Generic IComparable implementation (default) for comparing AcUser objects to sort by DisplayName from...
Definition: AcUsers.cs:119
A user's AccuRev principal attributes name, ID, and status (active or inactive). In addition...
Definition: AcUsers.cs:37
override int GetHashCode()
Override appropriate for type AcUser.
Definition: AcUsers.cs:102
IDictionary< string, object > Other
Additional Active Directory user properties not in the regular default set.
Definition: AcUsers.cs:263
bool Equals(AcUser other)
IEquatable implementation to determine the equality of instances of type AcUser. Uses the user's prin...
Definition: AcUsers.cs:79
string ToString(string format, IFormatProvider provider)
The ToString implementation.
Definition: AcUsers.cs:289
string Title
The title from a field-title pair in .exe.config.
Definition: PropElement.cs:63
The list of Active Directory domain host-path pairs from .exe.config.
PrinStatus
Whether the principal is active or inactive in AccuRev.
Definition: AcPrincipal.cs:28
Contains the AccuRev principal attributes name, ID and status (active or inactive) for users and grou...
Definition: AcPrincipal.cs:52
string EmailAddress
User's email address from Active Directory.
Definition: AcUsers.cs:207
async Task< bool > initAsync(IProgress< int > progress=null)
Populate this container with AcUser objects as per constructor parameters.
Definition: AcUsers.cs:565
string Host
The host from a host-path pair in .exe.config.
string DistinguishedName
User's distinguished name from Active Directory.
Definition: AcUsers.cs:223
string getGroups()
Get the list of groups this user is a member of as a formatted string. List optionally initialized by...
Definition: AcUsers.cs:476
string CmdResult
The command result (usually XML) emitted by AccuRev.
Definition: AcCommand.cs:70
int RetVal
The AccuRev program return value for the command, otherwise minus one (-1) on error.
Definition: AcCommand.cs:61
static void Log(string message, bool formatting=true)
Write the message text to STDOUT, to weekly log files located in %LOCALAPPDATA%\AcTools\Logs, and to trigger.log in the AccuRev server's ..storage\site_slice\logs folder in the case of triggers.
Definition: AcDebug.cs:378
async Task< bool > initUserPropsAsync(AcUser user)
Helper function for initializing user with their Active Directory properties and group memberships as...
Definition: AcUsers.cs:624
string Name
AccuRev principal name for the user or group.
Definition: AcPrincipal.cs:154
async Task< bool > initFromADAsync(DomainCollection dc, PropCollection pc=null)
Optionally called during list construction to initialize the default set of regular user properties a...
Definition: AcUsers.cs:361
string MiddleName
User's middle name from Active Directory.
Definition: AcUsers.cs:175
Exception thrown when an AccuRev command fails. The AccuRev program return value is zero (0) on succe...
AccuRev command processing.
Definition: AcCommand.cs:138
static async Task< AcResult > runAsync(string command, ICmdValidate validator=null)
Run the AccuRev command asynchronously with non-blocking I/O.
Definition: AcCommand.cs:184
Use to log and display error and general purpose text messages, and to save the XML param data sent b...
Definition: AcDebug.cs:100
string GivenName
User's given name from Active Directory, e.g. John
Definition: AcUsers.cs:167
A container of AcUser objects that define AccuRev users.
Definition: AcUsers.cs:497
override bool Equals(object other)
Overridden to determine equality.
Definition: AcUsers.cs:90
SortedSet< string > Members
The list of groups a user has membership in, or the list of principals (users and groups) in a group...
Definition: AcPrincipal.cs:177
string DisplayName
User's display name from Active Directory, e.g. Doe, John
Definition: AcUsers.cs:191
AcUser(int id, string name, PrinStatus status)
Initialize user's AccuRev principal attributes name, ID, and status (active or inactive). AcUser objects are instantiated during AcUsers list construction. This constructor is called internally and not by user code.
Definition: AcUsers.cs:62
PrinStatus Status
Whether the principal is active or inactive in AccuRev.
Definition: AcPrincipal.cs:163
AcUsers(DomainCollection dc=null, PropCollection pc=null, bool includeGroupsList=false, bool includeDeactivated=false)
A container of AcUser objects that define AccuRev users. Elements contain user principal attributes n...
Definition: AcUsers.cs:548
AcUser getUser(string name)
Get the AcUser object for AccuRev principal name.
Definition: AcUsers.cs:660
string Path
The path from a host-path pair in .exe.config.