text:
//******************************************************************************************************
//  AdoSecurityProvider.cs - Gbtc
//
//  Copyright © 2012, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/MIT
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  11/24/2010 - Mehulbhai P Thakkar
//       Generated original version of source code.
//  07/18/2011 - Stephen C. Wills
//       Modified a SELECT statement in RefreshData to explicitly alias columns to
//       fix an error when using SQLite databases.
//  09/01/2011 - Stephen C. Wills
//       Modified references to views in the database whose names have changed.
//  09/28/2011 - Stephen C. Wills
//       Modified database queries to work with Oracle database.
//  12/20/2012 - Starlynn Danyelle Gilliam
//       Modified Header.
//  01/16/2014 - J. Ritchie Carroll
//       Added security context caching so security system can load without database access
//       using last cached security state.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading;
using GSF.Collections;
using GSF.Configuration;
using GSF.Data;
using GSF.Diagnostics;
using GSF.Identity;

#pragma warning disable S2068

namespace GSF.Security
{
    ///
    /// Represents an <see cref="ISecurityProvider"/> that uses ADO.NET data source (SQL Server, MySQL, Oracle, etc.) for its
    /// backend data store and authenticates internal users against Active Directory and external users against the database.
    ///
    /// <example>
    /// Required config file entries (automatically added):
    ///
    /// <![CDATA[
    /// <?xml version="1.0"?>
    /// <configuration>
    ///   <configSections>
    ///

    ///   </configSections>
    ///   <categorizedSettings>
    ///     <securityProvider>
    ///       <add name="ProviderType" value="GSF.Security.AdoSecurityProvider, GSF.Security" description="The type to be used for enforcing security."
    ///         encrypted="false" />
    ///       <add name="UserCacheTimeout" value="5" description="Defines the timeout, in whole minutes, for a user's provider cache. Any value less than 1 will cause cache reset every minute."
    ///         encrypted="false" />
    ///       <add name="ConnectionString" value="Eval(systemSettings.ConnectionString)" description="Configuration database connection string"
    ///         encrypted="false"/>
    ///       <add name="DataProviderString" value="Eval(systemSettings.DataProviderString)" description="Configuration database ADO.NET data provider assembly type creation string"
    ///         encrypted="false"/>
    ///       <add name="LdapPath" value="" description="Specifies the LDAP path used to initialize the security provider."
    ///         encrypted="false" />
    ///       <add name="ApplicationName" value="SEC_APP" description="Name of the application being secured."
    ///         encrypted="false" />
    ///       <add name="IncludedResources" value="*=*" description="Semicolon delimited list of resources to be secured along with role names."
    ///         encrypted="false" />
    ///       <add name="ExcludedResources" value="" description="Semicolon delimited list of resources to be excluded from being secured."
    ///         encrypted="false" />
    ///       <add name="NotificationSmtpServer" value="localhost" description="SMTP server to be used for sending out email notification messages."
    ///         encrypted="false" />
    ///       <add name="NotificationSenderEmail" value="sender@company.com" description="Email address of the sender of email notification messages."
    ///         encrypted="false" />
    ///       <add name="CacheRetryDelayInterval" value="200" description="Wait interval, in milliseconds, before retrying load of user data cache."
    ///         encrypted="false"/>
    ///       <add name="CacheMaximumRetryAttempts" value="10" description="Maximum retry attempts allowed for loading user data cache."
    ///         encrypted="false"/>
    ///       <add name="EnableOfflineCaching" value="True" description="True to enable caching of user information for authentication in offline state, otherwise False."
    ///         encrypted="false"/>
    ///       <add name="PasswordRequirementsRegex" value="^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$" description="Regular expression used to validate new passwords for database users."
    ///         encrypted="false" />
    ///       <add name="PasswordRequirementsError" value="Invalid Password: Password must be at least 8 characters; must contain at least 1 number, 1 upper case letter, and 1 lower case letter" description="Error message to be displayed when new database user password fails regular expression test."
    ///         encrypted="false" />
    ///       <add name="DefaultRoles" value="Viewer" description="If set this is a list of Roles assigned to a user that has no defined Roles."
    ///         encrypted="false" />
    ///     </securityProvider>
    ///     <activeDirectory>
    ///       <add name="PrivilegedDomain" value="" description="Domain of privileged domain user account."
    ///         encrypted="false" />
    ///       <add name="PrivilegedUserName" value="" description="Username of privileged domain user account."
    ///         encrypted="false" />
    ///       <add name="PrivilegedPassword" value="" description="Password of privileged domain user account."
    ///         encrypted="true" />
    ///     </activeDirectory>
    ///   </categorizedSettings>
    /// </configuration>
    /// ]]>
    ///
    /// </example>
    /// <remarks>
    /// Minimum expected table schema for ADO Security Provider:
    ///
    /// <![CDATA[
    /// CREATE TABLE UserAccount
    /// (
    ///     ID UNIQUEINDENTIFIER NOT NULL DEFAULT NEWID(),
    ///     Name VARCHAR(200) NOT NULL,
    ///     Password VARCHAR(200) DEFAULT NULL,
    ///     FirstName VARCHAR(200) DEFAULT NULL,
    ///     LastName VARCHAR(200) DEFAULT NULL,
    ///     Phone VARCHAR(200) DEFAULT NULL,
    ///     Email VARCHAR(200) DEFAULT NULL,
    ///     LockedOut TINYINT NOT NULL DEFAULT 0,
    ///     UseADAuthentication TINYINT NOT NULL DEFAULT 1,
    ///     ChangePasswordOn DATETIME DEFAULT NULL,
    ///     CONSTRAINT PK_UserAccount PRIMARY KEY (ID ASC),
    ///     CONSTRAINT IX_UserAccount UNIQUE KEY (Name)
    /// );
    ///
    /// CREATE TABLE SecurityGroup
    /// (
    ///     ID UNIQUEINDENTIFIER NOT NULL DEFAULT NEWID(),
    ///     Name VARCHAR(200) NOT NULL,
    ///     CONSTRAINT PK_SecurityGroup PRIMARY KEY (ID ASC),
    ///     CONSTRAINT IX_SecurityGroup UNIQUE KEY (Name)
    /// );
    ///
    /// CREATE TABLE SecurityGroupUserAccount
    /// (
    ///     SecurityGroupID UNIQUEINDENTIFIER NOT NULL,
    ///     UserAccountID UNIQUEINDENTIFIER NOT NULL
    /// );
    ///
    /// CREATE TABLE ApplicationRole
    /// (
    ///     ID UNIQUEINDENTIFIER NOT NULL DEFAULT NEWID(),
    ///     Name VARCHAR(200) NOT NULL,
    ///     NodeID UNIQUEINDENTIFIER NOT NULL,
    ///     CONSTRAINT PK_ApplicationRole PRIMARY KEY (ID ASC),
    ///     CONSTRAINT IX_ApplicationRole UNIQUE KEY (NodeID, Name)
    /// );
    ///
    /// CREATE TABLE ApplicationRoleUserAccount
    /// (
    ///     ApplicationRoleID UNIQUEINDENTIFIER NOT NULL,
    ///     UserAccountID UNIQUEINDENTIFIER NOT NULL
    /// );
    ///
    /// CREATE TABLE ApplicationRoleSecurityGroup
    /// (
    ///     ApplicationRoleID UNIQUEINDENTIFIER NOT NULL,
    ///     SecurityGroupID UNIQUEINDENTIFIER NOT NULL
    /// );
    /// ]]>
    ///
    /// </remarks>
    public class AdoSecurityProvider : LdapSecurityProvider
    {
        #region [ Members ]

        // Constants
        private const string UserAccountTable = "UserAccount";                                      // Table name for user accounts
        private const string SecurityGroupTable = "SecurityGroup";                                  // Table name for security groups
        private const string SecurityGroupUserAccountTable = "SecurityGroupUserAccount";            // Table name for security group user accounts
        private const string ApplicationRoleTable = "ApplicationRole";                              // Table name for application roles
        private const string ApplicationRoleUserAccountTable = "ApplicationRoleUserAccount";        // Table name for application role assignments for user accounts
        private const string ApplicationRoleSecurityGroupTable = "ApplicationRoleSecurityGroup";    // Table name for application role assignments for security groups

        ///
        /// Default Roles to be used if no Roles are supplied for a user.
        ///
        private const string DefaultDefaultRoles = "";

        ///
        /// Default regular expression used to validate new database user passwords.
        ///
        public const string DefaultPasswordRequirementsRegex = "^.*(?=.{8,})(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).*$";

        ///
        /// Default error message displayed when databases users fail regular expression test.
        ///
        public const string DefaultPasswordRequirementsError = "Invalid Password: Password must be at least 8 characters; must contain at least 1 number, 1 upper case letter, and 1 lower case letter";

        ///
        /// Default value for <see cref="UseDatabaseLogging"/>.
        ///
        public const bool DefaultUseDatabaseLogging = true;

        ///
        /// Defines the provider ID for the <see cref="AdoSecurityProvider"/>.
        ///
        public new const int ProviderID = 1;

        private bool m_successfulPassThroughAuthentication;
        private string m_passwordRequirementsRegex;
        private string m_passwordRequirementsError;
        private bool? m_lastLoggedLoginResult;

        #endregion

        #region [ Constructor ]

