diff --git a/Sources/UberAuth/AuthProviding.swift b/Sources/UberAuth/AuthProviding.swift
index 6290acc..20c46cb 100644
--- a/Sources/UberAuth/AuthProviding.swift
+++ b/Sources/UberAuth/AuthProviding.swift
@@ -49,12 +49,14 @@ extension AuthProviding where Self == AuthorizationCodeAuthProvider {
public static func authorizationCode(presentationAnchor: ASPresentationAnchor = .init(),
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
shouldExchangeAuthCode: Bool = true,
- prompt: Prompt? = nil) -> Self {
+ prompt: Prompt? = nil,
+ environment: UberEnvironment = .production) -> Self {
AuthorizationCodeAuthProvider(
presentationAnchor: presentationAnchor,
scopes: scopes,
shouldExchangeAuthCode: shouldExchangeAuthCode,
- prompt: prompt
+ prompt: prompt,
+ environment: environment
)
}
}
diff --git a/Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift b/Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift
index d3e9d9e..4724b65 100644
--- a/Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift
+++ b/Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift
@@ -67,15 +67,18 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
private let tokenManager: TokenManaging
private let scopes: [String]
-
+
private let prompt: Prompt?
-
+
+ private let baseUrl: String
+
// MARK: Initializers
-
+
public init(presentationAnchor: ASPresentationAnchor = .init(),
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
shouldExchangeAuthCode: Bool = false,
- prompt: Prompt? = nil) {
+ prompt: Prompt? = nil,
+ environment: UberEnvironment = .production) {
self.configurationProvider = ConfigurationProvider()
self.applicationLauncher = UIApplication.shared
self.authenticationSessionBuilder = nil
@@ -84,12 +87,13 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.redirectURI = configurationProvider.redirectURI
self.responseParser = AuthorizationCodeResponseParser()
self.shouldExchangeAuthCode = shouldExchangeAuthCode
- self.networkProvider = NetworkProvider(baseUrl: Constants.baseUrl)
- self.tokenManager = TokenManager()
+ self.baseUrl = environment.baseUrl + "/v2"
+ self.networkProvider = NetworkProvider(baseUrl: self.baseUrl)
+ self.tokenManager = TokenManager(environment: environment)
self.scopes = scopes
self.prompt = prompt
}
-
+
init(presentationAnchor: ASPresentationAnchor = .init(),
authenticationSessionBuilder: AuthenticationSessionBuilder? = nil,
scopes: [String] = AuthorizationCodeAuthProvider.defaultScopes,
@@ -98,9 +102,10 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
configurationProvider: ConfigurationProviding = ConfigurationProvider(),
applicationLauncher: ApplicationLaunching = UIApplication.shared,
responseParser: AuthorizationCodeResponseParsing = AuthorizationCodeResponseParser(),
- networkProvider: NetworkProviding = NetworkProvider(baseUrl: Constants.baseUrl),
- tokenManager: TokenManaging = TokenManager()) {
-
+ networkProvider: NetworkProviding = NetworkProvider(baseUrl: UberEnvironment.production.baseUrl + "/v2"),
+ tokenManager: TokenManaging = TokenManager(),
+ environment: UberEnvironment = .production) {
+
self.applicationLauncher = applicationLauncher
self.authenticationSessionBuilder = authenticationSessionBuilder
self.clientID = configurationProvider.clientID
@@ -109,6 +114,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
self.redirectURI = configurationProvider.redirectURI
self.responseParser = responseParser
self.shouldExchangeAuthCode = shouldExchangeAuthCode
+ self.baseUrl = environment.baseUrl + "/v2"
self.networkProvider = networkProvider
self.tokenManager = tokenManager
self.scopes = scopes
@@ -244,11 +250,11 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
scopes: scopes
)
- guard let url = request.url(baseUrl: Constants.baseUrl) else {
+ guard let url = request.url(baseUrl: baseUrl) else {
completion(.failure(.invalidRequest("Invalid base URL")))
return
}
-
+
guard let callbackURL = URL(string: redirectURI),
let callbackURLScheme = callbackURL.scheme else {
completion(.failure(.invalidRequest("Invalid redirect URI")))
@@ -284,7 +290,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
scopes: scopes
)
- guard let url = request.url(baseUrl: Constants.baseUrl) else {
+ guard let url = request.url(baseUrl: baseUrl) else {
throw UberAuthError.invalidRequest("Invalid base URL")
}
@@ -415,11 +421,11 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
scopes: scopes
)
- guard let url = request.url(baseUrl: Constants.baseUrl) else {
+ guard let url = request.url(baseUrl: baseUrl) else {
completion?(false)
return
}
-
+
DispatchQueue.main.async {
self.applicationLauncher.launch(
url,
@@ -448,7 +454,7 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
scopes: scopes
)
- guard let url = request.url(baseUrl: Constants.baseUrl) else { return false }
+ guard let url = request.url(baseUrl: baseUrl) else { return false }
return await applicationLauncher.launch(url)
}
@@ -545,13 +551,6 @@ public final class AuthorizationCodeAuthProvider: AuthProviding {
return client
}
- // MARK: Constants
-
- private enum Constants {
- static let clientIDKey = "ClientID"
- static let redirectURI = "RedirectURI"
- static let baseUrl = "https://auth.uber.com/v2"
- }
}
diff --git a/Sources/UberAuth/Token/TokenManager.swift b/Sources/UberAuth/Token/TokenManager.swift
index 81772e7..b92e52f 100644
--- a/Sources/UberAuth/Token/TokenManager.swift
+++ b/Sources/UberAuth/Token/TokenManager.swift
@@ -24,6 +24,7 @@
import Foundation
+import UberCore
/// @mockable
public protocol TokenManaging {
@@ -66,15 +67,19 @@ public extension TokenManaging {
}
public final class TokenManager: TokenManaging {
-
+
public static let defaultAccessTokenIdentifier: String = "UberAccessTokenKey"
-
+
public static let defaultKeychainAccessGroup: String = ""
-
+
private let keychainUtility: KeychainUtilityProtocol
-
- public init(keychainUtility: KeychainUtilityProtocol = KeychainUtility()) {
+
+ private let regionHost: String
+
+ public init(keychainUtility: KeychainUtilityProtocol = KeychainUtility(),
+ environment: UberEnvironment = .production) {
self.keychainUtility = keychainUtility
+ self.regionHost = environment.baseUrl
}
// MARK: Save
@@ -128,9 +133,9 @@ public final class TokenManager: TokenManaging {
// MARK: Private Interface
- /// Removes all cookies in the shared cookie store corresponding with the auth.uber.com domain
+ /// Removes all cookies in the shared cookie store corresponding with the auth domain
private func deleteCookies() {
- guard let loginUrl = URL(string: Constants.regionHost) else {
+ guard let loginUrl = URL(string: regionHost) else {
return
}
@@ -142,10 +147,4 @@ public final class TokenManager: TokenManaging {
}
}
}
-
- // MARK: Constants
-
- private enum Constants {
- static let regionHost = "https://auth.uber.com"
- }
}
diff --git a/Sources/UberCore/UberEnvironment.swift b/Sources/UberCore/UberEnvironment.swift
new file mode 100644
index 0000000..aea33a3
--- /dev/null
+++ b/Sources/UberCore/UberEnvironment.swift
@@ -0,0 +1,37 @@
+//
+// UberEnvironment.swift
+// UberCore
+//
+// Copyright © 2024 Uber Technologies, Inc. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+import Foundation
+
+public enum UberEnvironment {
+ case production
+ case sandbox
+
+ public var baseUrl: String {
+ switch self {
+ case .production: return "https://auth.uber.com"
+ case .sandbox: return "https://sandbox-login.uber.com"
+ }
+ }
+}
diff --git a/examples/UberSDK/UberSDK/ContentView.swift b/examples/UberSDK/UberSDK/ContentView.swift
index 4de0bef..803413e 100644
--- a/examples/UberSDK/UberSDK/ContentView.swift
+++ b/examples/UberSDK/UberSDK/ContentView.swift
@@ -48,6 +48,7 @@ final class Content {
var selection: Item?
var type: LoginType? = .authorizationCode
var destination: LoginDestination? = .inApp
+ var environment: LoginEnvironment? = .production
var isTokenExchangeEnabled: Bool = true
var shouldForceLogin: Bool = false
var shouldForceConsent: Bool = false
@@ -75,10 +76,11 @@ final class Content {
var prompt: Prompt = []
if shouldForceLogin { prompt.insert(.login) }
if shouldForceConsent { prompt.insert(.consent) }
-
+
let authProvider: AuthProviding = .authorizationCode(
shouldExchangeAuthCode: isTokenExchangeEnabled,
- prompt: prompt
+ prompt: prompt,
+ environment: environment == .sandbox ? .sandbox : .production
)
let authDestination: AuthDestination = {
@@ -116,6 +118,7 @@ final class Content {
enum Item: String, Hashable, Identifiable {
case type = "Auth Type"
case destination = "Destination"
+ case environment = "Environment"
case tokenExchange = "Exchange Auth Code for Token"
case forceLogin = "Always ask for Login"
case forceConsent = "Always ask for Consent"
@@ -166,6 +169,12 @@ struct ContentView: View {
options: LoginDestination.allCases
)
.presentationDetents([.height(200)])
+ case .environment:
+ SelectionView(
+ selection: $content.environment,
+ options: LoginEnvironment.allCases
+ )
+ .presentationDetents([.height(200)])
default:
EmptyView()
}
@@ -201,6 +210,7 @@ struct ContentView: View {
textRow(.type, value: content.type?.description)
textRow(.destination, value: content.destination?.description)
+ textRow(.environment, value: content.environment?.description)
toggleRow(.tokenExchange, value: $content.isTokenExchangeEnabled)
toggleRow(.forceLogin, value: $content.shouldForceLogin)
toggleRow(.forceConsent, value: $content.shouldForceConsent)
diff --git a/examples/UberSDK/UberSDK/Info.plist b/examples/UberSDK/UberSDK/Info.plist
index 0d0e4e4..c3dc43b 100644
--- a/examples/UberSDK/UberSDK/Info.plist
+++ b/examples/UberSDK/UberSDK/Info.plist
@@ -24,9 +24,9 @@
Uber
ClientID
- [Client ID]
+ u6fWe9U2aQv1hE-dPXhzPXmnmOD45n6N
RedirectURI
- com.uber.UberSDK://oauth/consumer
+ https://www.uber.com/
DisplayName
[App Name]
diff --git a/examples/UberSDK/UberSDK/SelectionOptions.swift b/examples/UberSDK/UberSDK/SelectionOptions.swift
index f415100..58aa06e 100644
--- a/examples/UberSDK/UberSDK/SelectionOptions.swift
+++ b/examples/UberSDK/UberSDK/SelectionOptions.swift
@@ -35,8 +35,16 @@ enum LoginType: String, CaseIterable, SelectionOption {
enum LoginDestination: String, CaseIterable, SelectionOption {
case inApp = "In App"
- case native = "Native"
-
+ case native = "Native"
+
+ var description: String { rawValue }
+ var id: String { rawValue }
+}
+
+enum LoginEnvironment: String, CaseIterable, SelectionOption {
+ case production = "Production"
+ case sandbox = "Sandbox"
+
var description: String { rawValue }
var id: String { rawValue }
}
diff --git a/examples/UberSDK/UberSDKTests/UberAuth/AuthorizationCodeAuthProviderTests.swift b/examples/UberSDK/UberSDKTests/UberAuth/AuthorizationCodeAuthProviderTests.swift
index a9b7a88..c611d75 100644
--- a/examples/UberSDK/UberSDKTests/UberAuth/AuthorizationCodeAuthProviderTests.swift
+++ b/examples/UberSDK/UberSDKTests/UberAuth/AuthorizationCodeAuthProviderTests.swift
@@ -1025,14 +1025,14 @@ extension AuthorizationCodeAuthProviderTests {
XCTAssertNotNil(error as? UberAuthError)
}
}
-
+
func test_execute_async_existingSession_throwsError() async {
let provider = AuthorizationCodeAuthProvider(
shouldExchangeAuthCode: false,
configurationProvider: configurationProvider
)
provider.currentSession = AuthenticationSessioningMock()
-
+
do {
_ = try await provider.execute(authDestination: .inApp, prefill: nil)
XCTFail("Should have thrown error")
@@ -1040,4 +1040,66 @@ extension AuthorizationCodeAuthProviderTests {
XCTAssertNotNil(error as? UberAuthError)
}
}
+
+ // MARK: Environment
+
+ func test_environment_production_usesProductionBaseUrl() {
+ var capturedUrl: URL?
+ let authenticationSessionBuilder: AuthorizationCodeAuthProvider.AuthenticationSessionBuilder = { _, _, url, _ in
+ capturedUrl = url
+ return AuthenticationSessioningMock()
+ }
+
+ let provider = AuthorizationCodeAuthProvider(
+ authenticationSessionBuilder: authenticationSessionBuilder,
+ configurationProvider: configurationProvider,
+ environment: .production
+ )
+
+ provider.execute(authDestination: .inApp, completion: { _ in })
+
+ XCTAssertTrue(capturedUrl?.absoluteString.contains("auth.uber.com") == true)
+ XCTAssertFalse(capturedUrl?.absoluteString.contains("sandbox-login.uber.com") == true)
+ }
+
+ func test_environment_sandbox_usesSandboxBaseUrl() {
+ var capturedUrl: URL?
+ let authenticationSessionBuilder: AuthorizationCodeAuthProvider.AuthenticationSessionBuilder = { _, _, url, _ in
+ capturedUrl = url
+ return AuthenticationSessioningMock()
+ }
+
+ let provider = AuthorizationCodeAuthProvider(
+ authenticationSessionBuilder: authenticationSessionBuilder,
+ configurationProvider: configurationProvider,
+ environment: .sandbox
+ )
+
+ provider.execute(authDestination: .inApp, completion: { _ in })
+
+ XCTAssertTrue(capturedUrl?.absoluteString.contains("sandbox-login.uber.com") == true)
+ XCTAssertFalse(capturedUrl?.absoluteString.contains("auth.uber.com") == true)
+ }
+
+ func test_environment_sandbox_nativeLogin_usesSandboxBaseUrl() {
+ configurationProvider.isInstalledHandler = { _, _ in true }
+
+ let expectation = XCTestExpectation()
+ let applicationLauncher = ApplicationLaunchingMock()
+ applicationLauncher.launchHandler = { url, completion in
+ XCTAssertTrue(url.absoluteString.contains("sandbox-login.uber.com"))
+ expectation.fulfill()
+ completion?(true)
+ }
+
+ let provider = AuthorizationCodeAuthProvider(
+ configurationProvider: configurationProvider,
+ applicationLauncher: applicationLauncher,
+ environment: .sandbox
+ )
+
+ provider.execute(authDestination: .native(appPriority: [.rides]), completion: { _ in })
+
+ wait(for: [expectation], timeout: 0.2)
+ }
}