65.9K
CodeProject 正在变化。 阅读更多。
Home

AD、SQL Server、GMail 同步工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (5投票s)

2009 年 4 月 23 日

CPOL

6分钟阅读

viewsIcon

30701

downloadIcon

542

GMail、AD 和 SQL Server 的多账户同步器。

引言

此程序旨在通过 Google 提供的 .NET API 来创建 GMail 账户。它使用 Visual Studio 2008 编写。整个项目都包含在内,在Release目录下应该有一个可运行的版本。

如何运行

第 1 步:设置 SQL 数据库配置

config.JPG

选择适合您用户账户和日志记录的 SQL Server。

第 2 步:创建 AD 账户和组

用户设置

用户可以从表或视图中拉取,并放置在执行账户有权访问的任何 Active Directory 位置。通过“自定义”选项卡,可以创建自定义列来添加字段映射。几乎所有 AD 中可写字段都可用。每个用户在创建后都会被放入一个默认组,以识别创建他们的用户同步。

组设置

组对于此工具来说并非必需,但似乎是很好的包含项,所以在此列出。它需要一个包含组列表的表或视图。该表中的唯一组集将在具有组追加名称的子 OU 中创建。要将用户分配到组,需要一个交叉引用表或视图。

  • 将任何 SQL Server 2000 数据转换为 Microsoft Active Directory 用户或安全组。
  • 通过字段分配将 AD 账户/组从 SQL Server 映射到 AD。
  • 根据数据库源中的用户唯一 ID 同步数据到 AD。
  • 账户数据不是实时同步的。

第 3 步:创建 GMail 账户

  • 从 AD 到 GMail 的转换。
  • 用户以 ID99999999@domain.gmail.apps.edu 的形式创建。
  • 用户电子邮件账户的别名为 first.last@domain.gmail.apps.edu;如果存在重复,将通过组合中间名和名字来缩写以获得唯一的名称。

执行

  • 任务计划程序可以调用 LDAP Magic 命令行,提供已保存映射的参数和操作类型。
  • LDAP Magic 执行选定的操作并创建日志。
  • 日志文件生成成功事务、警告和错误的记录。目前日志未保存。

Using the Code

代码量很大,我不知道哪些内容适合在此处列出。相当一部分代码已注释掉,简单的调试代码也隐藏在注释中。平均而言,每个函数都有一个简短的描述和一组预期的参数(如果它们不显而易见)。大部分代码设计得都非常健壮且易于重用。几乎所有可能出错的部分都配备了错误处理代码和日志记录。现在,让我们来看看一些精彩的部分。

首先,一个 SQL 查询逻辑的例子

//utils/toolset.cs

public SqlDataReader QueryInnerJoin(string table1, string table2, 
       string pkey1, string pkey2, ArrayList additionalFields, 
       SqlConnection sqlConn, LogFile log)
{
    // additionalFields takes the field names " table.field,"
    // Returns data from table1 where the row is in both table 1 and table2
    // additional fields table2.data2
    //**************************************************
    //| Table1   | Table2            | Returned result
    //**************************************************
    //| ID  Data | ID    Data  Data2 |              | Table1. Table1. Table2.
    //                                                 ID      DATA    data2
    //| 1   a    | 1     a     e     | RETURNED     | 1         a        e
    //| 2   b    | null  null  f     | NOT RETURNED |            
    //| 3   c    | 3     null  g     | RETURNED     | 3         c        g
    //| 4   d    | 4     e     h     | RETURNED     | 4         d        h

    SqlDataReader r;
    SqlCommand sqlComm;
    string additionalfields = "";
    foreach (string key in additionalFields)
    {
        additionalfields += key;
    }
    additionalfields = additionalfields.Remove(additionalfields.Length - 2);
    if (additionalFields.Count > 0)
    {
        sqlComm = new SqlCommand("SELECT DISTINCT " + table1 + ".*, " + additionalfields + 
                  " FROM " + table1 + " INNER JOIN " + table2 + " ON " + 
                  table1 + "." + pkey1 + " = " + table2 + "." + pkey2, sqlConn);
    }
    else
    {
        sqlComm = new SqlCommand("SELECT DISTINCT " + table1 + ".* FROM " + 
                  table1 + " INNER JOIN " + table2 + " ON " + table1 + 
                  "." + pkey1 + " = " + table2 + "." + pkey2, sqlConn);
    }

    try
    {
        sqlComm.CommandTimeout = 360;
        r = sqlComm.ExecuteReader();
        log.addTrn(sqlComm.CommandText.ToString(), "Query");
        return r;
    }
    catch (Exception ex)
    {
        log.addTrn("Failed SQL command " + sqlComm.CommandText.ToString() + 
                   " error " + ex.Message.ToString() + "\n" + 
                   ex.StackTrace.ToString(), "Error");
    }
    return null;
}

