Skip to content

Commit

Permalink
Merge pull request #1201 from Infomaniak/logToken
Browse files Browse the repository at this point in the history
fix: Emmit a Sentry event on Token auth error
  • Loading branch information
adrien-coye authored Jun 20, 2024
2 parents 8fcd378 + 8ea0b54 commit 3f66838
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 27 deletions.
83 changes: 60 additions & 23 deletions kDriveCore/Data/Api/DriveApiFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -476,29 +476,43 @@ class SyncedAuthenticator: OAuthAuthenticator {
@LazyInjectService var appContextService: AppContextServiceable
@LazyInjectService var keychainHelper: KeychainHelper

func handleFailedRefreshingToken(oldToken: ApiToken, error: Error?) -> Result<OAuthAuthenticator.Credential, Error> {
func handleFailedRefreshingToken(oldToken: ApiToken,
newToken: ApiToken?,
error: Error?) -> Result<OAuthAuthenticator.Credential, Error> {
guard let error else {
// Couldn't refresh the token, keep the old token and fetch it later. Maybe because of bad network ?
SentrySDK
.addBreadcrumb(oldToken.generateBreadcrumb(level: .error,
message: "Refreshing token failed - Other \(error.debugDescription)"))
Log.tokenAuthentication(
"Refreshing token failed - Other \(error.debugDescription)",
oldToken: oldToken,
newToken: newToken,
level: AbstractLogLevel.error
)

return .failure(DriveError.unknownError)
}

if case .noRefreshToken = (error as? InfomaniakLoginError) {
// Couldn't refresh the token because we don't have a refresh token
SentrySDK
.addBreadcrumb(oldToken.generateBreadcrumb(level: .error,
message: "Refreshing token failed - Cannot refresh infinite token"))
Log.tokenAuthentication(
"Refreshing token failed - Cannot refresh infinite token",
oldToken: oldToken,
newToken: newToken,
level: AbstractLogLevel.error
)

refreshTokenDelegate?.didFailRefreshToken(oldToken)
return .failure(error)
}

if (error as NSError).domain == "invalid_grant" {
// Couldn't refresh the token, API says it's invalid
SentrySDK
.addBreadcrumb(oldToken.generateBreadcrumb(level: .error,
message: "Refreshing token failed - Invalid grant"))
Log.tokenAuthentication(
"Refreshing token failed - Invalid grant",
oldToken: oldToken,
newToken: newToken,
level: AbstractLogLevel.error
)

refreshTokenDelegate?.didFailRefreshToken(oldToken)
return .failure(error)
}
Expand All @@ -514,32 +528,51 @@ class SyncedAuthenticator: OAuthAuthenticator {
) {
// Only resolve locally to break init loop
accountManager.refreshTokenLockedQueue.async {
let message = "Refreshing token - Starting"
let metadata = (credential as ApiToken).breadcrumbMetadata()
SentryDebug.addBreadcrumb(message: message, category: .apiToken, level: .info, metadata: metadata)
let storedToken = self.accountManager.getTokenForUserId(credential.userId)

Log.tokenAuthentication(
"Refreshing token - Starting",
oldToken: storedToken,
newToken: credential,
level: AbstractLogLevel.info
)

if !self.keychainHelper.isKeychainAccessible {
let message = "Refreshing token failed - Keychain unaccessible"
SentryDebug.addBreadcrumb(message: message, category: .apiToken, level: .error, metadata: metadata)
Log.tokenAuthentication(
"Refreshing token failed - Keychain unaccessible",
oldToken: storedToken,
newToken: credential,
level: AbstractLogLevel.error
)

completion(.failure(DriveError.refreshToken))
return
}

if let storedToken = self.accountManager.getTokenForUserId(credential.userId) {
if let storedToken {
// Someone else refreshed our token and we already have an infinite token
if storedToken.expirationDate == nil && credential.expirationDate != nil {
let message = "Refreshing token - Success with local (infinite)"
SentryDebug.addBreadcrumb(message: message, category: .apiToken, level: .info, metadata: metadata)
Log.tokenAuthentication(
"Refreshing token failed - Keychain unaccessible",
oldToken: storedToken,
newToken: credential,
level: AbstractLogLevel.info
)

completion(.success(storedToken))
return
}
// Someone else refreshed our token and we don't have an infinite token
if let storedTokenExpirationDate = storedToken.expirationDate,
let tokenExpirationDate = credential.expirationDate,
tokenExpirationDate > storedTokenExpirationDate {
let message = "Refreshing token - Success with local"
SentryDebug.addBreadcrumb(message: message, category: .apiToken, level: .info, metadata: metadata)
Log.tokenAuthentication(
"Refreshing token - Success with local",
oldToken: storedToken,
newToken: credential,
level: AbstractLogLevel.info
)

completion(.success(storedToken))
return
}
Expand All @@ -551,13 +584,17 @@ class SyncedAuthenticator: OAuthAuthenticator {
self.tokenable.refreshToken(token: credential) { token, error in
// New token has been fetched correctly
if let token {
let message = "Refreshing token - Success with remote"
SentryDebug.addBreadcrumb(message: message, category: .apiToken, level: .info, metadata: metadata)
Log.tokenAuthentication(
"Refreshing token - Success with remote",
oldToken: credential,
newToken: token,
level: AbstractLogLevel.info
)

self.refreshTokenDelegate?.didUpdateToken(newToken: token, oldToken: credential)
completion(.success(token))
} else {
completion(self.handleFailedRefreshingToken(oldToken: credential, error: error))
completion(self.handleFailedRefreshingToken(oldToken: credential, newToken: token, error: error))
}
expiringActivity.endAll()
}
Expand Down
52 changes: 52 additions & 0 deletions kDriveCore/Utils/AbstractLog+Category.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import Foundation
import InfomaniakLogin

/// Name spacing log methods by `category`.
public enum Log {
Expand Down Expand Up @@ -208,6 +209,57 @@ public enum Log {
tag: tag)
}

public static func tokenAuthentication(_ message: @autoclosure () -> Any,
oldToken: ApiToken?,
newToken: ApiToken?,
level: AbstractLogLevel = .debug,
file: StaticString = #file,
function: StaticString = #function,
line: UInt = #line,
tag: Any? = nil) {
let category = "SyncedAuthenticator"
let messageAny = message()
guard let messageString = messageAny as? String else {
assertionFailure("This should always cast to a String")
return
}

let oldTokenMetadata: Any = oldToken?.metadata ?? "NULL"
let newTokenMetadata: Any = newToken?.metadata ?? "NULL"
var metadata = [String: Any]()
metadata["oldToken"] = oldTokenMetadata
metadata["newToken"] = newTokenMetadata

SentryDebug.capture(
message: messageString,
context: metadata,
level: level.sentry,
extras: ["file": "\(file)", "function": "\(function)", "line": "\(line)"]
)

SentryDebug.addBreadcrumb(message: messageString, category: .DriveInfosManager, level: level.sentry, metadata: metadata)

ABLog(messageAny,
category: category,
level: level,
file: file,
function: function,
line: line,
tag: tag)

// log token if error state
if level == .error || level == .fault {
let tokenMessage = "old token:\(oldTokenMetadata) \nnew token:\(newTokenMetadata)"
ABLog(tokenMessage,
category: category,
level: level,
file: file,
function: function,
line: line,
tag: tag)
}
}

private static func defaultLogHandler(_ message: @autoclosure () -> Any,
category: String,
level: AbstractLogLevel,
Expand Down
17 changes: 17 additions & 0 deletions kDriveCore/Utils/AbstractLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import CocoaLumberjackSwift
import Foundation
import InfomaniakDI
import os.log
import Sentry

/// A representation of sandard log levels
public enum AbstractLogLevel {
Expand Down Expand Up @@ -49,6 +50,22 @@ public enum AbstractLogLevel {
return .fault
}
}

// Bridge to sentry
var sentry: SentryLevel {
switch self {
case .warning, .notice, .emergency, .alert, .critical:
return .warning
case .error:
return .error
case .info:
return .info
case .debug:
return .debug
case .fault:
return .error
}
}
}

private let categoryKey = "category"
Expand Down
16 changes: 16 additions & 0 deletions kDriveCore/Utils/Sentry/ApiToken+Sentry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import Foundation
import InfomaniakCore
import InfomaniakLogin

// TODO: Remove
public extension ApiToken {
func breadcrumbMetadata(keychainError: OSStatus = noErr) -> [String: Any] {
return ["User id": userId,
Expand All @@ -29,3 +30,18 @@ public extension ApiToken {
"Keychain error code": keychainError]
}
}

public extension ApiToken {
var isInfinite: Bool {
expirationDate == nil
}

var metadata: [String: Any] {
[
"User id": userId,
"Expiration date": expirationDate?.timeIntervalSince1970 ?? "infinite",
"Access Token": truncatedAccessToken,
"Refresh Token": truncatedRefreshToken,
]
}
}
10 changes: 6 additions & 4 deletions kDriveCore/Utils/Sentry/SentryDebug.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@ public enum SentryDebug {
public static func captureNoWindow() {
capture(message: "Trying to call show with no window")
}
}

// MARK: - SHARED -
// MARK: - SHARED -

public static func addBreadcrumb(
public extension SentryDebug {
static func addBreadcrumb(
message: String,
category: SentryDebug.Category,
level: SentryLevel,
Expand All @@ -116,7 +118,7 @@ public enum SentryDebug {
}
}

public static func capture(
static func capture(
error: Error,
context: [String: Any]? = nil,
contextKey: String? = nil,
Expand All @@ -135,7 +137,7 @@ public enum SentryDebug {
}
}

public static func capture(
static func capture(
message: String,
context: [String: Any]? = nil,
contextKey: String? = nil,
Expand Down

0 comments on commit 3f66838

Please sign in to comment.