        ///
        /// Initializes a new instance of the <see cref="AdoSecurityProvider"/> class.
        ///
        /// <param name="username">Name that uniquely identifies the user.</param>
        public AdoSecurityProvider(string username)
            : this(username, true, false, true)
        {
        }

        ///
        /// Initializes a new instance of the <see cref="AdoSecurityProvider"/> class.
        ///
        /// <param name="username">Name that uniquely identifies the user.</param>
        /// <param name="canRefreshData">true if the security provider can refresh <see cref="UserData"/> from the backend data store, otherwise false.</param>
        /// <param name="canResetPassword">true if the security provider can reset user password, otherwise false.</param>
        /// <param name="canChangePassword">true if the security provider can change user password, otherwise false.</param>
        protected AdoSecurityProvider(string username, bool canRefreshData, bool canResetPassword, bool canChangePassword)
            : base(username, canRefreshData, canResetPassword, canChangePassword)
        {
            base.ConnectionString = "Eval(systemSettings.ConnectionString)";

            m_passwordRequirementsRegex = DefaultPasswordRequirementsRegex;
            m_passwordRequirementsError = DefaultPasswordRequirementsError;
            UseDatabaseLogging = DefaultUseDatabaseLogging;
        }

        #endregion

        #region [ Properties ]

        ///
        /// Gets last exception reported by the <see cref="AdoSecurityProvider"/>.
        ///
        public Exception LastException
        {
            get;
            set;
        }

        ///
        /// Gets or sets flag that determines if <see cref="LogAuthenticationAttempt"/> and <see cref="LogError"/> should
        /// write to the database. Defaults to <c>true</c>.
        ///
        /// <remarks>
        /// Setting this flag to <c>false</c> may be necessary in cases where a database has been setup to use authentication
        /// but does not include an "AccessLog" or "ErrorLog" table.
        /// </remarks>
        public bool UseDatabaseLogging
        {
            get;
            set;
        }

        ///
        /// Gets or sets the Default Roles used when a user does not have a role defined.
        /// The user still needs to exist but they won't require a Role and will be assigned the DefaultRoles.
        /// It is a comma separate list for multiple Roles. If an empty String is supplied a Role is required for the user.
        ///
        public string DefaultRoles
        {
            get;
            set;
        }

        #endregion

        #region [ Methods ]

        ///
        /// Loads saved security provider settings from the config file if the <see cref="SecurityProviderBase.PersistSettings"/> property is set to true.
        ///
        /// <exception cref="ConfigurationErrorsException"><see cref="SecurityProviderBase.SettingsCategory"/> has a value of null or empty string.</exception>
        public override void LoadSettings()
        {
            base.LoadSettings();

            // Make sure default settings exist
            ConfigurationFile config = ConfigurationFile.Current;
            CategorizedSettingsElementCollection settings = config.Settings[SettingsCategory];

            settings.Add("DataProviderString", "Eval(systemSettings.DataProviderString)", "Configuration database ADO.NET data provider assembly type creation string to be used for connection to the backend security data store.");
            settings.Add("LdapPath", "", "Specifies the LDAP path used to initialize the security provider.");
            settings.Add("PasswordRequirementsRegex", DefaultPasswordRequirementsRegex, "Regular expression used to validate new passwords for database users.");
            settings.Add("PasswordRequirementsError", DefaultPasswordRequirementsError, "Error message to be displayed when new database user password fails regular expression test.");
            settings.Add("UseDatabaseLogging", DefaultUseDatabaseLogging, "Flag that determines if provider should write logs to the database.");
            settings.Add("DefaultRoles", DefaultDefaultRoles, "If set this is a list of Roles assigned to a user that has no defined Roles.");

            DefaultRoles = settings["DefaultRoles"].ValueAs(DefaultRoles);
            m_passwordRequirementsRegex = settings["PasswordRequirementsRegex"].ValueAs(m_passwordRequirementsRegex);
            m_passwordRequirementsError = settings["PasswordRequirementsError"].ValueAs(m_passwordRequirementsError);
        }

