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
50 changes: 38 additions & 12 deletions OAuth2.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import PackageDescription
let package = Package(
name: "OAuth2",
platforms: [
.macOS(.v10_15), .iOS(.v12), .tvOS(.v12), .watchOS(.v5)
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)
],
products: [
.library(name: "OAuth2", targets: ["OAuth2"]),
Expand All @@ -43,8 +43,9 @@ let package = Package(
.target(name: "Flows", dependencies: [
.target(name: "macOS"), .target(name: "iOS"), .target(name: "tvOS"), .target(name: "Constants")]),
.target(name: "DataLoader", dependencies: [.target(name: "Flows")]),
.testTarget(name: "BaseTests", dependencies: [.target(name: "Base"), .target(name: "Flows")]),
.testTarget(name: "FlowTests", dependencies: [.target(name: "Flows")]),
.target(name: "TestUtils", dependencies: [.target(name: "Base")]),
.testTarget(name: "BaseTests", dependencies: [.target(name: "TestUtils"), .target(name: "Base"), .target(name: "Flows")]),
.testTarget(name: "FlowTests", dependencies: [.target(name: "TestUtils"), .target(name: "Flows")]),
// .testTarget(name: "DataLoaderTests", dependencies: [.target(name: "DataLoader")]),
]
)
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ OAuth2 frameworks for **macOS**, **iOS** and **tvOS** written in Swift 5.
- [🖥 Sample macOS app][sample] (with data loader examples)
- [📖 Technical Documentation](https://p2.github.io/OAuth2)

OAuth2 requires Xcode 12.4, the built framework can be used on **OS X 10.15** or **iOS 12** and later.
OAuth2 requires Xcode 12.4, the built framework can be used on **OS X 10.15**, **iOS 13**, **tvOS 13**, **watchOS 6** and later.
Happy to accept pull requests, please see [CONTRIBUTING.md](./Docs/CONTRIBUTING.md)

### Swift Version
Expand Down Expand Up @@ -374,7 +374,6 @@ let oauth2 = OAuth2CodeGrant(settings: [
- [Facebook](https://github.com/p2/OAuth2/wiki/Facebook)
- [Reddit](https://github.com/p2/OAuth2/wiki/Reddit)
- [Google](https://github.com/p2/OAuth2/wiki/Google)
- [LinkedIn](https://github.com/p2/OAuth2/wiki/LinkedIn)
- [Instagram, Bitly, Pinterest, ...](https://github.com/p2/OAuth2/wiki/Instagram,-Bitly,-Pinterest-and-others)
- [Uber](https://github.com/p2/OAuth2/wiki/Uber)
- [BitBucket](https://github.com/p2/OAuth2/wiki/BitBucket)
Expand Down
3 changes: 3 additions & 0 deletions Sources/Base/OAuth2Actor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@globalActor public actor OAuth2Actor : GlobalActor {
public static let shared = OAuth2Actor()
}
17 changes: 3 additions & 14 deletions Sources/Base/OAuth2AuthConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,14 @@ import UIKit
/**
Simple struct to hold settings describing how authorization appears to the user.
*/
public struct OAuth2AuthConfig {
public struct OAuth2AuthConfig: Sendable {

/// Sub-stuct holding configuration relevant to UI presentation.
public struct UI {
public struct UI: Sendable {

/// Title to propagate to views handled by OAuth2, such as OAuth2WebViewController.
public var title: String? = nil

/// By assigning your own UIBarButtonItem (!) you can override the back button that is shown in the iOS embedded web view (does NOT apply to `SFSafariViewController` or `ASWebAuthenticationSession`).
@available(*, deprecated, message: "This will be removed in v6.")
public var backButton: AnyObject? = nil

/// If true it makes the login cancellable, otherwise the cancel button is not shown in the embedded web view.
@available(*, deprecated, message: "This will be removed in v6.")
public var showCancelButton = true

/// Starting with iOS 9, `SFSafariViewController` will be used for embedded authorization instead of our custom class. You can turn this off here.
public var useSafariView = false

/// Starting with iOS 12, `ASWebAuthenticationSession` can be used for embedded authorization instead of our custom class. You can turn this on here.
public var useAuthenticationSession = true

Expand Down Expand Up @@ -72,7 +61,7 @@ public struct OAuth2AuthConfig {
/// Context information for the authorization flow:
/// - iOS: The parent view controller to present from
/// - macOS: An NSWindow from which to present a modal sheet _or_ `nil` to present in a new window
public weak var authorizeContext: AnyObject? = nil
public nonisolated(unsafe) weak var authorizeContext: (AnyObject)? = nil

/// UI-specific configuration.
public var ui = UI()
Expand Down
1 change: 1 addition & 0 deletions Sources/Base/OAuth2AuthRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public enum OAuth2EndpointAuthMethod: String {
/**
Class representing an OAuth2 authorization request that can be used to create NSURLRequest instances.
*/
@OAuth2Actor
open class OAuth2AuthRequest {

/// The url of the receiver. Queries may by added by parameters specified on `params`.
Expand Down
3 changes: 2 additions & 1 deletion Sources/Base/OAuth2AuthorizerUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Foundation
/**
Platform-dependent authorizers must adopt this protocol.
*/
@OAuth2Actor
public protocol OAuth2AuthorizerUI {

/// The OAuth2 instance this authorizer belongs to.
Expand All @@ -44,5 +45,5 @@ public protocol OAuth2AuthorizerUI {
- parameter at: The authorize URL to open
- throws: Can throw OAuth2Error if the method is unable to show the authorize screen
*/
func authorizeEmbedded(with config: OAuth2AuthConfig, at url: URL) throws
func authorizeEmbedded(with config: OAuth2AuthConfig, at url: URL) async throws
}
29 changes: 15 additions & 14 deletions Sources/Base/OAuth2Base.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import Foundation
import CommonCrypto


/**
Class extending on OAuth2Requestable, exposing configuration and maintaining context, serving as base class for `OAuth2`.
*/
Expand Down Expand Up @@ -98,7 +97,7 @@ open class OAuth2Base: OAuth2Securable {

/// The receiver's id token.
open var idToken: String? {
get { return clientConfig.idToken }
get { return clientConfig.idToken }
set { clientConfig.idToken = newValue }
}

Expand Down Expand Up @@ -132,14 +131,8 @@ open class OAuth2Base: OAuth2Securable {
set { clientConfig.customUserAgent = newValue }
}


/// This closure is internally used with `authorize(params:callback:)` and only exposed for subclassing reason, do not mess with it!
public final var didAuthorizeOrFail: ((_ parameters: OAuth2JSON?, _ error: OAuth2Error?) -> Void)?

/// Returns true if the receiver is currently authorizing.
public final var isAuthorizing: Bool {
return nil != didAuthorizeOrFail
}
public final var isAuthorizing: Bool = false

/// Returns true if the receiver is currently exchanging the refresh token.
public final var isExchangingRefreshToken: Bool = false
Expand All @@ -159,6 +152,7 @@ open class OAuth2Base: OAuth2Securable {
*/
public final var internalAfterAuthorizeOrFail: ((_ wasFailure: Bool, _ error: OAuth2Error?) -> Void)?

public final var doAuthorizeContinuation: CheckedContinuation<OAuth2JSON, any Error>?

/**
Designated initializer.
Expand Down Expand Up @@ -268,7 +262,8 @@ open class OAuth2Base: OAuth2Securable {

- parameter redirect: The redirect URL returned by the server that you want to handle
*/
open func handleRedirectURL(_ redirect: URL) throws {
@discardableResult
open func handleRedirectURL(_ redirect: URL) async throws -> OAuth2JSON {
throw OAuth2Error.generic("Abstract class use")
}

Expand All @@ -285,11 +280,14 @@ open class OAuth2Base: OAuth2Securable {
storeTokensToKeychain()
}
callOnMainThread() {
self.didAuthorizeOrFail?(parameters, nil)
self.didAuthorizeOrFail = nil
self.isAuthorizing = false
self.internalAfterAuthorizeOrFail?(false, nil)
self.afterAuthorizeOrFail?(parameters, nil)
}

// Finish `doAuthorize` call
self.doAuthorizeContinuation?.resume(returning: parameters)
self.doAuthorizeContinuation = nil
}

/**
Expand All @@ -309,11 +307,14 @@ open class OAuth2Base: OAuth2Securable {
finalError = OAuth2Error.requestCancelled
}
callOnMainThread() {
self.didAuthorizeOrFail?(nil, finalError)
self.didAuthorizeOrFail = nil
self.isAuthorizing = false
self.internalAfterAuthorizeOrFail?(true, finalError)
self.afterAuthorizeOrFail?(nil, finalError)
}

// Finish `doAuthorize` call
self.doAuthorizeContinuation?.resume(throwing: error ?? OAuth2Error.requestCancelled)
self.doAuthorizeContinuation = nil
}

/**
Expand Down
8 changes: 4 additions & 4 deletions Sources/Base/OAuth2ClientConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ open class OAuth2ClientConfig {

- returns: A storable dictionary with credentials
*/
func storableCredentialItems() -> [String: Any]? {
func storableCredentialItems() -> [String: any Sendable]? {
guard let clientId = clientId, !clientId.isEmpty else { return nil }

var items: [String: Any] = ["id": clientId]
var items: [String: any Sendable] = ["id": clientId]
if let secret = clientSecret {
items["secret"] = secret
}
Expand All @@ -243,8 +243,8 @@ open class OAuth2ClientConfig {

- returns: A storable dictionary with token data
*/
func storableTokenItems() -> [String: Any]? {
var items = [String: Any]()
func storableTokenItems() -> [String: any Sendable]? {
var items = [String: any Sendable]()

if let access = accessToken {
items["accessToken"] = access
Expand Down
3 changes: 2 additions & 1 deletion Sources/Base/OAuth2CustomAuthorizerUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/**
Platform-dependent login presenters that present custom login views must adopt this protocol.
*/
@OAuth2Actor
public protocol OAuth2CustomAuthorizerUI {

/**
Expand All @@ -31,7 +32,7 @@ public protocol OAuth2CustomAuthorizerUI {
- parameter fromContext: The presenting context, typically another controller of platform-dependent type
- parameter animated: Whether the presentation should be animated
*/
func present(loginController: AnyObject, fromContext context: AnyObject?, animated: Bool) throws
func present(loginController: AnyObject, fromContext context: AnyObject?, animated: Bool) async throws

/**
This function must dismiss the login controller.
Expand Down
4 changes: 2 additions & 2 deletions Sources/Base/OAuth2DebugURLSessionDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Doing so is a REALLY BAD IDEA, even in development environments where you can us
Still, sometimes you'll have to do this so this class is provided, but DO NOT SUBMIT your app using self-signed SSL certs to the App
Store. You have been warned!
*/
open class OAuth2DebugURLSessionDelegate: NSObject, URLSessionDelegate {
final class OAuth2DebugURLSessionDelegate: NSObject, URLSessionDelegate {

/// The host to allow a self-signed SSL certificate for.
let host: String
Expand All @@ -42,7 +42,7 @@ open class OAuth2DebugURLSessionDelegate: NSObject, URLSessionDelegate {
self.host = host
}

open func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (Foundation.URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
if challenge.protectionSpace.host == host, let trust = challenge.protectionSpace.serverTrust {
Expand Down
3 changes: 2 additions & 1 deletion Sources/Base/OAuth2KeychainAccount.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SwiftKeychain
/**
Keychain integration handler for OAuth2.
*/
@OAuth2Actor
struct OAuth2KeychainAccount: KeychainGenericPasswordType {
/// The service name to use.
let serviceName: String
Expand Down Expand Up @@ -75,7 +76,7 @@ extension KeychainGenericPasswordType {

- returns: A [String: Any] dictionary of data fetched from the keychain
*/
mutating func fetchedFromKeychain() throws -> [String: Any] {
mutating func fetchedFromKeychain() throws -> [String: any Sendable] {
do {
try _ = fetchFromKeychain()
return data
Expand Down
1 change: 1 addition & 0 deletions Sources/Base/OAuth2Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ A simple protocol for loggers used in OAuth2.
The `OAuth2DebugLogger` is a simple implementation that logs to stdout. If you need more sophisticated logging, just adapt this protocol
and set your logger on the `OAuth2` instance you're using.
*/
@OAuth2Actor
public protocol OAuth2Logger {

/// The logger's logging level.
Expand Down
23 changes: 8 additions & 15 deletions Sources/Base/OAuth2RequestPerformer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@ Protocol for types that can perform `URLRequest`s.

The class `OAuth2DataTaskRequestPerformer` implements this protocol and is by default used by all `OAuth2` classes to perform requests.
*/
@OAuth2Actor
public protocol OAuth2RequestPerformer {

/**
This method should start executing the given request, returning a URLSessionTask if it chooses to do so. **You do not neet to call
`resume()` on this task**, it's supposed to already have started. It is being returned so you may be able to do additional stuff.
This method should execute the given request asynchronously.

- parameter request: An URLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
- parameter completionHandler: The completion handler to call when the load request is complete.
- returns: An already running session task
- returns: Data and response.
*/
func perform(request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask?
func perform(request: URLRequest) async throws -> (Data?, URLResponse)
}


Expand All @@ -36,7 +35,6 @@ open class OAuth2DataTaskRequestPerformer: OAuth2RequestPerformer {
/// The URLSession that should be used.
public var session: URLSession


/**
Designated initializer.
*/
Expand All @@ -45,18 +43,13 @@ open class OAuth2DataTaskRequestPerformer: OAuth2RequestPerformer {
}

/**
This method should start executing the given request, returning a URLSessionTask if it chooses to do so. **You do not neet to call
`resume()` on this task**, it's supposed to already have started. It is being returned so you may be able to do additional stuff.
This method should execute the given request asynchronously.

- parameter request: An URLRequest object that provides the URL, cache policy, request type, body data or body stream, and so on.
- parameter completionHandler: The completion handler to call when the load request is complete.
- returns: An already running session data task
- returns: Data and response.
*/
@discardableResult
open func perform(request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask? {
let task = session.dataTask(with: request, completionHandler: completionHandler)
task.resume()
return task
open func perform(request: URLRequest) async throws -> (Data?, URLResponse) {
try await session.data(for: request)
}
}

Loading