|
| 1 | +// |
| 2 | +// OAuth1.swift |
| 3 | +// AlamofireOAuth1 |
| 4 | +// |
| 5 | +// Created by Hakon Hanesand on 4/27/15. |
| 6 | +// Copyright (c) 2015 Hakon Hanesand. All rights reserved. |
| 7 | + |
| 8 | +import Alamofire |
| 9 | +import CommonCrypto |
| 10 | + |
| 11 | +extension Dictionary { |
| 12 | + mutating func merge<K, V>(dict: [K: V]){ |
| 13 | + for (k, v) in dict { |
| 14 | + self.updateValue(v as! Value, forKey: k as! Key) |
| 15 | + } |
| 16 | + } |
| 17 | +} |
| 18 | + |
| 19 | +//from http://stackoverflow.com/a/24888789/4080860 |
| 20 | +extension String { |
| 21 | + func stringByAddingPercentEncodingForFormUrlencoded() -> String? { |
| 22 | + let characterSet = NSMutableCharacterSet.alphanumericCharacterSet() |
| 23 | + characterSet.addCharactersInString("-._* ") |
| 24 | + |
| 25 | + return stringByAddingPercentEncodingWithAllowedCharacters(characterSet)?.stringByReplacingOccurrencesOfString(" ", withString: "+") |
| 26 | + } |
| 27 | + |
| 28 | + func indexOf(target: String) -> Int { |
| 29 | + var range = self.rangeOfString(target) |
| 30 | + |
| 31 | + if let range = range { |
| 32 | + return distance(self.startIndex, range.startIndex) |
| 33 | + } else { |
| 34 | + return -1 |
| 35 | + } |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +public enum Factual: URLRequestConvertible { |
| 40 | + |
| 41 | + static let baseURLString = "http://api.v3.factual.com/t/" |
| 42 | + |
| 43 | + static let clientId = "n5md5zTCv67RV2ctEQKrhK2cAzggCqs3khynDhKT" |
| 44 | + static let clientSecret = "Utn7HYXJ77lW3fTYMFiB9Zvu0GjT1AInnjeqYFct" |
| 45 | + |
| 46 | + case GetBarcode(String) |
| 47 | + |
| 48 | + var method: Alamofire.Method { |
| 49 | + switch self { |
| 50 | + case .GetBarcode: |
| 51 | + return .GET |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + var path: String { |
| 56 | + switch self { |
| 57 | + case .GetBarcode: |
| 58 | + return "products-cpg" |
| 59 | + } |
| 60 | + } |
| 61 | + |
| 62 | + public var URLRequest: NSURLRequest { |
| 63 | + let URL = NSURL(string: Factual.baseURLString)! |
| 64 | + let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) |
| 65 | + mutableURLRequest.HTTPMethod = method.rawValue; |
| 66 | + var parameters = OAuth.constructOAuthParametersWith(ClientId: Factual.clientId, clientSecret: Factual.clientSecret, tokenId: nil, tokenSecret: nil) |
| 67 | + |
| 68 | + switch self { |
| 69 | + case .GetBarcode(let barcode): |
| 70 | + parameters.merge(["q" : barcode]) |
| 71 | + let request = OAuth.process(Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters)).0 |
| 72 | + |
| 73 | + //we want to build the OAuth signature base string described here http://nouncer.com/oauth/authentication.html |
| 74 | + |
| 75 | + let indexOfQueryParameters = request.URLString.rangeOfString("?")!.startIndex |
| 76 | + |
| 77 | + let urlString = request.URLString.substringToIndex(indexOfQueryParameters) |
| 78 | + let queryParameters = request.URLString.substringFromIndex(advance(indexOfQueryParameters, 1)) //exclude ? in string |
| 79 | + let encodedQueryParameters = queryParameters.stringByAddingPercentEncodingForFormUrlencoded()! |
| 80 | + |
| 81 | + let oauthBaseString = "\(Alamofire.Method.GET.rawValue)&\(urlString)&\(encodedQueryParameters)" |
| 82 | + |
| 83 | + let oauthRequestKey = "\(Factual.clientSecret)&" |
| 84 | + |
| 85 | + let key = oauthRequestKey.cStringUsingEncoding(NSUTF8StringEncoding) |
| 86 | + let text = oauthBaseString.cStringUsingEncoding(NSUTF8StringEncoding) |
| 87 | +// let digestLength = |
| 88 | + |
| 89 | + |
| 90 | + return request |
| 91 | + } |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +public extension NSMutableURLRequest { |
| 96 | + |
| 97 | + |
| 98 | +} |
| 99 | + |
| 100 | + |
| 101 | +public class OAuth { |
| 102 | + |
| 103 | + private struct Variables { //class vars not supported yet |
| 104 | + static let nonceHistory: Set<String> = [] |
| 105 | + static var previousTimestamp: String = "" |
| 106 | + static var timestamp: String = "" |
| 107 | + } |
| 108 | + |
| 109 | + private class var previousTimestamp: String { |
| 110 | + get {return Variables.previousTimestamp} |
| 111 | + set {Variables.previousTimestamp = newValue} |
| 112 | + } |
| 113 | + |
| 114 | + private class var timestamp: String { |
| 115 | + var time = timeval() |
| 116 | + gettimeofday(&time, nil) |
| 117 | + |
| 118 | + Variables.previousTimestamp = Variables.timestamp |
| 119 | + Variables.timestamp = "\(time.tv_sec)" |
| 120 | + return Variables.timestamp |
| 121 | + } |
| 122 | + |
| 123 | + private class var nonceHistory: Set<String> { |
| 124 | + return Variables.nonceHistory |
| 125 | + } |
| 126 | + |
| 127 | + public class func generateNonceAndTimestamp() -> (nonce: String, timestamp: String) { |
| 128 | + var nonce: String |
| 129 | + var timestamp: String |
| 130 | + |
| 131 | + do { |
| 132 | + timestamp = self.timestamp; |
| 133 | + |
| 134 | + let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
| 135 | + let length = UInt32(count(letters)) |
| 136 | + |
| 137 | + nonce = (0..<32).map { (_) in letters[advance(letters.startIndex, Int(arc4random_uniform(length)))]} |
| 138 | + .reduce("") { (string, char) in string + [char]} |
| 139 | + |
| 140 | + |
| 141 | + } while timestamp != previousTimestamp && nonceHistory.contains(nonce) |
| 142 | + |
| 143 | + return (nonce, timestamp) |
| 144 | + } |
| 145 | + |
| 146 | + struct Constants { |
| 147 | + static let OAuthVersionKey = "oauth_version" |
| 148 | + static let OAuthVersion = "1.0" |
| 149 | + |
| 150 | + static let OAuthConsumerIdentifierKey = "oauth_consumer_key" |
| 151 | + static let OAuthTokenIdentifierKey = "oauth_token" |
| 152 | + |
| 153 | + static let OAuthSignatureMethod = "HMAC-SHA1" |
| 154 | + static let OAuthSignatureMethodKey = "oauth_signature_method" |
| 155 | + |
| 156 | + static let OAuthTimestampKey = "oauth_timestamp" |
| 157 | + static let OAuthNonceKey = "oauth_nonce" |
| 158 | + } |
| 159 | + |
| 160 | + public class func constructOAuthParametersWith(ClientId clientId: String, clientSecret: String, tokenId: String?, tokenSecret: String?) -> [String : AnyObject] { |
| 161 | + var parameters = [String : AnyObject]() |
| 162 | + |
| 163 | + if let tokenId = tokenId { |
| 164 | + parameters.updateValue(tokenId, forKey: Constants.OAuthTokenIdentifierKey) |
| 165 | + } |
| 166 | + |
| 167 | + parameters.updateValue(Constants.OAuthVersion, forKey: Constants.OAuthVersionKey) |
| 168 | + parameters.updateValue(Constants.OAuthSignatureMethod, forKey: Constants.OAuthSignatureMethodKey) |
| 169 | + parameters.updateValue(clientId, forKey: Constants.OAuthConsumerIdentifierKey) |
| 170 | + |
| 171 | + let (nonce, timestamp) = OAuth.generateNonceAndTimestamp() |
| 172 | + parameters.updateValue(nonce, forKey: Constants.OAuthNonceKey) |
| 173 | + parameters.updateValue(timestamp, forKey: Constants.OAuthTimestampKey) |
| 174 | + |
| 175 | + return parameters |
| 176 | + } |
| 177 | + |
| 178 | + public class func process(tuple: (NSURLRequest, NSError?)) -> (NSURLRequest, NSError?) { |
| 179 | + let mutableCopy = tuple.0.mutableCopy() as! NSMutableURLRequest |
| 180 | + return (tuple.0, tuple.1) |
| 181 | + } |
| 182 | +} |
| 183 | + |
| 184 | +public enum OAuth1SignatureMethod { |
| 185 | + case PlainText |
| 186 | + case HMAC_SHA1 |
| 187 | +} |
| 188 | + |
0 commit comments