Skip to content

Commit

Permalink
Add ability to select active directory login to select mapped user ac…
Browse files Browse the repository at this point in the history
…count #136
  • Loading branch information
twocanoes committed Dec 17, 2023
1 parent be30097 commit 19260d3
Show file tree
Hide file tree
Showing 14 changed files with 246 additions and 85 deletions.
26 changes: 26 additions & 0 deletions NomadLogin/DSQueryable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,33 @@ public extension DSQueryable {
throw error
}
}
func getUserRecord(kerberosPrincipalNameToFind:String) throws -> ODRecord {
do {
os_log("getting non system users.", type: .info)

let allRecords = try getAllNonSystemUsers()
os_log("filtering", type: .info)

let matchingRecords = allRecords.filter { (record) -> Bool in
guard let foundKerberosPrincipal = try? record.values(forAttribute: "dsAttrTypeNative:_xcreds_activedirectory_kerberosPrincipal") as? [String] else {
return false
}

os_log("checking \(foundKerberosPrincipal)", type: .info)

return foundKerberosPrincipal.first == kerberosPrincipalNameToFind
}
guard let userRecord = matchingRecords.first else {
TCSLogWithMark("no users match \(kerberosPrincipalNameToFind)")

throw DSQueryableErrors.notLocalUser
}
return userRecord
} catch {
os_log("ODError while finding local users.", type: .error)
throw error
}
}

