2 // NetworkService.swift
5 // Created by Pawel Dabrowski on 30/05/2018.
6 // Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
11 import AlamofireActivityLogger
13 import SwiftKeychainWrapper
20 typealias ConnectionCompletionHandler = (_ result: Result<Any>) -> ()
22 let keychainWrapperCredentialString = "credential"
24 final class NetworkService {
26 private let REQUEST_TOKEN_HEADER = "Token-Requested"
27 private let AUTH_REQUIRED_HEADER = "Authentication-Required"
28 private let AUTHORIZATION_HEADER = "Authorization"
30 private let OAUTH_REALM = "realm=\"API\", "
31 private let OAUTH_CONSUMER_KEY = "oauth_consumer_key"
32 private let OAUTH_NONCE = "oauth_nonce"
33 private let OAUTH_SIGNATURE = "oauth_signature"
34 private let OAUTH_SIGNATURE_METHOD = "oauth_signature_method"
35 private let OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1"
36 private let OAUTH_TIMESTAMP = "oauth_timestamp"
37 private let OAUTH_ACCESS_TOKEN = "oauth_token"
38 private let OAUTH_VERSION = "oauth_version"
39 private let OAUTH_VERSION_VALUE = "1.0"
40 private let ONE_SECOND = 1000
42 // Temporarly token used only for request access token
43 private var oAuthRequestToken: OAuthTokenModel?
45 // Place with sensitive data such as authentication tokens
46 private var credentials: Credentials
48 // Values used for OAuth authentication
49 private var signCredentials = OAuthSwiftCredential(consumerKey: Config.CONSUMER_KEY, consumerSecret: Config.CONSUMER_SECRET)
53 private var _privateManager: SessionManager?
54 var manager:SessionManager{
56 if let manager = _privateManager{
60 let configuration = URLSessionConfiguration.default
61 configuration.timeoutIntervalForRequest = 60
62 configuration.timeoutIntervalForResource = 60
64 _privateManager = Alamofire.SessionManager(configuration: configuration)
65 // _privateManager!.retrier = self
66 // _privateManager!.adapter = self
67 return _privateManager!
70 _privateManager = manager
75 credentials = (KeychainWrapper.standard.object(forKey: keychainWrapperCredentialString) as? Credentials) ?? Credentials()
76 self.baseURL = Config.BASE_URL
78 if SharedGlobals.shared.isFirstUse(){
81 setSignCredentials(oauthToken: credentials.oauthTokenModel?.token ?? "", oauthTokenSecret: credentials.oauthTokenModel?.tokenSecret ?? "")
85 updateUserCredentials(oAuthTokenModel: nil)
88 func isLoggedIn() -> Bool {
89 return credentials.isLoggedIn()
92 func setSignCredentials(oauthToken: String, oauthTokenSecret: String){
93 signCredentials.oauthToken = oauthToken
94 signCredentials.oauthTokenSecret = oauthTokenSecret
97 func updateUserCredentials(oAuthTokenModel: OAuthTokenModel?){
99 credentials.oauthTokenModel = oAuthTokenModel
100 if let oAuthTokenModel = oAuthTokenModel, oAuthTokenModel.isValid(){
101 setSignCredentials(oauthToken: oAuthTokenModel.token, oauthTokenSecret: oAuthTokenModel.tokenSecret)
104 setSignCredentials(oauthToken: "", oauthTokenSecret: "")
107 KeychainWrapper.standard.set(credentials, forKey:keychainWrapperCredentialString)
111 private func getNonce() -> String {
112 let uuid: String = UUID().uuidString
116 private func URLString(restAction: RestAction) ->String
118 let url = baseURL + restAction.endpoint
123 func performRequest<responseModel: Decodable>(with action:RestAction, responseModelType: responseModel.Type, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?)
126 performRequest(with: action, responseModelType: responseModelType, urlSuffix: "", params: params, completionHandler: completionHandler)
129 func performRequest<responseModel: Decodable>(with action:RestAction, responseModelType: responseModel.Type, urlSuffix: String, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?)
131 var components: [(String, String)] = []
133 let baseURLString = URLString(restAction: action) + urlSuffix
135 if let params = params{
136 for key in params.keys.sorted(by: <) {
137 let value = params[key]!
138 components += URLEncoding.default.queryComponents(fromKey: key, value: value)
142 let componentsString = components.map { "\($0)=\($1)" }.joined(separator: "&")
143 var urlParameters = ""
144 if componentsString.count > 0{
145 urlParameters = urlParameters + "?" + componentsString
148 let url = baseURLString + urlParameters
150 let method = action.httpMethod
152 var headers: HTTPHeaders? = nil
153 let acceptableContentType = "application/json"
155 if /*action.authenticationRequiredHeader || */isLoggedIn(){
157 let baseUrl = URL(string: baseURLString)!
159 var oAuthParameters = getOAuthParams()
161 if let params = params{
162 for key in params.keys.sorted(by: <) {
164 oAuthParameters[key] = params[key]!
168 let signature = signCredentials.signature(method: action.httpMethod == .post ? .POST : .GET, url: baseUrl, parameters: oAuthParameters)
170 var authorizationString = "OAuth " + OAUTH_REALM
171 authorizationString += OAUTH_CONSUMER_KEY + "=\"" + ((oAuthParameters[OAUTH_CONSUMER_KEY] as? String) ?? "") + "\", "
172 authorizationString += OAUTH_NONCE + "=\"" + ((oAuthParameters[OAUTH_NONCE] as? String) ?? "") + "\", "
173 authorizationString += OAUTH_SIGNATURE + "=\"" + signature + "\", "
174 authorizationString += OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", "
175 authorizationString += OAUTH_TIMESTAMP + "=\"" + ((oAuthParameters[OAUTH_TIMESTAMP] as? String) ?? "") + "\", "
176 authorizationString += OAUTH_ACCESS_TOKEN + "=\"" + signCredentials.oauthToken + "\", "
177 authorizationString += OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + "\"";
180 "Accept": "application/json",
183 headers![AUTHORIZATION_HEADER] = authorizationString
185 print(authorizationString)
188 headers = ["Accept": "application/json"]
192 let encoding : ParameterEncoding = URLEncoding.default //(method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default
193 self.manager.request(url, method: method, parameters: nil, encoding: encoding, headers: headers)
194 .validate(contentType: [acceptableContentType])
196 .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
197 .responseJSON { response in
199 guard response.error == nil else {
200 completionHandler?(.failure(response.error!))
204 if let data = response.data,
205 let model = try? JSONDecoder().decode(responseModel.self, from: data) {
206 completionHandler?(.success(model))
208 completionHandler?(.failure(NSError( domain: "wolnelektury",
210 userInfo: ["error":"wrong model"])))
215 func getOAuthParams() -> [String: Any]{
216 let timestamp = String(Int64(Date().timeIntervalSince1970))
217 let nonce = OAuthSwiftCredential.generateNonce()
218 let oAuthParameters = signCredentials.authorizationParameters(nil, timestamp: timestamp, nonce: nonce)
219 return oAuthParameters
222 func urlStringWithRequestTokenParameters(action: RestAction) -> String{
223 var url = URLString(restAction: action)
225 let urlUrl = URL(string: url)!
227 if action == .accessToken && oAuthRequestToken != nil{
228 signCredentials.oauthToken = oAuthRequestToken!.token
229 signCredentials.oauthTokenSecret = oAuthRequestToken!.tokenSecret
232 signCredentials.oauthToken = ""
233 signCredentials.oauthTokenSecret = ""
234 oAuthRequestToken = nil
237 var oAuthParameters = getOAuthParams()
238 let signature = signCredentials.signature(method: .GET, url: urlUrl, parameters: oAuthParameters)
241 OAUTH_CONSUMER_KEY.urlEncoded() + "=" + Config.CONSUMER_KEY.urlEncoded() + "&" +
242 OAUTH_NONCE.urlEncoded() + "=" + (oAuthParameters[OAUTH_NONCE] as! String).urlEncoded() + "&" +
243 OAUTH_SIGNATURE_METHOD.urlEncoded() + "=" + OAUTH_SIGNATURE_METHOD_VALUE.urlEncoded() + "&" +
244 OAUTH_TIMESTAMP.urlEncoded() + "=" + (oAuthParameters[OAUTH_TIMESTAMP] as! String) + "&" +
245 OAUTH_VERSION.urlEncoded() + "=" + OAUTH_VERSION_VALUE.urlEncoded() + "&" +
246 OAUTH_SIGNATURE.urlEncoded() + "=" + signature.urlEncoded()
248 if action == .accessToken && oAuthRequestToken != nil{
249 url = url + "&" + OAUTH_ACCESS_TOKEN.urlEncoded() + "=" + oAuthRequestToken!.token.urlEncoded()
255 func requestToken( completionHandler: ConnectionCompletionHandler?)
257 let action = RestAction.requestToken
258 let method = action.httpMethod
260 let acceptableContentType = "text/html"
261 let url = urlStringWithRequestTokenParameters(action: action)
264 let parameters:Parameters = [:]
265 let headers: HTTPHeaders? = nil
267 let encoding : ParameterEncoding = (method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default
268 self.manager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
269 .validate(contentType: [acceptableContentType])
271 .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
272 .response { response in
274 guard response.error == nil else {
275 completionHandler?(.failure(response.error!))
279 if let data = response.data, let string = String(data: data, encoding: .utf8){
282 var tokenModel: OAuthTokenModel?
283 let parameters = string.parametersFromQueryString
284 if let oauthToken = parameters["oauth_token"], let oauthTokenSecret = parameters["oauth_token_secret"] {
285 tokenModel = OAuthTokenModel(token: oauthToken, tokenSecret: oauthTokenSecret)
288 if let tokenModel = tokenModel, tokenModel.isValid(){
289 self.oAuthRequestToken = tokenModel
290 completionHandler?(.success(tokenModel))
293 self.oAuthRequestToken = nil
294 completionHandler?(.failure(NSError( domain: "wolnelektury",
296 userInfo: ["error":"wrong model"])))
302 func requestAccessToken( completionHandler: ConnectionCompletionHandler?)
304 let action = RestAction.accessToken
305 let method = action.httpMethod
307 let acceptableContentType = "text/html"
308 let url = urlStringWithRequestTokenParameters(action: action)
311 let parameters:Parameters = [:]
312 let headers: HTTPHeaders? = nil
314 let encoding : ParameterEncoding = (method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default
315 self.manager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers)
316 .validate(contentType: [acceptableContentType])
318 .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
319 .response { response in
321 guard response.error == nil else {
322 completionHandler?(.failure(response.error!))
326 if let data = response.data, let string = String(data: data, encoding: .utf8){
329 let parameters = string.parametersFromQueryString
331 var tokenModel: OAuthTokenModel?
332 if let oauthToken = parameters["oauth_token"], let oauthTokenSecret = parameters["oauth_token_secret"]{
333 tokenModel = OAuthTokenModel(token: oauthToken, tokenSecret: oauthTokenSecret)
336 if let tokenModel = tokenModel, tokenModel.isValid(){
338 completionHandler?(.success(tokenModel))
341 completionHandler?(.failure(NSError( domain: "wolnelektury",
343 userInfo: ["error":"wrong model"])))