Skip to content

Commit

Permalink
ropg at login window initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
twocanoes committed Dec 27, 2023
1 parent f651bc3 commit 32ad7b3
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 415 deletions.
1 change: 1 addition & 0 deletions Shared/Tokens.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct Creds {

}


}


Expand Down
17 changes: 14 additions & 3 deletions XCreds/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
import Cocoa

@main
class AppDelegate: NSObject, NSApplicationDelegate, DSQueryable {
class AppDelegate: NSObject, NSApplicationDelegate, DSQueryable, TokenManagerFeedbackDelegate {
func tokenError(_ err: String) {

}

func credentialsUpdated(_ credentials: Creds) {

}


@IBOutlet weak var loginPasswordWindow: NSWindow!
@IBOutlet var window: NSWindow!
Expand All @@ -24,8 +32,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, DSQueryable {
TCSLogWithMark("Build \(build)")

}


let tm = TokenManager()
tm.feedbackDelegate=self
tm.oidc().requestTokenWithROPG(ropgUsername:"[email protected]", ropgPassword: "ChairBook1!")
//
//
DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenLocked(_:)), name:NSNotification.Name("com.apple.screenIsLocked") , object: nil)

DistributedNotificationCenter.default().addObserver(self, selector: #selector(screenUnlocked(_:)), name:NSNotification.Name("com.apple.screenIsUnlocked") , object: nil)
Expand Down
1 change: 0 additions & 1 deletion XCreds/SelectLocalAccountWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ class SelectLocalAccountWindowController: NSWindowController, NSWindowDelegate {
return .createNewAccount
}
}
return .error("unknown error")

}
override func windowDidLoad() {
Expand Down
273 changes: 240 additions & 33 deletions XCreds/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,36 @@ protocol TokenManagerFeedbackDelegate {
func credentialsUpdated(_ credentials:Creds)

}
class TokenManager:DSQueryable, OIDCLiteDelegate {
class TokenManager: OIDCLiteDelegate,DSQueryable {
func authFailure(message: String) {

}

struct UserAccountInfo {
var fullName:String?
var firstName:String?
var lastName:String?
var username:String?
var groups:Array<String>?
var alias:String?
}
enum ParseHintsResult:Error {
case error(String)
}
enum ProcessTokenResult:Error {
case success
case error(String)
}
enum CalculateUserAccountInfoResult {
case success(UserAccountInfo)
case error(String)
}

func tokenResponse(tokens: OIDCLite.TokenResponse) {

TCSLogWithMark("======== tokenResponse =========")

RunLoop.main.perform {
// if let password = self.password {
TCSLogWithMark("----- Password was set")
let xcredCreds = Creds(password: nil, tokens: tokens)
self.feedbackDelegate?.credentialsUpdated(xcredCreds)
//TODO: post this?
Expand Down Expand Up @@ -149,66 +167,255 @@ class TokenManager:DSQueryable, OIDCLiteDelegate {

func getNewAccessToken(completion:@escaping (_ res:OIDCLiteTokenResult)->Void) -> Void {
TCSLogWithMark()
guard let endpoint = tokenEndpoint(), let url = URL(string: endpoint) else {
TCSLogWithMark()
completion(.error("bad setup for endpoint"))
return
}


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)


TCSLogWithMark()
if
let keychainAccountAndPassword = keychainAccountAndPassword,
DefaultsOverride.standardOverride.bool(forKey: PrefKeys.shouldVerifyPasswordWithRopg.rawValue) == true,

let keychainPassword = keychainAccountAndPassword.1,
let ropgClientID = DefaultsOverride.standardOverride.string(forKey: PrefKeys.clientID.rawValue) {
let ropgClientSecret = DefaultsOverride.standardOverride.string(forKey: PrefKeys.clientSecret.rawValue)
let keychainPassword = keychainAccountAndPassword.1{
TCSLogWithMark("Checking credentials in keychain using ROPG")
let currentUser = PasswordUtils.getCurrentConsoleUserRecord()
guard let userNames = try? currentUser?.values(forAttribute: "dsAttrTypeNative:_xcreds_oidc_username") as? [String], userNames.count>0 else {
guard let userNames = try? currentUser?.values(forAttribute: "dsAttrTypeNative:_xcreds_oidc_username") as? [String], userNames.count>0, let username = userNames.first else {
completion(.error("no username for oidc config"))
return
}
oidc().requestTokenWithROPG(ropgClientID: ropgClientID, ropgClientSecret: ropgClientSecret, userName: userNames[0],keychainPassword: keychainPassword, url: url)


oidc().requestTokenWithROPG(ropgUsername: username, ropgPassword: keychainPassword)

}
else if let refreshAccountAndToken = refreshAccountAndToken, let refreshToken = refreshAccountAndToken.1 {

oidcLocal?.refreshTokens(refreshToken)
// 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) {
// parameters.append("&client_secret=\(clientSecret)")
// }
//
// let postData = parameters.data(using: .utf8)
// req.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
//
// 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()
}
else {
TCSLogWithMark("clientID or refreshToken blank. clientid: \(clientID ?? "empty") refreshtoken:\(refreshAccountAndToken?.1 ?? "empty")")
completion(.success)

}
}
func tokenInfo(fromCredentials credentials:Creds) throws -> Dictionary<String, Any>? {
//if we have tokens, that means that authentication was successful.


guard let idToken = credentials.idToken else {
TCSLogErrorWithMark("invalid idToken")
throw ProcessTokenResult.error("invalid idToken")
// mechanismDelegate.denyLogin(message:"The identity token is invalid")
// return
}

let array = idToken.components(separatedBy: ".")

if array.count != 3 {
TCSLogErrorWithMark("idToken is invalid")
throw ProcessTokenResult.error("The identity token is incorrect length.")
// mechanismDelegate.denyLogin(message:"The identity token is incorrect length.")
}
let body = array[1]
TCSLogWithMark("base64 encoded IDToken: \(body)");
guard let data = base64UrlDecode(value:body ) else {
TCSLogErrorWithMark("error decoding id token base64")
throw ProcessTokenResult.error("The identity token could not be decoded from base64.")

// mechanismDelegate.denyLogin(message:"The identity token could not be decoded from base64.")
// return
}
if let decodedTokenString = String(data: data, encoding: .utf8) {
TCSLogWithMark("IDToken:\(decodedTokenString)")

}
let decoder = JSONDecoder()
var idTokenObject:IDToken
do {
idTokenObject = try decoder.decode(IDToken.self, from: data)

}
catch {
TCSLogErrorWithMark("error decoding idtoken::")
TCSLogErrorWithMark("Token:\(body)")
throw ProcessTokenResult.error("The identity token could not be decoded from json")
//
// // mechanismDelegate.denyLogin(message:"The identity token could not be decoded from json.")
// // return
//
}

let idTokenInfo = jwtDecode(value: idToken) //dictionary for mapping
guard var idTokenInfo = idTokenInfo else {
throw ProcessTokenResult.error("No idTokenInfo found")

// mechanismDelegate.denyLogin(message:"No idTokenInfo found.")
// return
}

idTokenInfo["idToken"]=idTokenObject
return idTokenInfo
}
func findUserAndUpdatePassword(idTokenInfo:Dictionary<String, Any>,newPassword:String) -> SelectLocalAccountWindowController.VerifyLocalCredentialsResult?{

TCSLogWithMark()
guard let subValue = idTokenInfo["sub"] as? String, let issuerValue = idTokenInfo["iss"] as? String else {
return nil
}

TCSLogWithMark("getting users")
let standardUsers = try? getAllStandardUsers()
let existingUser = try? getUserRecord(sub: subValue, iss: issuerValue)
let shouldPromptForMigration = DefaultsOverride.standardOverride.bool(forKey: PrefKeys.shouldPromptForMigration.rawValue)

if shouldPromptForMigration == false {
TCSLogWithMark("not prompting for migration")

}
if let existingUser = existingUser, let odUsername = existingUser.recordName {
TCSLogWithMark("prior local user found. using.")

return .successful(odUsername)
}
else if let standardUsers = standardUsers, standardUsers.count>0, shouldPromptForMigration == true {

TCSLogWithMark("Preference set to prompt for migration and there are no standard users, so prompting")


return SelectLocalAccountWindowController.selectLocalAccountAndUpdate(newPassword: newPassword)
}
return .createNewAccount
}
func setupUserAccountInfo(idTokenInfo:Dictionary<String, Any>) -> CalculateUserAccountInfoResult {

TCSLogWithMark()
var userAccountInfo = UserAccountInfo()
guard let idTokenObject = idTokenInfo["idToken"] as? IDToken else {
return .error("invalid token object")

}
let defaultsUsername = DefaultsOverride.standardOverride.string(forKey: PrefKeys.username.rawValue)

// username static map
if let defaultsUsername = defaultsUsername {
userAccountInfo.username = defaultsUsername
}
else if let mapKey = DefaultsOverride.standardOverride.object(forKey: "map_username") as? String, mapKey.count>0, let mapValue = idTokenInfo[mapKey] as? String, let leftSide = mapValue.components(separatedBy: "@").first{

TCSLogWithMark()
userAccountInfo.username = leftSide.replacingOccurrences(of: " ", with: "_").stripped
TCSLogWithMark("mapped username found: \(mapValue) clean version:\(userAccountInfo.username ?? "nil")")
}
else {
TCSLogWithMark()
var emailString:String

if let email = idTokenObject.email {
emailString=email.lowercased()
}
else if let uniqueName=idTokenObject.unique_name {
emailString=uniqueName
}

else {
TCSLogWithMark("no username found. Using sub.")
emailString=idTokenObject.sub
}
guard let tUsername = emailString.components(separatedBy: "@").first?.lowercased() else {
TCSLogErrorWithMark("email address invalid")
// throw ProcessTokenResult.error("The email address from the identity token is invalid.")

return .error("The email address from the identity token is invalid")
// mechanismDelegate.denyLogin(message:"The email address from the identity token is invalid")
// return

}

TCSLogWithMark("username found: \(tUsername)")
userAccountInfo.username = tUsername
}

//full name
TCSLogWithMark("checking map_fullname")

if let mapKey = DefaultsOverride.standardOverride.object(forKey: "map_fullname") as? String, mapKey.count>0, let mapValue = idTokenInfo[mapKey] as? String {
//we have a mapping so use that.
TCSLogWithMark("full name mapped to: \(mapKey)")
userAccountInfo.fullName = mapValue
// mechanismDelegate.setHint(type: .fullName, hint: "\(mapValue)")

}

else if let firstName = idTokenObject.given_name, let lastName = idTokenObject.family_name {
TCSLogWithMark("firstName: \(firstName)")
TCSLogWithMark("lastName: \(lastName)")
userAccountInfo.fullName = "\(firstName) \(lastName)"
// mechanismDelegate.setHint(type: .fullName, hint: "\(firstName) \(lastName)")

}


//first name
if let mapKey = DefaultsOverride.standardOverride.object(forKey: "map_firstname") as? String, mapKey.count>0, let mapValue = idTokenInfo[mapKey] as? String {
//we have a mapping for username, so use that.
TCSLogWithMark("first name mapped to: \(mapKey)")
userAccountInfo.firstName = mapValue
// mechanismDelegate.setHint(type: .firstName, hint:mapValue)
}

else if let given_name = idTokenObject.given_name {
TCSLogWithMark("firstName from token: \(given_name)")
userAccountInfo.firstName = given_name
// mechanismDelegate.setHint(type: .firstName, hint:firstName)

}
//last name
TCSLogWithMark("checking map_lastname")

if let mapKey = DefaultsOverride.standardOverride.object(forKey: "map_lastname") as? String, mapKey.count>0, let mapValue = idTokenInfo[mapKey] as? String {
//we have a mapping for lastName, so use that.
TCSLogWithMark("last name mapped to: \(mapKey)")
userAccountInfo.lastName = mapValue
// mechanismDelegate.setHint(type: .lastName, hint:mapValue)
}

else if let familyName = idTokenObject.family_name {
TCSLogWithMark("lastName from token: \(familyName)")
userAccountInfo.lastName = familyName
// mechanismDelegate.setHint(type: .lastName, hint:familyName)

}
//groups
if let mapValue = idTokenInfo["groups"] as? Array<String> {
TCSLogWithMark("setting groups: \(mapValue)")
userAccountInfo.groups = mapValue
// mechanismDelegate.setHint(type: .groups, hint:mapValue)
}
else {

TCSLogWithMark("No groups found")
}

let aliasClaim = DefaultsOverride.standardOverride.string(forKey: PrefKeys.aliasName.rawValue)
if let aliasClaim = aliasClaim, let aliasClaimValue = idTokenInfo[aliasClaim] as? String {
TCSLogWithMark("found alias claim: \(aliasClaim):\(aliasClaimValue)")

// mechanismDelegate.setHint(type: .aliasName, hint: aliasClaimValue)
userAccountInfo.alias = aliasClaimValue
}
else {
TCSLogWithMark("no alias claim: \(aliasClaim ?? "none")")
}


return .success(userAccountInfo)

}

}



2 changes: 1 addition & 1 deletion XCreds/XCredsMechanismProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ protocol XCredsMechanismProtocol {
func setContextStrings(_ contentStrings: [String : String])
func setContextString(type: String, value: String)
func setStickyContextString(type: String, value: String)

func setHint(type: HintType, hint: Any)
func reload()
func run()
func setupHints(fromCredentials credentials:Creds, password:String) -> Bool
}
Loading

0 comments on commit 32ad7b3

Please sign in to comment.