        ///
        /// Refreshes the <see cref="UserData"/>.
        ///
        /// <returns>true if <see cref="SecurityProviderBase.UserData"/> is refreshed, otherwise false.</returns>
        public override bool RefreshData()
        {
            if (string.IsNullOrEmpty(UserData.Username))
                return false;

            try
            {
                UserData userData = new UserData(UserData.Username);

                // Initialize user data.
                userData.Initialize();

                // We'll retrieve all the data we need about a user.
                //   Table1: Information about the user.
                //   Table2: Groups the user is a member of.
                //   Table3: Roles that are assigned to the user either implicitly (NT groups) or explicitly (database) or through a group.

                DataSet securityContext;

                try
                {
                    // Attempt to extract current security context from the database
                    using (AdoDataConnection database = new AdoDataConnection(SettingsCategory))
                    {
                        securityContext = ExtractSecurityContext(database.Connection, ex => LogError(ex.Source, ex.ToString()), UserData.Username);
                    }
                }
                catch (InvalidOperationException)
                {
                    // Failed to open ADO connection, attempt to fall back on using security context from last known cached information
                    using (AdoSecurityCache cache = AdoSecurityCache.GetCurrentCache())
                        securityContext = cache.DataSet;
                }

                try
                {
                    // Make sure primary keys are defined on tables that we use "Find" function on later...
                    UpdatePrimaryKey(securityContext.Tables[SecurityGroupTable], "SecurityGroupID");
                    UpdatePrimaryKey(securityContext.Tables[ApplicationRoleTable], "ApplicationRoleID");
                }
                catch (Exception ex)
                {
                    // Just going to fall back on slower "Select" function if this fails...
                    Logger.SwallowException(ex, "Failed to update primary key from cached security context, falling back on slower select function.", additionalFlags: MessageFlags.PerformanceIssue);
                }

                // Validate that needed security tables exist in the data set
                if ((object)securityContext == null)
                    throw new SecurityException("Failed to load a valid security context. Cannot proceed with user authentication.");

                foreach (string securityTable in s_securityTables)
                {
                    if (!securityContext.Tables.Contains(securityTable))
                        throw new SecurityException($"Failed to load a valid security context - missing table '{securityTable}'. Cannot proceed with user authentication.");
                }

                if (securityContext.Tables[ApplicationRoleTable].Rows.Count == 0 && string.IsNullOrEmpty(DefaultRoles))
                    throw new SecurityException($"Failed to load a valid security context - no application roles were found for node ID '{DefaultNodeID}', verify the node ID in the config file '{ConfigurationFile.Current.Configuration.FilePath}'. Cannot proceed with user authentication.");

                DataRow userAccount = null;
                Guid userAccountID = Guid.Empty;
                string userSID = UserInfo.UserNameToSID(userData.Username);

                // Filter user account data for the current user.
                DataRow[] userAccounts = securityContext.Tables[UserAccountTable].Select($"Name = '{EncodeEscapeSequences(userSID)}'");

                // If SID based lookup failed, try lookup by user name.  Note that is critical that SID based lookup
                // take precedence over name based lookup for proper cross-platform authentication.
                if (userAccounts.Length == 0)
                    userAccounts = securityContext.Tables[UserAccountTable].Select($"Name = '{EncodeEscapeSequences(userData.Username)}'");

                if (userAccounts.Length == 0)
                {
                    // User doesn't exist in the database, however, user may exist in an NT authentication group which
                    // may have an explicit role assignment. To test for this case we make the assumption that this is
                    // a Windows authenticated user and test for rights within groups
                    userData.IsDefined = true;
                    userData.IsExternal = false;
                }
                else
                {
                    userAccount = userAccounts[0];
                    userData.IsDefined = true;
                    userData.IsExternal = !Convert.ToBoolean(userAccount["UseADAuthentication"]);
                    userAccountID = Guid.Parse(Convert.ToString(userAccount["ID"]));
                }

                if (userData.IsExternal && (object)userAccount != null)
                {
                    // Load database user details
                    if (string.IsNullOrEmpty(userData.LoginID))
                        userData.LoginID = userData.Username;

                    if (!Convert.IsDBNull(userAccount["Password"]))
                        userData.Password = Convert.ToString(userAccount["Password"]);

                    if (!Convert.IsDBNull(userAccount["FirstName"]))
                        userData.FirstName = Convert.ToString(userAccount["FirstName"]);

                    if (!Convert.IsDBNull(userAccount["LastName"]))
                        userData.LastName = Convert.ToString(userAccount["LastName"]);

                    if (!Convert.IsDBNull(userAccount["Phone"]))
                        userData.PhoneNumber = Convert.ToString(userAccount["Phone"]);

                    if (!Convert.IsDBNull(userAccount["Email"]))
                        userData.EmailAddress = Convert.ToString(userAccount["Email"]);

                    if (!Convert.IsDBNull(userAccount["ChangePasswordOn"]))
                        userData.PasswordChangeDateTime = Convert.ToDateTime(userAccount["ChangePasswordOn"]);

                    if (!Convert.IsDBNull(userAccount["CreatedOn"]))
                        userData.AccountCreatedDateTime = Convert.ToDateTime(userAccount["CreatedOn"]);

                    // For possible future use:
                    //if (!Convert.IsDBNull(userDataRow["UserCompanyName"]))
                    //    userData.CompanyName = Convert.ToString(userDataRow["UserCompanyName"]);
                    //if (!Convert.IsDBNull(userDataRow["UserSecurityQuestion"]))
                    //    userData.SecurityQuestion = Convert.ToString(userDataRow["UserSecurityQuestion"]);
                    //if (!Convert.IsDBNull(userDataRow["UserSecurityAnswer"]))
                    //    userData.SecurityAnswer = Convert.ToString(userDataRow["UserSecurityAnswer"]);
                }
                else
                {
                    // Load implicitly assigned groups - this happens via user's NT/AD groups that get loaded into the
                    // user data group collection. When database group definitions are defined with the same name as
                    // their NT/AD equivalents, this will allow automatic external group management.
                    base.RefreshData(userData, userData.Groups, ProviderID);
                }

                // Administrator can lock out NT/AD user as well as database-only user via database
                if (!userData.IsLockedOut && (object)userAccount != null && !Convert.IsDBNull(userAccount["LockedOut"]))
                    userData.IsLockedOut = Convert.ToBoolean(userAccount["LockedOut"]);

                // At this point an NT/AD based user will have a list of groups that is known to be available to the user. A database
                // user will have no groups defined yet. The next step will be to load any explicitly assigned groups the user is a
                // member of. Users can be explicitly assigned to any group, database or NT/AD group. This allows flexibility for
                // local database users to be assigned to an NT/AD group (in the database) as well as NT/AD users to be a member of
                // local database groups - allowing customizable role assignments to be fully managed via groups.

                // Note: some groups, such as "Everyone" (S-1-1-0), will not be automatically returned as valid groups by the base
                // LdapSecurityProvider (see UserInfo class for details). If the security provider needs to support such groups,
                // these groups will need to be manually added to the groups list here before testing to see if the user is a
                // member of these groups. Since these types of groups could be considered a security risk by some applications,
                // it is recommended that if this type of functionality is enabled, that it can be disabled in the configuration.

                if (userAccountID != Guid.Empty)
                {
                    // Filter explicitly assigned security groups for current user
                    DataRow[] userGroups = securityContext.Tables[SecurityGroupUserAccountTable].Select($"UserAccountID = '{EncodeEscapeSequences(userAccountID.ToString())}'");

                    foreach (DataRow row in userGroups)
                    {
                        // Locate associated security group record
                        DataRow securityGroup = null;

                        if (securityContext.Tables[SecurityGroupTable].PrimaryKey.Length > 0)
                        {
                            securityGroup = securityContext.Tables[SecurityGroupTable].Rows.Find(row["SecurityGroupID"]);
                        }
                        else
                        {
                            DataRow[] securityGroups = securityContext.Tables[SecurityGroupTable].Select($"ID = '{EncodeEscapeSequences(row["SecurityGroupID"].ToString())}'");

                            if (securityGroups.Length > 0)
                                securityGroup = securityGroups[0];
                        }

                        if ((object)securityGroup == null || Convert.IsDBNull(securityGroup["Name"]))
                            continue;

                        // Just in case a database user was manually assigned to an NT/AD group, make sure to convert
                        // the group name back to it's human readable name (from a SID). The UserInfo.SIDToAccountName
                        // function will return the original parameter value if name cannot be converted - this allows
                        // the function to work for database group names as well.
                        string groupName = UserInfo.SIDToAccountName(Convert.ToString(securityGroup["Name"]));

                        if (!userData.Groups.Contains(groupName, StringComparer.OrdinalIgnoreCase))
                            userData.Groups.Add(groupName);
                    }
                }

                // Explicitly assigned user roles will take precedence over any roles that may be implicitly available to the
                // user based on what group they may be a member of. In practice this means even though a user may be a member
                // of a group with role assignments, if there are any roles directly assigned to the user these will be their
                // effective roles. For example, if a user is in a group that is in the "Administrator" role and the user has
                // an explicit role assignment for the "Viewer" role - the user will be in the "Viewer" role only. This allows
                // individual users to have overridden role assignments even though they may also be part of a group.
                userData.Roles.Clear();

                // Filter explicitly assigned application roles for current user - this will return an empty set if no
                // explicitly defined roles exist for the user -or- user doesn't exist in the database.
                DataRow[] userApplicationRoles = securityContext.Tables[ApplicationRoleUserAccountTable].Select($"UserAccountID = '{EncodeEscapeSequences(userAccountID.ToString())}'");

                // If no explicitly assigned application roles are found for the current user, we check for implicitly assigned
                // application roles based on the role assignments of the groups the user is a member of.
                if (userApplicationRoles.Length == 0)
                {
                    List<DataRow> implicitRoles = new List<DataRow>();

                    // Filter implicitly assigned application roles for each of the user's database and NT/AD groups. Note that
                    // even if user is not defined in the database, an NT/AD group they are a member of may be associated with
                    // a role - this allows the user to get a role assignment based on this group.
                    foreach (string groupName in userData.Groups)
                    {
                        // Convert NT/AD group names back to SIDs for lookup in the database
                        string groupSID = UserInfo.GroupNameToSID(groupName);

                        // Locate associated security group record
                        DataRow[] securityGroups = securityContext.Tables[SecurityGroupTable].Select($"Name = '{EncodeEscapeSequences(groupSID)}'");

                        // If SID based lookup failed, try lookup by group name.  Note that is critical that SID based lookup
                        // take precedence over name based lookup for proper cross-platform authentication.
                        if (securityGroups.Length == 0)
                            securityGroups = securityContext.Tables[SecurityGroupTable].Select($"Name = '{EncodeEscapeSequences(groupName)}'");

                        if (securityGroups.Length > 0)
                        {
                            // Found security group by name, access group ID to lookup application roles defined for the group
                            DataRow securityGroup = securityGroups[0];

                            if (!Convert.IsDBNull(securityGroup["ID"]))
                                implicitRoles.AddRange(securityContext.Tables[ApplicationRoleSecurityGroupTable].Select($"SecurityGroupID = '{EncodeEscapeSequences(securityGroup["ID"].ToString())}'"));
                        }
                    }

                    userApplicationRoles = implicitRoles.ToArray();
                }

                // Populate user roles collection - both ApplicationRoleUserAccount and ApplicationRoleSecurityGroup tables contain ApplicationRoleID column
                foreach (DataRow role in userApplicationRoles)
                {
                    if (Convert.IsDBNull(role["ApplicationRoleID"]))
                        continue;

                    // Locate associated application role record
                    DataRow applicationRole = null;

                    if (securityContext.Tables[ApplicationRoleTable].PrimaryKey.Length > 0)
                    {
                        applicationRole = securityContext.Tables[ApplicationRoleTable].Rows.Find(role["ApplicationRoleID"]);
                    }
                    else
                    {
                        DataRow[] applicationRoles = securityContext.Tables[ApplicationRoleTable].Select($"ID = '{EncodeEscapeSequences(role["ApplicationRoleID"].ToString())}'");

                        if (applicationRoles.Length > 0)
                            applicationRole = applicationRoles[0];
                    }

                    if ((object)applicationRole == null || Convert.IsDBNull(applicationRole["Name"]))
                        continue;

                    // Found application role by ID, add role name to user roles if not already defined
                    string roleName = Convert.ToString(applicationRole["Name"]);

                    if (!string.IsNullOrEmpty(roleName) && !userData.Roles.Contains(roleName, StringComparer.OrdinalIgnoreCase))
                        userData.Roles.Add(roleName);
                }

                // Add DefaultRoles if no Roles are present
                if (!string.IsNullOrEmpty(DefaultRoles) && userData.Roles.Count() == 0)
                    foreach(string role in DefaultRoles.Split(','))
                        userData.Roles.Add(role);

                UserData = userData;

                // Cache last user roles
                ThreadPool.QueueUserWorkItem(CacheLastUserRoles);

                return true;
            }
            catch (Exception ex)
            {
                LastException = ex;
                LogError(ex.Source, ex.ToString());
                throw;
            }
        }

