From dde4c6101bc0e7c509841f48cca8f8980ae82a9f Mon Sep 17 00:00:00 2001 From: Rockford Wei Date: Tue, 31 Jan 2017 11:47:09 -0500 Subject: [PATCH] Cleanup Documents --- README.md | 196 ++++++++++++++++++++++++-------- README.zh_CN.md | 229 ++++++++++++++++++++++++++++++++++++++ Sources/PerfectLDAP.swift | 30 ++--- 3 files changed, 384 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 8c3c98d..44d09d2 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ This package builds with Swift Package Manager and is part of the [Perfect](http Ensure you have installed and activated the latest Swift 3.0 tool chain. +*Caution*: for the reason that LDAP is widely using in many different operating systems with variable implementations, API marked with (⚠️EXPERIMENTAL⚠️) indicates that this method might not be fully applicable to certain context. However, as an open source software library, you may modify the source code to meet a specific requirement. + ## Quick Start Add the following dependency to your project's Package.swift file: @@ -59,9 +61,63 @@ Then import PerfectLDAP to your source code: import PerfectLDAP ``` +## Connect to LDAP Server + +You can create actual connections as need with or without login credential. The full API is `LDAP(url:String, loginData: Login?, codePage: Iconv.CodePage)`. The `codePage` option is for those servers applying character set other than .UTF8, e.g., set `codePage: .GB2312` to connect to LDAP server in Simplified Chinese. + +### TLS Option + +PerfectLDAP provides TLS options for network security considerations, i.e, you can choose either `ldap://` or `ldaps://` for connections, as demo below: + +``` swift +// this will connect to a 389 port without any encryption +let ld = try LDAP(url: "ldap://perfect.com") +``` +or, + +``` swift +// this will connect to a 636 port with certificates +let ld = try LDAP(url: "ldaps://perfect.com") +``` + +### Connection Timeout + +Once connected, LDAP object could be set with timeout option, and the timing unit is second: + +``` swift +// set the timeout for communication. In this example, connection will be timeout in ten seconds. +connection.timeout = 10 +``` + +### Login or Anonymous + +Many servers mandate login before performing any actual LDAP operations, however, PerfectLDAP provides multiple login options as demo below: + +``` swift +// this snippet demonstrate how to connect to LDAP server with a login credential +// NOTE: this kind of connection will block the thread until server return or timeout. +// create login credential +let credential = LDAP.login( ... ) +let connection = try LDAP(url: "ldaps://...", loginData: login) +``` +Aside the above synchronous login option, a two phased threading login process could also bring more controls to the application: + +``` swift +// first create a connection +let connection = try LDAP(url: "ldaps:// ...") + +// setup login info +let credential = LDAP.login( ... ) + +// login in a separated thread +connection.login(info: credential) { err in + // if err is not nil, then something must be wrong in the login process. +} +``` + ## Login Options -PerfectLDAP provides a special object called `LDAP.login` to store essential account information for LDAP connections and the form of constructor is subject to the authentication types: +PerfectLDAP provides a special object called `LDAP.Login` to store essential account information for LDAP connections and the form of constructor is subject to the authentication types: ### Simple Login @@ -71,15 +127,15 @@ To use simple login method, simply call `LDAP.login(binddn: String, password: St let credential = LDAP.Login(binddn: "CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP") ``` -### Digest-MD5 (*EXPERIMENTAL*) +### Digest-MD5 (⚠️EXPERIMENTAL⚠️) To apply Digest-MD5 interactive login, call `LDAP.login(authname: String, user: String, password: String, realm: String)` as demo below: ``` swift let credential = LDAP.Login(authname: "judy", user: "DN:CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP", realm: "PERFECT.COM") ``` -*NOTE* The `authname` is equivalent to `SASL_CB_AUTHNAME` and `user` is actually the macro of `SASL_CB_USER`. If any parameter above is not applicable to your case, simply assign an empty string "" to ignore it. +*⚠️NOTE⚠️* The `authname` is equivalent to `SASL_CB_AUTHNAME` and `user` is actually the macro of `SASL_CB_USER`. If any parameter above is not applicable to your case, simply assign an empty string "" to ignore it. -### GSSAPI and GSS-SPNEGO (*EXPERIMENTAL*) +### GSSAPI and GSS-SPNEGO (⚠️EXPERIMENTAL⚠️) To apply GSSAPI / GSS-SPNEGO authentication, call `LDAP.login(mechanism: AuthType)` to construct a login credential: @@ -94,82 +150,126 @@ or let credential = LDAP.login(mechanism: .SPNEGO) ``` -## Connect to LDAP Server +## Search -You can create actual connections as need with or without login credential. The full API is `LDAP(url:String, loginData: Login?, codePage: Iconv.CodePage)`. +PerfectLDAP provides asynchronous and synchronous version of searching API with the same parameters: -### TLS Option +### Synchronous Search -You can choose either `ldap://` or `ldaps://` for connections, as demo below: +Synchronous search will block the thread until server returns, the full api is `LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String) throws -> [String:[String:Any]]`. Here is an example: ``` swift -// this will connect to a 389 port without any encryption -let ld = try LDAP(url: "ldap://perfect.com") +// perform an ldap search synchronously, which will return a full set of attributes +// with a natural (unsorted) order, in form of a dictionary. +let res = try connection.search(base: "CN=Users,DC=perfect,DC=com", filter:"(objectclass=*)") + +print(res) ``` -or, + +### Asynchronous Search + +Asynchronous search allows performing search in an independent thread. Once completed, the thread will call back with the result set in a dictionary. Full api of asynchronous search is `LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String, completion: @escaping ([String:[String:Any]])-> Void)`. The equivalent example is: ``` swift -// this will connect to a 636 port with certificates -let ld = try LDAP(url: "ldaps://perfect.com") +// perform an ldap search asynchronously, which will return a full set of attributes +// with a natural (unsorted) order, in form of a dictionary. +connection.search(base: "CN=Users,DC=perfect,DC=com", filter:"(objectclass=*)") { + res in + print(res) +} ``` -### Login or Anonymous +### Parameters of Search +- base: String, search base domain (dn), default = "" +- filter: String, the filter of query, default is `"(objectclass=*)"`, means all possible results +- scope: Searching Scope, i.e., .BASE, .SINGLE_LEVEL, .SUBTREE or .CHILDREN +- sortedBy: a sorting string, may also be generated by `LDAP.sortingString()` +- completion: callback with a parameter of dictionary, empty if failed -Connection with login credential will block the main thread until timeout. +#### Server Side Sort (⚠️EXPERIMENTAL⚠️) +The `sortedBy` parameters is a string that indicates the remote server to perform search with a sorted set. PerfectLDAP provides a more verbal way to build such a string, i.e, an array of tuples to describe what attributes would control the result set: ``` swift -// this snippet demonstrate how to connect to LDAP server with a login credential -// create login credential -let url = "ldaps://..." -let credential = LDAP.login( ... ) -let connection = try LDAP(url: url, loginData: login) +// each tuple consists two parts: the sorting field and its order - .ASC or .DSC +let sort = LDAP.sortingString(sortedBy: [("description", .ASC)]) ``` -However, a two phased threading login process could also bring more controls to the application: + +### Limitation of Searching Result + +Once connected, LDAP object could be set with an limitation option - `LDAP.limitation`. It is an integer which specifies the maximum number of entries that can be returned on a search operation. ``` swift -// first create a connection -let connection = try LDAP(url: "ldaps:// ...") +// set the limitation for searching result set. In this example, only the first 1000 entries will return. +connection.limitation = 1000 +``` -// set the timeout for communication. In this example, connection will be timeout in ten seconds. -connection.timeout = 10 +## Attribute Operations -// setup login info -let credential = LDAP.login( ... ) +PerfectLDAP provides add() / modify() and delete() for attributes operations with both synchronous and asynchronous options. -// login in a separated thread -connection.login(info: credential) { err in - // if err is not nil, then something must be wrong in the login process. +### Add Attributes (⚠️EXPERIMENTAL⚠️) + +Function `LDAP.add()` can add attributes to a specific DN with parameters below: +- distinguishedName: String, specific DN +- attributes:[String:[String]], attributes as an dictionary to add. In this dictionary, every attribute, as a unique key in the dictionary, could have a series of values as an array. + +Both asynchronous add() and synchronous add() share the same parameters above, take example: + +``` swift +// try an add() synchronously. +do { + try connection.add(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["mail":["judy@perfect.com", "judy@perfect.org"]]) +}catch (let err) { + // failed for some reason +} + +// try and add() asynchronously: +connection.add(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["mail":["judy@perfect.com", "judy@perfect.org"]]) { err in + // if nothing wrong, err will be nil } ``` -## Search +### Modify Attributes -PerfectLDAP provides asynchronous and synchronous version of searching API with the same parameters: +Function `LDAP.modify()` can modify attributes from a specific DN with parameters below: +- distinguishedName: String, specific DN +- attributes:[String:[String]], attributes as an dictionary to modify. In this dictionary, every attribute, as a unique key in the dictionary, could have a series of values as an array. -### Synchronous Search +Both asynchronous modify() and synchronous modify() share the same parameters above, take example: ``` swift -LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String) throws -> [String:[String:Any]] -``` +// try an modify() synchronously. +do { + try connection.modify(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["codePage":["437"]]) +}catch (let err) { + // failed for some reason +} -### Asynchronous Search -``` swift -LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String, , completion: @escaping ([String:[String:Any]])-> Void) +// try and modify() asynchronously: +connection.modify(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes:["codePage":["437"]]) { err in + // if nothing wrong, err will be nil +} ``` -### Parameters of Search -- base: String, search base domain (dn), default = "" -- filter: String, the filter of query, default = "(objectclass=*)", means all possible results -- scope: Searching Scope, i.e., .BASE, .SINGLE_LEVEL, .SUBTREE or .CHILDREN -- sortedBy: a sorting string, may also be generated by LDAP.sortingString() -- completion: callback with a parameter of dictionary, empty if failed +### Delete Attributes (⚠️EXPERIMENTAL⚠️) -### Server Side Sort (*EXPERIMENTAL*) -The `sortedBy` parameters is a string that indicates the remote server to perform search with a sorted set. PerfectLDAP provides a more verbal way to build such a string, i.e, an array of tuples to describe what attributes would control the result set: +Function `LDAP.delete()` can delete attributes from a specific DN with only one parameter: +- distinguishedName: String, specific DN + +Both asynchronous delete() and synchronous delete() share the same parameter above, take example: ``` swift -// each tuple consists two parts: the sorting field and its order - .ASC or .DSC -let sort = LDAP.sortingString(sortedBy: [("description", .ASC)]) +// try an delete() synchronously. +do { + try connection.delete(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com") +}catch (let err) { + // failed for some reason +} + +// try and delete() asynchronously: +connection.delete(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com") { err in + // if nothing wrong, err will be nil +} ``` ## Issues diff --git a/README.zh_CN.md b/README.zh_CN.md index 1610a27..4321e39 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -49,6 +49,235 @@ 请确保您已经安装并激活了最新版本的 Swift 3.0 tool chain 工具链。 +*注意*: 由于LDAP在很多操作系统上都存在不同的服务器实现,因此本文中凡是标明了 (⚠️试验性质⚠️) 的方法,都以为着可能并不适用于某种场合。但是作为开源软件函数库,您可以随时根据需要修改源代码以达到目标要求。 + +## 快速上手 + +首先请在您的项目 Package.swift 文件中增加依存关系: + +``` swift +.Package(url: "https://github.com/PerfectlySoft/Perfect-LDAP.git", majorVersion: 1) +``` + +然后在源代码中导入 PerfectLDAP 函数库: + +``` swift +import PerfectLDAP +``` + +## 连接到 LDAP 服务器 + +在连接到服务器时,您可以选择署名连接或者匿名连接。署名连接意味着您需要提供用户名密码之类的登陆信息,而匿名连接则不需要这些登录信息。完整的函数形式为: `LDAP(url:String, loginData: Login?, codePage: Iconv.CodePage)`. 其中 `codePage` 选项是为某些采用非UTF8编码方式的服务器而设置的。比如,如果使用`codePage: .GB2312`则可以连接到以简体中文为主要数据库编码的 LDAP 服务器。 + +### TLS 加密选项 + +PerfectLDAP 提供 TLS 加密选项,能够提高访问安全性。换句话说,连接的URL地址可以是 `ldap://` 或者 `ldaps://`,如下所示: + +``` swift +// 连接到389端口,无编码加密 +let ld = try LDAP(url: "ldap://perfect.com") +``` +或者 + +``` swift +// 连接到636端口,证书加密 +let ld = try LDAP(url: "ldaps://perfect.com") +``` + +### 连接超时 + +连接完成后,LDAP 对象可以设置超时属性,单位是秒: + +``` swift +// 设置超时限制,以下例子将连接会话限制在10秒之内: +connection.timeout = 10 +``` + +### 分阶段登录 + +许多服务器都要求执行操作前必须登录,但 PerfectLDAP 允许不同的登录方式,如下所示: + +``` swift +// 以下代码展示了连接时同步登录。 +// 注意当前线程会被锁住直到服务器返回或者超时。 +// 首先准备登录信息: +let credential = LDAP.login( ... ) +let connection = try LDAP(url: "ldaps://...", loginData: login) +``` +除了以上同步连接暨登录方法之外,您还可以选择先连接后登录,实现多线程异步操作: + +``` swift +// 首先创建连接 +let connection = try LDAP(url: "ldaps:// ...") + +// 准备登录信息 +let credential = LDAP.login( ... ) + +// 采用异步方式登录 +connection.login(info: credential) { err in + // 注意正常情况下 err 是 nil ,如果 err 非空,则说明登录失败 +} +``` + +## 登录方式选择 + +PerfectLDAP 使用一个`LDAP.Login`对象来实现不同的登录选项。各个不同的安全方式区别在于该对象的构造函数。 + +### 用户名密码登录 + +最简单的方法就是用用户名(DN)密码进行登录,调用构造函数 `LDAP.login(binddn: String, password: String)`即可,如下所示: + +``` swift +let credential = LDAP.Login(binddn: "CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP") +``` + +### Digest-MD5 (⚠️试验性性质⚠️) + +Digest-MD5 加密方式采用内部交互登录。调用构造函数`LDAP.login(authname: String, user: String, password: String, realm: String)` 可尝试该加密方法: + +``` swift +let credential = LDAP.Login(authname: "judy", user: "DN:CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP", realm: "PERFECT.COM") +``` + +*⚠️注意⚠️* 参数 `authname` 等价于 `SASL_CB_AUTHNAME`,而 `user` 对应 `SASL_CB_USER`名称。如果您的程序不需要其中的某些参数,只要将该参数设置为空(“”)即可忽略。 + +### GSSAPI 和 GSS-SPNEGO (⚠️试验性性质⚠️) + +如果您希望程序中使用 GSSAPI / GSS-SPNEGO 认证方式,请调用`LDAP.login(mechanism: AuthType)` 构造函数,参考如下: + +``` swift +// 设置登录方式为 GSSAPI +let credential = LDAP.login(mechanism: .GSSAPI) +``` +或者 + +``` swift +// 设置登录方式为 GSS-SPNEGO +let credential = LDAP.login(mechanism: .SPNEGO) +``` + +## 检索 + +PerfectLDAP 提供了同步检索和异步检索两种方式,但函数参数都是一样的。 + +### 同步检索 + +同步检索将阻塞当前进程直到服务器返回或超时。完整的函数调用是`LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String) throws -> [String:[String:Any]]`,举例如下: + +``` swift +// 执行同步检索并返回完整的属性集合,采用自然方式排序;返回结果为字典类型 +let res = try connection.search(base: "CN=Users,DC=perfect,DC=com", filter:"(objectclass=*)") + +print(res) +``` + +### 异步检索 + +异步检索将在单独的线程执行。线程结束时会调用回调函数,并将返回的查询结果传递给回调函数。完整的调用为 `LDAP.search(base:String, filter:String, scope:Scope, attributes: [String], sortedBy: String, completion: @escaping ([String:[String:Any]])-> Void)`。和前面的例子等价的异步操作为: + +``` swift +// 执行 +// 执行异步检索。结束时回调并返回完整的属性集合,采用自然方式排序;返回结果为字典类型 +connection.search(base: "CN=Users,DC=perfect,DC=com", filter:"(objectclass=*)") { + res in + print(res) +} +``` + +### 检索参数 +- base: String, 检索时采用的基本DN,默认为空 +- filter: String, 检索过滤条件,默认为 `"(objectclass=*)"`,即所有对象类 +- scope: Scope, 检索范围,即 .BASE(基本) .SINGLE_LEVEL(单层) .SUBTREE(子树) 或 .CHILDREN(所有分支) +- sortedBy: String, 用于排序的字符串;可以用 `LDAP.sortingString()` 函数构造。 +- completion: 回调函数,其返回参数为一个字典。如果字典为空则意味着查询失败。 + +#### 服务器端排序 (⚠️试验性性质⚠️) +其中,`sortedBy` 参数是一个字符串,通知远程服务器按照该字符串的内容进行排序。但是您可以选择用下列方法构造该字符串,即用一个元组数组表示希望用一系列字段及其排序方法进行排序: + +``` swift +// 每个元组包括两个部分:排序字段以及排序方法 - .ASC 升序或 .DSC 降序 +let sort = LDAP.sortingString(sortedBy: [("description", .ASC)]) +``` + +### 限制查询结果集 + +连接后,LDAP可以为查询结果设置限制选项`LDAP.limitation`。该整型变量用于说明每一个检索操作所能返回的最多记录数。 + +``` swift +// 设置检索返回结果集。本例中,每个检索只能返回前一千个记录 +connection.limitation = 1000 +``` + +## 属性操作 + +PerfectLDAP 为属性操作提供了增删改功能。每个操作都有同步和异步选项。 + +### 增加属性 (⚠️试验性性质⚠️) + +函数 `LDAP.add()` 可以针对一个特定 DN 增加属性,参数如下: +- distinguishedName: String, 目标 DN (唯一命名) +- attributes:[String:[String]], 以字典方式表达的属性集合。该字典中,每一个属性对应一个条目,每个条目之下都允许一个数组来保存多个字符串值。 + +无论同步add()还是异步add()都采用上面的参数,比如: + +``` swift +// 同步增加属性 +do { + try connection.add(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["mail":["judy@perfect.com", "judy@perfect.org"]]) +}catch (let err) { + // 增加属性失败 +} + +// 异步增加属性 +connection.add(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["mail":["judy@perfect.com", "judy@perfect.org"]]) { err in + // 如果成功,err 应该是 nil +} +``` + +### 修改属性 + +函数 `LDAP.modify()` 可以针对一个特定 DN 修改属性,参数如下: +- distinguishedName: String, 目标 DN (唯一命名) +- attributes:[String:[String]], 以字典方式表达的属性集合。该字典中,每一个属性对应一个条目,每个条目之下都允许一个数组来保存多个字符串值。 + +无论同步修改还是异步修改都采用上面的参数,比如: + +``` swift +// 同步修改属性 +do { + try connection.modify(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["codePage":["437"]]) +}catch (let err) { + // 修改属性失败 +} + +// 异步修改属性 +connection.modify(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes:["codePage":["437"]]) { err in + // 如果成功,err 应该是 nil +} +``` + +### 删除属性(⚠️试验性性质⚠️) + +函数 `LDAP.delete()` 用于删除特定DN的所有属性,只有一个参数: +- distinguishedName: String, 目标 DN (唯一命名) + +无论同步删除还是异步删除都采用上面的参数,比如: + +``` swift +// 同步删除属性 +do { + try connection.delete(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com") +}catch (let err) { + // 删除属性失败 +} + +// 异步删除属性 +connection.delete(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com") { err in + // 如果成功,err 应该是 nil +} +``` + + ### 问题报告、内容贡献和客户支持 我们目前正在过渡到使用JIRA来处理所有源代码资源合并申请、修复漏洞以及其它有关问题。因此,GitHub 的“issues”问题报告功能已经被禁用了。 diff --git a/Sources/PerfectLDAP.swift b/Sources/PerfectLDAP.swift index 733870a..e698d00 100644 --- a/Sources/PerfectLDAP.swift +++ b/Sources/PerfectLDAP.swift @@ -49,7 +49,7 @@ public class LDAP { case DIGEST /// OTHER case OTHER - }//end + }//end /// Login Data @@ -150,7 +150,7 @@ public class LDAP { }//end set }//end timetout - /// Searching result memory size limitations, for example, 1000 for 1000 lines? + /// the maximum number of entries that can be returned on a search operation public var limitation: Int { get { var limit = 0 @@ -159,13 +159,13 @@ public class LDAP { }//end get set { var limit = limitation - let _ = ldap_set_option(ldap, LDAP_OPT_TIMEOUT, &limit) + let _ = ldap_set_option(ldap, LDAP_OPT_SIZELIMIT, &limit) }//end set }//end limitation /// LDAP handler pointer internal var ldap: OpaquePointer? = nil - + /// codepage convertor internal var iconv: Iconv? = nil @@ -506,7 +506,7 @@ public class LDAP { /// referrals as an array of string, read only public var referrals: [String] { get { return _ref } } - + /// constructor of Result /// - parameters: /// - ldap: the LDAP handler @@ -621,7 +621,7 @@ public class LDAP { }//end sortingString /// synchronized search - /// - parameters: + /// - parameters: /// - base: String, search base domain (dn), default = "" /// - filter: String, the filter of query, default = "(objectclass=*)", means all possible results /// - scope: See Scope, BASE, SINGLE_LEVEL, SUBTREE or CHILDREN @@ -821,7 +821,7 @@ public class LDAP { // otherwise callback an error completion("\(err)") }//end do - + }//end dispatch }//end func @@ -868,19 +868,3 @@ public class LDAP { }//end dispatch } }//end class - - - - - - - - - - - - - - - -