Flags attribute like userAccountControl

Jan 6, 2012 at 5:10 PM

Can EnumStoredAsInt() cope with enums that should be marked with the FlagsAttribute such as userAccountControl which is identified as an Enumeration and which can be defined like this:

    [Flags]
    public enum UserAccountControlFlags
    {
        SCRIPT = 0x0001,                                // 1
        ACCOUNTDISABLE = 0x0002,                        // 2
        HOMEDIR_REQUIRED = 0x0008,                      // 8
        LOCKOUT = 0x0010,                               // 16
        PASSWD_NOTREQD = 0x0020,                        // 32
        PASSWD_CANT_CHANGE = 0x0040,                    // 64
        ENCRYPTED_TEXT_PWD_ALLOWED = 0x0080,            // 128
        TEMP_DUPLICATE_ACCOUNT = 0x0100,                // 256
        NORMAL_ACCOUNT = 0x0200,                        // 512
        INTERDOMAIN_TRUST_ACCOUNT = 0x0800,             // 2048
        WORKSTATION_TRUST_ACCOUNT = 0x1000,             // 4096
        SERVER_TRUST_ACCOUNT = 0x2000,                  // 8192
        DONT_EXPIRE_PASSWORD = 0x10000,                 // 65536
        MNS_LOGON_ACCOUNT = 0x20000,                    // 131072
        SMARTCARD_REQUIRED = 0x40000,                   // 262144
        TRUSTED_FOR_DELEGATION = 0x80000,               // 524288
        NOT_DELEGATED = 0x100000,                       // 1048576
        USE_DES_KEY_ONLY = 0x200000,                    // 2097152
        DONT_REQ_PREAUTH = 0x400000,                    // 4194304
        PASSWORD_EXPIRED = 0x800000,                    // 8388608
        TRUSTED_TO_AUTH_FOR_DELEGATION = 0x1000000      // 16777216
    }
?

Coordinator
Jan 6, 2012 at 11:58 PM

The integers are just stored as strings in the directory so this will work.  I must admit I've never seen a disabled account marked simply as 2.  I've always had to do a bitwise comparison to see if the account is disabled (value & 2 == 2).  In order for this list to work you'll have to include at least 514 and 546 as well (both for disabled accounts).

Jan 7, 2012 at 8:20 AM

Yes, that's the 'pure' version of the flags but what's applied is often several of them OR'd together.

Jan 7, 2012 at 4:54 PM

I had the same problem and solved this with a custom mapping.

I don’t know if this is the most elegant solution, but this is my code :

For mapping :

       [Flags]

       public enum ADUserFlags

       {

            

     /// <summary></summary>

             Script = 0x0001,

             /// <summary></summary>

             Accountdisable = 0x0002,

             /// <summary></summary>

             HomedirRequired = 0x0008,

             /// <summary></summary>

             Lockout = 0x0010,

             /// <summary></summary>

             PasswdNotreqd = 0x0020,

             /// <summary></summary>

             PasswdCantChange = 0x0040,

             /// <summary></summary>

             EncryptedTextPasswordAllowed = 0x0080,

 

          /// <summary></summary>

             TempDuplicateAccount = 0x0100,

             /// <summary></summary>

             NormalAccount = 0x0200,

             /// <summary></summary>

             InterdomainTrustAccount = 0x0800,

             /// <summary></summary>

             WorkstationTrustAccount = 0x1000,

             /// <summary></summary>

             ServerTrustAccount = 0x2000,

 

            /// <summary></summary>

             DontExpirePasswd = 0x10000,

             /// <summary></summary>

             MnsLogonAccount = 0x20000,

             /// <summary></summary>

             SmartcardRequired = 0x40000,

             /// <summary></summary>

             TrustedForDelegation = 0x80000,

             /// <summary></summary>

             NotDelegated = 0x100000,

              /// <summary></summary>

             UseDesKeyOnly = 0x200000,

             /// <summary></summary>

             DontRequirePreauth = 0x400000,

             /// <summary></summary>

             PasswordExpired = 0x800000,

 

 

       /// <summary></summary>

             TrustedToAuthenticateForDelegation = 0x1000000

 

       }

 

User.cs

       private int _useraccountcontrol;

       [Category("Account options")]

       [Browsable(false)]

       public int UserAccountControl

       {

           get { return _useraccountcontrol; }

           set

           {

               // Parse

               ADUserFlags accountcontrolflags = (ADUserFlags)Convert.ToUInt32(value);

               AccountControlFlags = accountcontrolflags;

               IsAccountDisabled = (accountcontrolflags & ADUserFlags.Accountdisable) > 0;

               IsLockedOut = (accountcontrolflags & ADUserFlags.Lockout) > 0;

               IsPasswordExpired = (accountcontrolflags & ADUserFlags.PasswordExpired) > 0;

 

               _useraccountcontrol = value;

           }

       }

 

       [Category("Account options")]

       public ADUserFlags AccountControlFlags { get; private set; }

       [Category("Account options")]

       public bool IsAccountDisabled { get; private set; }

       [Category("Account options")]

       public bool IsLockedOut { get; private set; }

       [Category("Account options")]

       public bool IsPasswordExpired { get; private set; }

 

For querying I use a filter value as follows :

   // FilterWith User Account

   public const string FilterWith_USER_ACCOUNTDISABLED = "((useraccountcontrol:1.2.840.113556.1.4.803:=2))";

 

   var rslt = context.Query(example, SearchScope.Subtree, namingContext, objectCategory: "Person")

                   .FilterWith("(|(objectClass=contact)(objectClass=user))")

                   .FilterWith(ADConstants.FilterWith_USER_ACCOUNTDISABLED)

                     .ToList();

 

 

Kind regards,

JohnV

Coordinator
Jan 9, 2012 at 6:23 PM
Edited Jan 9, 2012 at 6:26 PM

There's a check to make sure the enum is defined when getting it back from the directory.  I'm going remove that check to support this.  However, there's currently no way to translate a bitwise comparison to a filter value (other than the method that John demonstrated).  

I'm definatley going to add a MatchRule method to Filter to simplify searching with ruleOIDs.  However, I need to to a little more research translating bitwise comparisons into filter values.  Currently it seems AD is the only directory that supports this.  Other directories may end up supporting this, but with a different ruleOID.

Jan 30, 2012 at 12:25 PM

I've done a test with build 11038 and defining my GroupTypes like this:

    [Flags]
    public enum GroupTypes
    {
        System = 1,                 // (0x00000001) a group created by the system. 
        Global = 2,                 // (0x00000002) a group with global scope. 
        DomainLocal = 4,            // (0x00000004) a group with domain local scope.
        Universal = 8,              // (0x00000008) a group with universal scope.
        AzManAppBasic = 16,         // (0x00000010) an APP_BASIC group for AzMan. 
        AzManAppQuery = 32,         // (0x00000020) an APP_QUERY group for AzMan.
        SecurityGroup = -2147483648 // (0x80000000) a security group. If this flag is
                                    // not set, then the group is a distribution group. 
    }

and it worked on reads, writes and queries e.g.:

    .Where(g => g.groupType == (GroupTypes.Global | GroupTypes.SecurityGroup))

In fact, the only thing that didn't work was using:

    .Where(g => g.groupType.Equals(GroupTypes.Global | GroupTypes.SecurityGroup))

when I got a System.NotSupportedException: Method Equals on type System.Object could not be translated. in DirectoryQueryProvider.cs but clearly == works so that's hardly an issue.

Thanks.