        ///
        /// Authenticates the user.
        ///
        /// <returns>true if the user is authenticated, otherwise false.</returns>
        public override bool Authenticate()
        {
            Exception authenticationException = null;

            // Reset authenticated state and failure reason
            bool isAuthenticated = false;
            AuthenticationFailureReason = null;
            m_successfulPassThroughAuthentication = false;

            // Test for pre-authentication failure modes. Note that blank password should be allowed so that LDAP
            // can authenticate current credentials for pass through authentication, if desired.
            if (!UserData.IsDefined)
            {
                AuthenticationFailureReason = $"User \"{UserData.LoginID}\" is not defined.";
            }
            else if (UserData.IsDisabled)
            {
                AuthenticationFailureReason = $"User \"{UserData.LoginID}\" is disabled.";
            }
            else if (UserData.IsLockedOut)
            {
                AuthenticationFailureReason = $"User \"{UserData.LoginID}\" is locked out.";
            }
            else if (UserData.PasswordChangeDateTime != DateTime.MinValue && UserData.PasswordChangeDateTime <= DateTime.UtcNow)
            {
                AuthenticationFailureReason = $"User \"{UserData.LoginID}\" has an expired password or password has not been set.";
            }
            else if (UserData.Roles.Count == 0)
            {
                AuthenticationFailureReason = $"User \"{UserData.LoginID}\" has not been assigned any roles and therefore has no rights. Contact your administrator.";
            }
            else
            {
                try
                {
                    // Determine if user is LDAP or database authenticated
                    if (UserData.IsExternal)
                    {
                        // Test password for database user authentication
                        isAuthenticated =
                            UserData.Password == Password ||
                            UserData.Password == SecurityProviderUtility.EncryptPassword(Password);
                    }
                    else
                    {
                        // Execute operating system authentication using provided credentials
                        isAuthenticated = base.Authenticate();
                    }
                }
                catch (Exception ex)
                {
                    authenticationException = ex;
                }
            }

            // Update the UserData object with new authentication state
            IsUserAuthenticated = isAuthenticated;

            // Determine if user succeeded with pass-through authentication
            m_successfulPassThroughAuthentication = (string.IsNullOrWhiteSpace(Password) && IsUserAuthenticated);

            try
            {
                // Log user authentication result
                LogAuthenticationAttempt(IsUserAuthenticated);
            }
            catch (Exception ex)
            {
                // Writing data will fail for read-only databases;
                // all we can do is track last exception in this case
                LastException = ex;
                Log.Publish(MessageLevel.Warning, MessageFlags.SecurityMessage, "Authenticate", "Failed to log authentication attempt to database.", "Database or AccessLog table may be read-only or inaccessible.", ex);
            }

            // If an exception occurred during authentication, rethrow it after logging authentication attempt
            if ((object)authenticationException != null)
            {
                LastException = authenticationException;
                LogError(authenticationException.Source, authenticationException.ToString());
                throw authenticationException;
            }

            return IsUserAuthenticated;
        }

        ///
        /// Changes user password in the backend data store.
        ///
        /// <param name="oldPassword">User's current password.</param>
        /// <param name="newPassword">User's new password.</param>
        /// <returns>true if the password is changed, otherwise false.</returns>
        /// <exception cref="SecurityException"><paramref name="newPassword"/> does not meet password requirements.</exception>
        public override bool ChangePassword(string oldPassword, string newPassword)
        {
            // Check prerequisites.
            if (!UserData.IsDefined || UserData.IsDisabled || UserData.IsLockedOut)
                return false;

            try
            {
                // Check if old and new passwords are different.
                if (oldPassword == newPassword)
                    throw new Exception("New password cannot be same as old password.");

                // If needed, perform password change for internal NT/AD users.
                if (!UserData.IsExternal)
                    return base.ChangePassword(oldPassword, newPassword);

                // Verify old password.
                if (oldPassword != UserData.Password && SecurityProviderUtility.EncryptPassword(oldPassword) != UserData.Password)
                    return false;

                // Verify new password.
                if (!Regex.IsMatch(newPassword, m_passwordRequirementsRegex))
                    throw new SecurityException(m_passwordRequirementsError);

                using (IDbConnection dbConnection = (new AdoDataConnection(SettingsCategory)).Connection)
                {
                    if ((object)dbConnection == null)
                        return false;

                    bool oracle = dbConnection.GetType().Name == "OracleConnection";
                    IDbCommand command = dbConnection.CreateCommand();
                    command.CommandType = CommandType.Text;

                    if (command.Connection.ConnectionString.Contains("Microsoft.Jet.OLEDB"))
                        command.CommandText = "UPDATE UserAccount SET [Password] = @newPassword, ChangePasswordOn = @changePasswordOn WHERE Name = @name AND [Password] = @oldPassword";
                    else if (oracle)
                        command.CommandText = "UPDATE UserAccount SET Password = :newPassword, ChangePasswordOn = :changePasswordOn WHERE Name = :name AND Password = :oldPassword";
                    else
                        command.CommandText = "UPDATE UserAccount SET Password = @newPassword, ChangePasswordOn = @changePasswordOn WHERE Name = @name AND Password = @oldPassword";

                    IDbDataParameter param = command.CreateParameter();
                    param.ParameterName = oracle ? ":newPassword" : "@newPassword";
                    param.Value = SecurityProviderUtility.EncryptPassword(newPassword);
                    command.Parameters.Add(param);

                    param = command.CreateParameter();
                    param.ParameterName = oracle ? ":changePasswordOn" : "@changePasswordOn";
                    param.Value = DateTime.UtcNow.AddDays(90.0D);
                    command.Parameters.Add(param);

                    param = command.CreateParameter();
                    param.ParameterName = oracle ? ":name" : "@name";
                    param.Value = UserData.Username;
                    command.Parameters.Add(param);

                    param = command.CreateParameter();
                    param.ParameterName = oracle ? ":oldPassword" : "@oldPassword";
                    param.Value = UserData.Password;
                    command.Parameters.Add(param);

                    command.ExecuteNonQuery();
                }

                return true;
            }
            catch (SecurityException ex)
            {
                LastException = ex;
                Log.Publish(MessageLevel.Warning, MessageFlags.SecurityMessage, "ChangePassword", "Security exception occurred during attempt to change password.", exception: ex);
                throw;
            }
            catch (Exception ex)
            {
                LastException = ex;
                LogError(ex.Source, ex.ToString());
                throw;
            }
            finally
            {
                RefreshData();
            }
        }

        ///
        /// Logs user authentication attempt.
        ///
        /// <param name="loginSuccess">true if user authentication was successful, otherwise false.</param>
        protected virtual void LogAuthenticationAttempt(bool loginSuccess)
        {
            if (m_lastLoggedLoginResult == loginSuccess)
                return;

            if ((object)UserData != null && !string.IsNullOrWhiteSpace(UserData.Username))
            {
                string message = $"User \"{UserData.Username}\" login attempt {(loginSuccess ? "succeeded using " + (m_successfulPassThroughAuthentication ? "pass-through authentication" : "user acquired password") : "failed")}.";
                EventLogEntryType entryType = loginSuccess ? EventLogEntryType.SuccessAudit : EventLogEntryType.FailureAudit;

                // Suffix authentication failure reason on failed logins if available
                if (!loginSuccess && !string.IsNullOrWhiteSpace(AuthenticationFailureReason))
                    message = string.Concat(message, " ", AuthenticationFailureReason);

                // Attempt to write success or failure to the event log
                try
                {
                    Log.Publish(MessageLevel.Info, MessageFlags.SecurityMessage, "AuthenticationAttempt", message);
                    LogEvent(ApplicationName, message, entryType, 1);
                }
                catch (Exception ex)
                {
                    LogError(ex.Source, ex.ToString());
                }

                // Attempt to write success or failure to the database - we allow caller to catch any possible exceptions here so that
                // database exceptions can be tracked separately (via LastException property) from other login exceptions, e.g., when
                // a read-only database is being used or current user only has read-only access to database.
                if (!string.IsNullOrWhiteSpace(SettingsCategory) && UseDatabaseLogging)
                {
                    using (AdoDataConnection connection = new AdoDataConnection(SettingsCategory))
                    {
                        connection.ExecuteNonQuery("INSERT INTO AccessLog (UserName, AccessGranted) VALUES ({0}, {1})", UserData.Username, loginSuccess);
                    }
                }
            }

            m_lastLoggedLoginResult = loginSuccess;
        }

        ///
        /// Logs information about an encountered exception to the backend data store.
        ///
        /// <param name="source">Source of the exception.</param>
        /// <param name="message">Detailed description of the exception.</param>
        /// <returns>true if logging was successful, otherwise false.</returns>
        protected virtual bool LogError(string source, string message)
        {
            if (!string.IsNullOrWhiteSpace(SettingsCategory) && !string.IsNullOrWhiteSpace(source) && !string.IsNullOrWhiteSpace(message))
            {
                Log.Publish(MessageLevel.Error, MessageFlags.SecurityMessage, source, message);

                if (UseDatabaseLogging)
                {
                    try
                    {
                        using (AdoDataConnection connection = new AdoDataConnection(SettingsCategory))
                        {
                            connection.ExecuteNonQuery("INSERT INTO ErrorLog (Source, Message) VALUES ({0}, {1})", source, message);
                        }

                        return true;
                    }
                    catch (Exception ex)
                    {
                        // Writing data will fail for read-only databases;
                        // all we can do is track last exception in this case
                        LastException = ex;
                        Log.Publish(MessageLevel.Warning, MessageFlags.SecurityMessage, "LogErrorToDatabase", "Failed to log error to database.", "Database or ErrorLog table may be read-only or inaccessible.", ex);
                    }
                }
            }

            return false;
        }

        ///
        /// Gets the LDAP path.
        ///
        /// <returns>The LDAP path.</returns>
        protected override string GetLdapPath()
        {
            // Load connection settings from the system settings category
            ConfigurationFile config = ConfigurationFile.Current;
            CategorizedSettingsElementCollection configSettings = config.Settings[SettingsCategory];
            return configSettings["LdapPath"].Value;
        }

