added iOS source code
[wl-app.git] / iOS / WolneLektury / Connection / NetworkService.swift
1 //
2 //  NetworkService.swift
3 //  WolneLektury
4 //
5 //  Created by Pawel Dabrowski on 30/05/2018.
6 //  Copyright © 2018 Fundacja Nowoczesna Polska. All rights reserved.
7 //
8
9 import UIKit
10 import Alamofire
11 import AlamofireActivityLogger
12 import OAuthSwift
13 import SwiftKeychainWrapper
14
15 enum Result<Model> {
16     case success(Model)
17     case failure(Error)
18 }
19
20 typealias ConnectionCompletionHandler = (_ result: Result<Any>) -> ()
21
22 let keychainWrapperCredentialString = "credential"
23
24 final class NetworkService {
25     
26     private let REQUEST_TOKEN_HEADER = "Token-Requested"
27     private let AUTH_REQUIRED_HEADER = "Authentication-Required"
28     private let AUTHORIZATION_HEADER = "Authorization"
29     
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
41     
42     // Temporarly token used only for request access token
43     private var oAuthRequestToken: OAuthTokenModel?
44     
45     // Place with sensitive data such as authentication tokens
46     private var credentials: Credentials
47
48     // Values used for OAuth authentication
49     private var signCredentials = OAuthSwiftCredential(consumerKey: Config.CONSUMER_KEY, consumerSecret: Config.CONSUMER_SECRET)
50     
51     let baseURL: String
52     
53     private var _privateManager: SessionManager?
54     var manager:SessionManager{
55         get{
56             if let manager = _privateManager{
57                 return manager
58             }
59             
60             let configuration = URLSessionConfiguration.default
61             configuration.timeoutIntervalForRequest = 60
62             configuration.timeoutIntervalForResource = 60
63             
64             _privateManager = Alamofire.SessionManager(configuration: configuration)
65             //            _privateManager!.retrier = self
66             //            _privateManager!.adapter = self
67             return _privateManager!
68         }
69         set(newVal){
70             _privateManager = manager
71         }
72     }
73     
74     init() {
75         credentials = (KeychainWrapper.standard.object(forKey: keychainWrapperCredentialString) as? Credentials) ?? Credentials()
76         self.baseURL = Config.BASE_URL
77         
78         if SharedGlobals.shared.isFirstUse(){
79             logout()
80         }
81         setSignCredentials(oauthToken: credentials.oauthTokenModel?.token ?? "", oauthTokenSecret: credentials.oauthTokenModel?.tokenSecret ?? "")
82     }
83
84     func logout(){
85         updateUserCredentials(oAuthTokenModel: nil)
86     }
87     
88     func isLoggedIn() -> Bool {
89         return credentials.isLoggedIn()
90     }
91     
92     func setSignCredentials(oauthToken: String, oauthTokenSecret: String){
93         signCredentials.oauthToken = oauthToken
94         signCredentials.oauthTokenSecret = oauthTokenSecret
95     }
96     
97     func updateUserCredentials(oAuthTokenModel: OAuthTokenModel?){
98         
99         credentials.oauthTokenModel = oAuthTokenModel
100         if let oAuthTokenModel = oAuthTokenModel, oAuthTokenModel.isValid(){
101             setSignCredentials(oauthToken: oAuthTokenModel.token, oauthTokenSecret: oAuthTokenModel.tokenSecret)
102         }
103         else{
104             setSignCredentials(oauthToken: "", oauthTokenSecret: "")
105         }
106         
107         KeychainWrapper.standard.set(credentials, forKey:keychainWrapperCredentialString)
108     }
109
110     
111     private func getNonce() -> String {
112         let uuid: String = UUID().uuidString
113         return uuid[0..<8]
114     }
115     
116     private func URLString(restAction: RestAction) ->String
117     {
118         let url = baseURL + restAction.endpoint
119         return url
120     }
121     
122     
123     func performRequest<responseModel: Decodable>(with action:RestAction, responseModelType: responseModel.Type, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?)
124     {
125         
126         performRequest(with: action, responseModelType: responseModelType, urlSuffix: "", params: params, completionHandler: completionHandler)
127     }
128     
129     func performRequest<responseModel: Decodable>(with action:RestAction, responseModelType: responseModel.Type, urlSuffix: String, params: Parameters? = nil, completionHandler: ConnectionCompletionHandler?)
130     {
131         var components: [(String, String)] = []
132         
133         let baseURLString = URLString(restAction: action) + urlSuffix
134
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)
139             }
140         }
141         
142         let componentsString = components.map { "\($0)=\($1)" }.joined(separator: "&")
143         var urlParameters = ""
144         if componentsString.count > 0{
145             urlParameters = urlParameters + "?" + componentsString
146         }
147
148         let url = baseURLString + urlParameters
149         
150         let method = action.httpMethod
151         
152         var headers: HTTPHeaders? = nil
153         let acceptableContentType = "application/json"
154         
155         if /*action.authenticationRequiredHeader || */isLoggedIn(){
156             
157             let baseUrl = URL(string: baseURLString)!
158
159             var oAuthParameters = getOAuthParams()
160             
161             if let params = params{
162                 for key in params.keys.sorted(by: <) {
163                     
164                     oAuthParameters[key] = params[key]!
165                 }
166             }
167            
168             let signature = signCredentials.signature(method: action.httpMethod == .post ? .POST : .GET, url: baseUrl, parameters: oAuthParameters)
169             
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 + "\"";
178             
179             headers = [
180                 "Accept": "application/json",
181             ]
182             
183             headers![AUTHORIZATION_HEADER] = authorizationString
184
185             print(authorizationString)
186         }
187         else{
188             headers = ["Accept": "application/json"]
189         }
190         
191         
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])
195             .validate()
196             .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
197             .responseJSON { response in
198                 
199                 guard response.error == nil else {
200                     completionHandler?(.failure(response.error!))
201                     return
202                 }
203                 
204                 if  let data = response.data,
205                     let model = try? JSONDecoder().decode(responseModel.self, from: data) {
206                     completionHandler?(.success(model))
207                 } else {
208                     completionHandler?(.failure(NSError(  domain: "wolnelektury",
209                                                           code: 1000,
210                                                           userInfo: ["error":"wrong model"])))
211                 }
212         }
213     }
214     
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
220     }
221     
222     func urlStringWithRequestTokenParameters(action: RestAction) -> String{
223         var url = URLString(restAction: action)
224         
225         let urlUrl = URL(string: url)!
226         
227         if action == .accessToken && oAuthRequestToken != nil{
228             signCredentials.oauthToken = oAuthRequestToken!.token
229             signCredentials.oauthTokenSecret = oAuthRequestToken!.tokenSecret
230         }
231         else{
232             signCredentials.oauthToken = ""
233             signCredentials.oauthTokenSecret = ""
234             oAuthRequestToken = nil
235         }
236         
237         var oAuthParameters =  getOAuthParams()
238         let signature = signCredentials.signature(method: .GET, url: urlUrl, parameters: oAuthParameters)
239         
240         url = url + "?" +
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()
247         
248         if action == .accessToken && oAuthRequestToken != nil{
249             url = url + "&" + OAUTH_ACCESS_TOKEN.urlEncoded() + "=" + oAuthRequestToken!.token.urlEncoded()
250         }
251         
252         return url
253     }
254     
255     func requestToken( completionHandler: ConnectionCompletionHandler?)
256     {
257         let action = RestAction.requestToken
258         let method = action.httpMethod
259
260         let acceptableContentType = "text/html"
261         let url = urlStringWithRequestTokenParameters(action: action)
262         print(url)
263         
264         let parameters:Parameters = [:]
265         let headers: HTTPHeaders? = nil
266         
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])
270             .validate()
271             .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
272             .response { response in
273                 
274                 guard response.error == nil else {
275                     completionHandler?(.failure(response.error!))
276                     return
277                 }
278                 
279                 if let data = response.data, let string = String(data: data, encoding: .utf8){
280                     print(string)
281                     
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)
286                     }
287                     
288                     if let tokenModel = tokenModel, tokenModel.isValid(){
289                         self.oAuthRequestToken = tokenModel
290                         completionHandler?(.success(tokenModel))
291                     }
292                     else{
293                         self.oAuthRequestToken = nil
294                         completionHandler?(.failure(NSError(  domain: "wolnelektury",
295                                                               code: 1000,
296                                                               userInfo: ["error":"wrong model"])))
297                     }
298                 }
299         }
300     }
301     
302     func requestAccessToken( completionHandler: ConnectionCompletionHandler?)
303     {
304         let action = RestAction.accessToken
305         let method = action.httpMethod
306         
307         let acceptableContentType = "text/html"
308         let url = urlStringWithRequestTokenParameters(action: action)
309         print(url)
310         
311         let parameters:Parameters = [:]
312         let headers: HTTPHeaders? = nil
313         
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])
317             .validate()
318             .log(level: .all, options: [.onlyDebug, .jsonPrettyPrint, .includeSeparator], printer: NativePrinter())
319             .response { response in
320                 
321                 guard response.error == nil else {
322                     completionHandler?(.failure(response.error!))
323                     return
324                 }
325                 
326                 if let data = response.data, let string = String(data: data, encoding: .utf8){
327                     print(string)
328                     
329                     let parameters = string.parametersFromQueryString
330
331                     var tokenModel: OAuthTokenModel?
332                     if let oauthToken = parameters["oauth_token"], let oauthTokenSecret = parameters["oauth_token_secret"]{
333                         tokenModel = OAuthTokenModel(token: oauthToken, tokenSecret: oauthTokenSecret)
334                     }
335                     
336                     if let tokenModel = tokenModel, tokenModel.isValid(){
337                         
338                         completionHandler?(.success(tokenModel))
339                     }
340                     else{
341                         completionHandler?(.failure(NSError(  domain: "wolnelektury",
342                                                               code: 1000,
343                                                               userInfo: ["error":"wrong model"])))
344                     }
345                 }
346         }
347     }
348 }