/// Returns all the non-system users on a system above UID 500.
///
Expand Down
45 changes: 22 additions & 23 deletions NomadLogin/LocalCheckAndMigrate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum MigrationType {
case fullMigration // perform full migration
case skipMigration // no need to migrate
case syncPassword // local password needs to be synced with local
case mappedUserFound(ODRecord)
case userMatchSkipMigration
case complete // all good
}
Expand All @@ -30,28 +31,27 @@ class LocalCheckAndMigrate : NSObject, DSQueryable {

public var migrationUsers: [String]?

func run(userToCheck: String, passToCheck: String) -> MigrationType {
func migrationTypeRequired(userToCheck: String, passToCheck: String, kerberosPrincipalName:String?) -> MigrationType {

TCSLogWithMark()
user = userToCheck
pass = passToCheck

let migrate = (getManagedPreference(key: .Migrate) as? Bool ?? false)

if let kerberosPrincipalName = kerberosPrincipalName, let foundRecord = try? getUserRecord(kerberosPrincipalNameToFind: kerberosPrincipalName) {

return .mappedUserFound(foundRecord)
}
let shouldPromptToMigrate = DefaultsOverride.standardOverride.bool(forKey: PrefKeys.shouldPromptForMigration.rawValue)

// check local user pass to see if user exists

do {
if try isLocalPasswordValid(userName: userToCheck, userPass: passToCheck) {

TCSLogWithMark("Network creds match local creds, nothing to migrate or update.")

if migrate {
TCSLogWithMark("Migrate set, adding migration name hint.")
// set the migration hint
delegate?.setHint(type: .existingLocalUserName, hint: userToCheck)
return .userMatchSkipMigration
} else {
return .complete
}
return .userMatchSkipMigration

} else {

TCSLogWithMark("Local name matches, but not password")
Expand All @@ -72,21 +72,20 @@ class LocalCheckAndMigrate : NSObject, DSQueryable {
} catch DSQueryableErrors.notLocalUser {
TCSLogWithMark("User is not a local user")

if migrate {
getMigrationCandidates()

if migrationUsers?.count ?? 0 < 1 {
TCSLogWithMark("No possible migration candidates, skipping migration")
return .skipMigration
} else {
return .fullMigration
}
} else {
if shouldPromptToMigrate == false {
return .complete
}

TCSLogWithMark("prompting to migrate set. checking for local accounts as candidates")
// getMigrationCandidates()
let standardUsers = try? getAllLocalUserRecords()
guard let standardUsers = standardUsers, standardUsers.count>0 else {
return .skipMigration
}
return .fullMigration

} catch {
TCSLogWithMark("Unknown migration check error")

TCSLogWithMark("Unknown migration check error. skipping migration.")
return .errorSkipMigration
}
}
Expand Down
2 changes: 1 addition & 1 deletion Profile Manifest/com.twocanoes.xcreds.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<key>pfm_app_url</key>
<string>https://github.com/twocanoes/xcreds</string>
<key>pfm_description</key>
<string>XCreds 4.0 (6023) OAuth Settings</string>
<string>XCreds 4.0 (6043) OAuth Settings</string>
<key>pfm_documentation_url</key>
<string>https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences</string>
<key>pfm_domain</key>
Expand Down
13 changes: 8 additions & 5 deletions StatusMenuWindowController.xib
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11163" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11163"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22154"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="StatusMenuWindowController" customModuleProvider="target">
<customObject id="-2" userLabel="File's Owner" customClass="StatusMenuWindowController" customModule="XCreds" customModuleProvider="target">
<connections>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
Expand All @@ -15,14 +17,15 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
<rect key="screenRect" x="0.0" y="0.0" width="1496" height="910"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="107" y="10"/>
</window>
</objects>
</document>
4 changes: 2 additions & 2 deletions XCreds/MainController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ class MainController: NSObject, NoMADUserSessionDelegate {
// in the keychain
let accountAndPassword = localAccountAndPassword()

let domainName = DefaultsOverride.standardOverride.string(forKey: PrefKeys.aDDomain.rawValue)

if let userName=accountAndPassword.0, let passString = accountAndPassword.1, passString.isEmpty==false{

if let domainName = userName.components(separatedBy: "@").last, let shortName = userName.components(separatedBy: "@").first, domainName.isEmpty==false, shortName.isEmpty==false{
if let domainName = domainName, let shortName = userName.components(separatedBy: "@").first, domainName.isEmpty==false, shortName.isEmpty==false{
session = NoMADSession.init(domain: domainName, user: shortName)
TCSLogWithMark("NoMAD Login User: \(shortName), Domain: \(domainName)")
guard let session = session else {
Expand All @@ -59,7 +60,6 @@ class MainController: NSObject, NoMADUserSessionDelegate {
session.recursiveGroupLookup = getManagedPreference(key: .RecursiveGroupLookup) as? Bool ?? false

if let ignoreSites = getManagedPreference(key: .IgnoreSites) as? Bool {
// os_log("Ignoring AD sites", log: uiLog, type: .debug)

session.siteIgnore = ignoreSites
}
Expand Down
3 changes: 1 addition & 2 deletions XCreds/PasswordUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,9 @@ class PasswordUtils: NSObject {
let userRecord = try PasswordUtils.getLocalRecord(userName)
// TCSLogWithMark("Checking if password is allowed")
// try userRecord.passwordChangeAllowed(userPass)
//xyzzy
TCSLogWithMark("checking password")
try userRecord.verifyPassword(userPass)
TCSLogWithMark("checking password done")
TCSLogWithMark("checking password done, returning success")
return .success

} catch {
Expand Down
6 changes: 1 addition & 5 deletions XCredsLoginPlugIn/LoginWindow/LoginWebViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class LoginWebViewController: WebViewController, DSQueryable {
username = userAccountSelected
case .canceled:
TCSLogWithMark("User cancelled. Denying login")
mechanism.denyLogin(message:nil)
mechanism.denyLogin(message:"nil")
return
case .createNewAccount:
break;
Expand Down Expand Up @@ -276,10 +276,6 @@ class LoginWebViewController: WebViewController, DSQueryable {

TCSLogWithMark("setting tokens")
mechanism.setHint(type: .tokens, hint: [tokens.idToken ?? "",tokens.refreshToken ?? "",tokens.accessToken ?? ""])
// if let resolutionObserver = resolutionObserver {
// NotificationCenter.default.removeObserver(resolutionObserver)
// }
//
TCSLogWithMark("calling allowLogin")

if let controller = self.view.window?.windowController as? MainLoginWindowController {
Expand Down
65 changes: 42 additions & 23 deletions XCredsLoginPlugIn/LoginWindow/SignInWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -391,39 +391,26 @@ let checkADLog = OSLog(subsystem: "menu.nomad.login.ad", category: "CheckADMech"
///
/// - Parameter authResult:`Authorizationresult` enum value that indicates if login should proceed.
fileprivate func completeLogin(authResult: AuthorizationResult) {
if let delegate = mechanism {

}
else {
TCSLogWithMark("delegate not defined")

}
switch authResult {
case .allow:
TCSLogWithMark("Complete login process with allow")
mechanism?.allowLogin()
// window?.close()

case .deny:
TCSLogWithMark("Complete login process with deny")
mechanism?.denyLogin(message:nil)

// window?.close()

default:
TCSLogWithMark("Complete login process with unknown")
// window?.close()
TCSLogWithMark("deny login process with unknown error")
mechanism?.denyLogin(message:nil)

}
TCSLogWithMark("Complete login process")
// let error = mech?.fPlugin.pointee.fCallbacks.pointee.SetResult((mech?.fEngine)!, authResult)
// if error != noErr {
// TCSLogWithMark("Got error setting authentication result", log: uiLog, type: .
// }
TCSLogWithMark()
NSApp.stopModal()
}



//MARK: - Update Local User Account Methods

// fileprivate func showPasswordSync() {
Expand Down Expand Up @@ -479,8 +466,31 @@ let checkADLog = OSLog(subsystem: "menu.nomad.login.ad", category: "CheckADMech"
// }


fileprivate func showMigration() {
fileprivate func showMigration(password:String) {

TCSLogWithMark()
switch VerifyLocalCredentialsWindowController.selectLocalAccountAndUpdate(newPassword: password) {

case .successful(let username):
TCSLogWithMark("Successful local account verification. Allowing")
shortName = username
setRequiredHintsAndContext()
completeLogin(authResult: .allow)
return

case .canceled:
TCSLogWithMark("selectLocalAccountAndUpdate cancelled")
completeLogin(authResult: .deny)
return
case .createNewAccount:
TCSLogWithMark("selectLocalAccountAndUpdate createNewAccount")
completeLogin(authResult: .allow)

case .error(let error):
TCSLogWithMark("selectLocalAccountAndUpdate error:\(error)")
completeLogin(authResult: .deny)

}
//need to prompt for username and passsword to select an account. Perhaps use code from the cloud login.
// //RunLoop.main.perform {
// // hide other possible boxes
Expand Down Expand Up @@ -770,41 +780,50 @@ extension SignInViewController: NoMADUserSessionDelegate {
// check for any migration and local auth requirements
let localCheck = LocalCheckAndMigrate()
localCheck.delegate = mechanism
// localCheck.mech = self.mech
switch localCheck.run(userToCheck: user.shortName, passToCheck: passString) {
switch localCheck.migrationTypeRequired(userToCheck: user.shortName, passToCheck: passString, kerberosPrincipalName:user.userPrincipal) {

case .fullMigration:
showMigration()
TCSLogWithMark()
showMigration(password:passString)
case .syncPassword:
// first check to see if we can resolve this ourselves
TCSLogWithMark("Sync password called.")
// showPasswordSync()

if let mechanism = mechanism as? XCredsLoginMechanism {
let res = mechanism.promptForLocalPassword(username: user.shortName)


completeLogin(authResult: res)


}
case .errorSkipMigration, .skipMigration, .userMatchSkipMigration, .complete:
completeLogin(authResult: .allow)
case .mappedUserFound(let foundODUserRecord):
shortName = foundODUserRecord.recordName
TCSLogWithMark("Mapped user found: \(shortName)")
setRequiredHintsAndContext()
completeLogin(authResult: .allow)
}
} else {
authFail()
TCSLogWithMark("auth fail")
// alertText.stringValue = "Not authorized to login."
// showResetUI()
}
}

fileprivate func setHints(user: ADUserRecord) {
TCSLogWithMark()
os_log("NoMAD Login Looking up info for: %{public}@", log: uiLog, type: .default, user.shortName)
TCSLogWithMark("NoMAD Login Looking up info");
setRequiredHintsAndContext()
mechanism?.setHint(type: .firstName, hint: user.firstName)
mechanism?.setHint(type: .lastName, hint: user.lastName)
mechanism?.setHint(type: .noMADDomain, hint: domainName)
mechanism?.setHint(type: .groups, hint: user.groups)
mechanism?.setHint(type: .fullName, hint: user.cn)
TCSLogWithMark("setting kerberos principal to \(user.userPrincipal)")

mechanism?.setHint(type: .kerberos_principal, hint: user.userPrincipal)
mechanism?.setHint(type: .ntName, hint: user.ntName)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,6 @@ class VerifyLocalCredentialsWindowController: NSWindowController, NSWindowDelega
switch isValidPassword {
case .success:
isDone = true
//// username = localUsername
//// passwordHintSet=true
//// TCSLogWithMark("setting original password to use to unlock keychain later")
//// mechanism.setHint(type: .existingLocalUserPassword, hint: localPassword)
////
//// guard let username = username else {
////
//// isDone = true
//// TCSLogErrorWithMark("username is not set")
//// mechanism.denyLogin(message:"username is not set")
//// return
////
//// }
let localUser = try? PasswordUtils.getLocalRecord(localUsername)
guard let localUser = localUser else {

Expand All @@ -82,14 +69,24 @@ class VerifyLocalCredentialsWindowController: NSWindowController, NSWindowDelega

}
do {
TCSLogWithMark("Changing password")
if localPassword == newPassword {
TCSLogWithMark("cloud password is already the local password.")

return .successful(localUsername)
}
try localUser.changePassword(localPassword, toPassword: newPassword)

TCSLogWithMark("local password set successfully to network / cloud password")
return .successful(localUsername)

}
catch {
TCSLogErrorWithMark("Error setting local password to cloud password")
return .error("Error setting local password to cloud password")
}

case .incorrectPassword:
case .incorrectPassword: //don't return b/c we just loop and ask again
TCSLogErrorWithMark("Incorrect Password")

case .accountDoesNotExist:
Expand Down

0 comments on commit 19260d3

Please sign in to comment.