        // Cache last user roles and monitor for changes
        private void CacheLastUserRoles(object state)
        {
            try
            {
                // Using an inter-process cache for user roles
                using (UserRoleCache userRoleCache = UserRoleCache.GetCurrentCache())
                {
                    HashSet<string> currentRoles;
                    string[] cachedRoles;

                    // Retrieve current user roles
                    currentRoles = new HashSet<string>(UserData.Roles, StringComparer.OrdinalIgnoreCase);

                    // Attempt to retrieve cached user roles
                    cachedRoles = userRoleCache[UserData.Username];

                    bool rolesChanged = false;
                    string message;
                    EventLogEntryType entryType;

                    if ((object)cachedRoles == null)
                    {
                        if (currentRoles.Count == 0)
                        {
                            // New user access granted
                            message = $"Initial Encounter: user \"{UserData.Username}\" attempted login with no assigned roles.";
                            entryType = EventLogEntryType.FailureAudit;
                            rolesChanged = true;
                        }
                        else
                        {
                            // New user access granted
                            message = $"Initial Encounter: user \"{UserData.Username}\" granted access with role{(currentRoles.Count == 1 ? "" : "s")} \"{currentRoles.ToDelimitedString(", ")}\".";
                            entryType = EventLogEntryType.Information;
                            rolesChanged = true;
                        }
                    }
                    else if (!currentRoles.SetEquals(cachedRoles))
                    {
                        if (currentRoles.Count == 0)
                        {
                            // New user access granted
                            message = $"Subsequent Encounter: user \"{UserData.Username}\" attempted login with no assigned roles - role assignment that existed at last login was \"{cachedRoles.ToDelimitedString(", ")}\".";
                            entryType = EventLogEntryType.FailureAudit;
                            rolesChanged = true;
                        }
                        else
                        {
                            // User role access changed
                            message = $"Subsequent Encounter: user \"{UserData.Username}\" granted access with new role{(currentRoles.Count == 1 ? "" : "s")} \"{currentRoles.ToDelimitedString(", ")}\" - role assignment is different from last login, was \"{cachedRoles.ToDelimitedString(", ")}\".";
                            entryType = EventLogEntryType.Warning;
                            rolesChanged = true;
                        }
                    }
                    else
                    {
                        if (currentRoles.Count == 0)
                        {
                            // New user access granted
                            message = $"Subsequent Encounter: user \"{UserData.Username}\" attempted login with no assigned roles - same as last login attempt.";
                            entryType = EventLogEntryType.FailureAudit;
                            rolesChanged = true;
                        }
                        else
                        {
                            message = $"Subsequent Encounter: user \"{UserData.Username}\" granted access with role{(currentRoles.Count == 1 ? "" : "s")} \"{currentRoles.ToDelimitedString(", ")}\" - role assignment is the same as last login.";
                            entryType = EventLogEntryType.SuccessAudit;
                        }
                    }

                    // Log granted role access to event log
                    try
                    {
                        LogEvent(ApplicationName, message, entryType, 0);
                    }
                    catch (Exception ex)
                    {
                        LogError(ex.Source, ex.ToString());
                    }

                    // If role has changed, update cache
                    if (rolesChanged)
                    {
                        userRoleCache[UserData.Username] = currentRoles.ToArray();
                        userRoleCache.WaitForSave();

                        MessageLevel level;

                        switch (entryType)
                        {
                            case EventLogEntryType.SuccessAudit:
                            case EventLogEntryType.Information:
                                level = MessageLevel.Info;
                                break;
                            case EventLogEntryType.FailureAudit:
                            case EventLogEntryType.Warning:
                                level = MessageLevel.Warning;
                                break;
                            default:
                                level = MessageLevel.Error;
                                break;
                        }

                        Log.Publish(level, MessageFlags.SecurityMessage, "UserRoleAccessChanged", message);
                    }
                }
            }
            catch (Exception ex)
            {
                LogError(ex.Source, ex.ToString());
            }
        }

        ///
        /// Gets a list of Roles for this user for a specified ApplicationId.
        ///
        /// <param name="applicationId">The applicationId for the roles to be returned.</param>
        /// <returns>The roles that the specified user has.</returns>
        public override List<string> GetUserRoles(string applicationId)
        {
            List<string> roles = new List<string>();

            DataSet securityContext = new DataSet("AdoSecurityContext");

            try
            {
                Guid nodeId = new Guid(applicationId);
                // Attempt to extract current security context from the database
                using (AdoDataConnection database = new AdoDataConnection(SettingsCategory))
                {
                    // Read the security context tables from the database connection
                    foreach (string securityTable in s_securityTables)
                    {
                        AddSecurityContextTable(database.Connection, securityContext, securityTable, securityTable == ApplicationRoleTable ? nodeId : default(Guid));
                    }
                }
            }
            catch (InvalidOperationException)
            {
                // Failed to open ADO connection, return empty roleset instead
                return roles;
            }

            string userSID = UserInfo.UserNameToSID(UserData.Username);

            // Filter user account data for the current user.
            DataRow[] userAccounts = securityContext.Tables[UserAccountTable].Select($"Name = '{EncodeEscapeSequences(userSID)}'");

            // If SID based lookup failed, try lookup by user name.  Note that is critical that SID based lookup
            // take precedence over name based lookup for proper cross-platform authentication.
            if (userAccounts.Length == 0)
                userAccounts = securityContext.Tables[UserAccountTable].Select($"Name = '{EncodeEscapeSequences(UserData.Username)}'");

            Guid userAccountID = Guid.Empty;

            if (userAccounts.Length > 0)
                userAccountID = Guid.Parse(Convert.ToString(userAccounts[0]["ID"]));

            // Filter explicitly assigned application roles for current user - this will return an empty set if no
            // explicitly defined roles exist for the user -or- user doesn't exist in the database.
            DataRow[] userApplicationRoles = securityContext.Tables[ApplicationRoleUserAccountTable].Select($"UserAccountID = '{EncodeEscapeSequences(userAccountID.ToString())}'");

            // If no explicitly assigned application roles are found for the current user, we check for implicitly assigned
            // application roles based on the role assignments of the groups the user is a member of.
            if (userApplicationRoles.Length == 0)
            {
                List<DataRow> implicitRoles = new List<DataRow>();

                // Filter implicitly assigned application roles for each of the user's database and NT/AD groups. Note that
                // even if user is not defined in the database, an NT/AD group they are a member of may be associated with
                // a role - this allows the user to get a role assignment based on this group.
                foreach (string groupName in UserData.Groups)
                {
                    // Convert NT/AD group names back to SIDs for lookup in the database
                    string groupSID = UserInfo.GroupNameToSID(groupName);

                    // Locate associated security group record
                    DataRow[] securityGroups = securityContext.Tables[SecurityGroupTable].Select($"Name = '{EncodeEscapeSequences(groupSID)}'");

                    // If SID based lookup failed, try lookup by group name.  Note that is critical that SID based lookup
                    // take precedence over name based lookup for proper cross-platform authentication.
                    if (securityGroups.Length == 0)
                        securityGroups = securityContext.Tables[SecurityGroupTable].Select($"Name = '{EncodeEscapeSequences(groupName)}'");

                    if (securityGroups.Length > 0)
                    {
                        // Found security group by name, access group ID to lookup application roles defined for the group
                        DataRow securityGroup = securityGroups[0];

                        if (!Convert.IsDBNull(securityGroup["ID"]))
                            implicitRoles.AddRange(securityContext.Tables[ApplicationRoleSecurityGroupTable].Select($"SecurityGroupID = '{EncodeEscapeSequences(securityGroup["ID"].ToString())}'"));
                    }
                }

                userApplicationRoles = implicitRoles.ToArray();

            }

            // Populate user roles collection - both ApplicationRoleUserAccount and ApplicationRoleSecurityGroup tables contain ApplicationRoleID column
            foreach (DataRow role in userApplicationRoles)
            {
                if (Convert.IsDBNull(role["ApplicationRoleID"]))
                    continue;

                // Locate associated application role record
                DataRow applicationRole = null;

                if (securityContext.Tables[ApplicationRoleTable].PrimaryKey.Length > 0)
                {
                    applicationRole = securityContext.Tables[ApplicationRoleTable].Rows.Find(role["ApplicationRoleID"]);
                }
                else
                {
                    DataRow[] applicationRoles = securityContext.Tables[ApplicationRoleTable].Select($"ID = '{EncodeEscapeSequences(role["ApplicationRoleID"].ToString())}'");

                    if (applicationRoles.Length > 0)
                        applicationRole = applicationRoles[0];
                }

                if ((object)applicationRole == null || Convert.IsDBNull(applicationRole["Name"]))
                    continue;

                // Found application role by ID, add role name to user roles if not already defined
                string roleName = Convert.ToString(applicationRole["Name"]);

                if (!string.IsNullOrEmpty(roleName) && !roles.Contains(roleName, StringComparer.OrdinalIgnoreCase))
                    roles.Add(roleName);
            }

            // Add DefaultRoles if no Roles are present
            if (!string.IsNullOrEmpty(DefaultRoles) && roles.Count() == 0)
                foreach (string role in DefaultRoles.Split(','))
                    roles.Add(role);

            return roles;
        }

        #endregion

        #region [ Static ]

        // Static Events

        ///
        /// Raised when the security context is refreshed.
        ///
        public static event EventHandler<EventArgs<Dictionary<string, string[]>>> SecurityContextRefreshed;

        // Static Fields
        private static readonly LogPublisher Log = Logger.CreatePublisher(typeof(AdoSecurityProvider), MessageClass.Component);

