Skip to content

Commit bbc6fa1

Browse files
author
Pascal Pfiffner
committed
Merge branch 'develop'
2 parents f328825 + 947522e commit bbc6fa1

File tree

9 files changed

+62
-50
lines changed

9 files changed

+62
-50
lines changed

CONTRIBUTORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Contributors
33

44
Contributors to the codebase, in reverse chronological order:
55

6+
- Seb Skuse, @sebskuse
67
- David Hardiman, @dhardiman
78
- Amaury David, @amaurydavid
89
- Lubbo, @lubbo

Sources/Base/OAuth2Base.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,15 +311,17 @@ open class OAuth2Base: OAuth2Securable {
311311
- returns: An OAuth2Error
312312
*/
313313
open func assureNoErrorInResponse(_ params: OAuth2JSON, fallback: String? = nil) throws {
314-
315-
// "error_description" is optional, we prefer it if it's present
316-
if let err_msg = params["error_description"] as? String, err_msg.count > 0 {
317-
throw OAuth2Error.responseError(err_msg)
314+
let err_code = params["error"] as? String
315+
let err_msg = params["error_description"] as? String
316+
317+
// "error_description" is optional, we use it if it's present and code is not.
318+
if let message = err_msg, message.count > 0 && (err_code?.isEmpty ?? true) {
319+
throw OAuth2Error.responseError(message)
318320
}
319-
321+
320322
// the "error" response is required for error responses, so it should be present
321-
if let err_code = params["error"] as? String {
322-
throw OAuth2Error.fromResponseError(err_code, fallback: fallback)
323+
if let code = err_code, code.count > 0 {
324+
throw OAuth2Error.fromResponseError(code, description: err_msg, fallback: fallback)
323325
}
324326
}
325327

Sources/Base/OAuth2Error.swift

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
9191
/// Unable to open the authorize URL.
9292
case unableToOpenAuthorizeURL
9393

94-
/// The request is invalid.
95-
case invalidRequest
94+
/// The request is invalid. Passes the underlying error_description.
95+
case invalidRequest(String?)
9696

9797
/// The request was canceled.
9898
case requestCancelled
@@ -103,7 +103,7 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
103103
/// There was no token type in the response.
104104
case noTokenType
105105

106-
/// The token type is not supported.
106+
/// The token type is not supported. Passes the underlying error_description.
107107
case unsupportedTokenType(String)
108108

109109
/// There was no data in the response.
@@ -130,30 +130,33 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
130130

131131
// MARK: - OAuth2 errors
132132

133-
/// The client is unauthorized (HTTP status 401).
134-
case unauthorizedClient
133+
/// The client is unauthorized (HTTP status 401). Passes the underlying error_description.
134+
case unauthorizedClient(String?)
135135

136136
/// The request was forbidden (HTTP status 403).
137137
case forbidden
138138

139139
/// Username or password was wrong (HTTP status 403 on password grant).
140140
case wrongUsernamePassword
141141

142-
/// Access was denied.
143-
case accessDenied
142+
/// Access was denied. Passes the underlying error_description.
143+
case accessDenied(String?)
144144

145-
/// Response type is not supported.
146-
case unsupportedResponseType
145+
/// Response type is not supported. Passes the underlying error_description.
146+
case unsupportedResponseType(String?)
147147

148-
/// Scope was invalid.
149-
case invalidScope
148+
/// Scope was invalid. Passes the underlying error_description.
149+
case invalidScope(String?)
150150

151151
/// A 500 was thrown.
152152
case serverError
153153

154-
/// The service is temporarily unavailable.
155-
case temporarilyUnavailable
156-
154+
/// The service is temporarily unavailable. Passes the underlying error_description.
155+
case temporarilyUnavailable(String?)
156+
157+
/// Invalid grant. Passes the underlying error_description.
158+
case invalidGrant(String?)
159+
157160
/// Other response error, as defined in its String.
158161
case responseError(String)
159162

@@ -162,27 +165,30 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
162165
Instantiate the error corresponding to the OAuth2 response code, if it is known.
163166

164167
- parameter code: The code, like "access_denied", that should be interpreted
168+
- parameter description: The description provided in the response
165169
- parameter fallback: The error string to use in case the error code is not known
166170
- returns: An appropriate OAuth2Error
167171
*/
168-
public static func fromResponseError(_ code: String, fallback: String? = nil) -> OAuth2Error {
172+
public static func fromResponseError(_ code: String, description: String? = nil, fallback: String? = nil) -> OAuth2Error {
169173
switch code {
170174
case "invalid_request":
171-
return .invalidRequest
175+
return .invalidRequest(description)
172176
case "unauthorized_client":
173-
return .unauthorizedClient
177+
return .unauthorizedClient(description)
174178
case "access_denied":
175-
return .accessDenied
179+
return .accessDenied(description)
176180
case "unsupported_response_type":
177-
return .unsupportedResponseType
181+
return .unsupportedResponseType(description)
178182
case "invalid_scope":
179-
return .invalidScope
183+
return .invalidScope(description)
180184
case "server_error":
181185
return .serverError
182186
case "temporarily_unavailable":
183-
return .temporarilyUnavailable
187+
return .temporarilyUnavailable(description)
188+
case "invalid_grant":
189+
return .invalidGrant(description)
184190
default:
185-
return .responseError(fallback ?? "Authorization error: \(code)")
191+
return .responseError(description ?? fallback ?? "Authorization error: \(code)")
186192
}
187193
}
188194

@@ -253,22 +259,24 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
253259
case .utf8DecodeError:
254260
return "Failed to decode given data as a UTF-8 string"
255261

256-
case .unauthorizedClient:
257-
return "Unauthorized"
262+
case .unauthorizedClient(let message):
263+
return message ?? "Unauthorized"
258264
case .forbidden:
259265
return "Forbidden"
260266
case .wrongUsernamePassword:
261267
return "The username or password is incorrect"
262-
case .accessDenied:
263-
return "The resource owner or authorization server denied the request."
264-
case .unsupportedResponseType:
265-
return "The authorization server does not support obtaining an access token using this method."
266-
case .invalidScope:
267-
return "The requested scope is invalid, unknown, or malformed."
268+
case .accessDenied(let message):
269+
return message ?? "The resource owner or authorization server denied the request."
270+
case .unsupportedResponseType(let message):
271+
return message ?? "The authorization server does not support obtaining an access token using this method."
272+
case .invalidScope(let message):
273+
return message ?? "The requested scope is invalid, unknown, or malformed."
268274
case .serverError:
269275
return "The authorization server encountered an unexpected condition that prevented it from fulfilling the request."
270-
case .temporarilyUnavailable:
271-
return "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
276+
case .temporarilyUnavailable(let message):
277+
return message ?? "The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server."
278+
case .invalidGrant(let message):
279+
return message ?? "The authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."
272280
case .responseError(let message):
273281
return message
274282
}
@@ -309,14 +317,15 @@ public enum OAuth2Error: Error, CustomStringConvertible, Equatable {
309317
case (.utf8EncodeError, .utf8EncodeError): return true
310318
case (.utf8DecodeError, .utf8DecodeError): return true
311319

312-
case (.unauthorizedClient, .unauthorizedClient): return true
320+
case (.unauthorizedClient(let lhm), .unauthorizedClient(let rhm)): return lhm == rhm
313321
case (.forbidden, .forbidden): return true
314322
case (.wrongUsernamePassword, .wrongUsernamePassword): return true
315-
case (.accessDenied, .accessDenied): return true
323+
case (.accessDenied(let lhm), .accessDenied(let rhm)): return lhm == rhm
316324
case (.unsupportedResponseType, .unsupportedResponseType): return true
317-
case (.invalidScope, .invalidScope): return true
325+
case (.invalidScope(let lhm), .invalidScope(let rhm)): return lhm == rhm
318326
case (.serverError, .serverError): return true
319-
case (.temporarilyUnavailable, .temporarilyUnavailable): return true
327+
case (.temporarilyUnavailable(let lhm), .temporarilyUnavailable(let rhm)): return lhm == rhm
328+
case (.invalidGrant(let lhm), .invalidGrant(let rhm)): return lhm == rhm
320329
case (.responseError(let lhm), .responseError(let rhm)): return lhm == rhm
321330
default: return false
322331
}

Sources/Base/OAuth2Response.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ open class OAuth2Response {
8686
throw error
8787
}
8888
else if 401 == response.statusCode {
89-
throw OAuth2Error.unauthorizedClient
89+
throw OAuth2Error.unauthorizedClient(nil)
9090
}
9191
else if 403 == response.statusCode {
9292
throw OAuth2Error.forbidden

Sources/DataLoader/OAuth2DataLoader.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ open class OAuth2DataLoader: OAuth2Requestable {
115115
super.perform(request: request) { response in
116116
do {
117117
if self.alsoIntercept403, 403 == response.response.statusCode {
118-
throw OAuth2Error.unauthorizedClient
118+
throw OAuth2Error.unauthorizedClient(nil)
119119
}
120120
let _ = try response.responseData()
121121
callback(response)

Tests/BaseTests/OAuth2Tests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class OAuth2Tests: XCTestCase {
134134

135135
func testQueryParamConversion() {
136136
let qry = OAuth2RequestParams.formEncodedQueryStringFor(["a": "AA", "b": "BB", "x": "yz"])
137-
XCTAssertEqual(14, qry.characters.count, "Expecting a 14 character string")
137+
XCTAssertEqual(14, qry.count, "Expecting a 14 character string")
138138

139139
let dict = OAuth2.params(fromQuery: qry)
140140
XCTAssertEqual(dict["a"]!, "AA", "Must unpack `a`")
@@ -144,7 +144,7 @@ class OAuth2Tests: XCTestCase {
144144

145145
func testQueryParamEncoding() {
146146
let qry = OAuth2RequestParams.formEncodedQueryStringFor(["uri": "https://api.io", "str": "a string: cool!", "num": "3.14159"])
147-
XCTAssertEqual(60, qry.characters.count, "Expecting a 60 character string")
147+
XCTAssertEqual(60, qry.count, "Expecting a 60 character string")
148148

149149
let dict = OAuth2.params(fromQuery: qry)
150150
XCTAssertEqual(dict["uri"]!, "https://api.io", "Must correctly unpack `uri`")

Tests/FlowTests/OAuth2CodeGrantTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class OAuth2CodeGrantTests: XCTestCase {
9494
XCTAssertNil(query["client_secret"], "Must not have `client_secret`")
9595
XCTAssertEqual(query["response_type"]!, "code", "Expecting correct `response_type`")
9696
XCTAssertEqual(query["redirect_uri"]!, "oauth2://callback", "Expecting correct `redirect_uri`")
97-
XCTAssertTrue(8 == (query["state"]!).characters.count, "Expecting an auto-generated UUID for `state`")
97+
XCTAssertTrue(8 == (query["state"]!).count, "Expecting an auto-generated UUID for `state`")
9898
}
9999

100100
func testRedirectURI() {

Tests/FlowTests/OAuth2DynRegTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class OAuth2DynRegTests: XCTestCase {
114114

115115
class OAuth2TestDynReg: OAuth2DynReg {
116116
override func register(client: OAuth2, callback: @escaping ((OAuth2JSON?, OAuth2Error?) -> Void)) {
117-
callback(nil, OAuth2Error.temporarilyUnavailable)
117+
callback(nil, OAuth2Error.temporarilyUnavailable(nil))
118118
}
119119
}
120120

Tests/FlowTests/OAuth2ImplicitGrantTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class OAuth2ImplicitGrantTests: XCTestCase
8080
oauth.didAuthorizeOrFail = { authParameters, error in
8181
XCTAssertNil(authParameters, "Nil auth dict expected")
8282
XCTAssertNotNil(error, "Error message expected")
83-
XCTAssertEqual(error, OAuth2Error.accessDenied)
83+
XCTAssertEqual(error, OAuth2Error.accessDenied(nil))
8484
XCTAssertEqual(error?.description, "The resource owner or authorization server denied the request.")
8585
}
8686
oauth.handleRedirectURL(URL(string: "https://auth.ful.io#error=access_denied")!)

0 commit comments

Comments
 (0)