Skip to content

Commit 3014514

Browse files
committed
Add support for debug logging
1 parent 1fd0584 commit 3014514

13 files changed

+168
-44
lines changed

Sources/OpenPass/DeviceAuthorizationFlow.swift

+13-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
//
2626

2727
import Foundation
28+
import OSLog
2829

2930
/// A client for Device Authorization, a two-step flow where an input constrained device such as a TV requests a code
3031
/// and another device inputs this code and provides authorization.
@@ -63,17 +64,22 @@ public final class DeviceAuthorizationFlow {
6364
private let tokenValidator: IDTokenValidation
6465
private let tokensObserver: ((OpenPassTokens) async -> Void)
6566
private let dateGenerator: DateGenerator
67+
private let log: OSLog
6668
private let clock: Clock
6769

6870
internal init(
6971
openPassClient: OpenPassClient,
7072
tokenValidator: IDTokenValidation,
73+
isLoggingEnabled: Bool,
7174
dateGenerator: DateGenerator = .init { Date() },
7275
clock: Clock = RealClock(),
7376
tokensObserver: @escaping ((OpenPassTokens) async -> Void)
7477
) {
7578
self.openPassClient = openPassClient
7679
self.tokenValidator = tokenValidator
80+
self.log = isLoggingEnabled
81+
? .init(subsystem: "com.myopenpass", category: "DeviceAuthorizationFlow")
82+
: .disabled
7783
self.dateGenerator = dateGenerator
7884
self.clock = clock
7985
self.tokensObserver = tokensObserver
@@ -165,8 +171,13 @@ public final class DeviceAuthorizationFlow {
165171
/// - Parameter idToken: ID Token To Verify
166172
/// - Returns: true if valid, false if invalid
167173
private func verify(_ idToken: IDToken) async throws -> Bool {
168-
let jwks = try await openPassClient.fetchJWKS()
169-
return try tokenValidator.validate(idToken, jwks: jwks)
174+
do {
175+
let jwks = try await openPassClient.fetchJWKS()
176+
return try tokenValidator.validate(idToken, jwks: jwks)
177+
} catch {
178+
os_log("Error verifying tokens from flow", log: log, type: .error)
179+
throw error
180+
}
170181
}
171182
}
172183

Sources/OpenPass/OpenPassClient.swift

+60-10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import CryptoKit
2828
import Foundation
29+
import OSLog
2930

3031
/// Networking layer for OpenPass API Server
3132
@available(iOS 13.0, tvOS 16.0, *)
@@ -40,25 +41,31 @@ internal final class OpenPassClient {
4041

4142
let clientId: String
4243
private let session = URLSession.shared
44+
private let log: OSLog
4345

4446
convenience init(configuration: OpenPassConfiguration) {
4547
self.init(
4648
baseURL: configuration.baseURL,
4749
sdkName: configuration.sdkName,
4850
sdkVersion: configuration.sdkVersion,
49-
clientId: configuration.clientId
51+
clientId: configuration.clientId,
52+
isLoggingEnabled: configuration.isLoggingEnabled
5053
)
5154
}
5255

5356
init(
5457
baseURL: String,
5558
sdkName: String,
5659
sdkVersion: String = openPassSdkVersion,
57-
clientId: String
60+
clientId: String,
61+
isLoggingEnabled: Bool
5862
) {
5963
self.baseURL = baseURL
6064
self.baseRequestParameters = BaseRequestParameters(sdkName: sdkName, sdkVersion: sdkVersion)
6165
self.clientId = clientId
66+
self.log = isLoggingEnabled
67+
? .init(subsystem: "com.myopenpass", category: "OpenPassClient")
68+
: .disabled
6269
}
6370

6471
// MARK: - Tokens
@@ -75,25 +82,29 @@ internal final class OpenPassClient {
7582
codeVerifier: String,
7683
redirectUri: String
7784
) async throws -> OpenPassTokensResponse {
85+
let logTag: StaticString = "Updating Tokens"
86+
os_log(logTag, log: log, type: .debug)
7887
let request = Request.authorizationCode(
7988
clientId: clientId,
8089
code: code,
8190
codeVerifier: codeVerifier,
8291
redirectUri: redirectUri
8392
)
84-
return try await execute(request)
93+
return try await execute(request, String(logTag))
8594
}
8695

8796
/// Refresh tokens using an existing `refreshToken`
8897
/// - Parameters:
8998
/// - refreshToken: A refresh token
9099
/// - Returns: Refreshed ``OpenPassTokensResponse``
91100
func refreshTokens(_ refreshToken: String) async throws -> OpenPassTokensResponse {
101+
let logTag: StaticString = "Refreshing token"
102+
os_log(logTag, log: log, type: .debug)
92103
let request = Request.refresh(
93104
clientId: clientId,
94105
refreshToken: refreshToken
95106
)
96-
return try await execute(request)
107+
return try await execute(request, String(logTag))
97108
}
98109

99110
// MARK: - Device Authorization Flow
@@ -102,8 +113,10 @@ internal final class OpenPassClient {
102113
/// `/v1/api/authorize-device`
103114
/// - Returns: ``DeviceAuthorizationResponse`` transfer object
104115
func getDeviceCode() async throws -> DeviceAuthorizationResponse {
116+
let logTag: StaticString = "Fetching device code"
117+
os_log(logTag, log: log, type: .debug)
105118
let request = Request.authorizeDevice(clientId: clientId)
106-
return try await execute(request)
119+
return try await execute(request, String(logTag))
107120
}
108121

109122
/// Get Device Token from Endpoint
@@ -112,14 +125,16 @@ internal final class OpenPassClient {
112125
/// - deviceCode: Device Code retrieved from `/v1/api/authorize-device`
113126
/// - Returns: ``OpenPassTokens`` or an error if the request was not successful.
114127
func getTokenFromDeviceCode(deviceCode: String) async throws -> OpenPassTokensResponse {
128+
let logTag: StaticString = "Fetching token from device code"
129+
os_log(logTag, log: log, type: .debug)
115130
let request = Request.deviceToken(clientId: clientId, deviceCode: deviceCode)
116-
return try await execute(request)
131+
return try await execute(request, String(logTag))
117132
}
118133

119134
// MARK: - JWKS
120135

121136
func fetchJWKS() async throws -> JWKS {
122-
try await execute(Request<JWKS>(path: "/.well-known/jwks"))
137+
try await execute(Request<JWKS>(path: "/.well-known/jwks"), "Fetching JWKS")
123138
}
124139

125140
// MARK: - Request Execution
@@ -152,14 +167,40 @@ internal final class OpenPassClient {
152167
return Data(query.utf8)
153168
}
154169

155-
private func execute<ResponseType: Decodable>(_ request: Request<ResponseType>) async throws -> ResponseType {
170+
private func execute<ResponseType: Decodable>(
171+
_ request: Request<ResponseType>,
172+
_ logTag: String
173+
) async throws -> ResponseType {
156174
let urlRequest = urlRequest(
157175
request
158176
)
159-
let data = try await session.data(for: urlRequest).0
177+
let data: Data
178+
let response: HTTPURLResponse
179+
do {
180+
(data, response) = try await self.data(for: urlRequest)
181+
} catch {
182+
os_log("Client request error %@", log: log, type: .error, logTag)
183+
throw error
184+
}
185+
if response.statusCode != 200 {
186+
os_log("Client request error (%d) %@", log: log, type: .error, response.statusCode, logTag)
187+
}
160188
let decoder = JSONDecoder()
161189
decoder.keyDecodingStrategy = .convertFromSnakeCase
162-
return try decoder.decode(ResponseType.self, from: data)
190+
do {
191+
return try decoder.decode(ResponseType.self, from: data)
192+
} catch {
193+
os_log("Error parsing response %@", log: log, type: .error, logTag)
194+
throw error
195+
}
196+
}
197+
198+
private func data(for request: URLRequest) async throws -> (Data, HTTPURLResponse) {
199+
let (data, response) = try await session.data(for: request)
200+
// `URLResponse` is always `HTTPURLResponse` for HTTP requests
201+
// https://developer.apple.com/documentation/foundation/urlresponse
202+
// swiftlint:disable:next force_cast
203+
return (data, response as! HTTPURLResponse)
163204
}
164205
}
165206

@@ -205,3 +246,12 @@ private func generateCodeChallengeFromVerifierCode(verifier: String) -> String {
205246

206247
return base64UrlEncodedHashed
207248
}
249+
250+
private extension String {
251+
init(_ string: StaticString) {
252+
self = string.withUTF8Buffer {
253+
// swiftlint:disable:next optional_data_string_conversion
254+
String(decoding: $0, as: UTF8.self)
255+
}
256+
}
257+
}

Sources/OpenPass/OpenPassConfiguration.swift

+4
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ struct OpenPassConfiguration: Hashable, Sendable {
4343
baseURL: String = defaultBaseURL,
4444
clientId: String,
4545
redirectHost: String,
46+
isLoggingEnabled: Bool,
4647
sdkNameSuffix: String = "",
4748
sdkVersion: String = openPassSdkVersion
4849
) {
4950
self.baseURL = baseURL
5051
self.clientId = clientId
5152
self.redirectHost = redirectHost
53+
self.isLoggingEnabled = isLoggingEnabled
5254
self.sdkName = Self.defaultSdkName.appending(sdkNameSuffix)
5355
self.sdkVersion = sdkVersion
5456
}
@@ -83,6 +85,7 @@ struct OpenPassConfiguration: Hashable, Sendable {
8385
""
8486
}
8587
}(),
88+
isLoggingEnabled: OpenPassSettings.shared.isLoggingEnabled,
8689
sdkNameSuffix: OpenPassSettings.shared.sdkNameSuffix ?? ""
8790
)
8891
}
@@ -92,4 +95,5 @@ struct OpenPassConfiguration: Hashable, Sendable {
9295
var redirectHost: String
9396
var sdkName: String
9497
var sdkVersion: String
98+
var isLoggingEnabled: Bool
9599
}

Sources/OpenPass/OpenPassManager.swift

+17-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import AuthenticationServices
2828
import Foundation
29+
import OSLog
2930
import Security
3031

3132
/// Primary app interface for integrating with OpenPass SDK.
@@ -66,6 +67,9 @@ public final class OpenPassManager {
6667

6768
private let tokenValidator: IDTokenValidation
6869

70+
private let isLoggingEnabled: Bool
71+
private let log: OSLog
72+
6973
/// Internal dependency
7074
private let clock: Clock
7175

@@ -84,6 +88,7 @@ public final class OpenPassManager {
8488
assert(!configuration.redirectHost.isEmpty, "Missing `OpenPassRedirectHost` in Info.plist")
8589
self.clientId = configuration.clientId
8690
self.redirectHost = configuration.redirectHost
91+
self.isLoggingEnabled = configuration.isLoggingEnabled
8792

8893
self.openPassClient = OpenPassClient(
8994
configuration: configuration
@@ -93,6 +98,9 @@ public final class OpenPassManager {
9398
clientID: clientId,
9499
issuerID: configuration.baseURL.trimmingTrailing("/")
95100
)
101+
self.log = isLoggingEnabled
102+
? OSLog(subsystem: "com.myopenpass", category: "OpenPassManager")
103+
: .disabled
96104
self.clock = clock
97105
// Check for cached signin
98106
self.openPassTokens = KeychainManager.main.getOpenPassTokensFromKeychain()
@@ -101,6 +109,8 @@ public final class OpenPassManager {
101109
/// Signs user out by clearing all sign-in data currently in SDK. This includes keychain and in-memory data.
102110
/// - Returns: True if signed out, False if still signed in.
103111
public func signOut() -> Bool {
112+
os_log("Signing Out", log: log, type: .debug)
113+
os_log("Clearing Tokens", log: log, type: .debug)
104114
if KeychainManager.main.deleteOpenPassTokensFromKeychain() {
105115
self.openPassTokens = nil
106116
return true
@@ -132,7 +142,8 @@ public final class OpenPassManager {
132142
SignInFlow(
133143
openPassClient: openPassClient,
134144
tokenValidator: tokenValidator,
135-
redirectHost: redirectHost
145+
redirectHost: redirectHost,
146+
isLoggingEnabled: isLoggingEnabled
136147
) { [weak self] tokens in
137148
guard let self else {
138149
return
@@ -153,7 +164,8 @@ public final class OpenPassManager {
153164
RefreshTokenFlow(
154165
openPassClient: openPassClient,
155166
clientId: clientId,
156-
tokenValidator: tokenValidator
167+
tokenValidator: tokenValidator,
168+
isLoggingEnabled: isLoggingEnabled
157169
) { [weak self] tokens in
158170
guard let self else {
159171
return
@@ -168,6 +180,7 @@ public final class OpenPassManager {
168180
DeviceAuthorizationFlow(
169181
openPassClient: openPassClient,
170182
tokenValidator: tokenValidator,
183+
isLoggingEnabled: isLoggingEnabled,
171184
clock: clock
172185
) { [weak self] tokens in
173186
guard let self else {
@@ -179,8 +192,10 @@ public final class OpenPassManager {
179192

180193
/// Utility function for persisting OpenPassTokens data after its been loaded from the API Server.
181194
internal func setOpenPassTokens(_ openPassTokens: OpenPassTokens) {
195+
os_log("Updating Tokens", log: log, type: .debug)
182196
assert(openPassTokens.idToken != nil, "ID Token must not be nil")
183197
self.openPassTokens = openPassTokens
198+
os_log("Saving Tokens", log: log, type: .debug)
184199
KeychainManager.main.saveOpenPassTokensToKeychain(openPassTokens)
185200
}
186201
}

Sources/OpenPass/OpenPassSettings.swift

+17
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ public final class OpenPassSettings: NSObject, @unchecked Sendable {
109109
}
110110
}
111111

112+
private var _isLoggingEnabled = false
113+
114+
/// Enable OS logging. The default value is `false`.
115+
@objc
116+
public var isLoggingEnabled: Bool {
117+
get {
118+
queue.sync {
119+
_isLoggingEnabled
120+
}
121+
}
122+
set {
123+
queue.sync {
124+
_isLoggingEnabled = newValue
125+
}
126+
}
127+
}
128+
112129
@objc
113130
public static let shared = OpenPassSettings()
114131
}

Sources/OpenPass/RefreshTokenFlow.swift

+14-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
//
2626

2727
import Foundation
28+
import OSLog
2829

2930
@MainActor
3031
public final class RefreshTokenFlow {
@@ -33,15 +34,21 @@ public final class RefreshTokenFlow {
3334
private let tokenValidator: IDTokenValidation
3435
private let tokensObserver: ((OpenPassTokens) async -> Void)
3536

37+
private let log: OSLog
38+
3639
init(
3740
openPassClient: OpenPassClient,
3841
clientId: String,
3942
tokenValidator: IDTokenValidation,
43+
isLoggingEnabled: Bool,
4044
tokensObserver: @escaping ((OpenPassTokens) async -> Void)
4145
) {
4246
self.openPassClient = openPassClient
4347
self.clientId = clientId
4448
self.tokenValidator = tokenValidator
49+
self.log = isLoggingEnabled
50+
? .init(subsystem: "com.myopenpass", category: "RefreshTokenFlow")
51+
: .disabled
4552
self.tokensObserver = tokensObserver
4653
}
4754

@@ -62,7 +69,12 @@ public final class RefreshTokenFlow {
6269
/// - Parameter idToken: ID Token To Verify
6370
/// - Returns: true if valid, false if invalid
6471
private func verify(_ idToken: IDToken) async throws -> Bool {
65-
let jwks = try await openPassClient.fetchJWKS()
66-
return try tokenValidator.validate(idToken, jwks: jwks)
72+
do {
73+
let jwks = try await openPassClient.fetchJWKS()
74+
return try tokenValidator.validate(idToken, jwks: jwks)
75+
} catch {
76+
os_log("Error verifying tokens from flow", log: log, type: .error)
77+
throw error
78+
}
6779
}
6880
}

0 commit comments

Comments
 (0)