Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Sources/UberAuth/AuthProviding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
}
45 changes: 22 additions & 23 deletions Sources/UberAuth/Authorize/AuthorizationCodeAuthProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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")))
Expand Down Expand Up @@ -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")
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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"
}
}


Expand Down
25 changes: 12 additions & 13 deletions Sources/UberAuth/Token/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@


import Foundation
import UberCore

/// @mockable
public protocol TokenManaging {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -142,10 +147,4 @@ public final class TokenManager: TokenManaging {
}
}
}

// MARK: Constants

private enum Constants {
static let regionHost = "https://auth.uber.com"
}
}
37 changes: 37 additions & 0 deletions Sources/UberCore/UberEnvironment.swift
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
14 changes: 12 additions & 2 deletions examples/UberSDK/UberSDK/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions examples/UberSDK/UberSDK/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
<key>Uber</key>
<dict>
<key>ClientID</key>
<string>[Client ID]</string>
<string>u6fWe9U2aQv1hE-dPXhzPXmnmOD45n6N</string>
<key>RedirectURI</key>
<string>com.uber.UberSDK://oauth/consumer</string>
<string>https://www.uber.com/</string>
<key>DisplayName</key>
<string>[App Name]</string>
</dict>
Expand Down
12 changes: 10 additions & 2 deletions examples/UberSDK/UberSDK/SelectionOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -1025,19 +1025,81 @@ 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")
} catch {
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)
}
}
Loading