|
5 | 5 | // Created by Achyut Kumar M on 21/03/24. |
6 | 6 | // |
7 | 7 |
|
8 | | -import Foundation |
| 8 | +import SwiftUI |
| 9 | +import SafariServices |
| 10 | +import osmapi |
| 11 | + |
| 12 | +struct OAuthViewController: UIViewControllerRepresentable { |
| 13 | + |
| 14 | + private struct OAuthServer { |
| 15 | + let authURL: String |
| 16 | + let apiURL: String |
| 17 | + let client_id: String |
| 18 | + } |
| 19 | + |
| 20 | + private let servers: [OAuthServer] = [ |
| 21 | + OAuthServer(authURL: "https://www.openstreetmap.org/", |
| 22 | + apiURL: "https://api.openstreetmap.org/", |
| 23 | + client_id: "oR9y-ytJ1O1OnM1hnPXc8WHjBwmephYdu3Az0a4rXNU"), |
| 24 | + |
| 25 | + OAuthServer(authURL: "https://master.apis.dev.openstreetmap.org/", |
| 26 | + apiURL: "https://api06.dev.openstreetmap.org/", |
| 27 | + client_id: "oR9y-ytJ1O1OnM1hnPXc8WHjBwmephYdu3Az0a4rXNU") |
| 28 | + ] |
| 29 | + |
| 30 | + private let client_secret = "eRY8q6sI5JDA3FU2iw5awIyXShuGUD6z2hL0YTNjfGg" |
| 31 | + private var server: OAuthServer { servers.first(where: { $0.apiURL == OSM_API_URL }) ?? servers[1] } |
| 32 | + var client_id: String { server.client_id } |
| 33 | + var serverURL: String { server.authURL } |
| 34 | + var oauthUrl: URL { return URL(string: serverURL)!.appendingPathComponent("oauth2") } |
| 35 | + |
| 36 | + private let redirect_uri = "goinfogame://oauth/callback" |
| 37 | + private let scope = "read_prefs write_prefs write_diary write_api write_notes write_redactions openid" |
| 38 | + |
| 39 | + let state = "random" |
| 40 | + |
| 41 | + func makeUIViewController(context: Context) -> SFSafariViewController { |
| 42 | + let url = url(withPath: "authorize", with: [ |
| 43 | + "client_id": client_id, |
| 44 | + "redirect_uri": redirect_uri, |
| 45 | + "response_type": "code", |
| 46 | + "scope": scope, |
| 47 | + "state": state |
| 48 | + ]) |
| 49 | + return SFSafariViewController(url: url) |
| 50 | + } |
| 51 | + |
| 52 | + func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { |
| 53 | + |
| 54 | + } |
| 55 | + |
| 56 | + func getAccessTokenFor(url: URL, completion: @escaping (String)-> Void) { |
| 57 | + |
| 58 | + guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { |
| 59 | + // apiCallback(.failure(OAuthError.badRedirectURL(url.absoluteString))); |
| 60 | + //TODO: Handle error |
| 61 | + return |
| 62 | + } |
| 63 | + guard |
| 64 | + let code = components.queryItems?.first(where: { $0.name == "code" })?.value, |
| 65 | + let state = components.queryItems?.first(where: { $0.name == "state" })?.value |
| 66 | + else { |
| 67 | + // handleError(from: components) |
| 68 | + return |
| 69 | + } |
| 70 | + guard state == self.state else { |
| 71 | + // apiCallback(.failure(OAuthError.stateMismatch)) |
| 72 | + //TODO: Handle error |
| 73 | + return |
| 74 | + } |
| 75 | + fetchAccessTokenFor(authCode: code, completion: completion) |
| 76 | + } |
| 77 | + |
| 78 | + func fetchAccessTokenFor(authCode:String, completion: @escaping (String)-> Void) { |
| 79 | + let postingJSON = [ |
| 80 | + "client_id": client_id, |
| 81 | + "redirect_uri": redirect_uri, |
| 82 | + "code": "\(authCode)", |
| 83 | + "grant_type": "authorization_code", |
| 84 | + "client_secret": client_secret, |
| 85 | + ] |
| 86 | + |
| 87 | + let postingBody = query(postingJSON).data(using: .utf8, allowLossyConversion: false) |
| 88 | + |
| 89 | + let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil) |
| 90 | + let url: String = "https://master.apis.dev.openstreetmap.org/oauth2/token" |
| 91 | + let request: NSMutableURLRequest = NSMutableURLRequest() |
| 92 | + request.url = NSURL(string: url) as URL? |
| 93 | + request.httpMethod = "POST" |
| 94 | + //add params to request |
| 95 | + request.httpBody = postingBody |
| 96 | + let dataTask = session.dataTask(with: request as URLRequest) { (data: Data?, response:URLResponse?, error: Error?) -> Void in |
| 97 | + if((error) != nil) { |
| 98 | + print(error!.localizedDescription) |
| 99 | + } else { |
| 100 | + print("Succes:") |
| 101 | + do { |
| 102 | + let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any] |
| 103 | + if let theAccessToken = parsedData["access_token"] as? String { |
| 104 | + let accessToken = theAccessToken |
| 105 | + _ = KeychainManager.save(key: "accessToken", data: accessToken) |
| 106 | + completion(accessToken) |
| 107 | + } |
| 108 | + } catch let error as NSError { |
| 109 | + print(error) |
| 110 | + } |
| 111 | + } |
| 112 | + } |
| 113 | + dataTask.resume() |
| 114 | + |
| 115 | + } |
| 116 | + |
| 117 | + public func query(_ parameters: [String: String]) -> String { |
| 118 | + var components: [(String, String)] = [] |
| 119 | + |
| 120 | + for key in parameters.keys.sorted(by: <) { |
| 121 | + let value = parameters[key]! |
| 122 | + components += [(key, escape(value))]//queryComponents(fromKey: key, value: value) |
| 123 | + } |
| 124 | + |
| 125 | + return components.map { "\($0)=\($1)" }.joined(separator: "&") |
| 126 | + } |
| 127 | + |
| 128 | + /// Function that uri encodes strings |
| 129 | + /// |
| 130 | + /// - Parameter string: un encoded uri query parameter |
| 131 | + /// - Returns: encoded parameter |
| 132 | + public func escape(_ string: String) -> String { |
| 133 | + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 |
| 134 | + let subDelimitersToEncode = "!$&'()*+,;=" |
| 135 | + |
| 136 | + var allowedCharacterSet = CharacterSet.urlQueryAllowed |
| 137 | + allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") |
| 138 | + |
| 139 | + var escaped = "" |
| 140 | + |
| 141 | + //========================================================================================================== |
| 142 | + // |
| 143 | + // Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few |
| 144 | + // hundred Chinese characters causes various malloc error crashes. To avoid this issue until iOS 8 is no |
| 145 | + // longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more |
| 146 | + // info, please refer to: |
| 147 | + // |
| 148 | + // - https://github.com/Alamofire/Alamofire/issues/206 |
| 149 | + // |
| 150 | + //========================================================================================================== |
| 151 | + |
| 152 | + if #available(iOS 8.3, *) { |
| 153 | + escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string |
| 154 | + } else { |
| 155 | + let batchSize = 50 |
| 156 | + var index = string.startIndex |
| 157 | + |
| 158 | + while index != string.endIndex { |
| 159 | + let startIndex = index |
| 160 | + let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex |
| 161 | + let range = startIndex..<endIndex |
| 162 | + |
| 163 | + let substring = string.substring(with: range) |
| 164 | + |
| 165 | + escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring |
| 166 | + |
| 167 | + index = endIndex |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + return escaped |
| 172 | + } |
| 173 | + |
| 174 | + private func url(withPath path: String, with dict: [String: String]) -> URL { |
| 175 | + var components = URLComponents(string: oauthUrl.appendingPathComponent(path).absoluteString)! |
| 176 | + components.queryItems = dict.map({ k, v in URLQueryItem(name: k, value: v) }) |
| 177 | + return components.url! |
| 178 | + } |
| 179 | +} |
| 180 | + |
| 181 | +extension URL { |
| 182 | + var queryParameters: [String: String]? { |
| 183 | + guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true), |
| 184 | + let queryItems = components.queryItems else { |
| 185 | + return nil |
| 186 | + } |
| 187 | + |
| 188 | + var parameters = [String: String]() |
| 189 | + queryItems.forEach { parameters[$0.name] = $0.value } |
| 190 | + return parameters |
| 191 | + } |
| 192 | +} |
0 commit comments