        private static readonly string[] s_securityTables =
        {
            UserAccountTable,                   // User accounts
            SecurityGroupTable,                 // Security groups
            SecurityGroupUserAccountTable,      // Security group user accounts (i.e., users in a group)
            ApplicationRoleTable,               // Application roles (node specific)
            ApplicationRoleUserAccountTable,    // Application role assignments for user accounts
            ApplicationRoleSecurityGroupTable   // Application role assignments for security groups
        };

        ///
        /// Gets current default Node ID for security.
        ///
        public static readonly Guid DefaultNodeID;

        // Static Constructor
        static AdoSecurityProvider()
        {
            // Access configuration file system settings
            CategorizedSettingsElementCollection systemSettings = ConfigurationFile.Current.Settings["systemSettings"];

            // Make sure NodeID setting exists
            systemSettings.Add("NodeID", Guid.NewGuid().ToString(), "Unique Node ID");

            // Get NodeID as currently defined in configuration file
            DefaultNodeID = Guid.Parse(systemSettings["NodeID"].Value.ToNonNullString(Guid.NewGuid().ToString()));

            // Determine whether the node exists in the database and create it if it doesn't
            if (DefaultNodeID != Guid.Empty)
            {
                try
                {
                    using (AdoDataConnection connection = new AdoDataConnection(DefaultSettingsCategory))
                    {
                        const string NodeCountFormat = "SELECT COUNT(*) FROM Node";
                        const string NodeInsertFormat = "INSERT INTO Node(Name, Description, Enabled) VALUES('Default', 'Default node', 1)";
                        const string NodeUpdateFormat = "UPDATE Node SET ID = {0}";

                        int nodeCount = connection.ExecuteScalar<int?>(NodeCountFormat) ?? 0;

                        if (nodeCount == 0)
                        {
                            connection.ExecuteNonQuery(NodeInsertFormat);
                            connection.ExecuteNonQuery(NodeUpdateFormat, connection.Guid(DefaultNodeID));
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.SwallowException(ex, "AdoSecurityProvider: Failed to create default database node ID");
                }
            }
        }

        // Static Methods

        ///
        /// Extracts the current security context from the database.
        ///
        /// <param name="connection">Existing database connection used to extract security context.</param>
        /// <param name="exceptionHandler">Exception handler to use for any exceptions encountered while updating security cache.</param>
        /// <param name="currentUserName">Current user name, if applicable in calling context.</param>
        /// <returns>A new <see cref="DataSet"/> containing the latest security context.</returns>
        public static DataSet ExtractSecurityContext(IDbConnection connection, Action<Exception> exceptionHandler, string currentUserName = null)
        {
            DataSet securityContext = new DataSet("AdoSecurityContext");

            // Read the security context tables from the database connection
            foreach (string securityTable in s_securityTables)
            {
                AddSecurityContextTable(connection, securityContext, securityTable, securityTable == ApplicationRoleTable ? DefaultNodeID : default(Guid));
            }

            // Always cache security context after successful extraction
            Thread cacheSecurityContext = new Thread(() =>
            {
                try
                {
                    using (AdoSecurityCache cache = AdoSecurityCache.GetCurrentCache())
                    {
                        cache.DataSet = securityContext;
                        cache.WaitForSave();
                    }
                }
                catch (Exception ex)
                {
                    exceptionHandler(ex);
                }
            });

            cacheSecurityContext.IsBackground = true;
            cacheSecurityContext.Start();

            // Raise an event that will send a notification when the security context for a user has been refreshed
            if ((object)SecurityContextRefreshed != null)
            {
                try
                {
                    Dictionary<string, string[]> userRoles = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
                    string[] roles;

                    using (UserRoleCache userRoleCache = UserRoleCache.GetCurrentCache())
                    {
                        foreach (DataRow row in securityContext.Tables[UserAccountTable].Rows)
                        {
                            string userName = UserInfo.SIDToAccountName(Convert.ToString(row["Name"]));

                            if (userRoleCache.TryGetUserRole(userName, out roles))
                                userRoles[userName] = roles;
                        }

                        // Also make sure current user is added since user may have implicit rights based on group
                        if (!string.IsNullOrEmpty(currentUserName))
                        {
                            if (!userRoles.ContainsKey(currentUserName) && userRoleCache.TryGetUserRole(currentUserName, out roles))
                                userRoles[currentUserName] = roles;
                        }

                        if (userRoles.Count > 0)
                            SecurityContextRefreshed(typeof(AdoSecurityProvider), new EventArgs<Dictionary<string, string[]>>(userRoles));
                    }
                }
                catch (Exception ex)
                {
                    exceptionHandler(new InvalidOperationException($"Failed to raise \"SecurtyContextRefreshed\" event: {ex.Message}", ex));
                }
            }

            return securityContext;
        }

        private static void UpdatePrimaryKey(DataTable table, string columnName)
        {
            if (table.PrimaryKey.Length == 0)
                table.PrimaryKey = new[] { table.Columns[columnName] };
        }

        private static void AddSecurityContextTable(IDbConnection connection, DataSet securityContext, string tableName, Guid nodeID)
        {
            string tableQuery;

            if (nodeID == default(Guid))
                tableQuery = $"SELECT * FROM {tableName}";
            else
                tableQuery = $"SELECT * FROM {tableName} WHERE NodeID = '{nodeID}'";

            using (IDataReader reader = connection.ExecuteReader(tableQuery))
            {
                securityContext.Tables.Add(tableName).Load(reader);
            }
        }

        private static string EncodeEscapeSequences(string value)
        {
            return value.ToNonNullString().Replace("\\", "\\\\");
        }

        #endregion
    }
}


text:
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from django import forms
from .models import Project, Card, Board, ProjectComment, CardComment

class ProjectCreateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProjectCreateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = Project
        exclude = ('slug', 'owner', 'deleted')

class ProjectUpdateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProjectUpdateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = Project
        exclude = ('slug', 'owner')

class BoardCreateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(BoardCreateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = Board
        exclude = ('project', 'position')

class CardCreateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(CardCreateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = Card
        exclude = ('slug', 'content', 'project', 'deleted', 'done')

class ProjectCommentCreateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ProjectCommentCreateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = ProjectComment
        exclude = ('author', 'project', 'content')

class CardCommentCreateForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(CardCommentCreateForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper(self)

        self.helper.layout.append(Submit('save', 'save'))

    class Meta:
        model = CardComment
        exclude = ('author', 'project', 'card', 'content')

redpajama_set_name: RedPajamaGithub


text:
I am a ComposableModel that has two text model:
	- The one on the top to write regular LaTeX.
	- The one on the bottom to write Mathex (i.e. it is possible to use literals without calling #asMathex).
		For example: (($a power: 2) + ($b power: 2))equal: ($c power: 2)


text:
<?php
class Kwc_Directories_Item_Directory_Controller extends Kwf_Controller_Action_Auto_Kwc_Grid
{
    protected $_buttons = array(
        'save',
        'delete',
        'reload',
        'add'
    );

    protected $_filters = array('text'=>true);
    protected $_paging = 25;

    public function preDispatch()
    {
        parent::preDispatch();
        if (is_instance_of(Kwc_Abstract::getSetting($this->_getParam('class'), 'extConfig'), 'Kwc_Directories_Item_Directory_ExtConfigEditButtons')
            || is_instance_of(Kwc_Abstract::getSetting($this->_getParam('class'), 'extConfigControllerIndex'), 'Kwc_Directories_Item_Directory_ExtConfigEditButtons')) {
            $url = Kwc_Admin::getInstance($this->_getParam('class'))->getControllerUrl('Form');
            $editDialog = array(
                'autoForm' => 'Kwc.Directories.Item.Directory.EditFormPanel',
                'controllerUrl' => $url
            );
            if (!empty($this->_editDialog['width'])) $editDialog['width'] = $this->_editDialog['width'];
            if (!empty($this->_editDialog['height'])) $editDialog['height'] = $this->_editDialog['height'];
            $this->_editDialog = $editDialog;
        }

        if ($this->_columns[0] instanceof Kwf_Grid_Column_Button) {
            throw new Kwf_Exception("Override Controller and add at least one column (button must not be first)");
        }
        if ($this->_columns[0] instanceof Kwf_Grid_Column && $this->_columns[0]->getName() == 'component_class') {
            throw new Kwf_Exception("Override Controller and add at least one column (component_class must not be first)");
        }
    }

    protected function _initColumns()
    {
        $extConfigType = false;
        if (is_instance_of(Kwc_Abstract::getSetting($this->_getParam('class'), 'extConfig'), 'Kwc_Directories_Item_Directory_ExtConfigEditButtons')) {
            $extConfigType = 'extConfig';
        } else if (is_instance_of(Kwc_Abstract::getSetting($this->_getParam('class'), 'extConfigControllerIndex'), 'Kwc_Directories_Item_Directory_ExtConfigEditButtons')) {
            $extConfigType = 'extConfigControllerIndex';
        }
        if ($extConfigType) {
            //shows editDialog
            if ($this->_editDialog) {
                $this->_columns->add(new Kwf_Grid_Column_Button('properties', ' ', 20))
                    ->setButtonIcon('/assets/silkicons/newspaper.png')
                    ->setTooltip(trlKwf('Properties'));
            }

            $extConfig = Kwf_Component_Abstract_ExtConfig_Abstract::getInstance($this->_getParam('class'), $extConfigType)
                        ->getConfig(Kwf_Component_Abstract_ExtConfig_Abstract::TYPE_DEFAULT);

            $extConfig = $extConfig['items'];
                    if ($extConfig['countDetailClasses'] > 1 && !$this->_getModel()->hasColumn('component')) {
                throw new Kwf_Exception('If you have more than one detail-component your table has to have a column named "component"');
            }
            if ($extConfig['countDetailClasses'] == 1 && $this->_getModel()->hasColumn('component')) {
                throw new Kwf_Exception('If you have just one detail-component your table is not allowed to have a column named "component"');
            }
            $i=0;
            foreach ($extConfig['contentEditComponents'] as $ec) {
                $name = Kwf_Trl::getInstance()->trlStaticExecute(Kwc_Abstract::getSetting($ec['componentClass'], 'componentName'));
                $icon = Kwc_Abstract::getSetting($ec['componentClass'], 'componentIcon');
                $icon = new Kwf_Asset($icon);
                $this->_columns->add(new Kwc_Directories_Item_Directory_ControllerEditButton('edit_'.$i, ' ', 20))
                    ->setColumnType('editContent')
                    ->setEditComponentClass($ec['componentClass'])
                    ->setEditComponent($ec['component'])
                    ->setEditType($ec['type'])
                    ->setEditIdTemplate($ec['idTemplate'])
                    ->setEditComponentIdSuffix($ec['componentIdSuffix'])
                    ->setButtonIcon($icon->toString(array('arrow')))
                    ->setTooltip(trlKwf('Edit {0}', $name));
                $i++;
            }
        }
        if ($this->_model->hasColumn('component')) {
            $this->_columns->add(new Kwf_Grid_Column('component'));
        }
        $this->_columns->add(new Kwf_Grid_Column('component_class'))
            ->setData(new Kwf_Data_Kwc_ComponentClass($this->_getParam('class'), 'detail'));
        $this->_columns->add(new Kwf_Grid_Column('edit_components'))
            ->setData(new Kwf_Data_Kwc_EditComponents($this->_getParam('class'), 'detail'));
    }

    protected function _beforeSave(Kwf_Model_Row_Interface $row)
    {
        parent::_beforeSave($row);
        if ($row->getModel()->hasColumn('visible') && !$row->visible) {
            $this->_checkRowIndependence($row, trlKwf('hide'));
        }
    }

    protected function _beforeDelete(Kwf_Model_Row_Interface $row)
    {
        parent::_beforeDelete($row);
        $this->_checkRowIndependence($row, trlKwf('delete'));
    }

    private function _checkRowIndependence(Kwf_Model_Row_Interface $row, $msgMethod)
    {
        $c = Kwf_Component_Data_Root::getInstance()
            ->getComponentByDbId($this->_getParam('componentId'));
        // wenn zB Newsletter statisch in root erstellt wurde, gibts kein visible
        if (!$c) {
            //wenn seite offline ist ignorieren
            //  ist nicht natürlich nicht korrekt, wir *müssten* die überprüfung
            //  nachholen, sobald die seite online gestellt wird
            return;
        }
        $components = array();
        foreach (Kwc_Admin::getDependsOnRowInstances() as $a) {
            if ($a instanceof Kwf_Component_Abstract_Admin_Interface_DependsOnRow) {
                $components = array_merge($components, $a->getComponentsDependingOnRow($row));
            }
        }

        $g = Kwc_Abstract::getSetting($this->_getParam('class'), 'generators');
        if (isset($g['detail']['dbIdShortcut'])) {
            //wenn auf sich selbst verlinkt ignorieren
            foreach ($components as $k=>&$c) {
                $c = $c->getPage();
                $news = Kwf_Component_Data_Root::getInstance()
                    ->getComponentsByDbId($g['detail']['dbIdShortcut'].$row->id);
                foreach ($news as $n) {
                    if ($c->componentId == $n->getPage()->componentId) {
                        unset($components[$k]);
                    }
                }
            }
        }
        if ($components) {
            $msg = trlKwf("You can not {0} this entry as it is used on the following pages:", $msgMethod);
            $msg .= Kwf_Util_Component::getHtmlLocations($components);
            throw new Kwf_ClientException($msg);
        }
    }

    public function jsonDuplicateAction()
    {
        if (!isset($this->_permissions['duplicate']) || !$this->_permissions['duplicate']) {
            throw new Kwf_Exception("Duplicate is not allowed.");
        }

        $ids = $this->getRequest()->getParam($this->_primaryKey);
        $ids = explode(';', $ids);

        $progressBar = null;

        $this->view->data = array('duplicatedIds' => array());
        ignore_user_abort(true);
        if (Zend_Registry::get('db')) Zend_Registry::get('db')->beginTransaction();
        $dir = Kwf_Component_Data_Root::getInstance()->getComponentByDbId(
            $this->_getParam('componentId'),
            array('ignoreVisible'=>true, 'limit'=>1)
        );
        foreach ($ids as $id) {
            $child = $dir->getChildComponent(array('id'=>'-'.$id, 'ignoreVisible'=>true));
            $newChild = Kwf_Util_Component::duplicate($child, $dir, $progressBar);
            $newChild->row->save();
            $this->view->data['duplicatedIds'][] = $newChild->id;
        }
        if (Zend_Registry::get('db')) Zend_Registry::get('db')->commit();
    }

    public function jsonMultiUploadAction()
    {
        $componentId = $this->_getParam('componentId');
        $component = Kwf_Component_Data_Root::getInstance()
            ->getComponentByDbId($componentId, array('ignoreVisible' => true, 'limit' => 1));
        if (Kwc_Abstract::getSetting($component->componentClass, 'multiFileUpload')) {
            $uploadIds = $this->_getParam('uploadIds');
            $uploadIds = explode(',', $uploadIds);
            foreach ($uploadIds as $uploadId) {
                $this->_createNewDetailComponentFromUpload($uploadId, $component);
            }
        }
    }

    protected function _createNewDetailComponentFromUpload($uploadId, $component)
    {
        throw new Kwf_Exception_NotYetImplemented('You have to override _createNewDetailComponentFormUpload function to create child components on multiFileUpload');
    }
}


text: package config // import "moul.io/assh/v2/pkg/config"


text:
@interface UIImage (AFUtilities)

+ (UIImage *)af_imageNamed:(NSString *)imageName bundleName:(NSString *)bundleName;
+ (UIImage *)af_imageWithColor:(UIColor *)color;

@end


text:
<?php

/* $Id: renderer.cls.php 4474 2012-01-30 12:22:27Z cburela $ */

/**
 * Concrete renderer
 *
 * Instantiates several specific renderers in order to render any given
 * frame.
 *
 * @access private
 * @package dompdf
 */
class Renderer extends Abstract_Renderer {

  /**
   * Array of renderers for specific frame types
   *
   * @var array
   */
  protected $_renderers;

  /**
   * Cache of the callbacks array
   *
   * @var array
   */
  private $_callbacks;

  /**
   * Class destructor
   */
  function __destruct() {
  	clear_object($this);
  }

  /**
   * Advance the canvas to the next page
   */
  function new_page() {
    $this->_canvas->new_page();
  }

  /**
   * Render frames recursively
   *
   * @param Frame $frame the frame to render
   */
  function render(Frame $frame) {
    global $_dompdf_debug;

    if ( $_dompdf_debug ) {
      echo $frame;
      flush();
    }

    $style = $frame->get_style();
    $display = $style->display;

    // Starts the CSS transformation
    if ( $style->transform && is_array($style->transform) ) {
      $this->_canvas->save();
      list($x, $y, $w, $h) = $frame->get_padding_box();
      $origin = $style->transform_origin;

      foreach($style->transform as $transform) {
        list($function, $values) = $transform;
        if ( $function === "matrix" ) {
          $function = "transform";
        }

        $values = array_map("floatval", $values);
        $values[] = $x + $style->length_in_pt($origin[0], $style->width);
        $values[] = $y + $style->length_in_pt($origin[1], $style->height);

        call_user_func_array(array($this->_canvas, $function), $values);
      }
    }

    switch ($display) {

    case "block":
    case "list-item":
    case "inline-block":
    case "table":
    case "table-row-group":
    case "table-header-group":
    case "table-footer-group":
    case "inline-table":
      $this->_render_frame("block", $frame);
      break;

    case "inline":
      if ( $frame->get_node()->nodeName === "#text" )
        $this->_render_frame("text", $frame);
      else
        $this->_render_frame("inline", $frame);
      break;

    case "table-cell":
      $this->_render_frame("table-cell", $frame);
      break;

    case "-dompdf-list-bullet":
      $this->_render_frame("list-bullet", $frame);
      break;

    case "-dompdf-image":
      $this->_render_frame("image", $frame);
      break;

    case "none":
      $node = $frame->get_node();

      if ( $node->nodeName === "script" ) {
        if ( $node->getAttribute("type") === "text/php" ||
             $node->getAttribute("language") === "php" ) {
          // Evaluate embedded php scripts
          $this->_render_frame("php", $frame);
        }

        elseif ( $node->getAttribute("type") === "text/javascript" ||
             $node->getAttribute("language") === "javascript" ) {
          // Insert JavaScript
          $this->_render_frame("javascript", $frame);
        }
      }

      // Don't render children, so skip to next iter
      return;

    default:
      break;

    }

    // Check for begin frame callback
    $this->_check_callbacks("begin_frame", $frame);

    // Starts the overflow: hidden box
    if ( $style->overflow === "hidden" ) {
      list($x, $y, $w, $h) = $frame->get_padding_box();
      $this->_canvas->clipping_rectangle($x, $y, $w, $h);
    }

    $page = $frame->get_root()->get_reflower();

    foreach ($frame->get_children() as $child) {
      $child_style = $child->get_style();

      // Stacking context
      if ( $child_style->z_index !== false && ($child_style->z_index !== "auto" || in_array($child_style->position, Style::$POSITIONNED_TYPES)) ) {
        $z_index = ($child_style->z_index === "auto") ? 0 : intval($child_style->z_index);
        $page->add_frame_to_stacking_context($child, $z_index);
        $child_style->z_index = false;
      }

      else {
        $this->render($child);
      }
    }

    // Ends the overflow: hidden box
    if ( $style->overflow === "hidden" ) {
      $this->_canvas->clipping_end();
    }

    if ( $style->transform && is_array($style->transform) ) {
      $this->_canvas->restore();
    }

    // Check for end frame callback
    $this->_check_callbacks("end_frame", $frame);

  }

  /**
   * Check for callbacks that need to be performed when a given event
   * gets triggered on a frame
   *
   * @param string $event the type of event
   * @param Frame $frame the frame that event is triggered on
   */
  protected function _check_callbacks($event, $frame) {
    if (!isset($this->_callbacks)) {
      $this->_callbacks = $this->_dompdf->get_callbacks();
    }

    if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) {
      $info = array(0 => $this->_canvas, "canvas" => $this->_canvas,
                    1 => $frame, "frame" => $frame);
      $fs = $this->_callbacks[$event];
      foreach ($fs as $f) {
        if (is_callable($f)) {
          if (is_array($f)) {
            $f[0]->$f[1]($info);
          } else {
            $f($info);
          }
        }
      }
    }
  }

  /**
   * Render a single frame
   *
   * Creates Renderer objects on demand
   *
   * @param string $type type of renderer to use
   * @param Frame $frame the frame to render
   */
  protected function _render_frame($type, $frame) {

    if ( !isset($this->_renderers[$type]) ) {

      switch ($type) {
      case "block":
        $this->_renderers[$type] = new Block_Renderer($this->_dompdf);
        break;

      case "inline":
        $this->_renderers[$type] = new Inline_Renderer($this->_dompdf);
        break;

      case "text":
        $this->_renderers[$type] = new Text_Renderer($this->_dompdf);
        break;

      case "image":
        $this->_renderers[$type] = new Image_Renderer($this->_dompdf);
        break;

      case "table-cell":
        $this->_renderers[$type] = new Table_Cell_Renderer($this->_dompdf);
        break;

      case "list-bullet":
        $this->_renderers[$type] = new List_Bullet_Renderer($this->_dompdf);
        break;

      case "php":
        $this->_renderers[$type] = new PHP_Evaluator($this->_canvas);
        break;

      case "javascript":
        $this->_renderers[$type] = new Javascript_Embedder($this->_dompdf);
        break;

      }
    }

    $this->_renderers[$type]->render($frame);

  }
}


text:
const moment = require('moment');

customElements.define('v-clock', class extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.innerHTML = `<style>${require('./clock.css')}</style>${require('./clock.html')}`;

        const divDate = shadowRoot.querySelector('.date');
        const divTime = shadowRoot.querySelector('.time');

        setInterval(() => {
            const now = moment();

            divDate.innerHTML = now.format('ll');
            divTime.innerHTML = now.format('LTS');
        }, 250);
    }
});


text:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package plugin;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JScrollPane;
import syntaxhighlight.Parser;
import syntaxhighlight.SyntaxHighlighter;
import syntaxhighlight.SyntaxHighlighterPane;
import syntaxhighlight.Theme;

/**
 *
 * @author amine
 */
public class WindowDecorator extends SyntaxHighlighter {

    //maybe decorate the whole scroll pane ?!
    //contain a SyntaxHilighter(JscrollPane) and use its paint method to actually override the container's paint method!
    private JScrollPane target;

    public WindowDecorator(Parser parser, Theme theme) {
        super(parser, theme);
        SyntaxHighlighterPane highlighterPane = highlighter;
        WindowDecorator highlighter = this;
        highlighterPane.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {
                try {
                    highlighter.setContent(highlighterPane.getText());
                }catch(Exception exeption){}
            }

            @Override
            public void keyPressed(KeyEvent e) {
            }

            @Override
            public void keyReleased(KeyEvent e) {
            }

        });
    }