这是用于合并数据的查询样本之一。大多数查询是重复的;一个返回 `SQLDataReader`;互补的查询使用 `SELECT INTO` 并创建一个 SQL 表供其他方法使用。

接下来是批量 Active Directory 用户创建代码片段

//utils/toolset.cs

public void CreateUsersAccounts(string ouPath, SqlDataReader users, 
       string groupDn, string ldapDomain, UserSynch usersyn, LogFile log)
{
    // oupath holds the path for the AD OU to hold the Users 
    // users is a sqldatareader witht the required fields
    // in it ("CN") other Datastructures would be easy to substitute 
    // groupDN is a base group which all new users get automatically inserted into

    int i;
    int fieldcount;
    int val;
    string name = "";
    string last = "";
    string first = "";
    fieldcount = users.FieldCount;
    try
    {
        while (users.Read())
        {
            try
            {
                if (users[usersyn.User_password].ToString() != "")
                {
                    if (!DirectoryEntry.Exists("LDAP://CN=" + 
                        System.Web.HttpUtility.UrlEncode(
                        users[usersyn.User_sAMAccount].ToString()
                        ).Replace("+", " ").Replace("*", "%2A") + "," + ouPath))
                    {

                        DirectoryEntry entry = new DirectoryEntry("LDAP://" + ouPath);
                        DirectoryEntry newUser = entry.Children.Add("CN=" + 
                          System.Web.HttpUtility.UrlEncode(
                          users[usersyn.User_CN].ToString()).Replace(
                          "+", " ").Replace("*", "%2A"), "user");
                        // generated
                        newUser.Properties["samAccountName"].Value = 
                          System.Web.HttpUtility.UrlEncode(
                          users[usersyn.User_sAMAccount].ToString()).Replace(
                          "+", " ").Replace("*", "%2A");
                        //newUser.Properties["mail"].Value = System.Web.HttpUtility.UrlEncode(
                        //  users[usersyn.User_mail].ToString()).Replace("+", " ").Replace(
                        //  "*", "%2A") + "@" + System.Web.HttpUtility.UrlEncode(
                        //  users[usersyn.UserEmailDomain].ToString()).Replace(
                        //  "+", " ").Replace("*", "%2A");
                        newUser.Properties["UserPrincipalName"].Value = 
                          System.Web.HttpUtility.UrlEncode(
                          users[usersyn.User_sAMAccount].ToString()).Replace(
                          "+", " ").Replace("*", "%2A");
                        newUser.Properties["displayName"].Value = 
                          System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_Lname]).Replace(
                          "+", " ").Replace("*", "%2A") + ", " + 
                          System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_Fname]).Replace(
                          "+", " ").Replace("*", "%2A");
                        newUser.Properties["description"].Value = 
                          System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_Lname]).Replace("+", " ").Replace(
                          "*", "%2A") + ", " + System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_Fname]).Replace(
                           "+", " ").Replace("*", "%2A");

                        newUser.CommitChanges();

                        // SQL query generated ensures matching field
                        // names between the SQL form fields and AD
                        for (i = 0; i < fieldcount; i++)
                        {
                            name = users.GetName(i);
                            // eliminiate non updatable fields
                            if (name != "password" && name != "CN")
                            {
                                // mail needs some special handling
                                if (name != "mail")
                                {
                                    if ((string)users[name] != "")
                                    {
                                        newUser.Properties[name].Value = 
                                          System.Web.HttpUtility.UrlEncode(
                                          (string)users[name]).Replace(
                                          "+", " ").Replace("*", "%2A");
                                    }
                                }
                                else
                                {
                                    first = System.Web.HttpUtility.UrlEncode(
                                      (string)users[name]).Replace("+", " ").Replace(
                                      "*", "%2A").Replace("%40", "@");
                                    last = (string)users[name];
                                    // check to see if mail field has illegal characters
                                    if (first == last)
                                    {
                                        // no illegal characters input the value into AD
                                        newUser.Properties[name].Value = 
                                          System.Web.HttpUtility.UrlEncode(
                                          (string)users[name]).Replace("+", " ").Replace(
                                          "*", "%2A").Replace("!", "%21").Replace(
                                          "(", "%28").Replace(")", "%29").Replace(
                                          "'", "%27").Replace("_", "%5f").Replace(
                                          " ", "%20").Replace("%40", "@");
                                    }
                                    else
                                    {
                                        // newUser.Properties[name].Value = "";
                                    }
                                }
                            }
                        }

                        AddUserToGroup("CN=" + System.Web.HttpUtility.UrlEncode(
                          users[usersyn.User_sAMAccount].ToString()).Replace(
                          "+", " ").Replace("*", "%2A") + "," + usersyn.UserHoldingTank, 
                          groupDn, false, ldapDomain, log);
                        newUser.Invoke("SetPassword", new object[] { 
                          System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_password]).Replace(
                           "+", " ").Replace("*", "%2A") });
                        newUser.CommitChanges();

                        val = (int)newUser.Properties["userAccountControl"].Value;
                        // set to normal user
                        newUser.Properties["userAccountControl"].Value = 
                                val | (int)accountFlags.ADS_UF_NORMAL_ACCOUNT;
                        // set to enabled account val & ~0c0002 creates
                        // a bitmask which reverses the disabled bit
                        newUser.Properties["userAccountControl"].Value = 
                                           val & ~(int)accountFlags.ADS_UF_ACCOUNTDISABLE;
                        newUser.CommitChanges();
                        newUser.Close();
                        newUser.Dispose();
                        entry.Close();
                        entry.Dispose();
                        log.addTrn("User added |" + (string)users[usersyn.User_sAMAccount] + 
                                   " " + usersyn.UserHoldingTank, "Transaction");
                    }
                    else
                    {
                        log.addTrn("CN=" + System.Web.HttpUtility.UrlEncode(
                          (string)users[usersyn.User_sAMAccount]).Replace("+", " ").Replace(
                          "*", "%2A") + "," + ouPath + 
                          " user already exists from adding", "Error");
                        //MessageBox.Show("CN=" + System.Web.HttpUtility.UrlEncode(
                          (string)users["CN"]).Replace("+", " ").Replace("*", "%2A") + 
                          "," + ouPath + " user already exists from adding");
                    }
                }

            }
            catch (Exception ex)
            {
                string debugdata = "";
                for (i = 0; i < fieldcount; i++)
                {

                    debugdata += users.GetName(i) + "=" + 
                      System.Web.HttpUtility.UrlEncode((string)users[i]).Replace(
                      "+", " ").Replace("*", "%2A") + ", ";

                }
                log.addTrn("issue create user LDAP://CN=" + System.Web.HttpUtility.UrlEncode(
                  (string)users["CN"]).Replace("+", " ").Replace("*", "%2A") + "," + ouPath + 
                  "\n" + debugdata + " User create failed, commit error" + name + " | " + 
                  ex.Message.ToString() + "\n" + ex.StackTrace.ToString(), "Error");
                // MessageBox.Show(e.Message.ToString() + "issue create user LDAP://CN=" + 
                //  System.Web.HttpUtility.UrlEncode((string)users["CN"]).Replace("+", 
                //  " ").Replace("*", "%2A") + "," + ouPath + "\n" + debugdata);
            }
        }

    }
    catch (Exception ex)
    {
        if (users != null)
        {
            string debugdata = "";
            for (i = 0; i < fieldcount; i++)
            {
                debugdata += users.GetName(i) + "=" + System.Web.HttpUtility.UrlEncode(
                  (string)users[i]).Replace("+", " ").Replace("*", "%2A") + ", ";
            }
            log.addTrn("issue create user LDAP://CN=" + System.Web.HttpUtility.UrlEncode(
              (string)users["CN"]).Replace("+", " ").Replace("*", "%2A") + "," + ouPath + 
              "\n" + debugdata + " failed field maybe " + name + " | " + 
              ex.Message.ToString() + "\n" + ex.StackTrace.ToString(), "Error");
            // MessageBox.Show(e.Message.ToString() + "issue create user LDAP://CN=" + 
            // System.Web.HttpUtility.UrlEncode((string)users["CN"]).Replace(
            // "+", " ").Replace("*", "%2A") + "," + ouPath + "\n" + debugdata);
        }
        else
        {
            log.addTrn("issue creating users datareader is null " + 
              ex.Message.ToString() + "\n" + ex.StackTrace.ToString(), "Error");
        }
    }
    /*
    //Add this to the create account method
    int val = (int)newUser.Properties["userAccountControl"].Value; 
         //newUser is DirectoryEntry object
    newUser.Properties["userAccountControl"].Value = val | 0x80000; 
}

请注意,`UserSynch` 包含了从 GUI 界面收集的大部分字段名称。此代码负责接收 `SqlDataReader` 并创建 Active Directory 账户。有很多通用的方法可以做同样的事情。但是,如果您尝试从 SQL 表创建 AD 账户,这是一个很好的起点。

最后,用于从 SQLDataReader 创建 GMail 账户的代码

public DataTable Create_Gmail_Users(AppsService service, 
       GmailUsers gusersyn, SqlDataReader users, LogFile log)
{
    // user alising not created yet
    // Takes the SQLDataReader and creates all users in the reader

    // create the table for holding the users final
    // gmail account infomation for email writeback
    DataTable returnvalue = new DataTable();
    DataRow row;

    returnvalue.TableName = "users";

    returnvalue.Columns.Add(gusersyn.Writeback_primary_key);
    returnvalue.Columns.Add(gusersyn.Writeback_email_field);

    row = returnvalue.NewRow();
    string studentID = "";
    string first_name = "";
    string last_name = "";
    string middle_name = "";
    string password = "";
    string userNickName = "Aliasing off";
    try
    {
        while (users.Read())
        {
            try
            {
                // using _ as escape character allows illegal characters in username
                studentID = System.Web.HttpUtility.UrlEncode(
                  users[gusersyn.User_StuID].ToString()).Replace(
                  "+", " ").Replace("*", "%2A").Replace("!", "%21").Replace("(", 
                  "%28").Replace(")", "%29").Replace("'", "%27").Replace(
                  "_", "%5f").Replace(" ", "%20").Replace("%", "_");
                // names are less restricitve the only illegal characters are < > =
                first_name = users[gusersyn.User_Fname].ToString().Replace(
                  "<", "%3c").Replace(">", "%3e").Replace("=", "%3d").Replace("%", "%25");
                middle_name = users[gusersyn.User_Mname].ToString().Replace("<", 
                              "%3c").Replace(">", "%3e").Replace(
                              "=", "%3d").Replace("%", "%25");
                last_name = users[gusersyn.User_Lname].ToString().Replace("<", 
                            "%3c").Replace(">", "%3e").Replace(
                            "=", "%3d").Replace("%", "%25");
                if (gusersyn.User_password_generate_checkbox == false)
                {
                    // password needs to bea able to handle special characters
                    password = users[gusersyn.User_password].ToString();
                }
                else
                {
                    password = GetPassword(14);
                }
                if (gusersyn.User_password_short_fix_checkbox == true && password.Length < 8)
                {
                    password = GetPassword(14);
                }

                //Create a new user.
                UserEntry insertedEntry = 
                   service.CreateUser(studentID, first_name, last_name, password);

                //if (gusersyn.Levenshtein == true)
                //{
                // create user ailas here
                userNickName = GetNewUserNickname(service, studentID, 
                                     first_name, middle_name, last_name, 0, false);

                row[0] = studentID;
                if (userNickName != "failure")
                {
                    row[1] = userNickName + "@" + gusersyn.Admin_domain;
                }
                else
                {
                    row[1] = studentID + "@" + gusersyn.Admin_domain;
                }

                returnvalue.Rows.Add(row);
                row = returnvalue.NewRow();

                log.addTrn("Added Gmail user " + studentID + "@" + 
                      gusersyn.Admin_domain + 
                      " Aliased as " + userNickName + "@" + 
                      gusersyn.Admin_domain, "Transaction");
                // }
            }
            catch (AppsException e)
            {
                log.addTrn("Failed adding Gmail user " + studentID + "@" + 
                  gusersyn.Admin_domain + 
                  " Aliased as " + userNickName + "@" + gusersyn.Admin_domain + 
                  " failed " + e.Message.ToString() + " reason " + 
                  e.Reason.ToString(), "Error");
            }
            catch (Exception ex)
            {
                log.addTrn("Failed adding Gmail user " + studentID + "@" + 
                  gusersyn.Admin_domain + " Aliased as " + userNickName + "@" + 
                  gusersyn.Admin_domain + " failed " + ex.Message.ToString() + 
                  "\n" + ex.StackTrace.ToString(), "Error");
            }
        }
    }
    catch (Exception ex)
    {
        log.addTrn("Issue adding gmail users datareader is null " + 
          ex.Message.ToString() + "\n" + ex.StackTrace.ToString(), "Error");
    }
    return returnvalue;
}

此代码负责接收 `SQLDataReader` 并创建 GMail 账户。在创建 GMail 账户之前,它会进行一些关于非法字符的简单检查。它通过用更长的 14 个字符的密码覆盖来处理非法密码长度。最后,该方法构建一个包含成功创建的用户的 datatable 并返回它。

编译代码

该代码使用了 Google Data API 1.7.0.1,没有它将无法编译。请安装它,并确保 Google.Gdata 的引用指向您的安装。

关注点

这个项目最有趣的部分是花时间重新设计了其中的很多部分。每次,我都扔掉了 3/5 的代码。我花了大量时间研究数据结构和追求速度改进。该程序利用 SQL Server 来完成大部分处理,大部分功能都来自查询。

代码设计

LDAP 驱动程序分为四个主要文件

  • Program.cs 包含命令行逻辑。
  • Form1.cs 包含 GUI 代码。
  • utils/toolset.cs 包含所有逻辑。
  • utils/arguments.cs 包含命令行解析器。

LDAP Magic 警告

LDAP Magic 限制

  • 此程序依赖于已保存数据中选定列的存在;如果列名更改,则需要重新映射。
  • 此程序依赖于使用 (#) 临时表,因此需要对选定数据库的读写能力。
  • 此程序尝试检查空列表,并对其进行鲁棒处理。但是,如果发生问题,很可能来自函数之间传递的未正确处理的空列表。
  • 创建了 360 秒的自定义 SQL 连接超时,因此超时将不会与默认值匹配。当程序保持打开状态时,无限期保持的连接可能会成为一个问题。
  • 创建的对象集很大,可能会出现内存泄漏。大多数对象已关闭;但是,有些依赖于垃圾回收器。
  • 工具中的许多函数已重载,在更新工具时,请务必确保每个重载方法都符合新标准。
  • 此程序有一个日志文件,当抛出异常时,它会显示哪个函数失败,并尝试添加有用的数据(时间戳、失败的函数、传递的变量)。
  • 此程序必须由具有足够权限读取和写入 AD 用户和组的用户运行。

问题

  • 用于多实例同时运行的唯一表命名。
  • 创建后很少出现无法启用 Active Directory 账户的情况。

请勿

  • 使用(用户映射)或(执行顺序)选项卡,它们已损坏。
  • 除非使用配置中的临时表,否则不要同时运行多个副本。每个副本都将尝试访问数据库中的相同表。

愿望清单

  • 用户/组/账户的预览区域
  • AD 点击选择 OU 按钮
  • 修复所有未解决的问题

感谢

历史

  • 代码更新于 2011 年 6 月 28 日。
    • 已完成日志记录
    • 提高了稳定性
    • 更新了 GUI 设计
    • 修复了更新查询
    • 修复了 AD 中的删除问题
    • Levenshtein 的基础代码已到位但未激活 - 应该很简单
    • 包含的用户同步代码,只允许更新传输到 AD
    • 更新了对具有特殊条件的 Active Directory 字段的处理
    • 已更新以匹配 Gdata API 的新密码要求
  • 代码于 2009 年 4 月 22 日首次发布。
© . All rights reserved.