From 65a9bad95382b69c78ecf821ab6ea3fcad460c50 Mon Sep 17 00:00:00 2001 From: Rockford Wei Date: Mon, 30 Jan 2017 16:15:31 -0500 Subject: [PATCH] Finishing TLS / drafting SASL --- Package.swift | 1 - Sources/PerfectLDAP.swift | 315 ++++++++++-------- Sources/Utilities.swift | 22 -- Tests/PerfectLDAPTests/PerfectLDAPTests.swift | 179 +++------- 4 files changed, 225 insertions(+), 292 deletions(-) diff --git a/Package.swift b/Package.swift index ae68760..744df68 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,6 @@ let package = Package( name: "PerfectLDAP", dependencies:[ .Package(url: "https://github.com/PerfectSideRepos/Perfect-ICONV.git", majorVersion: 1), - .Package(url: "https://github.com/PerfectSideRepos/Perfect-CArrayHelper.git", majorVersion: 1), .Package(url:"https://github.com/PerfectlySoft/Perfect-Thread.git", majorVersion: 2), .Package(url:"https://github.com/PerfectlySoft/Perfect-libSASL.git", majorVersion: 1), .Package(url:"https://github.com/PerfectlySoft/Perfect-OpenLDAP.git", majorVersion: 1) diff --git a/Sources/PerfectLDAP.swift b/Sources/PerfectLDAP.swift index 7f87fa6..c1c0fba 100644 --- a/Sources/PerfectLDAP.swift +++ b/Sources/PerfectLDAP.swift @@ -29,9 +29,6 @@ import PerfectThread /// Iconv import PerfectICONV -/// CArray Helper -import PerfectCArray - /// Perfect LDAP Module public class LDAP { @@ -50,8 +47,80 @@ public class LDAP { case SPNEGO /// DIGEST MD5 case DIGEST + /// OTHER + case OTHER }//end + + /// Login Data + public class Login { + + /// the name of SASL_CB_AUTHNAME, the username to authenticate, + /// without any symbols or suffix, usually a lowercased short name "someone" + public var authname = "" // "Someone" + + /// distinguished name, usually in form of "CN=Someone,CN=User,CN=domain,CN=com" + public var binddn = "" + + /// the name of SASL_CB_USER, the username to use for proxy authorization + /// usually with a prefix of "dn=" with binddn: "DN:CN=Someone,CN=User,CN=domain,CN=com" + public var user = "" + + /// the password of login + public var password = "" + + /// the name of SASL_CB_GETREALM, the realm for the authentication attempt + public var realm = "" + + /// mechanism for authentication + public var mechanism: AuthType = .SIMPLE + + /// garbage manager + internal var trashcan: [UnsafeMutablePointer?] = [] + + public func drop(garbage: UnsafeMutablePointer?) { trashcan.append(garbage) } + + /// contructor for simple login + /// - parameters: + /// - binddn: distinguished name, usually in form of "CN=Someone,CN=User,CN=domain,CN=com" + /// - password: the password + public init(binddn: String = "", password: String = "") { + self.binddn = binddn + self.password = password + self.mechanism = .SIMPLE + }//end init + + /// constructor for DIGEST-MD5 + /// - parameters: + /// - authname: the name of SASL_CB_AUTHNAME, the username to authenticate, without any symbols or suffix, usually a lowercased short name "someone" + /// - user: the name of SASL_CB_USER, the username to use for proxy authorization, usually with a prefix of "dn=" with binddn: "DN:CN=Someone,CN=User,CN=domain,CN=com" + /// - password: the password of login + /// - realm: the name of SASL_CB_GETREALM, the realm for the authentication attempt + public init(authname: String = "", user: String = "", password: String = "", realm: String = "") { + self.binddn = "" + self.authname = authname + self.user = user + self.password = password + self.realm = realm + self.mechanism = .DIGEST + }//end init + + /// constructor for GSSAPI / GSS-SPNEGO + public init(mechanism: AuthType) { + self.mechanism = mechanism + }//end init + + deinit { + for garbage in trashcan { + if garbage == nil { + continue + }//end if + ber_memfree(garbage) + }//next + trashcan.removeAll() + }//end + }//end Login + /// Error Handling public enum Exception: Error { /// Error with Message @@ -143,75 +212,16 @@ public class LDAP { }//end str }//end string - private var _supportedControl = [String]() - private var _supportedExtension = [String]() - private var _supportedSASLMechanisms = [String]() - private var _saslMech: [AuthType:String] = [:] - - public var supportedControl: [String] { get { return _supportedControl } } - public var supportedExtension: [String] { get { return _supportedExtension } } - public var supportedSASLMechanisms: [String] { get { return _supportedSASLMechanisms } } - public var supportedSASL: [AuthType:String] { get { return _saslMech } } - - public func withUnsafeSASLDefaultsPointer(mech: String = "", realm: String = "", authcid: String = "", passwd: String = "", authzid: String = "",_ body: (UnsafeMutableRawPointer?) throws -> R) rethrows -> R { - var def = lutilSASLdefaults(mech: nil, realm: nil, authcid: nil, passwd: nil, authzid: nil, resps: nil, nresps: 0) - if mech.isEmpty { - let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_MECH, &(def.mech)) - } else { - def.mech = ber_strdup(mech) - }//end if - if realm.isEmpty { - let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_REALM, &(def.realm)) - } else { - def.realm = ber_strdup(realm) - }//end if - if authcid.isEmpty { - let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_AUTHCID, &(def.authcid)) - } else { - def.authcid = ber_strdup(authcid) - }//end if - if authzid.isEmpty { - let _ = ldap_get_option(self.ldap, LDAP_OPT_X_SASL_AUTHZID, &(def.authzid)) - } else { - def.authzid = ber_strdup(authzid) - }//end if - if !passwd.isEmpty { - def.passwd = ber_strdup(passwd) - }//end if - - let r = try body(UnsafeMutablePointer(mutating: &def)) - - if def.mech != nil { - ber_memfree(def.mech) - }//end if - if def.realm != nil { - ber_memfree(def.realm) - }//end if - if def.authcid != nil { - ber_memfree(def.authcid) - }//end if - if def.authzid != nil { - ber_memfree(def.authzid) - }//end if - if def.passwd != nil { - ber_memfree(def.passwd) - }//end if - - return r - } - /// constructor of LDAP. could be a simple LDAP() to local server or LDAP(url) with / without logon options. /// if login parameters were input, the process would block until finished. /// so it is strongly recommanded that call LDAP() without login option and call ldap.login() {} in async mode /// - parameters: - /// - url: String, something like ldap://somedomain.com:port - /// - username: String, user name to login, optional. - /// - password: String, password for login, optional. - /// - auth: AuthType, such as SIMPLE, GSSAPI, SPNEGO or DIGEST MD5 + /// - url: String, something like ldap://somedomain.com:port or ldaps://somedomain.com + /// - login: login data. /// - codePage: object server coding page, e.g., GB2312, BIG5 or JS /// - throws: /// possible exceptions of initial failed or access denied - public init(url:String = "ldap://localhost", username: String? = nil, password: String? = nil, realm: String? = nil, auth: AuthType = .SIMPLE, codePage: Iconv.CodePage = .UTF8) throws { + public init(url:String = "ldaps://localhost", loginData: Login? = nil, codePage: Iconv.CodePage = .UTF8) throws { if codePage != .UTF8 { // we need a pair of code pages to transit in both directions. @@ -220,104 +230,120 @@ public class LDAP { }//end if ldap = OpaquePointer(bitPattern: 0) - let r = ldap_initialize(&ldap, url) + var r = ldap_initialize(&ldap, url) + guard r == 0 else { throw Exception.message(LDAP.error(r)) }//end guard - guard let dse = try search() else { - throw Exception.message("ROOT DSE FAULT") - }//end dse + var proto = LDAP_VERSION3 - guard let root = dse.dictionary[""] else { - throw Exception.message("ROOT DSE HAS NO EXPECTED KEY") - }//end root - - _supportedControl = root["supportedControl"] as? [String] ?? [] - _supportedExtension = root["supportedExtension"] as? [String] ?? [] - _supportedSASLMechanisms = root["supportedSASLMechanisms"] as? [String] ?? [] - - _supportedSASLMechanisms.forEach { mech in - if strstr(mech, "GSSAPI") != nil { - _saslMech[AuthType.GSSAPI] = mech - }else if strstr(mech, "GSS-SPNEGO") != nil { - _saslMech[AuthType.SPNEGO] = mech - }//end if - }//next + r = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &proto) // if no login required, skip. - if username == nil || password == nil { + if loginData == nil { return }//end if // call login internally - try login(username: username ?? "", password: password ?? "", realm: realm ?? "", auth: auth) + try login(info: loginData) }//end init - - /// login in synchronized mode, will block the calling thread /// - parameters: - /// - username: String, user name to login, optional. - /// - password: String, password for login, optional. - /// - auth: AuthType, such as SIMPLE, GSSAPI, SPNEGO or DIGEST MD5 - /// - returns: - /// true for a successful login. + /// - info: login data + /// - throws: + /// Exception message @discardableResult - public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE) throws { + public func login(info: Login?) throws { + + // load login data + guard let inf = info else { + throw Exception.message("LOGIN DATA NOT AVAILABLE") + }//end guard + + // prepare return value var r = Int32(0) - let ex = Exception.message("UNSUPPORTED SECURITY MECH") - switch auth { + + // prepare an empty credential structure + var cred = berval(bv_len: 0, bv_val: UnsafeMutablePointer(bitPattern: 0)) + + // different mechanisms will take different authentication process + switch inf.mechanism { + + // simple authorization doesn't mean unsafe - ldaps:// will encode the data case .SIMPLE: - var cred = berval(bv_len: ber_len_t(password.utf8.count), bv_val: ber_strdup(password)) - r = ldap_sasl_bind_s(self.ldap, username, nil, &cred, nil, nil, nil) + // simple auth just use password and binddn to login + cred.bv_val = strdup(inf.password) + cred.bv_len = strlen(cred.bv_val) + r = ldap_sasl_bind_s(self.ldap, inf.binddn, nil, &cred, nil, nil, nil) ber_memfree(cred.bv_val) - case .GSSAPI, .SPNEGO: - guard let mech = _saslMech[auth] else { - throw ex - }//end - r = self.withUnsafeSASLDefaultsPointer(mech: mech, realm: realm, authcid: username, passwd: password) { ldap_sasl_interactive_bind_s(ldap, username, _saslMech[.GSSAPI], nil, nil, LDAP_SASL_AUTOMATIC, { ldapHandle, flags, defaults, input in - guard flags != LDAP_SASL_QUIET else { - return LDAP_OTHER - }//END GUARD + // GSSAPI / SPNEGO use kerberos kinit to login, so there is nothing to fill + case .GSSAPI: + r = ldap_sasl_bind_s(self.ldap, "", "GSSAPI", &cred, nil, nil, nil) + case .SPNEGO: + r = ldap_sasl_bind_s(self.ldap, "", "GSS-SPNEGO", &cred, nil, nil, nil) - let defaultsPointer = unsafeBitCast(defaults, to: UnsafeMutablePointer.self) - let def = defaultsPointer.pointee + // MD5 using interactive binding. + case .DIGEST: - guard let pInput = input else { - return -1 - }//end guard + // turn the login info data into a pointer by this one. + var pinf = inf - var cursor: UnsafeMutablePointer = unsafeBitCast(pInput, to: UnsafeMutablePointer.self) + // call the binding + r = ldap_sasl_interactive_bind_s(self.ldap, inf.binddn, "DIGEST-MD5", nil, nil, LDAP_SASL_QUIET, { ld, flags, pRawDefaults, pRawInteract -> Int32 in - var interact = cursor.pointee + // in this callback, convert the pointer of pointers back to defaults + let pDef = unsafeBitCast(pRawDefaults, to: UnsafeMutablePointer.self) + let pInt = unsafeBitCast(pRawInteract, to: UnsafeMutablePointer.self) + let def = pDef.pointee + var pcursor:UnsafeMutablePointer? = nil + var interact: sasl_interact_t + pcursor = pInt - while(Int32(interact.id) != SASL_CB_LIST_END) { + // loop & answer the question asked by server + while(pcursor != nil) { + interact = (pcursor?.pointee)! + // prepare a blank pointer + var dflt = "" switch(Int32(interact.id)) { - case SASL_CB_GETREALM: - SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.realm) case SASL_CB_AUTHNAME: - SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.authcid) - case SASL_CB_PASS: - SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.passwd) + dflt = def.authname case SASL_CB_USER: - SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.authzid) - case SASL_CB_NOECHOPROMPT, SASL_CB_ECHOPROMPT: // skipped - () - default: //unknown - return -1 + dflt = def.user + case SASL_CB_PASS: + dflt = def.password + case SASL_CB_GETREALM: + dflt = def.realm + case SASL_CB_LIST_END: + return 0 + case SASL_CB_NOECHOPROMPT, SASL_CB_ECHOPROMPT: + dflt = "" + default: + return 0 }//end case - cursor.pointee = interact - cursor = cursor.successor() - interact = cursor.pointee + + // once + if dflt.isEmpty { + let str = strdup(dflt) + interact.result = unsafeBitCast(str, to: UnsafeRawPointer.self) + interact.len = UInt32(dflt.utf8.count) + def.drop(garbage: str) + }else{ + interact.len = 0 + }//end if + pcursor?.pointee = interact + pcursor = pcursor?.successor() }//end while + return 0 + }, unsafeBitCast(UnsafeMutablePointer(mutating: &pinf), to: UnsafeMutableRawPointer.self)) - return Int32(0)}, $0) } default: - throw ex + throw Exception.message("UNSUPPORTED SECURITY MECH") } + if r == 0 { return }else { @@ -327,18 +353,16 @@ public class LDAP { /// Login in asynchronized mode. Once completed, it would invoke the callback handler /// - parameters: - /// - username: String, user name to login, optional. - /// - password: String, password for login, optional. - /// - auth: AuthType, such as SIMPLE, GSSAPI, SPNEGO or DIGEST MD5 + /// - info: login data /// - completion: callback handler with a boolean parameter indicating whether login succeeded or not. - public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE, completion: @escaping (String?)->Void) { + public func login(info: Login, completion: @escaping (String?)->Void) { Threading.dispatch { do { - try self.login(username: username, password: password, realm: realm, auth: auth) + try self.login(info: info) completion(nil) }catch(let err) { completion("LOGIN FAILED: \(err)") - } + }//end do }//end thread }//end login @@ -349,7 +373,7 @@ public class LDAP { /// Attribute of a searching result - public struct Attribute { + internal struct Attribute { /// name of the attribute internal var _name = "" @@ -387,7 +411,7 @@ public class LDAP { }//end Attribute /// Attributes Set of a Searching result - public struct AttributeSet { + internal struct AttributeSet { /// name of the attribute internal var _name = "" @@ -423,7 +447,7 @@ public class LDAP { }//end class /// a reference record of an LDAP search result - public struct Reference { + internal struct Reference { /// value set in an array of string internal var _values = [String] () @@ -457,7 +481,7 @@ public class LDAP { }//end struct /// LDAP Result record - public struct Result { + internal struct Result { /// error code of result internal var _errCode = Int32(0) @@ -518,7 +542,7 @@ public class LDAP { }//end Result } /// Result set of a searching query - public struct ResultSet { + internal struct ResultSet { /// attribute set as an array internal var _attr = [AttributeSet]() @@ -607,7 +631,8 @@ public class LDAP { /// - throws: /// Exception.message @discardableResult - public func search(base:String = "", filter:String = "(objectclass=*)", scope:Scope = .BASE, attributes: [String] = [], sortedBy: String = "") throws -> ResultSet? { + public func search(base:String = "", filter:String = "(objectclass=*)", scope:Scope = .BASE, attributes: [String] = [], sortedBy: String = "") throws -> [String:[ + String:Any]] { var serverControl = UnsafeMutablePointer(bitPattern: 0) @@ -652,7 +677,7 @@ public class LDAP { // release the memory ldap_msgfree(msg) - return rs + return rs.dictionary }//end search /// asynchronized search @@ -663,13 +688,13 @@ public class LDAP { /// - sortedBy: a sorting string, may be generated by LDAP.sortingString() /// - completion: callback with a parameter of ResultSet, nil if failed @discardableResult - public func search(base:String = "", filter:String = "(objectclass=*)", scope:Scope = .BASE, sortedBy: String = "", completion: @escaping (ResultSet?)-> Void) { + public func search(base:String = "", filter:String = "(objectclass=*)", scope:Scope = .BASE, sortedBy: String = "", completion: @escaping ([String:[String:Any]])-> Void) { Threading.dispatch { - var rs: ResultSet? = nil + var rs: [String:[String:Any]] = [:] do { rs = try self.search(base: base, filter: filter, scope: scope, sortedBy: sortedBy) }catch { - rs = nil + rs = [:] }//end catch completion(rs) }//end threading diff --git a/Sources/Utilities.swift b/Sources/Utilities.swift index bd8305f..9e1273b 100644 --- a/Sources/Utilities.swift +++ b/Sources/Utilities.swift @@ -19,7 +19,6 @@ import PerfectICONV import OpenLDAP -import PerfectCArray /// C library of SASL import SASL @@ -83,27 +82,6 @@ public func withCArrayOfString(array: [String] = [], _ body: (UnsafeMutablePo return r }//end withCArrayOfString -public var CSTRHelper = CArray>() - -public func SASLReply(pInteract: UnsafeMutablePointer, pDefaults: UnsafeMutablePointer, pMsg: UnsafeMutablePointer) { - - var defaults = pDefaults.pointee - var interact = pInteract.pointee - - interact.len = UInt32(strlen(pMsg)) - if interact.len < 1 { - interact.result = unsafeBitCast(pMsg, to: UnsafeRawPointer.self) - return - }//end if - - var resps = defaults.resps - let _ = CSTRHelper.append(pArray: &resps, element: pMsg) - defaults.resps = resps - interact.result = CSTRHelper.withArray(of: defaults.resps) { array -> UnsafeRawPointer! in - return unsafeBitCast(array[array.count - 1]!, to: UnsafeRawPointer.self) - }//end result -}//end reply - diff --git a/Tests/PerfectLDAPTests/PerfectLDAPTests.swift b/Tests/PerfectLDAPTests/PerfectLDAPTests.swift index 3b94043..7f29b60 100644 --- a/Tests/PerfectLDAPTests/PerfectLDAPTests.swift +++ b/Tests/PerfectLDAPTests/PerfectLDAPTests.swift @@ -4,17 +4,38 @@ import Foundation import PerfectICONV import OpenLDAP +/* + Note of setup testing environments: + * Windows 2000 Advanced Server: Domain Controller with DNS Server, + Only Support Simple Login, PORT 389 + * Windows 2003 / 2008 Server: Support FULL functions, with SSL PORT 636 + Active Directory Domain Services + Active Directory Lightweight Directory Services + DNS Server + Identity Management for UNIX + * Windows 2008 SSL Configuration: Choose Certificate Template of Kerberos V and + * Then duplicate into LDAPoverSSL, import it to Local Computer Service Account. + * as described in + * https://social.technet.microsoft.com/wiki/contents/articles/2980.ldap-over-ssl-ldaps-certificate.aspx + * mac: /etc/openldap/ldap.conf / linux: /etc/ldap/ldap.conf + * MUST ADD: `TLS_REQCERT allow` + */ class PerfectLDAPTests: XCTestCase { - let testURL = "ldap://192.168.56.13" - let testUSR = "rocky@p.com" - let testPWD = "rockford" - let testRLM = "P" - - func testLogin() { + let testURL = "ldaps://192.168.56.15:636" + let testBDN = "CN=judy,CN=Users,DC=perfect,DC=com" + let testUSR = "DN:CN=judy,CN=Users,DC=perfect,DC=com" + let testATH = "judy" + let testPWD = "0penLDAP" + let testRLM = "PERFECT.COM" + let testBAS = "CN=Users,DC=perfect,DC=com" + let testCPG: Iconv.CodePage = .UTF8 + + func testLoginFail() { + let cred = LDAP.Login(mechanism: .GSSAPI) do { let logfail = expectation(description: "logfail") - let ldap = try LDAP(url: testURL, codePage: .GB2312) - ldap.login(username: "abc", password: "123") { err in + let ldap = try LDAP(url: testURL) + ldap.login(info: cred) { err in XCTAssertNotNil(err) logfail.fulfill() print("log failed passed") @@ -23,9 +44,17 @@ class PerfectLDAPTests: XCTestCase { waitForExpectations(timeout: 10) { error in XCTAssertNil(error) }//end wait + }catch(let err) { + XCTFail("testLogin error: \(err)") + } + }//end testLoginFailed + func testLoginPass() { + let cred = LDAP.Login(binddn: testBDN, password: testPWD) + do { let logsuc = expectation(description: "logsuc") - ldap.login(username: testUSR, password: testPWD) { err in + let ldap = try LDAP(url: testURL) + ldap.login(info: cred) { err in XCTAssertNil(err) logsuc.fulfill() print("log real passed") @@ -33,47 +62,19 @@ class PerfectLDAPTests: XCTestCase { waitForExpectations(timeout: 10) { error in XCTAssertNil(error) }//end wait - - }catch(let err) { XCTFail("testLogin error: \(err)") } }//end testLogin - func testLoginSync() { - do { - let ldap = try LDAP(url: testURL, codePage: .GB2312) - try ldap.login(username: "abc", password: "123") - - print(" -- -- --- ---") - print(ldap.supportedControl) - print(ldap.supportedExtension) - print(ldap.supportedSASLMechanisms) - }catch { - // bad password is supposed to fail. - } - - do { - let ldap = try LDAP(url: testURL, codePage: .GB2312) - print(" -- -- --- ---") - try ldap.login(username: testUSR, password: testPWD) - }catch(let err) { - XCTFail("testLoginSync error: \(err)") - } - }//end testLogin - - func testSearch () { + let cred = LDAP.Login(binddn: testBDN, password: testPWD) do { - let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, loginData:cred, codePage: testCPG) let ser = expectation(description: "search") - ldap.search(base:"cn=users,dc=p,dc=com", scope:.SUBTREE) { res in - guard let r = res else { - XCTFail("search return nil") - return - }//end guard - print(r.dictionary) + ldap.search(base:testBAS, scope:.SUBTREE) { res in + print(res) ser.fulfill() }//end search @@ -84,32 +85,16 @@ class PerfectLDAPTests: XCTestCase { XCTFail("error: \(err)") } } - func testSearchSync () { - do { - let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) - guard let rs = try ldap.search(base:"cn=users,dc=p,dc=com",filter: "(initials=RW)", scope:.SUBTREE, attributes: ["cn", "company", "displayName", "initials"]) else { - XCTFail("search failed") - return - }//end guard - print("-------------------------------------------------------") - print(rs.dictionary) - }catch(let err) { - XCTFail("error: \(err)") - } - - } func testServerSort () { + let cred = LDAP.Login(binddn: testBDN, password: testPWD) do { - let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) + let ldap = try LDAP(url: testURL, loginData:cred, codePage: testCPG) print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") let sort = LDAP.sortingString(sortedBy: [("displayName", .DSC), ("initials", .ASC)]) print(sort) - guard let res = try ldap.search(base:"cn=users,dc=p,dc=com",scope:.SUBTREE, attributes: ["displayName", "initials"], sortedBy: sort) else { - XCTFail("server control failed") - return - }//end guard - print(res.dictionary) + let res = try ldap.search(base:testBAS,scope:.SUBTREE, attributes: ["displayName", "initials"], sortedBy: sort) + print(res) }catch(let err) { XCTFail("server control: \(err)") } @@ -117,29 +102,24 @@ class PerfectLDAPTests: XCTestCase { } func testAttributeMod () { + let cred = LDAP.Login(binddn: testBDN, password: testPWD) do { - let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312) - guard let rs = try ldap.search(base:"cn=users,dc=p,dc=com",filter: "(initials=RW)", scope:.SUBTREE) else { - XCTFail("search failed") - return - }//end guard + let ldap = try LDAP(url: testURL, loginData:cred, codePage: testCPG) + let rs = try ldap.search(base:testBDN, scope:.SUBTREE) print("=======================================================") - print(rs.dictionary) + print(rs) print("=======================================================") let mod = expectation(description: "search") - ldap.modify(distinguishedName: "CN=Rockford Wei,CN=Users,DC=p,DC=com", attributes: ["mail":["rocky@perfect.org", "rockywei@gmx.com"], "otherMailbox":["rockywei524@gmail.com"]]) { err in + ldap.modify(distinguishedName: testBDN, attributes: ["mail":["emai1@perfect.com", "email2@perfect.com"], "otherMailbox":["email3@perfect.org"]]) { err in mod.fulfill() XCTAssertNil(err) }//end add self.waitForExpectations(timeout: 10){ error in XCTAssertNil(error) } - guard let res = try ldap.search(base:"cn=users,dc=p,dc=com",filter: "(initials=RW)", scope:.SUBTREE) else { - XCTFail("search failed") - return - }//end guard + let res = try ldap.search(base:testBDN, scope:.SUBTREE) print("=======================================================") - print(res.dictionary) + print(res) print("=======================================================") }catch(let err) { XCTFail("error: \(err)") @@ -147,62 +127,13 @@ class PerfectLDAPTests: XCTestCase { } - func testSASLDefaults () { - do { - - let ldap = try LDAP(url: testURL) - - let sasl = ldap.supportedSASL - guard let gssapi = sasl[LDAP.AuthType.GSSAPI] else { - XCTFail("GSSAPI FAULT") - return - } - print(gssapi) - - guard let spnego = sasl[LDAP.AuthType.SPNEGO] else { - XCTFail("SPNEGO FAULT") - return - } - print(spnego) - - let r = ldap.withUnsafeSASLDefaultsPointer(mech: "GSSAPI", realm: "PERFECT") { ptr -> Int in - guard let p = ptr else { - return 0 - }//end if - let pdef = unsafeBitCast(p, to: UnsafeMutablePointer.self) - let def = pdef.pointee - let mech = String(cString: def.mech) - let realm = String(cString: def.realm) - XCTAssertEqual(mech, "GSSAPI") - XCTAssertEqual(realm, "PERFECT") - return 100 - }//en r - XCTAssertEqual(r, 100) - }catch(let err) { - XCTFail("error: \(err)") - } - } - - func testSASLogin() { - do { - - let ldap = try LDAP(url: testURL) - try ldap.login(username: testUSR, password: testPWD, realm: testRLM, auth: .GSSAPI) - }catch(let err) { - XCTFail("error: \(err)") - } - } - static var allTests : [(String, (PerfectLDAPTests) -> () throws -> Void)] { return [ - ("testLogin", testLogin), - ("testLoginSync", testLoginSync), + ("testLoginFail", testLoginFail), + ("testLoginPass", testLoginPass), ("testSearch", testSearch), - ("testSearchSync", testSearchSync), ("testAttributeMod", testAttributeMod), ("testServerSort", testServerSort), - ("testSASLDefaults", testSASLDefaults), - ("testSASLogin", testSASLogin) ] } }