X-Git-Url: https://git.mdrn.pl/wl-app.git/blobdiff_plain/53b27422d140022594fc241cca91c3183be57bca..48b2fe9f7c2dc3d9aeaaa6dbfb27c7da4f3235ff:/iOS/WolneLektury/Connection/NetworkService.swift?ds=inline diff --git a/iOS/WolneLektury/Connection/NetworkService.swift b/iOS/WolneLektury/Connection/NetworkService.swift new file mode 100644 index 0000000..c95ba6c --- /dev/null +++ b/iOS/WolneLektury/Connection/NetworkService.swift @@ -0,0 +1,348 @@ +// +// 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"]))) + } + } + } + } +}