From f15815f202742d51a0f38d20482bc6a063330113 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 11:50:38 +0100 Subject: [PATCH 01/19] Add object modelling Proof Key For Code Exchange --- .../project.pbxproj | 8 +++++ .../OAuth/ProofKeyForCodeExchange.swift | 34 +++++++++++++++++++ .../OAuth/ProofKeyForCodeExchangeTests.swift | 16 +++++++++ 3 files changed, 58 insertions(+) create mode 100644 WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift create mode 100644 WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift diff --git a/WordPressAuthenticator.xcodeproj/project.pbxproj b/WordPressAuthenticator.xcodeproj/project.pbxproj index bfbe90dc3..f96d3acfc 100644 --- a/WordPressAuthenticator.xcodeproj/project.pbxproj +++ b/WordPressAuthenticator.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 3FE8071D293652BB0088420C /* OAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071C293652BB0088420C /* OAuthError.swift */; }; 3FE8071F2936558F0088420C /* URL+GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */; }; 3FE8072129365F6D0088420C /* URL+GoogleSignIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */; }; + 3FEC44F7293A0E4600EBDECF /* ProofKeyForCodeExchange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */; }; + 3FEC44F9293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */; }; 3FFF2FC123D7ED7C00D38C77 /* EmailClients.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3FFF2FC023D7ED7C00D38C77 /* EmailClients.plist */; }; 3FFF2FC323D7F53200D38C77 /* AppSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FFF2FC223D7F53200D38C77 /* AppSelector.swift */; }; 4A1DEF4A29341B1F00322608 /* LoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A1DEF4829341B1F00322608 /* LoggingTests.m */; }; @@ -243,6 +245,8 @@ 3FE8071C293652BB0088420C /* OAuthError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthError.swift; sourceTree = ""; }; 3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+GoogleSignInTests.swift"; sourceTree = ""; }; 3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+GoogleSignIn.swift"; sourceTree = ""; }; + 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofKeyForCodeExchange.swift; sourceTree = ""; }; + 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofKeyForCodeExchangeTests.swift; sourceTree = ""; }; 3FFF2FC023D7ED7C00D38C77 /* EmailClients.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = EmailClients.plist; sourceTree = ""; }; 3FFF2FC223D7F53200D38C77 /* AppSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelector.swift; sourceTree = ""; }; 4A1DEF4829341B1F00322608 /* LoggingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LoggingTests.m; sourceTree = ""; }; @@ -480,6 +484,7 @@ children = ( 3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */, 3FE8071C293652BB0088420C /* OAuthError.swift */, + 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */, 3FE80716293650190088420C /* Result+ConvenienceInit.swift */, ); path = OAuth; @@ -488,6 +493,7 @@ 3FE807192936504F0088420C /* OAuth */ = { isa = PBXGroup; children = ( + 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */, 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */, ); path = OAuth; @@ -1313,6 +1319,7 @@ 02A526CF28A3A35D00FD1812 /* PasswordCoordinator.swift in Sources */, 98ED483624802F8F00992B2D /* GoogleAuthViewController.swift in Sources */, F5C817E72582B2F300BD5A3B /* UIPasteboard+Detect.swift in Sources */, + 3FEC44F7293A0E4600EBDECF /* ProofKeyForCodeExchange.swift in Sources */, B56090EA208A51D000399AE4 /* LoginFields+Validation.swift in Sources */, F1DE08CC24F4266A007AE6B3 /* StoredCredentialsAuthenticator.swift in Sources */, CE1B18CC20EEC32400BECC3F /* WordPressComCredentials.swift in Sources */, @@ -1430,6 +1437,7 @@ 3F550D4E23DA429B007E5897 /* AppSelectorTests.swift in Sources */, BA53D64B24DFE07D001F1ABF /* WordpressAuthenticatorProvider.swift in Sources */, 4A1DEF4A29341B1F00322608 /* LoggingTests.m in Sources */, + 3FEC44F9293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift in Sources */, CE16177821B70C1A00B82A47 /* WordPressAuthenticatorDisplayTextTests.swift in Sources */, B501C048208FC79C00D1E58F /* LoginFacadeTests.m in Sources */, 3108613125AFA4830022F75E /* PasteboardTests.swift in Sources */, diff --git a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift new file mode 100644 index 000000000..b1004774c --- /dev/null +++ b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift @@ -0,0 +1,34 @@ +// https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier +// +// FIXME: follow spec! +// +// A code_verifier is a high-entropy cryptographic random string using the unreserved +// characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~", with a minimum length of 43 +// characters and a maximum length of 128 characters. +// +// The code verifier should have enough entropy to make it impractical to guess the value. +struct ProofKeyForCodeExchange { + + enum Mode { + case s256 + case plain + } + + let codeVerifier: String + let mode: Mode + + init(codeVerifier: String, mode: Mode) { + self.codeVerifier = codeVerifier + self.mode = mode + } + + var codeCallenge: String { + switch mode { + case .s256: + // TODO: code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) + fatalError() + case .plain: + return codeVerifier + } + } +} diff --git a/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift new file mode 100644 index 000000000..dbca7bf70 --- /dev/null +++ b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift @@ -0,0 +1,16 @@ +@testable import WordPressAuthenticator +import XCTest + +class ProofKeyForCodeExchangeTests: XCTestCase { + + func testCodeChallengeInPlainModeIsTheSameAsCodeVerifier() { + XCTAssertEqual( + ProofKeyForCodeExchange(codeVerifier: "abc", mode: .plain).codeCallenge, + "abc" + ) + } + + func testCodeChallengeInS256ModeIsEncodedAsPerSpec() { + // TODO: + } +} From 9c8b1d66e8bc6a031410bb14b50cb73ddaf506d3 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 11:54:24 +0100 Subject: [PATCH 02/19] Add computation of `code_challenge_method` --- .../OAuth/ProofKeyForCodeExchange.swift | 7 +++++++ .../OAuth/ProofKeyForCodeExchangeTests.swift | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift index b1004774c..009e7d411 100644 --- a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift +++ b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift @@ -12,6 +12,13 @@ struct ProofKeyForCodeExchange { enum Mode { case s256 case plain + + var method: String { + switch self { + case .plain: return "plain" + case .s256: return "S256" + } + } } let codeVerifier: String diff --git a/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift index dbca7bf70..ef4025d6a 100644 --- a/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift +++ b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift @@ -13,4 +13,12 @@ class ProofKeyForCodeExchangeTests: XCTestCase { func testCodeChallengeInS256ModeIsEncodedAsPerSpec() { // TODO: } + + func testModePlainMethod() { + XCTAssertEqual(ProofKeyForCodeExchange.Mode.plain.method, "plain") + } + + func testModeS256Method() { + XCTAssertEqual(ProofKeyForCodeExchange.Mode.s256.method, "S256") + } } From 7f93ab00e147333a055ce96e07667e5bd7e2d160 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 14:48:10 +0100 Subject: [PATCH 03/19] Add PKCE parameter to GoogleSignIn URL --- .../GoogleSignIn/URL+GoogleSignIn.swift | 4 +++- .../GoogleSignIn/URL+GoogleSignInTests.swift | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index 628a2f558..4f7ec6303 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -3,11 +3,13 @@ import Foundation extension URL { // TODO: This is incomplete - static func googleSignInAuthURL(clientId: String) throws -> URL { + static func googleSignInAuthURL(clientId: String, pkce: ProofKeyForCodeExchange) throws -> URL { let baseURL = "https://accounts.google.com/o/oauth2/v2/auth" let queryItems = [ ("client_id", clientId), + ("code_challenge", pkce.codeCallenge), + ("code_challenge_method", pkce.mode.method), ("redirect_uri", redirectURI(from: clientId)), ("response_type", "code") ].map { URLQueryItem(name: $0.0, value: $0.1) } diff --git a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift index 45d7759b5..429a35f73 100644 --- a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift +++ b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift @@ -4,7 +4,11 @@ import XCTest class URLGoogleSignInTests: XCTestCase { func testGoogleSignInAuthURL() throws { - let url = try URL.googleSignInAuthURL(clientId: "123-abc245def.apps.googleusercontent.com") + let pkce = ProofKeyForCodeExchange(codeVerifier: "test", mode: .plain) + let url = try URL.googleSignInAuthURL( + clientId: "123-abc245def.apps.googleusercontent.com", + pkce: pkce + ) assert(url, matchesBaseURL: "https://accounts.google.com/o/oauth2/v2/auth") assertQueryItems( @@ -12,6 +16,16 @@ class URLGoogleSignInTests: XCTestCase { includeItemNamed: "client_id", withValue: "123-abc245def.apps.googleusercontent.com" ) + assertQueryItems( + for: url, + includeItemNamed: "code_challenge", + withValue: pkce.codeCallenge + ) + assertQueryItems( + for: url, + includeItemNamed: "code_challenge_method", + withValue: pkce.mode.method + ) assertQueryItems( for: url, includeItemNamed: "redirect_uri", From 7c8b61c5b49ed519292611214fdeaff297adddee Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 14:50:48 +0100 Subject: [PATCH 04/19] Add `scope` parameter to GoogleSignIn URL --- .../GoogleSignIn/URL+GoogleSignIn.swift | 14 +++++++++++++- .../GoogleSignIn/URL+GoogleSignInTests.swift | 6 +++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index 4f7ec6303..9ffd85d07 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -11,7 +11,19 @@ extension URL { ("code_challenge", pkce.codeCallenge), ("code_challenge_method", pkce.mode.method), ("redirect_uri", redirectURI(from: clientId)), - ("response_type", "code") + ("response_type", "code"), + // TODO: We might want to add some of these or them configurable + // + // The request we make with the SDK asks for: + // + // - email + // - profile + // - https://www.googleapis.com/auth/userinfo.email + // - https://www.googleapis.com/auth/userinfo.profile + // - openid + // + // See https://developers.google.com/identity/protocols/oauth2/scopes + ("scope", "https://www.googleapis.com/auth/userinfo.email") ].map { URLQueryItem(name: $0.0, value: $0.1) } if #available(iOS 16.0, *) { diff --git a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift index 429a35f73..3a8da64ca 100644 --- a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift +++ b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift @@ -31,8 +31,12 @@ class URLGoogleSignInTests: XCTestCase { includeItemNamed: "redirect_uri", withValue: "com.googleusercontent.apps.123-abc245def:/oauth2callback" ) + assertQueryItems( + for: url, + includeItemNamed: "scope", + withValue: "https://www.googleapis.com/auth/userinfo.email" + ) assertQueryItems(for: url, includeItemNamed: "response_type", withValue: "code") - // TODO: need to check more parameters } } From 6b1db9794fd6682d7c9cac3afb000cbed7a6df72 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 14:51:48 +0100 Subject: [PATCH 05/19] Add `FIXME` note regarding GoogleSignIn URL error handling --- WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index 9ffd85d07..c69e53253 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -27,8 +27,10 @@ extension URL { ].map { URLQueryItem(name: $0.0, value: $0.1) } if #available(iOS 16.0, *) { + // FIXME: Throw error return URL(string: baseURL)!.appending(queryItems: queryItems) } else { + // FIXME: Throw error var components = URLComponents(string: baseURL)! components.queryItems = queryItems return try components.asURL() From ccdfc0b1aa45944743281169ebc77981ae3ef83b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 15:33:51 +0100 Subject: [PATCH 06/19] Extract base URL for first step of GoogleSignIn flow and force unwrap it --- .../GoogleSignIn/URL+GoogleSignIn.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index c69e53253..98adcbf0c 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -2,10 +2,12 @@ import Foundation extension URL { + // It's acceptable to force-unwrap here because, for this call to fail we'd need a developer + // error, which we would catch because the unit tests would crash. + static var googleSignInBaseURL = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! + // TODO: This is incomplete static func googleSignInAuthURL(clientId: String, pkce: ProofKeyForCodeExchange) throws -> URL { - let baseURL = "https://accounts.google.com/o/oauth2/v2/auth" - let queryItems = [ ("client_id", clientId), ("code_challenge", pkce.codeCallenge), @@ -27,11 +29,10 @@ extension URL { ].map { URLQueryItem(name: $0.0, value: $0.1) } if #available(iOS 16.0, *) { - // FIXME: Throw error - return URL(string: baseURL)!.appending(queryItems: queryItems) + return googleSignInBaseURL.appending(queryItems: queryItems) } else { // FIXME: Throw error - var components = URLComponents(string: baseURL)! + var components = URLComponents(url: googleSignInBaseURL, resolvingAgainstBaseURL: false)! components.queryItems = queryItems return try components.asURL() } From b20fbe7dcd18fbccdad960cd4a2684ad7125adfb Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 15:34:38 +0100 Subject: [PATCH 07/19] Throw error in iOS <= 15.0 branch if `URLComponents` fails to init --- .../GoogleSignIn/URL+GoogleSignIn.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index 98adcbf0c..d3e9fdbf4 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -31,8 +31,16 @@ extension URL { if #available(iOS 16.0, *) { return googleSignInBaseURL.appending(queryItems: queryItems) } else { - // FIXME: Throw error - var components = URLComponents(url: googleSignInBaseURL, resolvingAgainstBaseURL: false)! + let baseURL = googleSignInBaseURL + guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { + throw URLError( + .unsupportedURL, + userInfo: [ + NSLocalizedDescriptionKey: "Could not create `URLComponents` instance from \(baseURL)" + ] + ) + } + components.queryItems = queryItems return try components.asURL() } From 5c5e1605a9efd7d62bfafdf703b7e78c9249d8e9 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 15:43:43 +0100 Subject: [PATCH 08/19] Add object modelling request body for OAuth token --- .../project.pbxproj | 8 ++++ .../OAuth/OAuthTokenRequestBody.swift | 37 +++++++++++++++++++ .../OAuth/OAuthTokenRequestBodyTests.swift | 26 +++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift create mode 100644 WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift diff --git a/WordPressAuthenticator.xcodeproj/project.pbxproj b/WordPressAuthenticator.xcodeproj/project.pbxproj index f96d3acfc..b868ab1ae 100644 --- a/WordPressAuthenticator.xcodeproj/project.pbxproj +++ b/WordPressAuthenticator.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 3F550D4E23DA429B007E5897 /* AppSelectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D4D23DA429B007E5897 /* AppSelectorTests.swift */; }; 3F550D5123DA4A9C007E5897 /* LinkMailPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D5023DA4A9C007E5897 /* LinkMailPresenter.swift */; }; 3F550D5323DA4AC6007E5897 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D5223DA4AC6007E5897 /* URLHandler.swift */; }; + 3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */; }; + 3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */; }; 3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */; }; 3FE8071529364C410088420C /* Result+ConvenienceInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */; }; 3FE80717293650190088420C /* Result+ConvenienceInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE80716293650190088420C /* Result+ConvenienceInit.swift */; }; @@ -238,6 +240,8 @@ 3F550D4D23DA429B007E5897 /* AppSelectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSelectorTests.swift; sourceTree = ""; }; 3F550D5023DA4A9C007E5897 /* LinkMailPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkMailPresenter.swift; sourceTree = ""; }; 3F550D5223DA4AC6007E5897 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = ""; }; + 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBody.swift; sourceTree = ""; }; + 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBodyTests.swift; sourceTree = ""; }; 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPrologueViewController.swift; sourceTree = ""; }; 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInitTests.swift"; sourceTree = ""; }; 3FE80716293650190088420C /* Result+ConvenienceInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInit.swift"; sourceTree = ""; }; @@ -484,6 +488,7 @@ children = ( 3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */, 3FE8071C293652BB0088420C /* OAuthError.swift */, + 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */, 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */, 3FE80716293650190088420C /* Result+ConvenienceInit.swift */, ); @@ -493,6 +498,7 @@ 3FE807192936504F0088420C /* OAuth */ = { isa = PBXGroup; children = ( + 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */, 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */, 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */, ); @@ -1301,6 +1307,7 @@ files = ( CE73475624B77A3800A22660 /* SiteCredentialsViewController.swift in Sources */, EE633D02287560E50002DE03 /* UITableView+Helpers.swift in Sources */, + 3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */, 982C8E7923021C20003F1BA0 /* LoginPrologueLoginMethodViewController.swift in Sources */, B5609144208A563800399AE4 /* LoginPrologueSignupMethodViewController.swift in Sources */, B56090D1208A4F5400399AE4 /* NUXViewController.swift in Sources */, @@ -1440,6 +1447,7 @@ 3FEC44F9293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift in Sources */, CE16177821B70C1A00B82A47 /* WordPressAuthenticatorDisplayTextTests.swift in Sources */, B501C048208FC79C00D1E58F /* LoginFacadeTests.m in Sources */, + 3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */, 3108613125AFA4830022F75E /* PasteboardTests.swift in Sources */, 3FE8071F2936558F0088420C /* URL+GoogleSignInTests.swift in Sources */, D85C36F0256E118D00D56E34 /* NavigationToEnterAccountTests.swift in Sources */, diff --git a/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift new file mode 100644 index 000000000..6aa4f432f --- /dev/null +++ b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift @@ -0,0 +1,37 @@ +/// Models the request to send for an OAuth token +/// +/// - Note: See documentation at https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code +struct OAuthTokenRequestBody: Encodable { + let clientId: String + let clientSecret: String + let code: String + let codeVerifier: String + let grantType: String + let redirectURI: String + + enum CodingKeys: String, CodingKey { + case clientId = "client_id" + case clientSecret = "client_secret" + case code + case codeVerifier = "code_verifier" + case grantType = "grant_type" + case redirectURI = "redirect_uri" + } + + func asURLEncodedData() -> Data { + let params = [ + (CodingKeys.clientId.rawValue, clientId), + (CodingKeys.clientSecret.rawValue, clientSecret), + (CodingKeys.code.rawValue, code), + (CodingKeys.codeVerifier.rawValue, codeVerifier), + (CodingKeys.grantType.rawValue, grantType), + (CodingKeys.redirectURI.rawValue, redirectURI), + ] + + let items = params.map { URLQueryItem(name: $0.0, value: $0.1) } + + var components = URLComponents() + components.queryItems = items + return components.query!.data(using: .utf8)! + } +} diff --git a/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift b/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift new file mode 100644 index 000000000..e52c3685b --- /dev/null +++ b/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift @@ -0,0 +1,26 @@ +@testable import WordPressAuthenticator +import XCTest + +class OAuthTokenRequestBodyTests: XCTestCase { + + func testURLEncodedDataConversion() throws { + let body = OAuthTokenRequestBody( + clientId: "clientId", + clientSecret: "clientSecret", + code: "codeValue", + codeVerifier: "codeVerifier", + grantType: "grantType", + redirectURI: "redirectUri" + ) + + let data = body.asURLEncodedData() + + let decodedData = try XCTUnwrap(String(data: data, encoding: .utf8)) + + XCTAssertTrue(decodedData.contains("client_id=clientId")) + XCTAssertTrue(decodedData.contains("client_secret=clientSecret")) + XCTAssertTrue(decodedData.contains("code_verifier=codeVerifier")) + XCTAssertTrue(decodedData.contains("grant_type=grantType")) + XCTAssertTrue(decodedData.contains("redirect_uri=redirectUri")) + } +} From d5bdf021016325f6c186e59e47054ffc36d8fd09 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 15:48:17 +0100 Subject: [PATCH 09/19] Throw errors from `asURLEncodedData()` --- WordPressAuthenticator/OAuth/OAuthError.swift | 9 +++++++++ .../OAuth/OAuthTokenRequestBody.swift | 13 +++++++++++-- .../OAuth/OAuthTokenRequestBodyTests.swift | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/WordPressAuthenticator/OAuth/OAuthError.swift b/WordPressAuthenticator/OAuth/OAuthError.swift index a3edf30da..af9244e15 100644 --- a/WordPressAuthenticator/OAuth/OAuthError.swift +++ b/WordPressAuthenticator/OAuth/OAuthError.swift @@ -1,11 +1,20 @@ enum OAuthError: LocalizedError { + // ASWebAuthenticationSession case inconsistentWebAuthenticationSessionCompletion + // OAuth token request + case failedToBuildURLQuery + case failedToEncodeURLQuery(query: String) + var errorDescription: String { switch self { case .inconsistentWebAuthenticationSessionCompletion: return "ASWebAuthenticationSession authentication finished with neither a callback URL nor error" + case .failedToBuildURLQuery: + return "Failed to build URL query string" + case .failedToEncodeURLQuery(let query): + return "Failed to encode URL query string '\(query)'" } } } diff --git a/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift index 6aa4f432f..840407b3d 100644 --- a/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift +++ b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift @@ -18,7 +18,7 @@ struct OAuthTokenRequestBody: Encodable { case redirectURI = "redirect_uri" } - func asURLEncodedData() -> Data { + func asURLEncodedData() throws -> Data { let params = [ (CodingKeys.clientId.rawValue, clientId), (CodingKeys.clientSecret.rawValue, clientSecret), @@ -32,6 +32,15 @@ struct OAuthTokenRequestBody: Encodable { var components = URLComponents() components.queryItems = items - return components.query!.data(using: .utf8)! + + guard let query = components.query else { + throw OAuthError.failedToBuildURLQuery + } + + guard let data = query.data(using: .utf8) else { + throw OAuthError.failedToEncodeURLQuery(query: query) + } + + return data } } diff --git a/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift b/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift index e52c3685b..c03b29f7b 100644 --- a/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift +++ b/WordPressAuthenticatorTests/OAuth/OAuthTokenRequestBodyTests.swift @@ -13,7 +13,7 @@ class OAuthTokenRequestBodyTests: XCTestCase { redirectURI: "redirectUri" ) - let data = body.asURLEncodedData() + let data = try body.asURLEncodedData() let decodedData = try XCTUnwrap(String(data: data, encoding: .utf8)) From 93c78bc7300a3de9fb1e0e3ad3473babee7a596b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 15:54:45 +0100 Subject: [PATCH 10/19] Add object modelling OAuth token response --- .../project.pbxproj | 4 ++++ .../OAuth/OAuthTokenResponseBody.swift | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 WordPressAuthenticator/OAuth/OAuthTokenResponseBody.swift diff --git a/WordPressAuthenticator.xcodeproj/project.pbxproj b/WordPressAuthenticator.xcodeproj/project.pbxproj index b868ab1ae..4083f9f37 100644 --- a/WordPressAuthenticator.xcodeproj/project.pbxproj +++ b/WordPressAuthenticator.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 3F550D5323DA4AC6007E5897 /* URLHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F550D5223DA4AC6007E5897 /* URLHandler.swift */; }; 3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */; }; 3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */; }; + 3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */; }; 3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */; }; 3FE8071529364C410088420C /* Result+ConvenienceInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */; }; 3FE80717293650190088420C /* Result+ConvenienceInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE80716293650190088420C /* Result+ConvenienceInit.swift */; }; @@ -242,6 +243,7 @@ 3F550D5223DA4AC6007E5897 /* URLHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLHandler.swift; sourceTree = ""; }; 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBody.swift; sourceTree = ""; }; 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBodyTests.swift; sourceTree = ""; }; + 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenResponseBody.swift; sourceTree = ""; }; 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPrologueViewController.swift; sourceTree = ""; }; 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInitTests.swift"; sourceTree = ""; }; 3FE80716293650190088420C /* Result+ConvenienceInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInit.swift"; sourceTree = ""; }; @@ -489,6 +491,7 @@ 3FE8071A2936515F0088420C /* ASWebAuthenticationSession+Utils.swift .swift */, 3FE8071C293652BB0088420C /* OAuthError.swift */, 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */, + 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */, 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */, 3FE80716293650190088420C /* Result+ConvenienceInit.swift */, ); @@ -1317,6 +1320,7 @@ CE1B18C920EEC2C200BECC3F /* SocialService.swift in Sources */, F12F9FB424D8A68E00771BCE /* AuthenticatorAnalyticsTracker.swift in Sources */, 988AD8A324CB839900BD045E /* TwoFAViewController.swift in Sources */, + 3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */, CE6BCD2E24A3A235001BCDC5 /* TextLabelTableViewCell.swift in Sources */, B56090D3208A4F5400399AE4 /* NUXLinkAuthViewController.swift in Sources */, B5609120208A555E00399AE4 /* SignupNavigationController.swift in Sources */, diff --git a/WordPressAuthenticator/OAuth/OAuthTokenResponseBody.swift b/WordPressAuthenticator/OAuth/OAuthTokenResponseBody.swift new file mode 100644 index 000000000..4348fbb1d --- /dev/null +++ b/WordPressAuthenticator/OAuth/OAuthTokenResponseBody.swift @@ -0,0 +1,23 @@ +/// Models the response to an OAuth token request. +/// +/// - Note: See documentation at https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code +struct OAuthTokenResponseBody: Decodable { + let accessToken: String + let expiresIn: Int + /// This value is only returned if the request included an identity scope, such as openid, profile, or email. + /// The value is a JSON Web Token (JWT) that contains digitally signed identity information about the user. + let idToken: String? + let refreshToken: String? + let scope: String + /// The type of token returned. At this time, this field's value is always set to Bearer. + let tokenType: String + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case expiresIn = "expires_in" + case idToken = "id_token" + case refreshToken = "refresh_token" + case scope + case tokenType = "token_type" + } +} From 6e34f3a06881f1226aa2fe9448e73d5ec296e285 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 16:28:45 +0100 Subject: [PATCH 11/19] Add method to create preconfigured `URLRequest` for OAuth token --- .../project.pbxproj | 8 ++++++ .../OAuth/URLRequest+OAuth.swift | 11 ++++++++ .../OAuth/URLRequest+OAuthTests.swift | 25 +++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 WordPressAuthenticator/OAuth/URLRequest+OAuth.swift create mode 100644 WordPressAuthenticatorTests/OAuth/URLRequest+OAuthTests.swift diff --git a/WordPressAuthenticator.xcodeproj/project.pbxproj b/WordPressAuthenticator.xcodeproj/project.pbxproj index 4083f9f37..fb3ae9e31 100644 --- a/WordPressAuthenticator.xcodeproj/project.pbxproj +++ b/WordPressAuthenticator.xcodeproj/project.pbxproj @@ -23,6 +23,8 @@ 3F879FD5293A3AB6005C2B48 /* OAuthTokenRequestBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */; }; 3F879FD7293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */; }; 3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */; }; + 3F879FDD293A500D005C2B48 /* URLRequest+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */; }; + 3F879FDF293A501D005C2B48 /* URLRequest+OAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */; }; 3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */; }; 3FE8071529364C410088420C /* Result+ConvenienceInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */; }; 3FE80717293650190088420C /* Result+ConvenienceInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE80716293650190088420C /* Result+ConvenienceInit.swift */; }; @@ -244,6 +246,8 @@ 3F879FD4293A3AB6005C2B48 /* OAuthTokenRequestBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBody.swift; sourceTree = ""; }; 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenRequestBodyTests.swift; sourceTree = ""; }; 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenResponseBody.swift; sourceTree = ""; }; + 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuth.swift"; sourceTree = ""; }; + 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuthTests.swift"; sourceTree = ""; }; 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPrologueViewController.swift; sourceTree = ""; }; 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInitTests.swift"; sourceTree = ""; }; 3FE80716293650190088420C /* Result+ConvenienceInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInit.swift"; sourceTree = ""; }; @@ -494,6 +498,7 @@ 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */, 3FEC44F6293A0E4600EBDECF /* ProofKeyForCodeExchange.swift */, 3FE80716293650190088420C /* Result+ConvenienceInit.swift */, + 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */, ); path = OAuth; sourceTree = ""; @@ -504,6 +509,7 @@ 3F879FD6293A44F2005C2B48 /* OAuthTokenRequestBodyTests.swift */, 3FEC44F8293A0F2900EBDECF /* ProofKeyForCodeExchangeTests.swift */, 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */, + 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */, ); path = OAuth; sourceTree = ""; @@ -1353,6 +1359,7 @@ 3FE8071D293652BB0088420C /* OAuthError.swift in Sources */, B5609119208A555600399AE4 /* SiteInfoHeaderView.swift in Sources */, B560913E208A563800399AE4 /* SigninEditingState.swift in Sources */, + 3F879FDD293A500D005C2B48 /* URLRequest+OAuth.swift in Sources */, CE2D03E024E5DD4500D18942 /* UnifiedSignupViewController.swift in Sources */, 98CF18F7248725370047B66C /* GoogleSignupConfirmationViewController.swift in Sources */, 1A21EE9822832BC300C940C6 /* WordPressComOAuthClientFacade+Swift.swift in Sources */, @@ -1441,6 +1448,7 @@ B501C045208FC68700D1E58F /* LoginFieldsValidationTests.swift in Sources */, BA53D64824DFDF97001F1ABF /* WordPressSourceTagTests.swift in Sources */, 4A1DEF4B29341B1F00322608 /* LoggingTests.swift in Sources */, + 3F879FDF293A501D005C2B48 /* URLRequest+OAuthTests.swift in Sources */, D8610CEC2570A60C00A5DF27 /* NavigationToRootTests.swift in Sources */, BA53D64D24DFE4E6001F1ABF /* ModalViewControllerPresentingSpy.swift in Sources */, BA53D64624DFDE1D001F1ABF /* CredentialsTests.swift in Sources */, diff --git a/WordPressAuthenticator/OAuth/URLRequest+OAuth.swift b/WordPressAuthenticator/OAuth/URLRequest+OAuth.swift new file mode 100644 index 000000000..7710612a9 --- /dev/null +++ b/WordPressAuthenticator/OAuth/URLRequest+OAuth.swift @@ -0,0 +1,11 @@ +extension URLRequest { + + static func oauthTokenRequest(baseURL: URL) throws -> URLRequest { + var request = try URLRequest(url: baseURL, method: .post) + request.setValue( + "application/x-www-form-urlencoded; charset=UTF-8", + forHTTPHeaderField: "Content-Type" + ) + return request + } +} diff --git a/WordPressAuthenticatorTests/OAuth/URLRequest+OAuthTests.swift b/WordPressAuthenticatorTests/OAuth/URLRequest+OAuthTests.swift new file mode 100644 index 000000000..2ecc3590c --- /dev/null +++ b/WordPressAuthenticatorTests/OAuth/URLRequest+OAuthTests.swift @@ -0,0 +1,25 @@ +@testable import WordPressAuthenticator +import XCTest + +class URLRequestOAuthTokenRequestTests: XCTestCase { + + let testURL = URL(string: "https://test.com")! + + func testUsesGivenBaseURL() throws { + let request = try URLRequest.oauthTokenRequest(baseURL: testURL) + XCTAssertEqual(request.url, testURL) + } + + func testMethodPost() throws { + let request = try URLRequest.oauthTokenRequest(baseURL: testURL) + XCTAssertEqual(request.httpMethod, "POST") + } + + func testContentTypeFormURLEncoded() throws { + let request = try URLRequest.oauthTokenRequest(baseURL: testURL) + XCTAssertEqual( + request.value(forHTTPHeaderField: "Content-Type"), + "application/x-www-form-urlencoded; charset=UTF-8" + ) + } +} From 1798c5a1b6960e3bbaf2e1f134da9d6742327c72 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 2 Dec 2022 16:44:25 +0100 Subject: [PATCH 12/19] Add method to init `OAuthRequestBody` configured for GoogleSignIn --- .../project.pbxproj | 8 ++++++ .../OAuthRequestBody+GoogleSignIn.swift | 26 +++++++++++++++++++ .../GoogleSignIn/URL+GoogleSignIn.swift | 2 +- .../OAuthRequestBody+GoogleSignInTests.swift | 20 ++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift create mode 100644 WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift diff --git a/WordPressAuthenticator.xcodeproj/project.pbxproj b/WordPressAuthenticator.xcodeproj/project.pbxproj index fb3ae9e31..0cbb414c1 100644 --- a/WordPressAuthenticator.xcodeproj/project.pbxproj +++ b/WordPressAuthenticator.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 3F879FD9293A48B2005C2B48 /* OAuthTokenResponseBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */; }; 3F879FDD293A500D005C2B48 /* URLRequest+OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */; }; 3F879FDF293A501D005C2B48 /* URLRequest+OAuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */; }; + 3F879FE2293A53F5005C2B48 /* OAuthRequestBody+GoogleSignIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */; }; + 3F879FE4293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */; }; 3F9439BE27D6F9B60067183A /* LoginPrologueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */; }; 3FE8071529364C410088420C /* Result+ConvenienceInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */; }; 3FE80717293650190088420C /* Result+ConvenienceInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FE80716293650190088420C /* Result+ConvenienceInit.swift */; }; @@ -248,6 +250,8 @@ 3F879FD8293A48B2005C2B48 /* OAuthTokenResponseBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuthTokenResponseBody.swift; sourceTree = ""; }; 3F879FDC293A500D005C2B48 /* URLRequest+OAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuth.swift"; sourceTree = ""; }; 3F879FDE293A501D005C2B48 /* URLRequest+OAuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLRequest+OAuthTests.swift"; sourceTree = ""; }; + 3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthRequestBody+GoogleSignIn.swift"; sourceTree = ""; }; + 3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OAuthRequestBody+GoogleSignInTests.swift"; sourceTree = ""; }; 3F9439BD27D6F9B60067183A /* LoginPrologueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginPrologueViewController.swift; sourceTree = ""; }; 3FE8071429364C410088420C /* Result+ConvenienceInitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInitTests.swift"; sourceTree = ""; }; 3FE80716293650190088420C /* Result+ConvenienceInit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Result+ConvenienceInit.swift"; sourceTree = ""; }; @@ -517,6 +521,7 @@ 3FE8072229365F740088420C /* GoogleSignIn */ = { isa = PBXGroup; children = ( + 3F879FE0293A53CB005C2B48 /* OAuthRequestBody+GoogleSignIn.swift */, 3FE8072029365F6D0088420C /* URL+GoogleSignIn.swift */, ); path = GoogleSignIn; @@ -525,6 +530,7 @@ 3FE8072329365FC20088420C /* GoogleSignIn */ = { isa = PBXGroup; children = ( + 3F879FE3293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift */, 3FE8071E2936558F0088420C /* URL+GoogleSignInTests.swift */, ); path = GoogleSignIn; @@ -1380,6 +1386,7 @@ B56090F9208A533200399AE4 /* WordPressAuthenticator+Events.swift in Sources */, CEDE0D93242011E000CB3345 /* NSObject+Helpers.swift in Sources */, 020DEF6428AA091100C85D51 /* MagicLinkRequester.swift in Sources */, + 3F879FE2293A53F5005C2B48 /* OAuthRequestBody+GoogleSignIn.swift in Sources */, 020BE74A23B0BD2E007FE54C /* WordPressAuthenticatorDisplayImages.swift in Sources */, B560913A208A563800399AE4 /* LoginLinkRequestViewController.swift in Sources */, B560910C208A54F800399AE4 /* WordPressComOAuthClientFacade.m in Sources */, @@ -1466,6 +1473,7 @@ D85C36E6256E0DDE00D56E34 /* NavigationToEnterSiteTests.swift in Sources */, D85C3882256E3FEC00D56E34 /* WordPressComSiteInfoTests.swift in Sources */, D8611A672576236800A5DF27 /* NavigateBackTests.swift in Sources */, + 3F879FE4293A545C005C2B48 /* OAuthRequestBody+GoogleSignInTests.swift in Sources */, F12F9FB824D8A7FC00771BCE /* AnalyticsTrackerTests.swift in Sources */, B501C046208FC6A700D1E58F /* WordPressAuthenticatorTests.swift in Sources */, ); diff --git a/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift new file mode 100644 index 000000000..b96f13e65 --- /dev/null +++ b/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift @@ -0,0 +1,26 @@ +extension OAuthTokenRequestBody { + + static func googleSignInRequestBody( + clientId: String, + authCode: String, + pkce: ProofKeyForCodeExchange + ) -> Self { + .init( + clientId: clientId, + // "The client secret obtained from the API Console Credentials page." + // - https://developers.google.com/identity/protocols/oauth2/native-app#step-2:-send-a-request-to-googles-oauth-2.0-server + // + // There doesn't seem to be any secret for iOS app credentials. + // The process works with an empty string... + clientSecret: "", + code: authCode, + codeVerifier: pkce.codeVerifier, + // TODO: This might be hardcoded... + // + // As defined in the OAuth 2.0 specification, this field's value must be set to authorization_code. + // – https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code + grantType: "authorization_code", + redirectURI: URL.redirectURI(from: clientId) + ) + } +} diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index d3e9fdbf4..bb9809e8b 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -46,7 +46,7 @@ extension URL { } } - private static func redirectURI(from clientId: String) -> String { + static func redirectURI(from clientId: String) -> String { // Google's client id is in the form: 123-abc245def.apps.googleusercontent.com // The redirect URI uses the reverse-DNS notation. let reverseDNSClientId = clientId.split(separator: ".").reversed().joined(separator: ".") diff --git a/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift b/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift new file mode 100644 index 000000000..76d9f6e1d --- /dev/null +++ b/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift @@ -0,0 +1,20 @@ +@testable import WordPressAuthenticator +import XCTest + +class OAuthRequestBodyGoogleSignInTests: XCTestCase { + + func testGoogleSignInTokenRequestBody() throws { + let pkce = ProofKeyForCodeExchange(codeVerifier: "test", mode: .plain) + let body = OAuthTokenRequestBody.googleSignInRequestBody( + clientId: "com.app.123-abc", + authCode: "codeValue", + pkce: pkce + ) + + XCTAssertEqual(body.clientId, "com.app.123-abc") + XCTAssertEqual(body.clientSecret, "") + XCTAssertEqual(body.codeVerifier, pkce.codeVerifier) + XCTAssertEqual(body.grantType, "authorization_code") + XCTAssertEqual(body.redirectURI, "123-abc.app.com:/oauth2callback") + } +} From 39efd42c8c798337fa2e72498f2b459fe26e936e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Dec 2022 22:45:06 +1100 Subject: [PATCH 13/19] Remove an outdated TODO note --- .../GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift index b96f13e65..20d9642d5 100644 --- a/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/OAuthRequestBody+GoogleSignIn.swift @@ -15,8 +15,6 @@ extension OAuthTokenRequestBody { clientSecret: "", code: authCode, codeVerifier: pkce.codeVerifier, - // TODO: This might be hardcoded... - // // As defined in the OAuth 2.0 specification, this field's value must be set to authorization_code. // – https://developers.google.com/identity/protocols/oauth2/native-app#exchange-authorization-code grantType: "authorization_code", From bccc9c338a15c945a2a725ea9b0b7705597b9009 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Dec 2022 22:45:57 +1100 Subject: [PATCH 14/19] Remove another outdated TODO note --- WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index bb9809e8b..be89a8b3a 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -6,7 +6,6 @@ extension URL { // error, which we would catch because the unit tests would crash. static var googleSignInBaseURL = URL(string: "https://accounts.google.com/o/oauth2/v2/auth")! - // TODO: This is incomplete static func googleSignInAuthURL(clientId: String, pkce: ProofKeyForCodeExchange) throws -> URL { let queryItems = [ ("client_id", clientId), From d749c80ccc24d17e9c9040ec0fb1ee2ea4abfa24 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 5 Jan 2023 13:42:19 +1100 Subject: [PATCH 15/19] Simplify `googleSignInAuthURL` method See also discussion at https://github.com/wordpress-mobile/WordPressAuthenticator-iOS/pull/717/files#r1043265177 --- .../GoogleSignIn/URL+GoogleSignIn.swift | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index be89a8b3a..aeaa5a5db 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -30,16 +30,9 @@ extension URL { if #available(iOS 16.0, *) { return googleSignInBaseURL.appending(queryItems: queryItems) } else { - let baseURL = googleSignInBaseURL - guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) else { - throw URLError( - .unsupportedURL, - userInfo: [ - NSLocalizedDescriptionKey: "Could not create `URLComponents` instance from \(baseURL)" - ] - ) - } - + // Given `googleSignInBaseURL` is assumed as a valid URL, a `URLComponents` instance + // should always be available. + var components = URLComponents(url: googleSignInBaseURL, resolvingAgainstBaseURL: false)! components.queryItems = queryItems return try components.asURL() } From d5c6fd082497b9431cf9e71b716d18a7dae69eb7 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 5 Jan 2023 13:48:55 +1100 Subject: [PATCH 16/19] Do not use Alamofire code in `URL+GoogleSignIn.swift` --- WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index aeaa5a5db..9ec042596 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -34,7 +34,10 @@ extension URL { // should always be available. var components = URLComponents(url: googleSignInBaseURL, resolvingAgainstBaseURL: false)! components.queryItems = queryItems - return try components.asURL() + // Likewise, we can as long as the given `queryItems` are valid, we can assume `url` to + // not be nil. If `queryItems` are invalid, a developer error has been committed, and + // crashing is appropriate. + return components.url! } } From e963a391ff863f090f1824d531e77beef3aaea02 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 5 Jan 2023 13:56:38 +1100 Subject: [PATCH 17/19] Add additional documentation references to PKCE type --- WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift index 009e7d411..35edd0db6 100644 --- a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift +++ b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift @@ -1,4 +1,6 @@ -// https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier +// See: +// - https://developers.google.com/identity/protocols/oauth2/native-app#step1-code-verifier +// - https://www.rfc-editor.org/rfc/rfc7636 // // FIXME: follow spec! // @@ -7,6 +9,8 @@ // characters and a maximum length of 128 characters. // // The code verifier should have enough entropy to make it impractical to guess the value. +// +// Note: The common abbreviation of "Proof Key for Code Exchange" is PKCE and is pronounced "pixy". struct ProofKeyForCodeExchange { enum Mode { From 8abbb29ce3a4e795bcbb01890fb1826a7623e7f6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 5 Jan 2023 14:07:29 +1100 Subject: [PATCH 18/19] Rename PKCE `Mode` to `Method` to be in line with spec This also resulted in renaming the computed variable `method` to `testMethodURLQueryParameterValue` because `Method.method` would have been confusing. The new name also better reflects where the value is meant to be used. --- .../GoogleSignIn/URL+GoogleSignIn.swift | 2 +- .../OAuth/ProofKeyForCodeExchange.swift | 12 ++++++------ .../OAuthRequestBody+GoogleSignInTests.swift | 2 +- .../GoogleSignIn/URL+GoogleSignInTests.swift | 4 ++-- .../OAuth/ProofKeyForCodeExchangeTests.swift | 10 +++++----- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift index 9ec042596..e0e463a70 100644 --- a/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift +++ b/WordPressAuthenticator/GoogleSignIn/URL+GoogleSignIn.swift @@ -10,7 +10,7 @@ extension URL { let queryItems = [ ("client_id", clientId), ("code_challenge", pkce.codeCallenge), - ("code_challenge_method", pkce.mode.method), + ("code_challenge_method", pkce.method.urlQueryParameterValue), ("redirect_uri", redirectURI(from: clientId)), ("response_type", "code"), // TODO: We might want to add some of these or them configurable diff --git a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift index 35edd0db6..c06f4bf13 100644 --- a/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift +++ b/WordPressAuthenticator/OAuth/ProofKeyForCodeExchange.swift @@ -13,11 +13,11 @@ // Note: The common abbreviation of "Proof Key for Code Exchange" is PKCE and is pronounced "pixy". struct ProofKeyForCodeExchange { - enum Mode { + enum Method { case s256 case plain - var method: String { + var urlQueryParameterValue: String { switch self { case .plain: return "plain" case .s256: return "S256" @@ -26,15 +26,15 @@ struct ProofKeyForCodeExchange { } let codeVerifier: String - let mode: Mode + let method: Method - init(codeVerifier: String, mode: Mode) { + init(codeVerifier: String, method: Method) { self.codeVerifier = codeVerifier - self.mode = mode + self.method = method } var codeCallenge: String { - switch mode { + switch method { case .s256: // TODO: code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) fatalError() diff --git a/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift b/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift index 76d9f6e1d..6b9467847 100644 --- a/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift +++ b/WordPressAuthenticatorTests/GoogleSignIn/OAuthRequestBody+GoogleSignInTests.swift @@ -4,7 +4,7 @@ import XCTest class OAuthRequestBodyGoogleSignInTests: XCTestCase { func testGoogleSignInTokenRequestBody() throws { - let pkce = ProofKeyForCodeExchange(codeVerifier: "test", mode: .plain) + let pkce = ProofKeyForCodeExchange(codeVerifier: "test", method: .plain) let body = OAuthTokenRequestBody.googleSignInRequestBody( clientId: "com.app.123-abc", authCode: "codeValue", diff --git a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift index 3a8da64ca..79bf5d387 100644 --- a/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift +++ b/WordPressAuthenticatorTests/GoogleSignIn/URL+GoogleSignInTests.swift @@ -4,7 +4,7 @@ import XCTest class URLGoogleSignInTests: XCTestCase { func testGoogleSignInAuthURL() throws { - let pkce = ProofKeyForCodeExchange(codeVerifier: "test", mode: .plain) + let pkce = ProofKeyForCodeExchange(codeVerifier: "test", method: .plain) let url = try URL.googleSignInAuthURL( clientId: "123-abc245def.apps.googleusercontent.com", pkce: pkce @@ -24,7 +24,7 @@ class URLGoogleSignInTests: XCTestCase { assertQueryItems( for: url, includeItemNamed: "code_challenge_method", - withValue: pkce.mode.method + withValue: pkce.method.urlQueryParameterValue ) assertQueryItems( for: url, diff --git a/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift index ef4025d6a..a546669ce 100644 --- a/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift +++ b/WordPressAuthenticatorTests/OAuth/ProofKeyForCodeExchangeTests.swift @@ -5,7 +5,7 @@ class ProofKeyForCodeExchangeTests: XCTestCase { func testCodeChallengeInPlainModeIsTheSameAsCodeVerifier() { XCTAssertEqual( - ProofKeyForCodeExchange(codeVerifier: "abc", mode: .plain).codeCallenge, + ProofKeyForCodeExchange(codeVerifier: "abc", method: .plain).codeCallenge, "abc" ) } @@ -14,11 +14,11 @@ class ProofKeyForCodeExchangeTests: XCTestCase { // TODO: } - func testModePlainMethod() { - XCTAssertEqual(ProofKeyForCodeExchange.Mode.plain.method, "plain") + func testMethodURLQueryParameterValuePlain() { + XCTAssertEqual(ProofKeyForCodeExchange.Method.plain.urlQueryParameterValue, "plain") } - func testModeS256Method() { - XCTAssertEqual(ProofKeyForCodeExchange.Mode.s256.method, "S256") + func testMethodURLQueryParameterValueS256() { + XCTAssertEqual(ProofKeyForCodeExchange.Method.s256.urlQueryParameterValue, "S256") } } From 8b6f6a89dd39665b903b73871e6f5c327f30770b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 5 Jan 2023 14:38:05 +1100 Subject: [PATCH 19/19] Remove redundant error handling from `asURLEncodedData` See discussion at https://github.com/wordpress-mobile/WordPressAuthenticator-iOS/pull/717/files#r1045479156 --- WordPressAuthenticator/OAuth/OAuthError.swift | 8 -------- .../OAuth/OAuthTokenRequestBody.swift | 11 ++--------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/WordPressAuthenticator/OAuth/OAuthError.swift b/WordPressAuthenticator/OAuth/OAuthError.swift index af9244e15..3a4dc4b1e 100644 --- a/WordPressAuthenticator/OAuth/OAuthError.swift +++ b/WordPressAuthenticator/OAuth/OAuthError.swift @@ -3,18 +3,10 @@ enum OAuthError: LocalizedError { // ASWebAuthenticationSession case inconsistentWebAuthenticationSessionCompletion - // OAuth token request - case failedToBuildURLQuery - case failedToEncodeURLQuery(query: String) - var errorDescription: String { switch self { case .inconsistentWebAuthenticationSessionCompletion: return "ASWebAuthenticationSession authentication finished with neither a callback URL nor error" - case .failedToBuildURLQuery: - return "Failed to build URL query string" - case .failedToEncodeURLQuery(let query): - return "Failed to encode URL query string '\(query)'" } } } diff --git a/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift index 840407b3d..722faa419 100644 --- a/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift +++ b/WordPressAuthenticator/OAuth/OAuthTokenRequestBody.swift @@ -33,14 +33,7 @@ struct OAuthTokenRequestBody: Encodable { var components = URLComponents() components.queryItems = items - guard let query = components.query else { - throw OAuthError.failedToBuildURLQuery - } - - guard let data = query.data(using: .utf8) else { - throw OAuthError.failedToEncodeURLQuery(query: query) - } - - return data + // We can assume `query` to never be nil because we set `queryItems` in the line above. + return Data(components.query!.utf8) } }