source/UberCore/Authentication/OAuthEndpoint.swift (125 lines of code) (raw):

// // OAuthEndpoint.swift // UberRides // // Copyright © 2017 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. /** OAuth endpoints. - ImplicitLogin: Used to login user and request access to specified scopes via implicit grant. - AuthorizationCodeLogin: Used to login user and request access to specified scopes via authorization code grant. - Refresh: Used to refresh an access token that has been aquired via SSO */ public enum OAuth: APIEndpoint { case implicitLogin(clientID: String, scopes: [UberScope], redirect: URL, requestUri: String? = nil) case authorizationCodeLogin(clientID: String, redirect: URL, scopes: [UberScope], state: String?, requestUri: String? = nil) case refresh(clientID: String, refreshToken: String) case par(clientID: String, loginHint: [String: String], responseType: ResponseType) public var method: UberHTTPMethod { switch self { case .implicitLogin, .authorizationCodeLogin: return .get case .refresh, .par: return .post } } public var host: String { OAuth.regionHost } public var body: Data? { switch self { case .refresh(let clientID, let refreshToken): let query = queryBuilder( ("client_id", clientID), ("refresh_token", refreshToken) ) var components = URLComponents() components.queryItems = query return components.query?.data(using: String.Encoding.utf8) case .par(let clientID, let loginHint, let responseType): let loginHintString = base64EncodedString(from: loginHint) ?? "" let query = queryBuilder( ("client_id", clientID), ("response_type", responseType.rawValue), ("login_hint", loginHintString) ) var components = URLComponents() components.queryItems = query return components.query?.data(using: String.Encoding.utf8) default: return nil } } static var regionHost: String { return "https://auth.uber.com" } public var path: String { switch self { case .implicitLogin: fallthrough case .authorizationCodeLogin: return "/oauth/v2/authorize" case .refresh: return "/oauth/v2/mobile/token" case .par: return "/oauth/v2/par" } } public var query: [URLQueryItem] { switch self { case .implicitLogin(let clientID, let scopes, let redirect, let requestUri): var loginQuery = baseLoginQuery(clientID, redirect: redirect, scopes: scopes) let additionalQueryItems = buildQueryItems([ ("response_type", ResponseType.token.rawValue), ("request_uri", requestUri) ]) loginQuery.append(contentsOf: additionalQueryItems) return loginQuery case .authorizationCodeLogin(let clientID, let redirect, let scopes, let state, let requestUri): var loginQuery = baseLoginQuery(clientID, redirect: redirect, scopes: scopes) let additionalQueryItems = buildQueryItems([ ("response_type", ResponseType.code.rawValue), ("state", state ?? ""), ("request_uri", requestUri) ]) loginQuery.append(contentsOf: additionalQueryItems) return loginQuery case .par: return queryBuilder() case .refresh: return queryBuilder() } } public var contentType: String? { switch self { case .implicitLogin, .authorizationCodeLogin, .refresh: return nil case .par: return "application/x-www-form-urlencoded" } } func baseLoginQuery(_ clientID: String, redirect: URL, scopes: [UberScope]) -> [URLQueryItem] { return queryBuilder( ("scope", scopes.toUberScopeString()), ("client_id", clientID), ("redirect_uri", redirect.absoluteString), ("signup_params", createSignupParameters())) } private func createSignupParameters() -> String { let signupParameters = [ "redirect_to_login" : true ] do { let json = try JSONSerialization.data(withJSONObject: signupParameters, options: JSONSerialization.WritingOptions(rawValue: 0)) return json.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters) } catch _ as NSError { return "" } } private func base64EncodedString(from dict: [String: String]) -> String? { (try? JSONSerialization.data(withJSONObject: dict))?.base64EncodedString() } private func buildQueryItems(_ items: [(String, String?)]) -> [URLQueryItem] { items.compactMap { pair -> [URLQueryItem]? in guard let value = pair.1 else { return nil } return self.queryBuilder((pair.0, value)) } .flatMap { $0 } } // MARK: - ResponseType public enum ResponseType: String { case code case token } }