Skip to content

Commit 5395442

Browse files
authored
Merge branch 'master' into SDK-7271
2 parents e6ca6e5 + 09b7eca commit 5395442

13 files changed

+901
-68
lines changed

Auth0.xcodeproj/project.pbxproj

Lines changed: 48 additions & 4 deletions
Large diffs are not rendered by default.

Auth0/Auth0.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,13 @@ public func webAuth(clientId: String, domain: String, session: URLSession = .sha
219219
func plistValues(bundle: Bundle) -> (clientId: String, domain: String)? {
220220
guard let path = bundle.path(forResource: "Auth0", ofType: "plist"),
221221
let values = NSDictionary(contentsOfFile: path) as? [String: Any] else {
222-
print("Missing Auth0.plist file with 'ClientId' and 'Domain' entries in main bundle!")
222+
Auth0Log.error(.configuration, "Missing Auth0.plist file with 'ClientId' and 'Domain' entries in main bundle!")
223223
return nil
224224
}
225225

226226
guard let clientId = values["ClientId"] as? String, let domain = values["Domain"] as? String else {
227-
print("Auth0.plist file at \(path) is missing 'ClientId' and/or 'Domain' entries!")
228-
print("File currently has the following entries: \(values)")
227+
Auth0Log.error(.configuration, "Auth0.plist file at \(path) is missing 'ClientId' and/or 'Domain' entries!")
228+
Auth0Log.debug(.configuration, "File currently has the following entries: \(String(describing: values))")
229229
return nil
230230
}
231231
return (clientId: clientId, domain: domain)

Auth0/Auth0Log.swift

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import Foundation
2+
import os.log
3+
4+
// MARK: - Unified Logging System
5+
6+
/// Represents different categories of logs within the SDK.
7+
enum LogCategory: String {
8+
/// networkTracing category is used for the network request and response traces
9+
case networkTracing = "NetworkTracing"
10+
/// configuration category is used for SDK configuration related logs
11+
case configuration = "Configuration"
12+
}
13+
14+
/// Log levels supported by the unified logging system.
15+
enum LogLevel {
16+
/// Detailed information for debugging
17+
case debug
18+
/// General informational messages
19+
case info
20+
/// Warning messages for potentially problematic situations
21+
case warning
22+
/// Error messages for failures
23+
case error
24+
/// Critical system errors
25+
case fault
26+
}
27+
28+
/// Unified logging interface for the SDK.
29+
/// Provides a consistent, type-safe API for logging across all SDK components.
30+
enum Auth0Log {
31+
32+
/// Shared logging service instance (can be replaced for testing).
33+
static var loggingService: UnifiedLogging = OSUnifiedLoggingService()
34+
35+
/// Centralized subsystem identifier for all Auth0 logs.
36+
static var subsystem: String { OSUnifiedLoggingService.subsystem }
37+
38+
/// Log a message with the specified category and level.
39+
///
40+
/// Use this method for fine-grained control over logging. For common use cases,
41+
/// prefer the convenience methods: `debug()`, `info()`, `warning()`, `error()`, `fault()`.
42+
///
43+
/// - Parameters:
44+
/// - category: The log category for filtering and organization. See `LogCategory` for available categories.
45+
/// - level: The severity level of the log. Defaults to `.debug`. See `LogLevel` for available levels.
46+
/// - message: The message to log. Evaluated lazily via autoclosure for performance.
47+
static func log(
48+
_ category: LogCategory,
49+
level: LogLevel = .debug,
50+
message: String
51+
) {
52+
loggingService.log(category, level: level, message: message)
53+
}
54+
55+
/// Log a debug message.
56+
///
57+
/// Use for detailed information useful during development and debugging.
58+
///
59+
/// - Parameters:
60+
/// - category: The log category for filtering
61+
/// - message: The message to log
62+
static func debug(
63+
_ category: LogCategory,
64+
_ message: String
65+
) {
66+
log(category, level: .debug, message: message)
67+
}
68+
69+
/// Log an informational message.
70+
///
71+
/// Use for general informational messages about application state.
72+
///
73+
/// - Parameters:
74+
/// - category: The log category for filtering
75+
/// - message: The message to log
76+
static func info(
77+
_ category: LogCategory,
78+
_ message: String
79+
) {
80+
log(category, level: .info, message: message)
81+
}
82+
83+
/// Log a warning message.
84+
///
85+
/// Use for potentially problematic situations that aren't errors.
86+
///
87+
/// - Parameters:
88+
/// - category: The log category for filtering
89+
/// - message: The message to log
90+
static func warning(
91+
_ category: LogCategory,
92+
_ message: String
93+
) {
94+
log(category, level: .warning, message: message)
95+
}
96+
97+
/// Log an error message.
98+
///
99+
/// Use for error conditions and failures that need attention.
100+
///
101+
/// - Parameters:
102+
/// - category: The log category for filtering
103+
/// - message: The message to log
104+
static func error(
105+
_ category: LogCategory,
106+
_ message: String
107+
) {
108+
log(category, level: .error, message: message)
109+
}
110+
111+
/// Log a fault message for critical system errors.
112+
///
113+
/// Use for serious errors that may cause data loss or application instability.
114+
///
115+
/// - Parameters:
116+
/// - category: The log category for filtering
117+
/// - message: The message to log
118+
static func fault(
119+
_ category: LogCategory,
120+
_ message: @autoclosure () -> String
121+
) {
122+
log(category, level: .fault, message: message())
123+
}
124+
}

Auth0/CredentialsManager.swift

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public struct CredentialsManager {
3737
let noSession: TimeInterval = -1
3838
var lastBiometricAuthTime: TimeInterval = -1
3939
let lock = NSLock()
40-
40+
4141
init() {
4242
lastBiometricAuthTime = noSession
4343
}
@@ -163,9 +163,12 @@ public struct CredentialsManager {
163163
/// ```
164164
///
165165
/// - Parameter audience: Identifier of the API the stored API credentials are for.
166+
/// - Parameter scope: Optional scope for which the API Credentials are stored. If the credentials were initially fetched/stored with scope,
167+
/// it is recommended to pass scope also while clearing them.
166168
/// - Returns: If the API credentials were removed.
167-
public func clear(forAudience audience: String) -> Bool {
168-
return self.storage.deleteEntry(forKey: audience)
169+
public func clear(forAudience audience: String, scope: String? = nil) -> Bool {
170+
let key = getAPICredentialsStorageKey(audience: audience, scope: scope)
171+
return self.storage.deleteEntry(forKey: key)
169172
}
170173

171174
#if WEB_AUTH_PLATFORM
@@ -180,13 +183,13 @@ public struct CredentialsManager {
180183
/// - Returns: `true` if the session is valid and biometric authentication can be skipped, `false` otherwise.
181184
public func isBiometricSessionValid() -> Bool {
182185
guard let bioAuth = self.bioAuth else { return false }
183-
186+
184187
self.biometricSession.lock.lock()
185188
defer { self.biometricSession.lock.unlock() }
186-
189+
187190
let lastAuth = self.biometricSession.lastBiometricAuthTime
188191
if lastAuth == self.biometricSession.noSession { return false }
189-
192+
190193
switch bioAuth.policy {
191194
case .session(let timeoutInSeconds), .appLifecycle(let timeoutInSeconds):
192195
let timeoutInterval = TimeInterval(timeoutInSeconds)
@@ -694,24 +697,39 @@ public struct CredentialsManager {
694697
callback: callback)
695698
}
696699

697-
public func store(apiCredentials: APICredentials, forAudience audience: String) -> Bool {
700+
public func store(apiCredentials: APICredentials, forAudience audience: String, forScope scope: String? = nil) -> Bool {
698701
guard let data = try? apiCredentials.encode() else {
699702
return false
700703
}
701704

702-
return self.storage.setEntry(data, forKey: audience)
705+
let key = getAPICredentialsStorageKey(audience: audience, scope: scope)
706+
return self.storage.setEntry(data, forKey: key)
703707
}
704708

705709
private func retrieveCredentials() -> Credentials? {
706710
guard let data = self.storage.getEntry(forKey: self.storeKey) else { return nil }
707711
return try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: data)
708712
}
709713

710-
private func retrieveAPICredentials(audience: String) -> APICredentials? {
711-
guard let data = self.storage.getEntry(forKey: audience) else { return nil }
714+
private func retrieveAPICredentials(audience: String, scope: String?) -> APICredentials? {
715+
let key = getAPICredentialsStorageKey(audience: audience, scope: scope)
716+
guard let data = self.storage.getEntry(forKey: key) else { return nil }
712717
return try? APICredentials(from: data)
713718
}
714719

720+
private func getAPICredentialsStorageKey(audience: String, scope: String?) -> String {
721+
// Use audience if scope is null else use a combination of audience and scope
722+
if let scope = scope {
723+
let normalisedScopes = scope
724+
.split(separator: " ")
725+
.sorted()
726+
.joined(separator: "::")
727+
return "\(audience)::\(normalisedScopes)"
728+
} else {
729+
return audience
730+
}
731+
}
732+
715733
// swiftlint:disable:next function_parameter_count
716734
private func retrieveCredentials(scope: String?,
717735
minTTL: Int,
@@ -832,10 +850,10 @@ public struct CredentialsManager {
832850
dispatchGroup.enter()
833851

834852
DispatchQueue.global(qos: .userInitiated).async {
835-
if let apiCredentials = self.retrieveAPICredentials(audience: audience),
853+
if let apiCredentials = self.retrieveAPICredentials(audience: audience, scope: scope),
836854
!self.hasExpired(apiCredentials.expiresIn),
837855
!self.willExpire(apiCredentials.expiresIn, within: minTTL),
838-
!self.hasScopeChanged(from: apiCredentials.scope, to: scope) {
856+
!self.hasScopeChanged(from: apiCredentials.scope, to: scope, ignoreOpenid: scope?.contains("openid") == false) {
839857
dispatchGroup.leave()
840858
return callback(.success(apiCredentials))
841859
}
@@ -867,7 +885,7 @@ public struct CredentialsManager {
867885
} else if !self.store(credentials: newCredentials) {
868886
dispatchGroup.leave()
869887
callback(.failure(CredentialsManagerError(code: .storeFailed)))
870-
} else if !self.store(apiCredentials: newAPICredentials, forAudience: audience) {
888+
} else if !self.store(apiCredentials: newAPICredentials, forAudience: audience, forScope: scope) {
871889
dispatchGroup.leave()
872890
callback(.failure(CredentialsManagerError(code: .storeFailed)))
873891
} else {
@@ -893,14 +911,30 @@ public struct CredentialsManager {
893911
return expiresIn < Date()
894912
}
895913

896-
func hasScopeChanged(from lastScope: String?, to newScope: String?) -> Bool {
914+
func hasScopeChanged(from lastScope: String?, to newScope: String?, ignoreOpenid: Bool = false) -> Bool {
915+
897916
if let lastScope = lastScope, let newScope = newScope {
898-
let lastScopeList = lastScope.lowercased().split(separator: " ").sorted()
899-
let newScopeList = newScope.lowercased().split(separator: " ").sorted()
900917

901-
return lastScopeList != newScopeList
902-
}
918+
var storedScopes = Set(
919+
lastScope
920+
.split(separator: " ")
921+
.filter { !$0.isEmpty }
922+
.map { String($0).lowercased() }
923+
)
903924

925+
if ignoreOpenid {
926+
storedScopes.remove("openid")
927+
}
928+
929+
let requiredScopes = Set(
930+
newScope
931+
.split(separator: " ")
932+
.filter { !$0.isEmpty }
933+
.map { String($0).lowercased() }
934+
)
935+
936+
return storedScopes != requiredScopes
937+
}
904938
return false
905939
}
906940

Auth0/Logger.swift

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@ public protocol Logger {
1414

1515
}
1616

17+
private let networkTraceQueue = DispatchQueue(label: "com.auth0.networkTrace", qos: .utility)
18+
1719
protocol LoggerOutput {
1820
func log(message: String)
1921
func newLine()
2022
}
2123

2224
struct DefaultOutput: LoggerOutput {
23-
func log(message: String) { print(message) }
24-
func newLine() { print() }
25+
func log(message: String) {
26+
Auth0Log.debug(.networkTracing, message)
27+
}
28+
29+
func newLine() {
30+
Auth0Log.debug(.networkTracing, "")
31+
}
2532
}
2633

2734
struct DefaultLogger: Logger {
@@ -33,31 +40,37 @@ struct DefaultLogger: Logger {
3340
}
3441

3542
func trace(request: URLRequest, session: URLSession) {
36-
guard let method = request.httpMethod, let url = request.url?.absoluteString else { return }
37-
output.log(message: "\(method) \(url) HTTP/1.1")
38-
session.configuration.httpAdditionalHeaders?.forEach { key, value in output.log(message: "\(key): \(value)") }
39-
request.allHTTPHeaderFields?.forEach { key, value in output.log(message: "\(key): \(value)") }
40-
if let data = request.httpBody, let string = String(data: data, encoding: .utf8) {
43+
networkTraceQueue.async { [output] in
44+
guard let method = request.httpMethod, let url = request.url?.absoluteString else { return }
45+
output.log(message: "\(method) \(url) HTTP/1.1")
46+
session.configuration.httpAdditionalHeaders?.forEach { key, value in output.log(message: "\(key): \(value)") }
47+
request.allHTTPHeaderFields?.forEach { key, value in output.log(message: "\(key): \(value)") }
48+
if let data = request.httpBody, let string = String(data: data, encoding: .utf8) {
49+
output.newLine()
50+
output.log(message: string)
51+
}
4152
output.newLine()
42-
output.log(message: string)
4353
}
44-
output.newLine()
4554
}
4655

4756
func trace(response: URLResponse, data: Data?) {
48-
if let http = response as? HTTPURLResponse {
49-
output.log(message: "HTTP/1.1 \(http.statusCode)")
50-
http.allHeaderFields.forEach { key, value in output.log(message: "\(key): \(value)") }
51-
if let data = data, let string = String(data: data, encoding: .utf8) {
57+
networkTraceQueue.async { [output] in
58+
if let http = response as? HTTPURLResponse {
59+
output.log(message: "HTTP/1.1 \(http.statusCode)")
60+
http.allHeaderFields.forEach { key, value in output.log(message: "\(key): \(value)") }
61+
if let data = data, let string = SensitiveDataRedactor.redact(data) {
62+
output.newLine()
63+
output.log(message: "API Response: \(string)")
64+
}
5265
output.newLine()
53-
output.log(message: string)
5466
}
55-
output.newLine()
5667
}
5768
}
5869

5970
func trace(url: URL, source: String?) {
60-
output.log(message: "\(source ?? "URL"): \(url.absoluteString)")
71+
networkTraceQueue.async { [output] in
72+
output.log(message: "\(source ?? "URL"): \(url.absoluteString)")
73+
}
6174
}
6275

6376
}

0 commit comments

Comments
 (0)