Skip to content

Commit 0e7df08

Browse files
committed
StorageAuthorizer to Swift (#10117)
1 parent 7b45336 commit 0e7df08

File tree

18 files changed

+425
-546
lines changed

18 files changed

+425
-546
lines changed

FirebaseCombineSwift.podspec

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ for internal testing only. It should not be published.
5151
s.osx.framework = 'AppKit'
5252
s.tvos.framework = 'UIKit'
5353

54-
s.dependency 'FirebaseCore', '~> 9.0'
55-
s.dependency 'FirebaseAuth', '~> 9.0'
56-
s.dependency 'FirebaseFunctions', '~> 9.0'
57-
s.dependency 'FirebaseFirestore', '~> 9.0'
58-
s.dependency 'FirebaseStorage', '~> 9.0'
54+
s.dependency 'FirebaseCore', '~> 9.5'
55+
s.dependency 'FirebaseAuth', '~> 9.5'
56+
s.dependency 'FirebaseFunctions', '~> 9.5'
57+
s.dependency 'FirebaseFirestore', '~> 9.5'
58+
s.dependency 'FirebaseStorage', '~> 9.5'
5959

6060
s.pod_target_xcconfig = {
6161
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"',

FirebaseCombineSwift/Sources/Storage/StorageReference+Combine.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
#if canImport(Combine) && swift(>=5.0)
1616

17+
import Foundation
1718
import Combine
1819
import FirebaseStorage
19-
import FirebaseStorageInternal
2020

2121
@available(swift 5.0)
2222
@available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)

FirebaseCombineSwift/Tests/Integration/Storage/StorageIntegration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class StorageIntegration: XCTestCase {
289289
case .finished:
290290
XCTFail("Unexpected success return from putFile)")
291291
case let .failure(error):
292-
XCTAssertEqual(String(describing: error), "unknown")
292+
XCTAssertTrue(String(describing: error).starts(with: "unknown"))
293293
expectation.fulfill()
294294
}
295295
}, receiveValue: { value in

FirebaseCombineSwift/Tests/Unit/Storage/StorageReferenceTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class StorageReferenceTests: XCTestCase {
5656
.sink { completion in
5757
if case let .failure(error) = completion {
5858
putFileExpectation.fulfill()
59-
XCTAssertEqual("unknown", String(describing: error))
59+
XCTAssertTrue(String(describing: error).starts(with: "unknown"))
6060
}
6161
} receiveValue: { metadata in
6262
XCTFail("💥 result unexpected")

FirebaseCore/Extension/FIRLogger.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ extern void FIRLogInfo(FIRLoggerService service, NSString *messageCode, NSString
116116
extern void FIRLogDebug(FIRLoggerService service, NSString *messageCode, NSString *message, ...)
117117
NS_FORMAT_FUNCTION(3, 4);
118118

119+
// TODO: Come up with a better logging scheme for Swift.
120+
/**
121+
* Logs a message to the Xcode console and the device log. If running from AppStore, will
122+
* not log any messages with a level higher than FirebaseLoggerLevelNotice to avoid log spamming.
123+
* This function is intended to be used by Swift clients that do not support variadic parameters.
124+
* (required) log level (one of the FirebaseLoggerLevel enum values).
125+
* (required) service name of type FirebaseLoggerService.
126+
* (required) message code starting with "I-" which means iOS, followed by a capitalized
127+
* three-character service identifier and a six digit integer message ID that is unique
128+
* within the service.
129+
* An example of the message code is @"I-COR000001".
130+
* (required) message string.
131+
*/
132+
extern void FIRLogDebugSwift(FIRLoggerService service, NSString *messageCode, NSString *message);
133+
119134
#ifdef __cplusplus
120135
} // extern "C"
121136
#endif // __cplusplus

FirebaseCore/Sources/FIRLogger.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ void FIRLogBasic(FIRLoggerLevel level,
157157
FIR_LOGGING_FUNCTION(Info)
158158
FIR_LOGGING_FUNCTION(Debug)
159159

160+
// Swift does not support variadic function calls
161+
void FIRLogDebugSwift(FIRLoggerService service, NSString *messageCode, NSString *message) {
162+
FIRLogDebug(service, messageCode, @"%@", message);
163+
}
164+
160165
#undef FIR_MAKE_LOGGER
161166

162167
#pragma mark - FIRLoggerWrapper

FirebaseStorage.podspec

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,11 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas
3737
'FirebaseStorage/Typedefs/*.h',
3838
]
3939

40-
s.dependency 'FirebaseStorageInternal', '~> 9.0'
4140
s.dependency 'FirebaseAppCheckInterop', '~> 9.0'
4241
s.dependency 'FirebaseAuthInterop', '~> 9.0'
4342
s.dependency 'FirebaseCore', '~> 9.0'
44-
s.dependency 'FirebaseCoreExtension', '~> 9.0'
45-
s.dependency 'GTMSessionFetcher/Core', '>= 1.7', '< 3.0'
43+
s.dependency 'FirebaseCoreExtension', '~> 9.5'
44+
s.dependency 'GTMSessionFetcher/Core', '~> 2.1'
4645

4746
s.test_spec 'ObjCIntegration' do |objc_tests|
4847
objc_tests.scheme = { :code_coverage => true }
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
import FirebaseAppCheckInterop
18+
import FirebaseAuthInterop
19+
import FirebaseCore
20+
@_implementationOnly import FirebaseCoreExtension
21+
22+
#if COCOAPODS
23+
import GTMSessionFetcher
24+
#else
25+
import GTMSessionFetcherCore
26+
#endif
27+
28+
internal class StorageTokenAuthorizer: NSObject, GTMSessionFetcherAuthorizer {
29+
func authorizeRequest(_ request: NSMutableURLRequest?,
30+
completionHandler handler: @escaping (Error?) -> Void) {
31+
// Set version header on each request
32+
let versionString = "ios/\(FirebaseVersion())"
33+
request?.setValue(versionString, forHTTPHeaderField: "x-firebase-storage-version")
34+
35+
// Set GMP ID on each request
36+
request?.setValue(googleAppID, forHTTPHeaderField: "x-firebase-gmpid")
37+
38+
var tokenError: NSError?
39+
let callbackQueue = fetcherService.callbackQueue ?? DispatchQueue.main
40+
let fetchTokenGroup = DispatchGroup()
41+
if let auth = auth {
42+
fetchTokenGroup.enter()
43+
auth.getToken(forcingRefresh: false) { token, error in
44+
if let error = error as? NSError {
45+
var errorDictionary = error.userInfo
46+
errorDictionary["ResponseErrorDomain"] = error.domain
47+
errorDictionary["ResponseErrorCode"] = error.code
48+
errorDictionary[NSLocalizedDescriptionKey] =
49+
"User is not authenticated, please authenticate" +
50+
" using Firebase Authentication and try again."
51+
tokenError = NSError(domain: "FIRStorageErrorDomain",
52+
code: StorageErrorCode.unauthenticated.rawValue,
53+
userInfo: errorDictionary)
54+
} else if let token = token {
55+
let firebaseToken = "Firebase \(token)"
56+
request?.setValue(firebaseToken, forHTTPHeaderField: "Authorization")
57+
}
58+
fetchTokenGroup.leave()
59+
}
60+
}
61+
if let appCheck = appCheck {
62+
fetchTokenGroup.enter()
63+
appCheck.getToken(forcingRefresh: false) { tokenResult in
64+
request?.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck")
65+
66+
if let error = tokenResult.error {
67+
// TODO: Define better way to use FIRLogger from Swift.
68+
FIRLogDebugSwift(
69+
"[FirebaseStorage]",
70+
"I-STR000001",
71+
"Failed to fetch AppCheck token. Error: \(error)"
72+
)
73+
}
74+
fetchTokenGroup.leave()
75+
}
76+
}
77+
fetchTokenGroup.notify(queue: callbackQueue) {
78+
handler(tokenError)
79+
}
80+
}
81+
82+
func authorizeRequest(_ request: NSMutableURLRequest?, delegate: Any, didFinish sel: Selector) {
83+
fatalError("Internal error: Should not call old authorizeRequest")
84+
}
85+
86+
// Note that stopAuthorization, isAuthorizingRequest, and userEmail
87+
// aren't relevant with the Firebase App/Auth implementation of tokens,
88+
// and thus aren't implemented. Token refresh is handled transparently
89+
// for us, and we don't allow the auth request to be stopped.
90+
// Auth is also not required so the world doesn't stop.
91+
func stopAuthorization() {}
92+
93+
func stopAuthorization(for request: URLRequest) {}
94+
95+
func isAuthorizingRequest(_ request: URLRequest) -> Bool {
96+
return false
97+
}
98+
99+
func isAuthorizedRequest(_ request: URLRequest) -> Bool {
100+
guard let authHeader = request.allHTTPHeaderFields?["Authorization"] else {
101+
return false
102+
}
103+
return authHeader.hasPrefix("Firebase")
104+
}
105+
106+
var userEmail: String?
107+
108+
internal let fetcherService: GTMSessionFetcherService
109+
private let googleAppID: String
110+
private let auth: AuthInterop?
111+
private let appCheck: AppCheckInterop?
112+
113+
private let serialAuthArgsQueue = DispatchQueue(label: "com.google.firebasestorage.authorizer")
114+
115+
init(googleAppID: String,
116+
fetcherService: GTMSessionFetcherService,
117+
authProvider: AuthInterop?,
118+
appCheck: AppCheckInterop?) {
119+
self.googleAppID = googleAppID
120+
self.fetcherService = fetcherService
121+
auth = authProvider
122+
self.appCheck = appCheck
123+
}
124+
}

FirebaseStorage/Sources/Storage.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
import Foundation
1616

17-
import FirebaseStorageInternal
1817
import FirebaseCore
1918
import FirebaseAppCheckInterop
2019
import FirebaseAuthInterop
@@ -286,7 +285,7 @@ import FirebaseAuthInterop
286285
fetcherService?.isRetryEnabled = true
287286
fetcherService?.retryBlock = retryWhenOffline
288287
fetcherService?.allowLocalhostRequest = true
289-
let authorizer = FIRStorageTokenAuthorizer(
288+
let authorizer = StorageTokenAuthorizer(
290289
googleAppID: app.options.googleAppID,
291290
fetcherService: fetcherService!,
292291
authProvider: auth,

0 commit comments

Comments
 (0)