Skip to content

Commit

Permalink
Drafting SASL / GSSAPI / GSS-SPNEGO Login Support
Browse files Browse the repository at this point in the history
  • Loading branch information
RockfordWei committed Jan 26, 2017
1 parent 0e57430 commit 136a314
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 34 deletions.
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ 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)
]
)
86 changes: 68 additions & 18 deletions Sources/PerfectLDAP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
//===----------------------------------------------------------------------===//
//

/// C library of SASL
import SASL

/// C library of OpenLDAP
import OpenLDAP

Expand All @@ -26,6 +29,9 @@ import PerfectThread
/// Iconv
import PerfectICONV

/// CArray Helper
import PerfectCArray

/// Perfect LDAP Module
public class LDAP {

Expand Down Expand Up @@ -205,7 +211,7 @@ public class LDAP {
/// - 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, auth: AuthType = .SIMPLE, codePage: Iconv.CodePage = .UTF8) throws {
public init(url:String = "ldap://localhost", username: String? = nil, password: String? = nil, realm: String? = nil, auth: AuthType = .SIMPLE, codePage: Iconv.CodePage = .UTF8) throws {

if codePage != .UTF8 {
// we need a pair of code pages to transit in both directions.
Expand Down Expand Up @@ -245,11 +251,11 @@ public class LDAP {
}//end if

// call login internally
guard login(username: username ?? "", password: password ?? "", auth: auth) else {
throw Exception.message("Access Denied")
}//end _login
try login(username: username ?? "", password: password ?? "", realm: realm ?? "", auth: auth)
}//end init



/// login in synchronized mode, will block the calling thread
/// - parameters:
/// - username: String, user name to login, optional.
Expand All @@ -258,25 +264,65 @@ public class LDAP {
/// - returns:
/// true for a successful login.
@discardableResult
public func login(username: String, password: String, auth: AuthType = .SIMPLE) -> Bool {
public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE) throws {
var r = Int32(0)
let ex = Exception.message("UNSUPPORTED SECURITY MECH")
switch auth {
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)
ber_memfree(cred.bv_val)
return r == 0
case .GSSAPI:
/*
r = ldap_sasl_interactive_bind_s(ldap, username, _saslMech[.GSSAPI], nil, nil, LDAP_SASL_AUTOMATIC, { opaque, uint32, raw1, raw2 in return Int32(0)}, <#T##defaults: UnsafeMutableRawPointer!##UnsafeMutableRawPointer!#>)
**/
return true
case .SPNEGO:
return true
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

let defaultsPointer = unsafeBitCast(defaults, to: UnsafeMutablePointer<lutilSASLdefaults>.self)
let def = defaultsPointer.pointee

guard let pInput = input else {
return -1
}//end guard

var cursor: UnsafeMutablePointer<sasl_interact_t> = unsafeBitCast(pInput, to: UnsafeMutablePointer<sasl_interact_t>.self)

var interact = cursor.pointee

while(Int32(interact.id) != SASL_CB_LIST_END) {

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)
case SASL_CB_USER:
SASLReply(pInteract: cursor, pDefaults: defaultsPointer, pMsg: def.authzid)
case SASL_CB_NOECHOPROMPT, SASL_CB_ECHOPROMPT: // skipped
()
default: //unknown
return -1
}//end case
cursor.pointee = interact
cursor = cursor.successor()
interact = cursor.pointee
}//end while

return Int32(0)}, $0) }
default:
// not support - always failed
return false
throw ex
}
if r == 0 {
return
}else {
throw Exception.message(LDAP.error(r))
}//end
}//end login

