Perfect-LDAP 简体中文
This project provides an express OpenLDAP class wrapper which enable access to OpenLDAP servers and Windows Active Directory server.
This package builds with Swift Package Manager and is part of the Perfect project.
Ensure you have installed and activated the latest Swift 4.0 tool chain.
Caution: for the reason that LDAP is widely using in many different operating systems with variable implementations, API marked with (
Add the following dependency to your project's Package.swift file:
.package(url: "https://github.com/PerfectlySoft/Perfect-LDAP.git",
from: "3.0.0")
// in the target section:
depedencies: ["PerfectLDAP"]
Then import PerfectLDAP to your source code:
import PerfectLDAP
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.
PerfectLDAP provides TLS options for network security considerations, i.e, you can choose either ldap://
or ldaps://
for connections, as demo below:
// this will connect to a 389 port without any encryption
let ld = try LDAP(url: "ldap://perfect.com")
or,
// this will connect to a 636 port with certificates
let ld = try LDAP(url: "ldaps://perfect.com")
Once connected, LDAP object could be set with timeout option, and the timing unit is second:
// set the timeout for communication. In this example, connection will be timeout in ten seconds.
connection.timeout = 10
Many servers mandate login before performing any actual LDAP operations, however, PerfectLDAP provides multiple login options as demo below:
// 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:
// 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.
}
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:
To use simple login method, simply call LDAP.login(binddn: String, password: String)
, as snippet below:
let credential = LDAP.Login(binddn: "CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP")
To apply GSSAPI authentication, call LDAP.login(user:String, mechanism: AuthType)
to construct a login credential (assuming the user has already acquired a valid ticket):
// this call will generate a GSSAPI login credential
let credential = LDAP.login(user: "judy", mechanism: .GSSAPI)
To apply other SASL mechanisms, such as GSS-SPNEGO and Digest-MD5 interactive logins, call LDAP.login(authname: String, user: String, password: String, realm: String, mechanism: AuthType)
as demo below:
// apply DIGEST-MD5 mechanism.
let credential = LDAP.Login(authname: "judy", user: "DN:CN=judy,CN=Users,DC=perfect,DC=com", password: "0penLDAP", realm: "PERFECT.COM", mechanism: .DIGEST)
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.
PerfectLDAP provides asynchronous and synchronous version of searching API with the same parameters:
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:
// 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)
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:
// 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)
}
- 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
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:
// each tuple consists two parts: the sorting field and its order - .ASC or .DSC
let sort = LDAP.sortingString(sortedBy: [("description", .ASC)])
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.
// set the limitation for searching result set. In this example, only the first 1000 entries will return.
connection.limitation = 1000
PerfectLDAP provides add() / modify() and delete() for attributes operations with both synchronous and asynchronous options.
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:
// 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
}
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.
- method: specify if an attribute should be added, removed or replaced (default)
- add: LDAP_MOD_ADD | LDAP_MOD_BVALUES
- remove: LDAP_MOD_DELETE | LDAP_MOD_BVALUES
- replace: LDAP_MOD_REPLACE | LDAP_MOD_BVALUES
Both asynchronous modify() and synchronous modify() share the same parameters above, take example:
// try and 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
}
// 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
}
Example: Add and remove user from group
// add user to group
do {
try connection.modify(distinguishedName: "CN=employee_group,CN=Group,DC=perfect,DC=com", attributes: ["member":["CN=judy,CN=User,DC=perfect,DC=com"]], method: LDAP_MOD_ADD | LDAP_MOD_BVALUES)
}catch (let err) {
// failed for some reason
}
// remove user from group
do {
try connection.modify(distinguishedName: "CN=employee_group,CN=Group,DC=perfect,DC=com", attributes: ["member":["CN=judy,CN=User,DC=perfect,DC=com"]], method: LDAP_MOD_DELETE | LDAP_MOD_BVALUES)
}catch (let err) {
// failed for some reason
}
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:
// 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
}
For more information on the Perfect project, please visit perfect.org.