Search code examples
iosxcodemacosshell

iOS How to use system password to run a script?


I am creating a macOS app (using SwiftUI), it will run a script. To run a script I need the system (or admin) password. because when I run that script in Terminal it asked the user password and than run the script. I am able to run the script on button click with a securefeild, but It's not the appropriate to show user a securefeild and ask his system password in the app, while its against the apple guide line. So I removed that feild. Now I dont have any idea how to run script after System password authentication. To show user authentication I am using this.

    let context = LAContext()

    func authorize() {
        var error: NSError? = nil // Create optional error variable
        let reason = "This action requires your authentication for security purposes."

        let policy: LAPolicy = .deviceOwnerAuthentication // Adjust policy based on device capabilities (e.g., biometrics, passcode)
        context.evaluatePolicy(policy, localizedReason: reason, reply: { (success, error) in
            if success {
                // User was successfully authenticated
                **// Proceed with your script execution here, but the problem is I dont have user password here, and not found even from the Keychain, because before fetch password from keychain it should be store there**
                print("Success")
            } else {
                // Authentication failed or was canceled
                if let error = error {
                    // Handle the error appropriately
                    print(error.localizedDescription)
                }
            }
        })
}

I think, I should save the password in keychain for security and than access but, first I need to get the password without breaking the apple guide lines.

Thats the whole problem, I did not found related this thing.

Edited:

When I run my script in the success case, it shows the message in the Xcode below terminal.

sudo: a password is required
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper

Solution

  • I found the solution of my problem by myself. In my case let context = LAContext() is not working and no need for that.

    To run script with Admin no need to store password in keychain. When writing script at that time write script with this thing.

    let osacScript = "osascript -e 'do shell script \"\(script)\" with administrator privileges'"
    

    We can write sudo script (sudo script needs when we want to perform some actions like create folder or move folder) like this

    let osacScript = "osascript -e 'do shell script \"sudo \(script)\" with administrator privileges'"
    

    The code solved my problem is below:

    func installScript(scriptPath: String, installingDir: String) {
        let script = "\(scriptPath) --noprompt --dir \(installingDir) --key ABCKEY --region name-for-region --server https://myserver.com"
        DispatchQueue.global(qos: .background).async { // I am using SwiftUI so to avoid blocking the UI running script in the background.
            let osacScript = "osascript -e 'do shell script \"\(script)\" with administrator privileges'"
            print(osacScript)
            var arguments = [String]()
            arguments.append("-c")
            arguments .append(osacScript)
            let process = Process()
            process.launchPath = "/bin/bash" // Specify the shell to use
            process.arguments = arguments
            
            let pipe = Pipe()
            process.standardOutput = pipe
            process.standardError = pipe // Redirect standard error to the same pipe
    
            let fileHandle = pipe.fileHandleForReading
            process.launch()
            process.waitUntilExit()
            let data = fileHandle.readDataToEndOfFile()
            let logFilePath = Bundle.main.path(forResource: "logfile", ofType: "txt")! // This thing can lead to error if file does not exist, so make sure it should be optional.
            if let output = String(data: data, encoding: .utf8) {
                print("output:\(output)")
                // Write output to file
                do {
                    try output.write(toFile: logFilePath, atomically: true, encoding: .utf8)
                } catch {
                    print("Error writing to file: \(error)")
                }
            }
        }
    }