-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e16dad3
commit dde4c61
Showing
3 changed files
with
384 additions
and
71 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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":["[email protected]", "[email protected]"]]) | ||
}catch (let err) { | ||
// failed for some reason | ||
} | ||
|
||
// try and add() asynchronously: | ||
connection.add(distinguishedName: "CN=judy,CN=User,DC=perfect,DC=com", attributes: ["mail":["[email protected]", "[email protected]"]]) { 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 | ||
|
Oops, something went wrong.