    public JScrollPane getTarget() {
        return target;
    }

    //i could not use a constructor as the Decorator states, but it's okey, this will do
    public void setTarget(JScrollPane target) {
        if (this.target == null) {
            this.target = target;
        }
    }
}


text:
/*****************************************************************************/
/**
 *  @file   CheckBoxGroup.cpp
 *  @author Naohisa Sakamoto
 */
/*****************************************************************************/
#include "CheckBoxGroup.h"
#include "CheckBox.h"
#include <kvs/ScreenBase>
#include <kvs/EventBase>
#include <kvs/MouseEvent>

namespace kvs
{

namespace glut
{

/*===========================================================================*/
/**
 *  @brief  Constructs a new CheckBoxGroup class.
 *  @param  screen [in] pointer to the parent screen
 */
/*===========================================================================*/
CheckBoxGroup::CheckBoxGroup( kvs::ScreenBase* screen ):
    kvs::glut::WidgetBase( screen )
{
    BaseClass::setEventType(
        kvs::EventBase::PaintEvent |
        kvs::EventBase::ResizeEvent |
        kvs::EventBase::MousePressEvent |
        kvs::EventBase::MouseReleaseEvent );
}

/*===========================================================================*/
/**
 *  @brief  Adds a check box.
 *  @param  box [in] pointer to the check box
 */
/*===========================================================================*/
void CheckBoxGroup::add( kvs::glut::CheckBox* box )
{
    box->attach_group( this );
    m_boxes.push_back( box );
}

/*===========================================================================*/
/**
 *  @brief  Removes the check box.
 *  @param  box [in] pointer to the check box
 */
/*===========================================================================*/
void CheckBoxGroup::remove( kvs::glut::CheckBox* box )
{
    box->detach_group();
    m_boxes.remove( box );
}

/*===========================================================================*/
/**
 *  @brief  Shows the boxes in the group.
 */
/*===========================================================================*/
void CheckBoxGroup::show()
{
    BaseClass::show();

    std::list<kvs::glut::CheckBox*>::iterator box = m_boxes.begin();
    std::list<kvs::glut::CheckBox*>::iterator last = m_boxes.end();
    while ( box != last )
    {
        (*box++)->show();
    }
}

/*===========================================================================*/
/**
 *  @brief  Hides the boxes in the group.
 */
/*===========================================================================*/
void CheckBoxGroup::hide()
{
    BaseClass::hide();

    std::list<kvs::glut::CheckBox*>::iterator box = m_boxes.begin();
    std::list<kvs::glut::CheckBox*>::iterator last = m_boxes.end();
    while ( box != last )
    {
        (*box++)->hide();
    }
}

/*===========================================================================*/
/**
 *  @brief  Paint event.
 */
/*===========================================================================*/
void CheckBoxGroup::paintEvent()
{
    this->screenUpdated();
}

/*===========================================================================*/
/**
 *  @brief  Resize event.
 *  @param  width [in] resized screen width
 *  @param  height [in] resized screen height
 */
/*===========================================================================*/
void CheckBoxGroup::resizeEvent( int width, int height )
{
    kvs::IgnoreUnusedVariable( width );
    kvs::IgnoreUnusedVariable( height );

    this->screenResized();
}

/*===========================================================================*/
/**
 *  @brief  Mouse press event.
 *  @param  event [in] pointer to the mouse event
 */
/*===========================================================================*/
void CheckBoxGroup::mousePressEvent( kvs::MouseEvent* event )
{
    if ( !BaseClass::isShown() ) return;

    int id = 0;
    std::list<kvs::glut::CheckBox*>::iterator box = m_boxes.begin();
    std::list<kvs::glut::CheckBox*>::iterator last = m_boxes.end();
    while ( box != last )
    {
        if ( (*box)->contains( event->x(), event->y() ) )
        {
            BaseClass::activate();
            this->pressed( *box );
            this->pressed( id );
        }

        box++;
        id++;
    }
}

/*===========================================================================*/
/**
 *  @brief  Mouse release event.
 *  @param  event [in] pointer to the mouse event
 */
/*===========================================================================*/
void CheckBoxGroup::mouseReleaseEvent( kvs::MouseEvent* event )
{
    if ( !BaseClass::isShown() ) return;

    if ( BaseClass::isActive() )
    {
        int id = 0;
        std::list<kvs::glut::CheckBox*>::iterator box = m_boxes.begin();
        std::list<kvs::glut::CheckBox*>::iterator last = m_boxes.end();
        while ( box != last )
        {
            if ( (*box)->contains( event->x(), event->y() ) )
            {
                this->released( *box );
                this->released( id );
                BaseClass::deactivate();
            }

            box++;
            id++;
        }
    }
}

} // end of namespace glut

} // end of namespace kvs