...
 
Commits (10)
......@@ -6,7 +6,9 @@
// Copyright © 2017 NoMAD. All rights reserved.
//
enum HintType: String {
enum HintType: String, CaseIterable {
case migratePass
case migrateUser
case networkSignIn
case noMADUser
case noMADDomain
......@@ -18,6 +20,8 @@ enum HintType: String {
case uid
case gid
case kerberos_principal
case passwordOverride // stmop on the password
}
// attribute statics
......
//
// DS+Okta.swift
// NoMADLoginOkta
//
// Created by Josh Wisenbaker on 9/20/18.
// Copyright © 2018 Orchard & Grove. All rights reserved.
//
import OpenDirectory
enum NoMADQueryErrors: Error {
case noMigrationCandidates
}
// MARK: - NoMAD extensions for the DSQueryable Protocol.
extension DSQueryable {
/// Check to see if a given local user has the `kODAttributeOktaUser` set on their account.
///
/// - Parameter shortName: The shortname of the user to check as a `String`.
/// - Returns: `true` if the user has an Okta attribute. Otherwise `false`.
/// - Throws: A `ODFrameworkErrors` or a `DSQueryableErrors` if there is an error.
public func checkForNoMADUser(_ shortName: String) throws -> Bool {
os_log("Checking for Okta username", type: .default)
do {
let userRecord = try getLocalRecord(shortName)
let names = try userRecord.values(forAttribute: kODAttributeADUser)
if names.isEmpty {
return false
}
return true
} catch DSQueryableErrors.notLocalUser {
return false
} catch {
throw error
}
}
/// Search in DSLocal and find any potential migration users.
///
/// - Parameter excludeList: An optional `Array` of `String` values to exclude from the candidate list. These are typically set in the `.MigrateUsersHide` preference key.
/// - Returns: The shortnames of the users to offer for Okta migration in an `Array` of `String` values.
/// - Throws: A `ODFrameworkErrors` or a `DSQueryableErrors` if there is an error. Throws `NoMADQueryErrors.noMigrationCandidates` if no results are found.
public func findNoMADMigrationCandidates(excludeList: [String] = [String]()) throws -> [String] {
do {
os_log("Checking for NoMAD migration users.", type: .default)
var candidates = [String]()
os_log("Getting all user records.", type: .default)
let records = try getAllNonSystemUsers()
os_log("Filtering records", type: .default)
let filtered = try records.filter({ (record) -> Bool in
if excludeList.contains(record.recordName) {
os_log("User is exluded", type: .default)
return false
}
if try checkForNoMADUser(record.recordName) {
os_log("User has a NoMAD Attribute", type: .default)
return false
}
return true
})
for record in filtered {
candidates.append(record.recordName)
}
if candidates.isEmpty {
throw NoMADQueryErrors.noMigrationCandidates
}
return candidates
} catch {
throw error
}
}
}
This diff is collapsed.
//
// LocalCheckAndMigrate.swift
// JamfConnectLogin
//
// Created by Joel Rennich on 2/19/19.
// Copyright © 2019 Jamf Inc. All rights reserved.
//
import Foundation
import OpenDirectory
enum MigrationType {
case errorSkipMigration // unable to complete migration
case fullMigration // perform full migration
case skipMigration // no need to migrate
case syncPassword // local password needs to be synced with local
case userMatchSkipMigration
case complete // all good
}
// class to handle local checks and migration
class LocalCheckAndMigrate : ContextAndHintHandling, DSQueryable {
var mech: MechanismRecord?
private var user = ""
private var pass = ""
public var migrationUsers: [String]?
func run(userToCheck: String, passToCheck: String) -> MigrationType {
user = userToCheck
pass = passToCheck
let migrate = (getManagedPreference(key: .Migrate) as? Bool ?? false)
// check local user pass to see if user exists
do {
if try isLocalPasswordValid(userName: userToCheck, userPass: passToCheck) {
os_log("Network creds match local creds, nothing to migrate or update.", log: uiLog, type: .default)
if migrate {
os_log("Migrate set, adding migration name hint.", log: uiLog, type: .default)
// set the migration hint
setHint(type: .migrateUser, hint: userToCheck)
return .userMatchSkipMigration
} else {
return .complete
}
} else {
os_log("Local name matches, but not password", log: uiLog, type: .default)
if (getManagedPreference(key: .PasswordOverwriteSilent) as? Bool ?? false) {
// set the hint and return complete
os_log("Setting password to be overwritten.", log: uiLog, type: .default)
setHint(type: .passwordOverride, hint: "true")
return .complete
} else {
return .syncPassword
}
}
} catch DSQueryableErrors.notLocalUser {
os_log("User is not a local user", log: uiLog, type: .default)
if migrate {
getMigrationCandidates()
if migrationUsers?.count ?? 0 < 1 {
os_log("No possible migration candidates, skipping migration", log: uiLog, type: .default)
return .skipMigration
} else {
return .fullMigration
}
} else {
return .complete
}
} catch {
os_log("Unknown migration check error", log: uiLog, type: .default)
return .errorSkipMigration
}
}
fileprivate func getMigrationCandidates() {
do {
if let hiddenMigrationUsers = getManagedPreference(key: .MigrateUsersHide) as? [String] {
migrationUsers = try findNoMADMigrationCandidates(excludeList: hiddenMigrationUsers)
} else {
os_log("No users are hidden from migration.", log: uiLog, type: .default)
migrationUsers = try findNoMADMigrationCandidates()
}
} catch NoMADQueryErrors.noMigrationCandidates {
os_log("No local users to possibly migrate.", log: uiLog, type: .default)
} catch {
let errorText = error.localizedDescription
os_log("Error while determining migration candidate users: %{public}@", log: uiLog, type: .error, errorText)
}
}
func syncPass(oldPass: String) -> Bool {
var userRecord: ODRecord?
do {
userRecord = try getLocalRecord(user)
try userRecord?.changePassword(oldPass, toPassword: pass)
} catch {
if userRecord == nil {
os_log("Unable to obtain local user record.", log: uiLog, type: .default)
} else {
os_log("Unable to change local user password.", log: uiLog, type: .default)
}
return false
}
os_log("Local password changed.", log: uiLog, type: .default)
setHint(type: .migratePass, hint: oldPass)
return true
}
}
......@@ -26,6 +26,8 @@ struct PluginRecord {
typedef struct PluginRecord PluginRecord;
extern OSStatus SecKeychainChangePassword(SecKeychainRef keychainRef, UInt32 oldPasswordLength, const void* oldPassword, UInt32 newPasswordLength, const void* newPassword);
#pragma mark - Mechanism
enum {
......
......@@ -8,7 +8,7 @@
import Foundation
enum Preferences: String {
enum Preferences: String, CaseIterable {
/// The desired AD domain as a `String`.
case ADDomain
/// Allows appending of other domains at the loginwindow. Set as a `Bool` to allow any, or as an Array of Strings to whitelist
......@@ -25,6 +25,8 @@ enum Preferences: String {
case CreateAdminIfGroupMember
/// Should existing mobile accounts be converted into plain local accounts? Set as a Bool`.
case DemobilizeUsers
/// should we check for a password in the hints before demobilzing the user?
case DemobilizeForcePasswordCheck
/// Dissallow local auth, and always do network authentication
case DenyLocal
/// Users to allow locally when DenyLocal is on
......@@ -47,6 +49,8 @@ enum Preferences: String {
case EULATitle
/// Subhead for EULA as a `String`.
case EULASubTitle
case HideRestart
case HideShutdown
/// Ignore sites in AD. This is a compatibility measure for AD installs that have issues with sites. Set as a `Bool`.
case IgnoreSites
/// Adds a NoMAD entry into the keychain. `Bool` value.
......@@ -69,12 +73,18 @@ enum Preferences: String {
case LoginLogoData
/// Should NoLo display a macOS-style login screen instead of a window? Set as a `Bool`,
case LoginScreen
/// should we migrate users?
case Migrate
/// should we hide users when we migrate?
case MigrateUsersHide
/// If Notify should add additional logging
case NotifyLogStyle
/// Path to script to run, currently only one script path can be used, if you want to run this multiple times, keep the logic in your script
case ScriptPath
/// Arguments for the script, if any
case ScriptArgs
/// what level the sign in window should be at
case SignInWindowLevel
/// Use the CN from AD as the full name
case UseCNForFullName
/// A string to show as the placeholder in the Username textfield
......@@ -82,6 +92,10 @@ enum Preferences: String {
/// A filesystem path to an image to set the user profile image to as a `String`
case UserProfileImage
//Messages
case MessagePasswordSync // what to show when the password needs to sync
//UserInput bits
case UserInputOutputPath
......@@ -89,6 +103,50 @@ enum Preferences: String {
case UserInputLogo
case UserInputTitle
case UserInputMainText
//Password update keys
case PasswordOverwriteSilent // will silently update user password to new one
case PasswordOverwriteOptional // allow the user to stomp on the password if interested
}
func printAllPrefs(writeOut: Bool=false) {
var result = ""
for key in Preferences.allCases {
let defaults = UserDefaults.init(suiteName: "menu.nomad.login.ad")
let pref = defaults?.object(forKey: key.rawValue) as AnyObject
switch String(describing: type(of: pref)) {
case "__NSCFBoolean" :
result.append("\t" + key.rawValue + ": " + String(describing: ( defaults?.bool(forKey: key.rawValue))))
case "__NSCFArray" :
result.append("\t" + key.rawValue + ": " + ( String(describing: (defaults?.array(forKey: key.rawValue)!))))
case "__NSTaggedDate", "__NSDate" :
result.append("\t" + key.rawValue + ": " + ( defaults?.object(forKey: key.rawValue) as! Date ).description(with: Locale.current))
case "__NSCFDictionary":
result.append("\t" + key.rawValue + ": " + String(describing: defaults?.dictionary(forKey: key.rawValue)!))
case "__NSCFData" :
result.append("\t" + key.rawValue + ": " + (defaults?.data(forKey: key.rawValue)?.base64EncodedString() ?? "ERROR"))
case "__NSCFNumber" :
result.append("\t" + key.rawValue + ": " + String(describing: defaults?.integer(forKey: key.rawValue)))
default :
result.append("\t" + key.rawValue + ": " + ( defaults?.object(forKey: key.rawValue) as? String ?? "Unset"))
}
if defaults?.objectIsForced(forKey: key.rawValue) ?? false {
result.append("\t\tForced")
}
result.append("\n")
}
if writeOut {
try? result.write(toFile: "/tmp/menu.nomad.login.ad.plist", atomically: true, encoding: String.Encoding.utf8)
} else {
print(result)
}
}
......
......@@ -2,7 +2,7 @@
"Mac" : [
{
"name" : "NoMAD_ADAuth",
"hash" : "d15a5ad01c1b4f6a6fcdbeccef7a3661649a3c6b7a5c65d690dd42812b9467bb"
"hash" : "f0478d781ee929666841f0b7662efc560e36612a380d67c61ada17c3a43fb146"
}
],
"watchOS" : [
......
......@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18D42</string>
<string>18C54</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
......
......@@ -12,7 +12,7 @@ import NoMAD_ADAuth
/// Mechanism to create a local user and homefolder.
class CreateUser: NoLoMechanism {
class CreateUser: NoLoMechanism, DSQueryable {
//MARK: - Properties
let session = ODSession.default()
......@@ -87,6 +87,18 @@ class CreateUser: NoLoMechanism {
// no user to create
os_log("Skipping local account creation", log: createUserLog, type: .default)
// check to see if we need to overwrite the password
if (getHint(type: .passwordOverride) as? String == "true") {
os_log("Attempting to override user password.", log: createUserLog)
do {
let localUserRecord = try getLocalRecord(nomadUser ?? "NONE")
try localUserRecord.changePassword(nil, toPassword: nomadPass)
} catch {
os_log("Unable to override user password", log: createUserLog)
}
}
// Set the login timestamp if requested
setTimestampFor(nomadUser as? String ?? "")
......@@ -339,6 +351,8 @@ class CreateUser: NoLoMechanism {
return "English" + templateName
case "fr":
return "French" + templateName
case "de":
return "German" + templateName
case "it":
return "Italian" + templateName
case "ja":
......
......@@ -61,6 +61,7 @@ class DeMobilize : NoLoMechanism {
return
}
if (getManagedPreference(key: Preferences.DemobilizeForcePasswordCheck) as? Bool ?? false) {
// sanity check to ensure we have valid information and a local user
os_log("Checking for password", log: demobilizeLog, type: .debug)
if passwordContext == nil {
......@@ -70,6 +71,7 @@ class DeMobilize : NoLoMechanism {
_ = allowLogin()
return
}
}
// get local user record
guard let userRecord = getLocalRecord(shortName) else {
......
......@@ -87,7 +87,26 @@ class KeychainAdd : NoLoMechanism {
os_log("Unable to unlock keychain reference.", log: keychainAddLog, type: .default)
// check if we should reset
if getManagedPreference(key: .KeychainReset) as? Bool == true {
if let resetPass = getHint(type: .migratePass) as? String {
os_log("Resetting keychain with migrated user/pass.", log: keychainAddLog)
var myKeychain : SecKeychain?
err = SecKeychainOpen(userKeychainPath, &myKeychain)
err = SecKeychainChangePassword(myKeychain, UInt32(resetPass.count), resetPass, UInt32(userpass.count), userpass)
if err != 0 {
os_log("Unable to reset keychain with migrated user/pass.", log: keychainAddLog, type: .error)
if (getManagedPreference(key: .KeychainReset) as? Bool ?? true ) {
os_log("Resetting keychain password.", log: keychainAddLog, type: .info)
clearKeychain(path: homeDir)
}
}
} else if (getManagedPreference(key: .KeychainReset) as? Bool ?? true ) {
os_log("Resetting keychain password.", log: keychainAddLog, type: .info)
clearKeychain(path: homeDir)
} else {
......
......@@ -75,6 +75,10 @@ class LogOnly : NoLoMechanism {
}
}
os_log("Printing all preferences", log: loggerMech, type: .debug)
let _ = allowLogin()
os_log("LogOnly mech complete", log: loggerMech, type: .debug)
}
......
......@@ -15,6 +15,13 @@ class Notify : NoLoMechanism {
@objc func run() {
// check if we should skip this or not
if FileManager.default.fileExists(atPath: "/tmp/.skipNotify") {
_ = allowLogin()
return
}
NSApp.activate(ignoringOtherApps: true)
let notifyWindow = NoLoNotify(windowNibName: NSNib.Name("NoLoNotify"))
......
......@@ -12,6 +12,14 @@ import Cocoa
@objc class UserInput : NoLoMechanism {
@objc func run() {
// check if we should skip this or not
if FileManager.default.fileExists(atPath: "/tmp/.skipUserInput") {
_ = allowLogin()
return
}
// run the UI if we have the settings
if let inputSettings = getManagedPreference(key: .UserInputUI) as? [ String : AnyObject ] {
......
......@@ -7,9 +7,11 @@
objects = {
/* Begin PBXBuildFile section */
0126150B2200E2F00058E5CA /* NoMAD_ADAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C7FA0B6203369DA00E7D67B /* NoMAD_ADAuth.framework */; };
0126150C2200E3000058E5CA /* NoMAD_ADAuth.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 9C7FA0B6203369DA00E7D67B /* NoMAD_ADAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
017F5D1722022A0700042737 /* NoMAD_ADAuth.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 017F5D1622022A0700042737 /* NoMAD_ADAuth.framework.dSYM */; };
1A43759E221CF00E00D1B976 /* LocalCheckAndMigrate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A43759D221CF00E00D1B976 /* LocalCheckAndMigrate.swift */; };
1A4375A0221CF08400D1B976 /* DSQueryable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A43759F221CF08400D1B976 /* DSQueryable.swift */; };
1A4375A2221CF13500D1B976 /* DS+NoMAD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A4375A1221CF13500D1B976 /* DS+NoMAD.swift */; };
1AA58EF9216610B300431201 /* UserInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA58EF8216610B300431201 /* UserInput.swift */; };
1AA58EFB216610C300431201 /* Notify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA58EFA216610C300431201 /* Notify.swift */; };
1AA58EFD216610C700431201 /* RunScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA58EFC216610C700431201 /* RunScript.swift */; };
......@@ -21,6 +23,7 @@
1AA58F03216611C100431201 /* UserInputUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AA58EEA2166101100431201 /* UserInputUI.swift */; };
1AA58F04216611C400431201 /* UserInputUI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1AA58EEB2166101100431201 /* UserInputUI.xib */; };
1AACDAE02217B72B006D8A65 /* NoLoUserInput Sample.mobileconfig in Resources */ = {isa = PBXBuildFile; fileRef = 1AACDADF2217B72A006D8A65 /* NoLoUserInput Sample.mobileconfig */; };
1AACDAE12217BAFC006D8A65 /* NoMAD_ADAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C7FA0B6203369DA00E7D67B /* NoMAD_ADAuth.framework */; };
1AF15E8721B1F85E00D36F3E /* NoLoWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AF15E8621B1F85E00D36F3E /* NoLoWindow.swift */; };
9C0A3EA41FF2CCD70030A04F /* LoggingDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C0A3EA31FF2CCD70030A04F /* LoggingDefinitions.swift */; };
9C468F812006A95400A475FC /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C468F802006A95400A475FC /* Preferences.swift */; };
......@@ -67,6 +70,9 @@
/* Begin PBXFileReference section */
017F5D1622022A0700042737 /* NoMAD_ADAuth.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; path = NoMAD_ADAuth.framework.dSYM; sourceTree = "<group>"; };
1A43759D221CF00E00D1B976 /* LocalCheckAndMigrate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalCheckAndMigrate.swift; sourceTree = "<group>"; };
1A43759F221CF08400D1B976 /* DSQueryable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSQueryable.swift; sourceTree = "<group>"; };
1A4375A1221CF13500D1B976 /* DS+NoMAD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DS+NoMAD.swift"; sourceTree = "<group>"; };
1AA58EEA2166101100431201 /* UserInputUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInputUI.swift; sourceTree = "<group>"; };
1AA58EEB2166101100431201 /* UserInputUI.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = UserInputUI.xib; sourceTree = "<group>"; };
1AA58EED2166104400431201 /* NoLoNotify.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NoLoNotify.xib; sourceTree = "<group>"; };
......@@ -114,7 +120,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
0126150B2200E2F00058E5CA /* NoMAD_ADAuth.framework in Frameworks */,
1AACDAE12217BAFC006D8A65 /* NoMAD_ADAuth.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -235,9 +241,12 @@
9CBDCFB61FC5CE2900CF73F4 /* Base */ = {
isa = PBXGroup;
children = (
1A43759F221CF08400D1B976 /* DSQueryable.swift */,
1A43759D221CF00E00D1B976 /* LocalCheckAndMigrate.swift */,
9CBDCFA71FC5CDB500CF73F4 /* NoMADLoginAD-Bridging-Header.h */,
9CBDCFB41FC5CE2100CF73F4 /* NoMADLoginAD.h */,
9CBDCFB31FC5CE2100CF73F4 /* NoMADLoginAD.m */,
1A4375A1221CF13500D1B976 /* DS+NoMAD.swift */,
9C6027451FE8224000B99B51 /* ContextAndHintHandling.swift */,
9C8A2608207D917900204336 /* DataExtension.swift */,
9C0A3EA31FF2CCD70030A04F /* LoggingDefinitions.swift */,
......@@ -343,10 +352,13 @@
9CA733FC202E491E00EA1FE6 /* PowerControl.swift in Sources */,
1AA58F01216611BA00431201 /* EULAUI.swift in Sources */,
1AA58EFF216611B400431201 /* Tracker.swift in Sources */,
1A43759E221CF00E00D1B976 /* LocalCheckAndMigrate.swift in Sources */,
1AA58F03216611C100431201 /* UserInputUI.swift in Sources */,
9C8A2609207D917900204336 /* DataExtension.swift in Sources */,
9CBDCFAF1FC5CDB600CF73F4 /* NoLoMechanism.swift in Sources */,
1A4375A2221CF13500D1B976 /* DS+NoMAD.swift in Sources */,
9CC74CC91FFED59B00EBCCD5 /* DeMobilize.swift in Sources */,
1A4375A0221CF08400D1B976 /* DSQueryable.swift in Sources */,
1AA58EF9216610B300431201 /* UserInput.swift in Sources */,
9CBDCFAD1FC5CDB600CF73F4 /* CreateUser.swift in Sources */,
9CBDCFBA1FC5CE6100CF73F4 /* SignIn.swift in Sources */,
......
......@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.3.0</string>
<string>1.4.0-Alpha5</string>
<key>CFBundleVersion</key>
<string>329</string>
<string>337</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Orchard &amp; Grove. All rights reserved.</string>
<key>NSPrincipalClass</key>
......
This diff is collapsed.
This diff is collapsed.