// // NetworkService.swift // WolneLektury // // Created by Pawel Dabrowski on 30/05/2018. // Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved. // import UIKit import Alamofire import AlamofireActivityLogger import OAuthSwift import SwiftKeychainWrapper enum Result { case success(Model) case failure(Error) } typealias ConnectionCompletionHandler = (_ result: Result) -> () let keychainWrapperCredentialString = "credential" final class NetworkService { private let REQUEST_TOKEN_HEADER = "Token-Requested" private let AUTH_REQUIRED_HEADER = "Authentication-Required" private let AUTHORIZATION_HEADER = "Authorization" private let OAUTH_REALM = "realm=\"API\", " private let OAUTH_CONSUMER_KEY = "oauth_consumer_key" private let OAUTH_NONCE = "oauth_nonce" private let OAUTH_SIGNATURE = "oauth_signature" private let OAUTH_SIGNATURE_METHOD = "oauth_signature_method" private let OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1" private let OAUTH_TIMESTAMP = "oauth_timestamp" private let OAUTH_ACCESS_TOKEN = "oauth_token" private let OAUTH_VERSION = "oauth_version" private let OAUTH_VERSION_VALUE = "1.0" private let ONE_SECOND = 1000 // Temporarly token used only for request access token private var oAuthRequestToken: OAuthTokenModel? // Place with sensitive data such as authentication tokens private var credentials: Credentials // Values used for OAuth authentication private var signCredentials = OAuthSwiftCredential(consumerKey: Config.CONSUMER_KEY, consumerSecret: Config.CONSUMER_SECRET) let baseURL: String private var _privateManager: SessionManager? var manager:SessionManager{ get{ if let manager = _privateManager{ return manager } let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 60 configuration.timeoutIntervalForResource = 60 _privateManager = Alamofire.SessionManager(configuration: configuration) // _privateManager!.retrier = self // _privateManager!.adapter = self return _privateManager! } set(newVal){ _privateManager = manager } } init() { credentials = (KeychainWrapper.standard.object(forKey: keychainWrapperCredentialString) as? Credentials) ?? Credentials() self.baseURL = Config.BASE_URL if SharedGlobals.shared.isFirstUse(){ logout() } setSignCredentials(oauthToken: credentials.oauthTokenModel?.token ?? "", oauthTokenSecret: credentials.oauthTokenModel?.tokenSecret ?? "") } func logout(){ updateUserCredentials(oAuthTokenModel: nil) } func isLoggedIn() -> Bool { return credentials.isLoggedIn() } func setSignCredentials(oauthToken: String, oauthTokenSecret: String){ signCredentials.oauthToken = oauthToken signCredentials.oauthTokenSecret = oauthTokenSecret } func updateUserCredentials(oAuthTokenModel: OAuthTokenModel?){ credentials.oauthTokenModel = oAuthTokenModel if let oAuthTokenModel = oAuthTokenModel, oAuthTokenModel.isValid(){ setSignCredentials(oauthToken: oAuthTokenModel.token, oauthTokenSecret: oAuthTokenModel.tokenSecret) } else{ setSignCredentials(oauthToken: "", oauthTokenSecret: "") } KeychainWrapper.standard.set(credentials, forKey:keychainWrapperCredentialString) } private func getNonce() -> String { let uuid: String = UUID().uuidString return uuid[0..<8] } private func URLString(restAction: RestAction) ->String { let url = baseURL + restAction.endpoint return url } func performRequest(with action:RestAction, responseModelType: responseModel.Type, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?) { performRequest(with: action, responseModelType: responseModelType, urlSuffix: "", params: params, completionHandler: completionHandler) } func performRequest(with action:RestAction, responseModelType: responseModel.Type, urlSuffix: String, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?) { var components: [(String, String)] = [] let baseURLString = URLString(restAction: action) + urlSuffix if let params = params{ for key in params.keys.sorted(by: <) { let value = params[key]! components += URLEncoding.default.queryComponents(fromKey: key, value: value) } } let componentsString = components.map { "\($0)=\($1)" }.joined(separator: "&") var urlParameters = "" if componentsString.count > 0{ urlParameters = urlParameters + "?" + componentsString } let url = baseURLString + urlParameters let method = action.httpMethod var headers: HTTPHeaders? = nil let acceptableContentType = "application/json" if /*action.authenticationRequiredHeader || */isLoggedIn(){ let baseUrl = URL(string: baseURLString)! var oAuthParameters = getOAuthParams() if let params = params{ for key in params.keys.sorted(by: <) { oAuthParameters[key] = params[key]! } } let signature = signCredentials.signature(method: action.httpMethod == .post ? .POST : .GET, url: baseUrl, parameters: oAuthParameters) var authorizationString = "OAuth " + OAUTH_REALM authorizationString += OAUTH_CONSUMER_KEY + "=\"" + ((oAuthParameters[OAUTH_CONSUMER_KEY] as? String) ?? "") + "\", " authorizationString += OAUTH_NONCE + "=\"" + ((oAuthParameters[OAUTH_NONCE] as? String) ?? "") + "\", " authorizationString += OAUTH_SIGNATURE + "=\"" + signature + "\", " authorizationString += OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", " authorizationString += OAUTH_TIMESTAMP + "=\"" + ((oAuthParameters[OAUTH_TIMESTAMP] as? String) ?? "") + "\", " authorizationString += OAUTH_ACCESS_TOKEN + "=\"" + signCredentials.oauthToken + "\", " authorizationString += OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + "\""; headers = [ "Accept": "application/json", ] headers![AUTHORIZATION_HEADER] = authorizationString print(authorizationString) } else{ headers = ["Accept": "application/json"] } let encoding : ParameterEncoding = URLEncoding.default //(method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default self.manager.request(url, method: method, parameters: nil, encoding: encoding, headers: headers) .validate(contentType: [acceptableContentType]) .validate() .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter()) .responseJSON { response in guard response.error == nil else { completionHandler?(.failure(response.error!)) return } if let data = response.data, let model = try? JSONDecoder().decode(responseModel.self, from: data) { completionHandler?(.success(model)) } else { completionHandler?(.failure(NSError( domain: "wolnelektury", code: 1000, userInfo: ["error":"wrong model"]))) } } } func getOAuthParams() -> [String: Any]{ let timestamp = String(Int64(Date().timeIntervalSince1970)) let nonce = OAuthSwiftCredential.generateNonce() let oAuthParameters = signCredentials.authorizationParameters(nil, timestamp: timestamp, nonce: nonce) return oAuthParameters } func urlStringWithRequestTokenParameters(action: RestAction) -> String{ var url = URLString(restAction: action) let urlUrl = URL(string: url)! if action == .accessToken && oAuthRequestToken != nil{ signCredentials.oauthToken = oAuthRequestToken!.token signCredentials.oauthTokenSecret = oAuthRequestToken!.tokenSecret } else{ signCredentials.oauthToken = "" signCredentials.oauthTokenSecret = "" oAuthRequestToken = nil } var oAuthParameters = getOAuthParams() let signature = signCredentials.signature(method: .GET, url: urlUrl, parameters: oAuthParameters) url = url + "?" + OAUTH_CONSUMER_KEY.urlEncoded() + "=" + Config.CONSUMER_KEY.urlEncoded() + "&" + OAUTH_NONCE.urlEncoded() + "=" + (oAuthParameters[OAUTH_NONCE] as! String).urlEncoded() + "&" + OAUTH_SIGNATURE_METHOD.urlEncoded() + "=" + OAUTH_SIGNATURE_METHOD_VALUE.urlEncoded() + "&" + OAUTH_TIMESTAMP.urlEncoded() + "=" + (oAuthParameters[OAUTH_TIMESTAMP] as! String) + "&" + OAUTH_VERSION.urlEncoded() + "=" + OAUTH_VERSION_VALUE.urlEncoded() + "&" + OAUTH_SIGNATURE.urlEncoded() + "=" + signature.urlEncoded() if action == .accessToken && oAuthRequestToken != nil{ url = url + "&" + OAUTH_ACCESS_TOKEN.urlEncoded() + "=" + oAuthRequestToken!.token.urlEncoded() } return url } func requestToken( completionHandler: ConnectionCompletionHandler?) { let action = RestAction.requestToken let method = action.httpMethod let acceptableContentType = "text/html" let url = urlStringWithRequestTokenParameters(action: action) print(url) let parameters:Parameters = [:] let headers: HTTPHeaders? = nil let encoding : ParameterEncoding = (method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default self.manager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers) .validate(contentType: [acceptableContentType]) .validate() .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter()) .response { response in guard response.error == nil else { completionHandler?(.failure(response.error!)) return } if let data = response.data, let string = String(data: data, encoding: .utf8){ print(string) var tokenModel: OAuthTokenModel? let parameters = string.parametersFromQueryString if let oauthToken = parameters["oauth_token"], let oauthTokenSecret = parameters["oauth_token_secret"] { tokenModel = OAuthTokenModel(token: oauthToken, tokenSecret: oauthTokenSecret) } if let tokenModel = tokenModel, tokenModel.isValid(){ self.oAuthRequestToken = tokenModel completionHandler?(.success(tokenModel)) } else{ self.oAuthRequestToken = nil completionHandler?(.failure(NSError( domain: "wolnelektury", code: 1000, userInfo: ["error":"wrong model"]))) } } } } func requestAccessToken( completionHandler: ConnectionCompletionHandler?) { let action = RestAction.accessToken let method = action.httpMethod let acceptableContentType = "text/html" let url = urlStringWithRequestTokenParameters(action: action) print(url) let parameters:Parameters = [:] let headers: HTTPHeaders? = nil let encoding : ParameterEncoding = (method == .get || method == .delete) ? URLEncoding.default : JSONEncoding.default self.manager.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers) .validate(contentType: [acceptableContentType]) .validate() .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter()) .response { response in guard response.error == nil else { completionHandler?(.failure(response.error!)) return } if let data = response.data, let string = String(data: data, encoding: .utf8){ print(string) let parameters = string.parametersFromQueryString var tokenModel: OAuthTokenModel? if let oauthToken = parameters["oauth_token"], let oauthTokenSecret = parameters["oauth_token_secret"]{ tokenModel = OAuthTokenModel(token: oauthToken, tokenSecret: oauthTokenSecret) } if let tokenModel = tokenModel, tokenModel.isValid(){ completionHandler?(.success(tokenModel)) } else{ completionHandler?(.failure(NSError( domain: "wolnelektury", code: 1000, userInfo: ["error":"wrong model"]))) } } } } }