/// Login in asynchronized mode. Once completed, it would invoke the callback handler
Expand All @@ -285,10 +331,14 @@ public class LDAP {
/// - password: String, password for login, optional.
/// - auth: AuthType, such as SIMPLE, GSSAPI, SPNEGO or DIGEST MD5
/// - completion: callback handler with a boolean parameter indicating whether login succeeded or not.
public func login(username: String, password: String, auth: AuthType = .SIMPLE, completion: @escaping (Bool)->Void) {
public func login(username: String, password: String, realm: String = "", auth: AuthType = .SIMPLE, completion: @escaping (String?)->Void) {
Threading.dispatch {
let r = self.login(username: username, password: password, auth: auth)
completion(r)
do {
try self.login(username: username, password: password, realm: realm, auth: auth)
completion(nil)
}catch(let err) {
completion("LOGIN FAILED: \(err)")
}
}//end thread
}//end login

Expand Down
31 changes: 31 additions & 0 deletions Sources/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import PerfectICONV
import OpenLDAP
import PerfectCArray
/// C library of SASL
import SASL

extension Iconv {

Expand Down Expand Up @@ -80,3 +83,31 @@ public func withCArrayOfString<R>(array: [String] = [], _ body: (UnsafeMutablePo
return r
}//end withCArrayOfString

public var CSTRHelper = CArray<UnsafeMutablePointer<Int8>>()

public func SASLReply(pInteract: UnsafeMutablePointer<sasl_interact_t>, pDefaults: UnsafeMutablePointer<lutilSASLdefaults>, pMsg: UnsafeMutablePointer<Int8>) {

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








47 changes: 31 additions & 16 deletions Tests/PerfectLDAPTests/PerfectLDAPTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ class PerfectLDAPTests: XCTestCase {
let testURL = "ldap://192.168.56.13"
let testUSR = "rocky@p.com"
let testPWD = "rockford"
let testRLM = "P"

func testLogin() {
do {
let logfail = expectation(description: "logfail")
let ldap = try LDAP(url: testURL, codePage: .GB2312)
ldap.login(username: "abc", password: "123") { result in
XCTAssertFalse(result)
ldap.login(username: "abc", password: "123") { err in
XCTAssertNotNil(err)
logfail.fulfill()
print("log failed passed")
}//end log
Expand All @@ -24,8 +25,8 @@ class PerfectLDAPTests: XCTestCase {
}//end wait

let logsuc = expectation(description: "logsuc")
ldap.login(username: testUSR, password: testPWD) { result in
XCTAssertTrue(result)
ldap.login(username: testUSR, password: testPWD) { err in
XCTAssertNil(err)
logsuc.fulfill()
print("log real passed")
}//end log
Expand All @@ -42,18 +43,20 @@ class PerfectLDAPTests: XCTestCase {
func testLoginSync() {
do {
let ldap = try LDAP(url: testURL, codePage: .GB2312)
let fail = ldap.login(username: "abc", password: "123")
XCTAssertFalse(fail)
try ldap.login(username: "abc", password: "123")

print(" -- -- --- ---")
print(ldap.supportedControl)
print(ldap.supportedExtension)
print(ldap.supportedSASLMechanisms)

print(" -- -- --- ---")
let succ = ldap.login(username: testUSR, password: testPWD)
XCTAssertTrue(succ)
}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)")
}
Expand All @@ -62,7 +65,7 @@ class PerfectLDAPTests: XCTestCase {

func testSearch () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312)
let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312)

let ser = expectation(description: "search")
ldap.search(base:"cn=users,dc=p,dc=com", scope:.SUBTREE) { res in
Expand All @@ -83,7 +86,7 @@ class PerfectLDAPTests: XCTestCase {
}
func testSearchSync () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312)
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
Expand All @@ -98,7 +101,7 @@ class PerfectLDAPTests: XCTestCase {

func testServerSort () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312)
let ldap = try LDAP(url: testURL, username: testUSR, password: testPWD, codePage: .GB2312)
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
let sort = LDAP.sortingString(sortedBy: [("displayName", .DSC), ("initials", .ASC)])
print(sort)
Expand All @@ -115,7 +118,7 @@ class PerfectLDAPTests: XCTestCase {

func testAttributeMod () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13", username: testUSR, password: testPWD, codePage: .GB2312)
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
Expand Down Expand Up @@ -146,7 +149,8 @@ class PerfectLDAPTests: XCTestCase {

func testSASLDefaults () {
do {
let ldap = try LDAP(url: "ldap://192.168.56.13")

let ldap = try LDAP(url: testURL)

let sasl = ldap.supportedSASL
guard let gssapi = sasl[LDAP.AuthType.GSSAPI] else {
Expand Down Expand Up @@ -179,6 +183,16 @@ class PerfectLDAPTests: XCTestCase {
}
}

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),
Expand All @@ -187,7 +201,8 @@ class PerfectLDAPTests: XCTestCase {
("testSearchSync", testSearchSync),
("testAttributeMod", testAttributeMod),
("testServerSort", testServerSort),
("testSASLDefaults", testSASLDefaults)
("testSASLDefaults", testSASLDefaults),
("testSASLogin", testSASLogin)
]
}
}

0 comments on commit 136a314

Please sign in to comment.