Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 13 additions & 1 deletion 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 @@ -76,9 +77,12 @@ final class Content {
if shouldForceLogin { prompt.insert(.login) }
if shouldForceConsent { prompt.insert(.consent) }

let uberEnvironment: UberEnvironment = environment == .sandbox ? .sandbox : .production
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just pass this through to authProvider?


let authProvider: AuthProviding = .authorizationCode(
shouldExchangeAuthCode: isTokenExchangeEnabled,
prompt: prompt
prompt: prompt,
environment: uberEnvironment
)

let authDestination: AuthDestination = {
Expand Down Expand Up @@ -116,6 +120,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 +171,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 +212,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
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