Skip to content

Commit

Permalink
added key for ROPG at login window
Browse files Browse the repository at this point in the history
  • Loading branch information
twocanoes committed Dec 24, 2023
1 parent c054c66 commit 716934b
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 90 deletions.
26 changes: 20 additions & 6 deletions 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 (6097) OAuth Settings</string>
<string>XCreds 4.0 (6100) 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 Expand Up @@ -462,7 +462,7 @@ Note that Google does not support the offline_access scope so instead use the pr
<key>pfm_default</key>
<false/>
<key>pfm_description</key>
<string>Favor using XCreds' local login screen over the cloud login UI.</string>
<string>Favor using XCreds&apos; local login screen over the cloud login UI.</string>
<key>pfm_documentation_url</key>
<string>https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences</string>
<key>pfm_name</key>
Expand All @@ -472,6 +472,20 @@ Note that Google does not support the offline_access scope so instead use the pr
<key>pfm_type</key>
<string>boolean</string>
</dict>
<dict>
<key>pfm_default</key>
<false/>
<key>pfm_description</key>
<string>When verifying password in the login window, use ROPG.</string>
<key>pfm_documentation_url</key>
<string>https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences</string>
<key>pfm_name</key>
<string>shouldUseROPGForOIDCLogin</string>
<key>pfm_title</key>
<string>Use ROPG when logging in at login window</string>
<key>pfm_type</key>
<string>boolean</string>
</dict>
<dict>
<key>pfm_default</key>
<false/>
Expand Down Expand Up @@ -500,7 +514,7 @@ Note that Google does not support the offline_access scope so instead use the pr
</dict>
<dict>
<key>pfm_description</key>
<string>Name of OIDC claim that contains an alias to add to a user account. Usually this is the "upn" (eg [email protected]) so the user can log in at the standard login window the same as the IdP login window. Adds the value to record name of the user account as an alias.</string>
<string>Name of OIDC claim that contains an alias to add to a user account. Usually this is the &quot;upn&quot; (eg [email protected]) so the user can log in at the standard login window the same as the IdP login window. Adds the value to record name of the user account as an alias.</string>
<key>pfm_documentation_url</key>
<string>https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences</string>
<key>pfm_name</key>
Expand Down Expand Up @@ -758,7 +772,7 @@ Note that Google does not support the offline_access scope so instead use the pr
<key>pfm_default</key>
<false/>
<key>pfm_description</key>
<string>Reset the keychain without prompting if the login password doesn't match the local password.</string>
<string>Reset the keychain without prompting if the login password doesn&apos;t match the local password.</string>
<key>pfm_documentation_url</key>
<string>https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences</string>
<key>pfm_name</key>
Expand Down Expand Up @@ -867,9 +881,9 @@ Note that Google does not support the offline_access scope so instead use the pr
</dict>
<dict>
<key>pfm_description</key>
<string>Password element id of the html element that has the password. It is read by using JavaScript to get the value (for example, for Azure, the JavaScript document.getElementById('i0118').value is sent. If this default is not set, standard values for Azure and Google Cloud will be used. To find out this value, use a browser to inspect the source of the page that has the password on it. Find the id of the textfield that has the password. Fill in the password and then open the JavaScript console. Run:
<string>Password element id of the html element that has the password. It is read by using JavaScript to get the value (for example, for Azure, the JavaScript document.getElementById(&apos;i0118&apos;).value is sent. If this default is not set, standard values for Azure and Google Cloud will be used. To find out this value, use a browser to inspect the source of the page that has the password on it. Find the id of the textfield that has the password. Fill in the password and then open the JavaScript console. Run:
document.getElementById('passwordID').value
document.getElementById(&apos;passwordID&apos;).value
changing “passwordID” to the correct element ID. If the value you typed into the textfield is returned, this is the correct ID.</string>
<key>pfm_documentation_url</key>
Expand Down
2 changes: 0 additions & 2 deletions UNIXUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,10 @@ public func cliTask(_ command: String,
let error = myErrorPipe.fileHandleForReading.readDataToEndOfFile()
let outputError = NSString(data: error, encoding: String.Encoding.utf8.rawValue)! as String
let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
myLogger.logit(.info, message:"CliTask completed, calling completion. Time: \(Date())")
completion(output + outputError)
}

myTask.launch()
myLogger.logit(.info, message:"Launching CliTask. Time: \(Date())")
}

/// A simple wrapper around NSTask that also doesn't wait for the `Task` termination signal.
Expand Down
8 changes: 3 additions & 5 deletions XCreds Login Overlay/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
import Cocoa
import AppKit




@main
class App {
static func main() {
Expand Down Expand Up @@ -43,8 +40,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
try? "".write(toFile: "/tmp/xcreds_return", atomically: false, encoding: .utf8)
// let _ = AuthorizationDBManager.shared.replace(right:"loginwindow:login", withNewRight: "XCredsLoginPlugin:LoginWindow")
let _ = AuthRightsHelper.addRights()
//let _ = cliTask("/usr/bin/killall loginwindow")
#warning("fix")
// let _ = cliTask("/usr/bin/killall loginwindow")

cliTask("/usr/bin/killall", arguments: ["loginwindow"], completion: {_ in })


}
Expand Down
1 change: 1 addition & 0 deletions XCreds/PrefKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ enum PrefKeys: String {
case ropgClientID
case ropgClientSecret
case shouldVerifyPasswordWithRopg
case shouldUseROPGForOIDCLogin
case actionItemOnly = "ActionItemOnly"
case aDDomain = "ADDomain"
case aDSite = "ADSite"
Expand Down
8 changes: 4 additions & 4 deletions XCreds/ScheduleManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ class ScheduleManager {
setNextCheckTime()
TCSLogWithMark("Checking token now (\(Date())). Next token check will be at \(nextCheckTime)")

TokenManager.shared.getNewAccessToken(completion: { isSuccessful, hadConnectionError in
TokenManager.shared.getNewAccessToken { res in

if hadConnectionError==true {
if res.hadConnectionError==true {
if DefaultsOverride.standardOverride.bool(forKey: PrefKeys.showDebug.rawValue) == true {

NotifyManager.shared.sendMessage(message: "Could not check token.")
}

return
}
else if isSuccessful == true {
else if res.hadError == true {

if DefaultsOverride.standardOverride.bool(forKey: PrefKeys.showDebug.rawValue) == true {
NotifyManager.shared.sendMessage(message: "Password unchanged")
Expand All @@ -98,7 +98,7 @@ class ScheduleManager {
}

}
})
}

}

Expand Down
150 changes: 84 additions & 66 deletions XCreds/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,78 @@ class TokenManager {
}
return true
}
func requestTokenWithROPG(ropgClientID:String,ropgClientSecret:String?, userName:String, keychainPassword: String, url:URL, completion:@escaping(_ res:TokenResult)->Void) {
var req = URLRequest(url: url)
var loginString = "\(ropgClientID)"

if let ropgClientSecret = ropgClientSecret {
loginString += ":\(ropgClientSecret)"
}


guard let loginData = loginString.data(using: .utf8) else {
completion(TokenResult(hadError: true, hadConnectionError: false))
return
}
let base64LoginString = loginData.base64EncodedString()
let parameters = "grant_type=password&username=\(userName)&password=\(keychainPassword)&scope=offline_access"

let postData = parameters.data(using: .utf8)
req.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")


req.httpMethod = "POST"
req.httpBody = postData

let task = URLSession.shared.dataTask(with: req) { data, response, error in
self.handleIdpResponse(data: data, response: response, error: error, keychainPassword: keychainPassword, completion: completion)
}

task.resume()

}
func handleIdpResponse(data: Data?, response: URLResponse?, error: Error?, keychainPassword: String, completion:@escaping(_ res:TokenResult)->Void) {
guard let data = data else {
print(String(describing: error))
if let error = error {
print(error.localizedDescription)
}
completion(TokenResult(hadError: false, hadConnectionError: true))
return
}
if let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
let decoder = JSONDecoder()
do {

let json = try decoder.decode(RefreshTokenResponse.self, from: data)
let expirationDate = Date().addingTimeInterval(TimeInterval(json.expiresIn))
DefaultsOverride.standardOverride.set(expirationDate, forKey: PrefKeys.expirationDate.rawValue)

let keychainUtil = KeychainUtil()
let _ = keychainUtil.updatePassword(serviceName: "xcreds",accountName:PrefKeys.refreshToken.rawValue, pass: json.refreshToken, shouldUpdateACL: true, keychainPassword: keychainPassword)
let _ = keychainUtil.updatePassword(serviceName: "xcreds",accountName:PrefKeys.accessToken.rawValue, pass: json.accessToken, shouldUpdateACL:true, keychainPassword: keychainPassword)
TCSLogWithMark("Credentials are current.")
completion(TokenResult(hadError: true, hadConnectionError: false))

}
catch {
TCSLogWithMark("Credentials are current, but failed to decode response")
completion(TokenResult(hadError: true, hadConnectionError: false))
return
}

}
else {
TCSLogErrorWithMark("got status code of \(response.statusCode):\(response)")
completion(TokenResult(hadError: true, hadConnectionError: false))

}
}
}

func tokenEndpoint() -> String? {

let prefTokenEndpoint = DefaultsOverride.standardOverride.string(forKey: PrefKeys.tokenEndpoint.rawValue)
Expand All @@ -145,95 +217,41 @@ class TokenManager {
return nil
}

func getNewAccessToken(completion:@escaping (_ isSuccessful:Bool,_ hadConnectionError:Bool)->Void) -> Void {
struct TokenResult {
var hadError:Bool
var hadConnectionError:Bool
}
func getNewAccessToken(completion:@escaping (_ res:TokenResult)->Void) -> Void {
TCSLogWithMark()
guard let endpoint = TokenManager.shared.tokenEndpoint(), let url = URL(string: endpoint) else {
TCSLogWithMark()
completion(false,true)
completion(TokenResult(hadError: true, hadConnectionError: true))
return
}

var req = URLRequest(url: url)

let keychainUtil = KeychainUtil()
TCSLogWithMark()
let refreshAccountAndToken = try? keychainUtil.findPassword(serviceName: "xcreds ".appending(PrefKeys.refreshToken.rawValue),accountName:PrefKeys.refreshToken.rawValue)

let clientID = defaults.string(forKey: PrefKeys.clientID.rawValue)
let keychainAccountAndPassword = try? keychainUtil.findPassword(serviceName: "xcreds local password",accountName:PrefKeys.password.rawValue)

func handleIdpResponse(data: Data?, response: URLResponse?, error: Error?, keychainPassword: String) {
guard let data = data else {
print(String(describing: error))
if let error = error {
print(error.localizedDescription)
}
completion(false,true)
return
}
if let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
let decoder = JSONDecoder()
do {

let json = try decoder.decode(RefreshTokenResponse.self, from: data)
let expirationDate = Date().addingTimeInterval(TimeInterval(json.expiresIn))
DefaultsOverride.standardOverride.set(expirationDate, forKey: PrefKeys.expirationDate.rawValue)

let keychainUtil = KeychainUtil()
let _ = keychainUtil.updatePassword(serviceName: "xcreds",accountName:PrefKeys.refreshToken.rawValue, pass: json.refreshToken, shouldUpdateACL: true, keychainPassword: keychainPassword)
let _ = keychainUtil.updatePassword(serviceName: "xcreds",accountName:PrefKeys.accessToken.rawValue, pass: json.accessToken, shouldUpdateACL:true, keychainPassword: keychainPassword)
TCSLogWithMark("Credentials are current.")
completion(true,false)

}
catch {
TCSLogWithMark("Credentials are current, but failed to decode response")
completion(true,false)
return
}

}
else {
TCSLogErrorWithMark("got status code of \(response.statusCode):\(response)")
completion(false,false)

}
}
}

TCSLogWithMark()
if DefaultsOverride.standardOverride.bool(forKey: PrefKeys.shouldVerifyPasswordWithRopg.rawValue) == true, let keychainAccountAndPassword = keychainAccountAndPassword, let keychainPassword = keychainAccountAndPassword.1, let ropgClientSecret = DefaultsOverride.standardOverride.string(forKey: PrefKeys.ropgClientSecret.rawValue), let ropgClientID = DefaultsOverride.standardOverride.string(forKey: PrefKeys.ropgClientID.rawValue) {
TCSLogWithMark("Checking credentials in keychain using ROPG")
let currentUser = PasswordUtils.getCurrentConsoleUserRecord()
guard let userName = currentUser?.recordName else {
completion(false,true)
return
}
let loginString = "\(ropgClientID):\(ropgClientSecret)"
guard let loginData = loginString.data(using: .utf8) else {
completion(false,true)
completion(TokenResult(hadError: false, hadConnectionError: true))
return
}
let base64LoginString = loginData.base64EncodedString()
let parameters = "grant_type=password&username=\(userName)&password=\(keychainPassword)&scope=offline_access"
requestTokenWithROPG(ropgClientID: ropgClientID, ropgClientSecret: ropgClientSecret, userName: userName,keychainPassword: keychainPassword, url: url, completion: completion)

let postData = parameters.data(using: .utf8)
req.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
req.addValue("application/json", forHTTPHeaderField: "Accept")


req.httpMethod = "POST"
req.httpBody = postData

let task = URLSession.shared.dataTask(with: req) { data, response, error in
handleIdpResponse(data: data, response: response, error: error, keychainPassword: keychainPassword)
}

task.resume()
}
else if let refreshAccountAndToken = refreshAccountAndToken, let refreshToken = refreshAccountAndToken.1, let clientID = clientID, let keychainAccountAndPassword = keychainAccountAndPassword, let keychainPassword = keychainAccountAndPassword.1 {
var req = URLRequest(url: url)

TCSLogWithMark("Checking credentials in keychain using refresh token")
var parameters = "grant_type=refresh_token&refresh_token=\(refreshToken)&client_id=\(clientID )"
if let clientSecret = defaults.string(forKey: PrefKeys.clientSecret.rawValue) {
Expand All @@ -247,14 +265,14 @@ class TokenManager {
req.httpBody = postData

let task = URLSession.shared.dataTask(with: req) { data, response, error in
handleIdpResponse(data: data, response: response, error: error, keychainPassword: keychainPassword)
self.handleIdpResponse(data: data, response: response, error: error, keychainPassword: keychainPassword, completion: completion)
}

task.resume()
}
else {
TCSLogWithMark("clientID or refreshToken blank. clientid: \(clientID ?? "empty") refreshtoken:\(refreshAccountAndToken?.1 ?? "empty")")
completion(false,false)
completion(TokenResult(hadError: false, hadConnectionError: false))

}
}
Expand Down
2 changes: 2 additions & 0 deletions XCreds/defaults.plist
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
<true/>
<key>shouldDetectNetworkToDetermineLoginWindow</key>
<false/>
<key>shouldUseROPGForOIDCLogin</key>
<false/>
<key>passwordPlaceholder</key>
<string>Password</string>
<key>shouldPromptForMigration</key>
Expand Down
Loading

0 comments on commit 716934b

Please sign in to comment.