├── .gitignore ├── Cartfile ├── Cartfile.private ├── Cartfile.resolved ├── GAuth Example ├── AppDelegate.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── GAuth tvOS Example ├── AppDelegate.swift ├── Assets.xcassets │ ├── App Icon & Top Shelf Image.brandassets │ │ ├── App Icon - Large.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── App Icon - Small.imagestack │ │ │ ├── Back.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Front.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ │ └── Middle.imagestacklayer │ │ │ │ ├── Content.imageset │ │ │ │ └── Contents.json │ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Top Shelf Image.imageset │ │ │ └── Contents.json │ ├── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Info.plist └── ViewController.swift ├── GAuth.podspec ├── GAuth.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── GAuth-iOS.xcscheme │ ├── GAuth-tvOS.xcscheme │ └── GAuth-watchOS.xcscheme ├── GAuth.xcworkspace └── contents.xcworkspacedata ├── GAuthTests ├── GoogleAuthenticatorClientSpec.swift ├── GoogleAuthenticatorSpec.swift ├── Info.plist └── JSON Responses │ ├── authorize-valid.json │ └── oauth-request-valid.json ├── LICENSE ├── README.md ├── Sources ├── G-Auth.h ├── GoogleAuthenticatorClient.swift ├── Info-iOS.plist ├── Info-tvOS.plist ├── Info-watchOS.plist ├── Models │ ├── DeviceAuthorizationToken.swift │ ├── GoogleAuthenticatorError.swift │ └── OAuthIntegrationProtocols.swift └── Utilities │ ├── DispatchAfter.swift │ ├── Extensions.swift │ └── HTTPClient.swift └── makefile /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | Carthage/Checkouts 52 | Carthage/Build 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | fastlane/test_output 65 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "antitypical/Result" -------------------------------------------------------------------------------- /Cartfile.private: -------------------------------------------------------------------------------- 1 | github "Quick/Quick" 2 | github "Quick/Nimble" 3 | github "fabiomassimo/OAuthSwift" "master" 4 | github "AliSoftware/OHHTTPStubs" -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "Quick/Nimble" "v4.0.1" 2 | github "fabiomassimo/OAuthSwift" "144cb0128616f9b21b78f7c70969e94453a4f218" 3 | github "Quick/Quick" "v0.9.2" 4 | github "antitypical/Result" "2.1.0" 5 | github "AliSoftware/OHHTTPStubs" "5.2.0" 6 | -------------------------------------------------------------------------------- /GAuth Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GoogleAuthenticatorExample 4 | // 5 | // Created by Fabio Milano on 28/03/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import OAuthSwift 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | var window: UIWindow? 17 | 18 | 19 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 20 | // Override point for customization after application launch. 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(application: UIApplication) { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 27 | } 28 | 29 | func applicationDidEnterBackground(application: UIApplication) { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(application: UIApplication) { 35 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | func applicationDidBecomeActive(application: UIApplication) { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | func applicationWillTerminate(application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | 48 | 49 | func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool { 50 | OAuthSwift.handleOpenURL(url) 51 | 52 | return true 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /GAuth Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /GAuth Example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GAuth Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GAuth Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /GAuth Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // GoogleAuthenticatorExample 4 | // 5 | // Created by Fabio Milano on 28/03/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Result 11 | import GAuth 12 | 13 | class ViewController: UIViewController { 14 | 15 | private var authenticator = GoogleAuthenticatorClient(consumerKey: "***CONSUMER KEY***", consumerSecret:"***CONSUMER SECRET***", bundleIdentifier: NSBundle.mainBundle().bundleIdentifier!, scope: GoogleServiceScope.GoogleAnalyticsRead) 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | } 20 | 21 | override func viewDidAppear(animated: Bool) { 22 | super.viewDidAppear(animated) 23 | 24 | authenticator.authorize { (result) in 25 | switch result { 26 | case .Success( _): 27 | print("I have it") 28 | case .Failure(let error): 29 | print(error) 30 | } 31 | } 32 | } 33 | 34 | override func didReceiveMemoryWarning() { 35 | super.didReceiveMemoryWarning() 36 | // Dispose of any resources that can be recreated. 37 | } 38 | 39 | 40 | } 41 | 42 | import OAuthSwift 43 | 44 | extension OAuthSwiftCredential: AuthorizedToken { 45 | public var accessToken: String { 46 | return self.oauth_token 47 | } 48 | 49 | public var refreshToken: String? { 50 | return self.oauth_refresh_token 51 | } 52 | 53 | public var expiresIn: NSTimeInterval? { 54 | return self.expiresIn 55 | } 56 | 57 | public var isExpired: Bool { 58 | return self.isTokenExpired() 59 | } 60 | 61 | public var scopes: [String]? { 62 | return nil 63 | } 64 | } 65 | 66 | extension NSError: GoogleAuthenticatorErrorAdapter { 67 | public var googleAuthenticatorError: GoogleAuthenticatorError { 68 | switch self.code { 69 | case OAuthSwiftErrorCode.GeneralError.rawValue: 70 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 71 | case OAuthSwiftErrorCode.TokenExpiredError.rawValue: 72 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 73 | case OAuthSwiftErrorCode.MissingStateError.rawValue: 74 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 75 | case OAuthSwiftErrorCode.StateNotEqualError.rawValue: 76 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 77 | case OAuthSwiftErrorCode.ServerError.rawValue: 78 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 79 | case OAuthSwiftErrorCode.EncodingError.rawValue: 80 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 81 | case OAuthSwiftErrorCode.AuthorizationPending.rawValue: 82 | return GoogleAuthenticatorError.AuthorizationPending 83 | default: 84 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 85 | } 86 | } 87 | } 88 | 89 | struct OAuth2SwiftGoogleAuthenticator: GoogleAuthenticatorOAuthClient { 90 | typealias OAuthClientType = OAuth2SwiftGoogleAuthenticator 91 | typealias TokenType = OAuthSwiftCredential 92 | typealias Failure = NSError 93 | 94 | let oauthClient: OAuth2Swift 95 | 96 | /// The client ID. 97 | let clientID: String 98 | 99 | /// The client secret. 100 | let clientSecret: String 101 | 102 | /// The authorize URL. 103 | let authorizeURL: NSURL 104 | 105 | /// The token URL. 106 | let tokenURL: NSURL? 107 | 108 | /// The redirect URL. 109 | let redirectURL: NSURL? 110 | 111 | /// The token 112 | let token: TokenType? 113 | 114 | /// The scopes. 115 | let scopes: [String] 116 | 117 | static func Google(clientID clientID: String, clientSecret: String, bundleIdentifier: String, scope: GoogleServiceScope) -> OAuth2SwiftGoogleAuthenticator { 118 | let oauthClient = OAuth2Swift(consumerKey: clientID, consumerSecret: clientSecret, authorizeUrl: AuthenticationConstants.AuthorizeUrl.rawValue, accessTokenUrl: AuthenticationConstants.AccessTokensUrl.rawValue, responseType: "code") 119 | oauthClient.allowMissingStateCheck = true 120 | 121 | return OAuth2SwiftGoogleAuthenticator(oauthClient: oauthClient, clientID: clientID, clientSecret: clientSecret, authorizeURL: AuthenticationConstants.AuthorizeUrl.URL, tokenURL: AuthenticationConstants.AccessTokensUrl.URL, redirectURL:nil, token: nil, scopes: [ scope.string() ] ) 122 | } 123 | 124 | func googleAuthenticatorRefreshToken(completion: Result -> Void) { 125 | oauthClient.refreshToken { result in 126 | completion(result) 127 | } 128 | } 129 | 130 | func googleAuthenticatorAuthorize(completion: Result -> Void) { 131 | let callbackURL = NSURL(string: NSBundle.mainBundle().bundleIdentifier! + AuthenticationConstants.CallbackPostfix.rawValue)! 132 | 133 | oauthClient.authorizeWithCallbackURL(callbackURL, scope: scopes.joinWithSeparator(" "), state: "", success: { credential, response, parameters in 134 | completion(.Success(credential)) 135 | }) { error in 136 | completion(.Failure(error)) 137 | } 138 | } 139 | 140 | func googleAuthenticatorAuthorizeDeviceCode(deviceCode: String, completion: Result -> Void) { 141 | oauthClient.authorizeDeviceCode(deviceCode) { result in 142 | completion(result) 143 | } 144 | } 145 | } 146 | 147 | extension OAuth2Swift { 148 | public func refreshToken(completion: Result -> Void) { 149 | // Due to lack of a public function to renew the token we ask to authorize a dummy request in order to retrieve a renewed token from the `onTokenRenewal` closure. 150 | startAuthorizedRequest("http://requestToRefreshToken", method: OAuthSwiftHTTPRequest.Method.GET, parameters: [:], headers: nil, onTokenRenewal: { credential in 151 | completion(.Success(credential)) 152 | }, success: { (data, response) in 153 | // completion block already called in previous onTokenRenewal closure. 154 | }) { error in 155 | completion(.Failure(error)) 156 | } 157 | } 158 | 159 | public func authorizeDeviceCode(deviceCode: String, completion: Result -> Void) { 160 | authorizeDeviceToken(deviceCode, success: { credential in 161 | completion(.Success(credential)) 162 | }) { error in 163 | completion(.Failure(error)) 164 | } 165 | } 166 | } 167 | 168 | 169 | -------------------------------------------------------------------------------- /GAuth tvOS Example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GoogleAuthenticatorTV Example 4 | // 5 | // Created by Fabio Milano on 13/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Large.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Back.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers" : [ 3 | { 4 | "filename" : "Front.imagestacklayer" 5 | }, 6 | { 7 | "filename" : "Middle.imagestacklayer" 8 | }, 9 | { 10 | "filename" : "Back.imagestacklayer" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Front.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - Small.imagestack/Middle.imagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "assets" : [ 3 | { 4 | "size" : "1280x768", 5 | "idiom" : "tv", 6 | "filename" : "App Icon - Large.imagestack", 7 | "role" : "primary-app-icon" 8 | }, 9 | { 10 | "size" : "400x240", 11 | "idiom" : "tv", 12 | "filename" : "App Icon - Small.imagestack", 13 | "role" : "primary-app-icon" 14 | }, 15 | { 16 | "size" : "1920x720", 17 | "idiom" : "tv", 18 | "filename" : "Top Shelf Image.imageset", 19 | "role" : "top-shelf-image" 20 | } 21 | ], 22 | "info" : { 23 | "version" : 1, 24 | "author" : "xcode" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "tv", 5 | "scale" : "1x" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "landscape", 5 | "idiom" : "tv", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "9.0", 8 | "scale" : "1x" 9 | } 10 | ], 11 | "info" : { 12 | "version" : 1, 13 | "author" : "xcode" 14 | } 15 | } -------------------------------------------------------------------------------- /GAuth tvOS Example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 40 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /GAuth tvOS Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | arm64 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /GAuth tvOS Example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // GoogleAuthenticatorTV Example 4 | // 5 | // Created by Fabio Milano on 13/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GAuth 11 | import Result 12 | 13 | class ViewController: UIViewController { 14 | @IBOutlet weak var accessTokenLabel: UILabel! 15 | @IBOutlet weak var deviceCodeLabel: UILabel! 16 | @IBOutlet weak var verificationUrlLabel: UILabel! 17 | 18 | private var authenticator = GoogleAuthenticatorClient(consumerKey: "***CONSUMER KEY***", consumerSecret:"***CONSUMER SECRET***", bundleIdentifier: NSBundle.mainBundle().bundleIdentifier!, scope: GoogleServiceScope.GoogleAnalyticsRead) 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | accessTokenLabel.text = "" 23 | } 24 | 25 | override func viewDidAppear(animated: Bool) { 26 | super.viewDidAppear(animated) 27 | 28 | authenticator.authorizeDevice({ [unowned self] (verificationUrl, userCode) in 29 | self.verificationUrlLabel.text = verificationUrl.absoluteString 30 | self.deviceCodeLabel.text = userCode 31 | }) { [unowned self] (result) in 32 | switch result { 33 | case .Success(): 34 | self.accessTokenLabel.textColor = UIColor.greenColor() 35 | self.accessTokenLabel.text = "Authorized" 36 | case .Failure(let error): 37 | self.accessTokenLabel.textColor = UIColor.redColor() 38 | switch error{ 39 | case .DeviceVerificationFailed(let description): 40 | self.accessTokenLabel.text = description 41 | case .AuthorizationError(let description): 42 | self.accessTokenLabel.text = description 43 | case .InvalidAccessToken: 44 | self.accessTokenLabel.text = "Invalid access token" 45 | default: 46 | self.accessTokenLabel.text = "Loading..." 47 | } 48 | 49 | } 50 | } 51 | accessTokenLabel.text = "Loading..." 52 | } 53 | 54 | override func didReceiveMemoryWarning() { 55 | super.didReceiveMemoryWarning() 56 | // Dispose of any resources that can be recreated. 57 | } 58 | 59 | 60 | } 61 | 62 | import OAuthSwift 63 | 64 | extension OAuthSwiftCredential: AuthorizedToken { 65 | public var accessToken: String { 66 | return self.oauth_token 67 | } 68 | 69 | public var refreshToken: String? { 70 | return self.oauth_refresh_token 71 | } 72 | 73 | public var expiresIn: NSTimeInterval? { 74 | return self.expiresIn 75 | } 76 | 77 | public var isExpired: Bool { 78 | return self.isTokenExpired() 79 | } 80 | 81 | public var scopes: [String]? { 82 | return nil 83 | } 84 | } 85 | 86 | extension NSError: GoogleAuthenticatorErrorAdapter { 87 | public var googleAuthenticatorError: GoogleAuthenticatorError { 88 | switch self.code { 89 | case OAuthSwiftErrorCode.GeneralError.rawValue: 90 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 91 | case OAuthSwiftErrorCode.TokenExpiredError.rawValue: 92 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 93 | case OAuthSwiftErrorCode.MissingStateError.rawValue: 94 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 95 | case OAuthSwiftErrorCode.StateNotEqualError.rawValue: 96 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 97 | case OAuthSwiftErrorCode.ServerError.rawValue: 98 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 99 | case OAuthSwiftErrorCode.EncodingError.rawValue: 100 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 101 | case OAuthSwiftErrorCode.AuthorizationPending.rawValue: 102 | return GoogleAuthenticatorError.AuthorizationPending 103 | default: 104 | return GoogleAuthenticatorError.AuthorizationError(self.localizedDescription) 105 | } 106 | } 107 | } 108 | 109 | struct OAuth2SwiftGoogleAuthenticator: GoogleAuthenticatorOAuthClient { 110 | typealias OAuthClientType = OAuth2SwiftGoogleAuthenticator 111 | typealias TokenType = OAuthSwiftCredential 112 | typealias Failure = NSError 113 | 114 | let oauthClient: OAuth2Swift 115 | 116 | /// The client ID. 117 | let clientID: String 118 | 119 | /// The client secret. 120 | let clientSecret: String 121 | 122 | /// The authorize URL. 123 | let authorizeURL: NSURL 124 | 125 | /// The token URL. 126 | let tokenURL: NSURL? 127 | 128 | /// The redirect URL. 129 | let redirectURL: NSURL? 130 | 131 | /// The token 132 | let token: TokenType? 133 | 134 | /// The scopes. 135 | let scopes: [String] 136 | 137 | static func Google(clientID clientID: String, clientSecret: String, bundleIdentifier: String, scope: GoogleServiceScope) -> OAuth2SwiftGoogleAuthenticator { 138 | let oauthClient = OAuth2Swift(consumerKey: clientID, consumerSecret: clientSecret, authorizeUrl: AuthenticationConstants.AuthorizeUrl.rawValue, accessTokenUrl: AuthenticationConstants.AccessTokensUrl.rawValue, responseType: "http://oauth.net/grant_type/device/1.0") 139 | 140 | return OAuth2SwiftGoogleAuthenticator(oauthClient: oauthClient, clientID: clientID, clientSecret: clientSecret, authorizeURL: AuthenticationConstants.AuthorizeUrl.URL, tokenURL: AuthenticationConstants.AccessTokensUrl.URL, redirectURL:nil, token: nil, scopes: [ scope.string() ] ) 141 | } 142 | 143 | func googleAuthenticatorRefreshToken(completion: Result -> Void) { 144 | oauthClient.refreshToken { result in 145 | completion(result) 146 | } 147 | } 148 | 149 | func googleAuthenticatorAuthorizeDeviceCode(deviceCode: String, completion: Result -> Void) { 150 | oauthClient.authorizeDeviceCode(deviceCode) { result in 151 | completion(result) 152 | } 153 | } 154 | } 155 | 156 | extension OAuth2Swift { 157 | public func refreshToken(completion: Result -> Void) { 158 | // Due to lack of a public function to renew the token we ask to authorize a dummy request in order to retrieve a renewed token from the `onTokenRenewal` closure. 159 | startAuthorizedRequest("http://requestToRefreshToken", method: OAuthSwiftHTTPRequest.Method.GET, parameters: [:], headers: nil, onTokenRenewal: { credential in 160 | completion(.Success(credential)) 161 | }, success: { (data, response) in 162 | // completion block already called in previous onTokenRenewal closure. 163 | }) { error in 164 | completion(.Failure(error)) 165 | } 166 | } 167 | 168 | public func authorizeDeviceCode(deviceCode: String, completion: Result -> Void) { 169 | authorizeDeviceToken(deviceCode, success: { credential in 170 | completion(.Success(credential)) 171 | }) { error in 172 | completion(.Failure(error)) 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /GAuth.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = "GAuth" 3 | spec.version = "0.2.0" 4 | spec.summary = "Authentication to Google services made easy." 5 | spec.homepage = "https://www.github.com/fabiomassimo/GAuth" 6 | spec.license = { type: 'MIT', file: 'LICENSE' } 7 | spec.authors = { "Fabio Milano" => 'fabio@touchwonders.com' } 8 | spec.social_media_url = "http://twitter.com/iamfabiomilano" 9 | 10 | spec.ios.deployment_target = '9.0' 11 | spec.tvos.deployment_target = '9.0' 12 | spec.watchos.deployment_target = '2.0' 13 | 14 | spec.requires_arc = true 15 | spec.source = { git: "https://github.com/fabiomassimo/GAuth.git", tag: "#{spec.version}", submodules: true } 16 | spec.source_files = "Sources/**/*.{h,swift}" 17 | 18 | spec.dependency "Result", "~> 2.1" 19 | end -------------------------------------------------------------------------------- /GAuth.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E505F5BC1CA998BD003FD2A4 /* G-Auth.h in Headers */ = {isa = PBXBuildFile; fileRef = E505F5BB1CA998BD003FD2A4 /* G-Auth.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | E505F5CA1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E505F5C71CA99914003FD2A4 /* GoogleAuthenticatorClient.swift */; }; 12 | E505F5CB1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E505F5C71CA99914003FD2A4 /* GoogleAuthenticatorClient.swift */; }; 13 | E505F5CC1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E505F5C71CA99914003FD2A4 /* GoogleAuthenticatorClient.swift */; }; 14 | E505F5D61CA99A82003FD2A4 /* G-Auth.h in Headers */ = {isa = PBXBuildFile; fileRef = E505F5BB1CA998BD003FD2A4 /* G-Auth.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | E505F5D71CA99A83003FD2A4 /* G-Auth.h in Headers */ = {isa = PBXBuildFile; fileRef = E505F5BB1CA998BD003FD2A4 /* G-Auth.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | E53459F21CA91A72000990A6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53459F11CA91A72000990A6 /* AppDelegate.swift */; }; 17 | E53459F41CA91A72000990A6 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E53459F31CA91A72000990A6 /* ViewController.swift */; }; 18 | E53459F71CA91A72000990A6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E53459F51CA91A72000990A6 /* Main.storyboard */; }; 19 | E53459F91CA91A72000990A6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E53459F81CA91A72000990A6 /* Assets.xcassets */; }; 20 | E53459FC1CA91A72000990A6 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E53459FA1CA91A72000990A6 /* LaunchScreen.storyboard */; }; 21 | E5A1AE871D1369F000C3142F /* OAuthSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5A1AE861D1369F000C3142F /* OAuthSwift.framework */; }; 22 | E5B6FE671D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE661D0CBF1100B9641D /* GoogleAuthenticatorError.swift */; }; 23 | E5B6FE681D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE661D0CBF1100B9641D /* GoogleAuthenticatorError.swift */; }; 24 | E5B6FE691D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE661D0CBF1100B9641D /* GoogleAuthenticatorError.swift */; }; 25 | E5B6FE731D0CC98C00B9641D /* GAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E505F5B91CA998BD003FD2A4 /* GAuth.framework */; }; 26 | E5B6FE741D0CC98C00B9641D /* GAuth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E505F5B91CA998BD003FD2A4 /* GAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 27 | E5B6FE7B1D0CCF0400B9641D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7A1D0CCF0400B9641D /* Extensions.swift */; }; 28 | E5B6FE7C1D0CCF0400B9641D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7A1D0CCF0400B9641D /* Extensions.swift */; }; 29 | E5B6FE7D1D0CCF0400B9641D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7A1D0CCF0400B9641D /* Extensions.swift */; }; 30 | E5B6FE7F1D0CE63C00B9641D /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7E1D0CE63C00B9641D /* HTTPClient.swift */; }; 31 | E5B6FE801D0CE63C00B9641D /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7E1D0CE63C00B9641D /* HTTPClient.swift */; }; 32 | E5B6FE811D0CE63C00B9641D /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE7E1D0CE63C00B9641D /* HTTPClient.swift */; }; 33 | E5B6FE831D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE821D0CE8B000B9641D /* DeviceAuthorizationToken.swift */; }; 34 | E5B6FE841D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE821D0CE8B000B9641D /* DeviceAuthorizationToken.swift */; }; 35 | E5B6FE851D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE821D0CE8B000B9641D /* DeviceAuthorizationToken.swift */; }; 36 | E5B6FE871D0CF7C000B9641D /* DispatchAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE861D0CF7C000B9641D /* DispatchAfter.swift */; }; 37 | E5B6FE881D0CF7C000B9641D /* DispatchAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE861D0CF7C000B9641D /* DispatchAfter.swift */; }; 38 | E5B6FE891D0CF7C000B9641D /* DispatchAfter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE861D0CF7C000B9641D /* DispatchAfter.swift */; }; 39 | E5B6FE911D0ED3BE00B9641D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE901D0ED3BE00B9641D /* AppDelegate.swift */; }; 40 | E5B6FE931D0ED3BE00B9641D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FE921D0ED3BE00B9641D /* ViewController.swift */; }; 41 | E5B6FE961D0ED3BE00B9641D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E5B6FE941D0ED3BE00B9641D /* Main.storyboard */; }; 42 | E5B6FE981D0ED3BE00B9641D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5B6FE971D0ED3BE00B9641D /* Assets.xcassets */; }; 43 | E5B6FED91D0EDDCC00B9641D /* GAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E505F5AC1CA9986D003FD2A4 /* GAuth.framework */; }; 44 | E5B6FEDA1D0EDDCC00B9641D /* GAuth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E505F5AC1CA9986D003FD2A4 /* GAuth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 45 | E5B6FEFA1D0EE30400B9641D /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FEF81D0EE30400B9641D /* Result.framework */; }; 46 | E5B6FEFE1D0EE42100B9641D /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FEFC1D0EE42100B9641D /* Result.framework */; }; 47 | E5B6FF021D0EE42D00B9641D /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FF001D0EE42D00B9641D /* Result.framework */; }; 48 | E5B6FF331D0EF1F400B9641D /* GoogleAuthenticatorClientSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FF321D0EF1F400B9641D /* GoogleAuthenticatorClientSpec.swift */; }; 49 | E5B6FF351D0EF1F400B9641D /* GAuth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E505F5B91CA998BD003FD2A4 /* GAuth.framework */; }; 50 | E5B6FF3E1D0EF31E00B9641D /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FF3B1D0EF31E00B9641D /* Nimble.framework */; }; 51 | E5B6FF3F1D0EF31E00B9641D /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FF3C1D0EF31E00B9641D /* OHHTTPStubs.framework */; }; 52 | E5B6FF401D0EF31E00B9641D /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5B6FF3D1D0EF31E00B9641D /* Quick.framework */; }; 53 | E5B6FF431D0EFAB200B9641D /* authorize-valid.json in Resources */ = {isa = PBXBuildFile; fileRef = E5B6FF421D0EFAB200B9641D /* authorize-valid.json */; }; 54 | E5B6FF451D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FF441D0F000F00B9641D /* OAuthIntegrationProtocols.swift */; }; 55 | E5B6FF461D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FF441D0F000F00B9641D /* OAuthIntegrationProtocols.swift */; }; 56 | E5B6FF471D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5B6FF441D0F000F00B9641D /* OAuthIntegrationProtocols.swift */; }; 57 | E5DAAE8A1D1049780092169A /* GoogleAuthenticatorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DAAE891D1049780092169A /* GoogleAuthenticatorSpec.swift */; }; 58 | /* End PBXBuildFile section */ 59 | 60 | /* Begin PBXContainerItemProxy section */ 61 | E5B6FE751D0CC98C00B9641D /* PBXContainerItemProxy */ = { 62 | isa = PBXContainerItemProxy; 63 | containerPortal = E5B77F331CA9121500F8BDD3 /* Project object */; 64 | proxyType = 1; 65 | remoteGlobalIDString = E505F5B81CA998BD003FD2A4; 66 | remoteInfo = "GoogleAuthenticator-iOS"; 67 | }; 68 | E5B6FEDB1D0EDDCC00B9641D /* PBXContainerItemProxy */ = { 69 | isa = PBXContainerItemProxy; 70 | containerPortal = E5B77F331CA9121500F8BDD3 /* Project object */; 71 | proxyType = 1; 72 | remoteGlobalIDString = E505F5AB1CA9986D003FD2A4; 73 | remoteInfo = "GoogleAuthenticator-tvOS"; 74 | }; 75 | E5B6FF361D0EF1F400B9641D /* PBXContainerItemProxy */ = { 76 | isa = PBXContainerItemProxy; 77 | containerPortal = E5B77F331CA9121500F8BDD3 /* Project object */; 78 | proxyType = 1; 79 | remoteGlobalIDString = E505F5B81CA998BD003FD2A4; 80 | remoteInfo = "GoogleAuthenticator-iOS"; 81 | }; 82 | /* End PBXContainerItemProxy section */ 83 | 84 | /* Begin PBXCopyFilesBuildPhase section */ 85 | E5B6FE6C1D0CC7EB00B9641D /* CopyFiles */ = { 86 | isa = PBXCopyFilesBuildPhase; 87 | buildActionMask = 2147483647; 88 | dstPath = ""; 89 | dstSubfolderSpec = 10; 90 | files = ( 91 | ); 92 | runOnlyForDeploymentPostprocessing = 0; 93 | }; 94 | E5B6FE771D0CC98D00B9641D /* Embed Frameworks */ = { 95 | isa = PBXCopyFilesBuildPhase; 96 | buildActionMask = 2147483647; 97 | dstPath = ""; 98 | dstSubfolderSpec = 10; 99 | files = ( 100 | E5B6FE741D0CC98C00B9641D /* GAuth.framework in Embed Frameworks */, 101 | ); 102 | name = "Embed Frameworks"; 103 | runOnlyForDeploymentPostprocessing = 0; 104 | }; 105 | E5B6FEDD1D0EDDCC00B9641D /* Embed Frameworks */ = { 106 | isa = PBXCopyFilesBuildPhase; 107 | buildActionMask = 2147483647; 108 | dstPath = ""; 109 | dstSubfolderSpec = 10; 110 | files = ( 111 | E5B6FEDA1D0EDDCC00B9641D /* GAuth.framework in Embed Frameworks */, 112 | ); 113 | name = "Embed Frameworks"; 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | /* End PBXCopyFilesBuildPhase section */ 117 | 118 | /* Begin PBXFileReference section */ 119 | E505F59F1CA9985F003FD2A4 /* GAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 120 | E505F5AC1CA9986D003FD2A4 /* GAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 121 | E505F5B91CA998BD003FD2A4 /* GAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 122 | E505F5BB1CA998BD003FD2A4 /* G-Auth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "G-Auth.h"; sourceTree = ""; }; 123 | E505F5C71CA99914003FD2A4 /* GoogleAuthenticatorClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoogleAuthenticatorClient.swift; sourceTree = ""; }; 124 | E505F5D01CA9996D003FD2A4 /* Info-iOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-iOS.plist"; sourceTree = ""; }; 125 | E505F5D21CA99974003FD2A4 /* Info-tvOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-tvOS.plist"; sourceTree = ""; }; 126 | E505F5D41CA9997B003FD2A4 /* Info-watchOS.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-watchOS.plist"; sourceTree = ""; }; 127 | E53459EF1CA91A72000990A6 /* GAuth Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GAuth Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 128 | E53459F11CA91A72000990A6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 129 | E53459F31CA91A72000990A6 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 130 | E53459F61CA91A72000990A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 131 | E53459F81CA91A72000990A6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 132 | E53459FB1CA91A72000990A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 133 | E53459FD1CA91A72000990A6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 134 | E5A1AE861D1369F000C3142F /* OAuthSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OAuthSwift.framework; path = Carthage/Build/tvOS/OAuthSwift.framework; sourceTree = SOURCE_ROOT; }; 135 | E5B6FE661D0CBF1100B9641D /* GoogleAuthenticatorError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoogleAuthenticatorError.swift; sourceTree = ""; }; 136 | E5B6FE7A1D0CCF0400B9641D /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 137 | E5B6FE7E1D0CE63C00B9641D /* HTTPClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; 138 | E5B6FE821D0CE8B000B9641D /* DeviceAuthorizationToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceAuthorizationToken.swift; sourceTree = ""; }; 139 | E5B6FE861D0CF7C000B9641D /* DispatchAfter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchAfter.swift; sourceTree = ""; }; 140 | E5B6FE8E1D0ED3BD00B9641D /* GAuthTV Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "GAuthTV Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 141 | E5B6FE901D0ED3BE00B9641D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 142 | E5B6FE921D0ED3BE00B9641D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 143 | E5B6FE951D0ED3BE00B9641D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 144 | E5B6FE971D0ED3BE00B9641D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 145 | E5B6FE991D0ED3BE00B9641D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 146 | E5B6FEF81D0EE30400B9641D /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/tvOS/Result.framework; sourceTree = SOURCE_ROOT; }; 147 | E5B6FEFC1D0EE42100B9641D /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = SOURCE_ROOT; }; 148 | E5B6FF001D0EE42D00B9641D /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/watchOS/Result.framework; sourceTree = SOURCE_ROOT; }; 149 | E5B6FF301D0EF1F400B9641D /* GAuthTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GAuthTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 150 | E5B6FF321D0EF1F400B9641D /* GoogleAuthenticatorClientSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleAuthenticatorClientSpec.swift; sourceTree = ""; }; 151 | E5B6FF341D0EF1F400B9641D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 152 | E5B6FF3B1D0EF31E00B9641D /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = Carthage/Build/iOS/Nimble.framework; sourceTree = SOURCE_ROOT; }; 153 | E5B6FF3C1D0EF31E00B9641D /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = Carthage/Build/iOS/OHHTTPStubs.framework; sourceTree = SOURCE_ROOT; }; 154 | E5B6FF3D1D0EF31E00B9641D /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = Carthage/Build/iOS/Quick.framework; sourceTree = SOURCE_ROOT; }; 155 | E5B6FF421D0EFAB200B9641D /* authorize-valid.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "authorize-valid.json"; sourceTree = ""; }; 156 | E5B6FF441D0F000F00B9641D /* OAuthIntegrationProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuthIntegrationProtocols.swift; sourceTree = ""; }; 157 | E5DAAE891D1049780092169A /* GoogleAuthenticatorSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoogleAuthenticatorSpec.swift; sourceTree = ""; }; 158 | /* End PBXFileReference section */ 159 | 160 | /* Begin PBXFrameworksBuildPhase section */ 161 | E505F59B1CA9985F003FD2A4 /* Frameworks */ = { 162 | isa = PBXFrameworksBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | E5B6FF021D0EE42D00B9641D /* Result.framework in Frameworks */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | E505F5A81CA9986D003FD2A4 /* Frameworks */ = { 170 | isa = PBXFrameworksBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | E5B6FEFA1D0EE30400B9641D /* Result.framework in Frameworks */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | E505F5B51CA998BD003FD2A4 /* Frameworks */ = { 178 | isa = PBXFrameworksBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | E5B6FEFE1D0EE42100B9641D /* Result.framework in Frameworks */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | E53459EC1CA91A72000990A6 /* Frameworks */ = { 186 | isa = PBXFrameworksBuildPhase; 187 | buildActionMask = 2147483647; 188 | files = ( 189 | E5B6FE731D0CC98C00B9641D /* GAuth.framework in Frameworks */, 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | E5B6FE8B1D0ED3BD00B9641D /* Frameworks */ = { 194 | isa = PBXFrameworksBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | E5B6FED91D0EDDCC00B9641D /* GAuth.framework in Frameworks */, 198 | E5A1AE871D1369F000C3142F /* OAuthSwift.framework in Frameworks */, 199 | ); 200 | runOnlyForDeploymentPostprocessing = 0; 201 | }; 202 | E5B6FF2D1D0EF1F400B9641D /* Frameworks */ = { 203 | isa = PBXFrameworksBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | E5B6FF351D0EF1F400B9641D /* GAuth.framework in Frameworks */, 207 | E5B6FF3E1D0EF31E00B9641D /* Nimble.framework in Frameworks */, 208 | E5B6FF3F1D0EF31E00B9641D /* OHHTTPStubs.framework in Frameworks */, 209 | E5B6FF401D0EF31E00B9641D /* Quick.framework in Frameworks */, 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | /* End PBXFrameworksBuildPhase section */ 214 | 215 | /* Begin PBXGroup section */ 216 | E505F55E1CA9463D003FD2A4 /* Frameworks */ = { 217 | isa = PBXGroup; 218 | children = ( 219 | E5A1AE861D1369F000C3142F /* OAuthSwift.framework */, 220 | E5B6FF3B1D0EF31E00B9641D /* Nimble.framework */, 221 | E5B6FF3C1D0EF31E00B9641D /* OHHTTPStubs.framework */, 222 | E5B6FF3D1D0EF31E00B9641D /* Quick.framework */, 223 | E5B6FF001D0EE42D00B9641D /* Result.framework */, 224 | E5B6FEFC1D0EE42100B9641D /* Result.framework */, 225 | E5B6FEF81D0EE30400B9641D /* Result.framework */, 226 | ); 227 | path = Frameworks; 228 | sourceTree = ""; 229 | }; 230 | E505F5C61CA99914003FD2A4 /* Sources */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | E5B6FE791D0CCEF300B9641D /* Utilities */, 234 | E505F5BB1CA998BD003FD2A4 /* G-Auth.h */, 235 | E505F5D41CA9997B003FD2A4 /* Info-watchOS.plist */, 236 | E505F5D21CA99974003FD2A4 /* Info-tvOS.plist */, 237 | E505F5D01CA9996D003FD2A4 /* Info-iOS.plist */, 238 | E505F5C71CA99914003FD2A4 /* GoogleAuthenticatorClient.swift */, 239 | E505F5C81CA99914003FD2A4 /* Models */, 240 | ); 241 | path = Sources; 242 | sourceTree = ""; 243 | }; 244 | E505F5C81CA99914003FD2A4 /* Models */ = { 245 | isa = PBXGroup; 246 | children = ( 247 | E5B6FE661D0CBF1100B9641D /* GoogleAuthenticatorError.swift */, 248 | E5B6FE821D0CE8B000B9641D /* DeviceAuthorizationToken.swift */, 249 | E5B6FF441D0F000F00B9641D /* OAuthIntegrationProtocols.swift */, 250 | ); 251 | path = Models; 252 | sourceTree = ""; 253 | }; 254 | E53459F01CA91A72000990A6 /* GAuth Example */ = { 255 | isa = PBXGroup; 256 | children = ( 257 | E53459F11CA91A72000990A6 /* AppDelegate.swift */, 258 | E53459F31CA91A72000990A6 /* ViewController.swift */, 259 | E53459F51CA91A72000990A6 /* Main.storyboard */, 260 | E53459F81CA91A72000990A6 /* Assets.xcassets */, 261 | E53459FA1CA91A72000990A6 /* LaunchScreen.storyboard */, 262 | E53459FD1CA91A72000990A6 /* Info.plist */, 263 | ); 264 | path = "GAuth Example"; 265 | sourceTree = ""; 266 | }; 267 | E5B6FE791D0CCEF300B9641D /* Utilities */ = { 268 | isa = PBXGroup; 269 | children = ( 270 | E5B6FE7A1D0CCF0400B9641D /* Extensions.swift */, 271 | E5B6FE7E1D0CE63C00B9641D /* HTTPClient.swift */, 272 | E5B6FE861D0CF7C000B9641D /* DispatchAfter.swift */, 273 | ); 274 | path = Utilities; 275 | sourceTree = ""; 276 | }; 277 | E5B6FE8F1D0ED3BE00B9641D /* GAuth tvOS Example */ = { 278 | isa = PBXGroup; 279 | children = ( 280 | E5B6FE901D0ED3BE00B9641D /* AppDelegate.swift */, 281 | E5B6FE921D0ED3BE00B9641D /* ViewController.swift */, 282 | E5B6FE941D0ED3BE00B9641D /* Main.storyboard */, 283 | E5B6FE971D0ED3BE00B9641D /* Assets.xcassets */, 284 | E5B6FE991D0ED3BE00B9641D /* Info.plist */, 285 | ); 286 | path = "GAuth tvOS Example"; 287 | sourceTree = ""; 288 | }; 289 | E5B6FF311D0EF1F400B9641D /* GAuthTests */ = { 290 | isa = PBXGroup; 291 | children = ( 292 | E5B6FF411D0EFA4200B9641D /* JSON Responses */, 293 | E5B6FF321D0EF1F400B9641D /* GoogleAuthenticatorClientSpec.swift */, 294 | E5B6FF341D0EF1F400B9641D /* Info.plist */, 295 | E5DAAE891D1049780092169A /* GoogleAuthenticatorSpec.swift */, 296 | ); 297 | path = GAuthTests; 298 | sourceTree = ""; 299 | }; 300 | E5B6FF411D0EFA4200B9641D /* JSON Responses */ = { 301 | isa = PBXGroup; 302 | children = ( 303 | E5B6FF421D0EFAB200B9641D /* authorize-valid.json */, 304 | ); 305 | path = "JSON Responses"; 306 | sourceTree = ""; 307 | }; 308 | E5B77F321CA9121500F8BDD3 = { 309 | isa = PBXGroup; 310 | children = ( 311 | E505F5C61CA99914003FD2A4 /* Sources */, 312 | E53459F01CA91A72000990A6 /* GAuth Example */, 313 | E5B6FE8F1D0ED3BE00B9641D /* GAuth tvOS Example */, 314 | E5B6FF311D0EF1F400B9641D /* GAuthTests */, 315 | E505F55E1CA9463D003FD2A4 /* Frameworks */, 316 | E5B77F3D1CA9121500F8BDD3 /* Products */, 317 | ); 318 | sourceTree = ""; 319 | }; 320 | E5B77F3D1CA9121500F8BDD3 /* Products */ = { 321 | isa = PBXGroup; 322 | children = ( 323 | E53459EF1CA91A72000990A6 /* GAuth Example.app */, 324 | E505F59F1CA9985F003FD2A4 /* GAuth.framework */, 325 | E505F5AC1CA9986D003FD2A4 /* GAuth.framework */, 326 | E505F5B91CA998BD003FD2A4 /* GAuth.framework */, 327 | E5B6FE8E1D0ED3BD00B9641D /* GAuthTV Example.app */, 328 | E5B6FF301D0EF1F400B9641D /* GAuthTests.xctest */, 329 | ); 330 | name = Products; 331 | sourceTree = ""; 332 | }; 333 | /* End PBXGroup section */ 334 | 335 | /* Begin PBXHeadersBuildPhase section */ 336 | E505F59C1CA9985F003FD2A4 /* Headers */ = { 337 | isa = PBXHeadersBuildPhase; 338 | buildActionMask = 2147483647; 339 | files = ( 340 | E505F5D71CA99A83003FD2A4 /* G-Auth.h in Headers */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | E505F5A91CA9986D003FD2A4 /* Headers */ = { 345 | isa = PBXHeadersBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | E505F5D61CA99A82003FD2A4 /* G-Auth.h in Headers */, 349 | ); 350 | runOnlyForDeploymentPostprocessing = 0; 351 | }; 352 | E505F5B61CA998BD003FD2A4 /* Headers */ = { 353 | isa = PBXHeadersBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | E505F5BC1CA998BD003FD2A4 /* G-Auth.h in Headers */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | /* End PBXHeadersBuildPhase section */ 361 | 362 | /* Begin PBXNativeTarget section */ 363 | E505F59E1CA9985F003FD2A4 /* GAuth-watchOS */ = { 364 | isa = PBXNativeTarget; 365 | buildConfigurationList = E505F5A61CA9985F003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-watchOS" */; 366 | buildPhases = ( 367 | E505F59A1CA9985F003FD2A4 /* Sources */, 368 | E505F59B1CA9985F003FD2A4 /* Frameworks */, 369 | E505F59C1CA9985F003FD2A4 /* Headers */, 370 | E505F59D1CA9985F003FD2A4 /* Resources */, 371 | ); 372 | buildRules = ( 373 | ); 374 | dependencies = ( 375 | ); 376 | name = "GAuth-watchOS"; 377 | productName = "GoogleAuthenticator-watchOS"; 378 | productReference = E505F59F1CA9985F003FD2A4 /* GAuth.framework */; 379 | productType = "com.apple.product-type.framework"; 380 | }; 381 | E505F5AB1CA9986D003FD2A4 /* GAuth-tvOS */ = { 382 | isa = PBXNativeTarget; 383 | buildConfigurationList = E505F5B11CA9986D003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-tvOS" */; 384 | buildPhases = ( 385 | E505F5A71CA9986D003FD2A4 /* Sources */, 386 | E505F5A81CA9986D003FD2A4 /* Frameworks */, 387 | E505F5A91CA9986D003FD2A4 /* Headers */, 388 | E505F5AA1CA9986D003FD2A4 /* Resources */, 389 | ); 390 | buildRules = ( 391 | ); 392 | dependencies = ( 393 | ); 394 | name = "GAuth-tvOS"; 395 | productName = "GoogleAuthenticator-tvOS"; 396 | productReference = E505F5AC1CA9986D003FD2A4 /* GAuth.framework */; 397 | productType = "com.apple.product-type.framework"; 398 | }; 399 | E505F5B81CA998BD003FD2A4 /* GAuth-iOS */ = { 400 | isa = PBXNativeTarget; 401 | buildConfigurationList = E505F5C21CA998BE003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-iOS" */; 402 | buildPhases = ( 403 | E505F5B41CA998BD003FD2A4 /* Sources */, 404 | E505F5B51CA998BD003FD2A4 /* Frameworks */, 405 | E505F5B61CA998BD003FD2A4 /* Headers */, 406 | E505F5B71CA998BD003FD2A4 /* Resources */, 407 | E5B6FE6C1D0CC7EB00B9641D /* CopyFiles */, 408 | ); 409 | buildRules = ( 410 | ); 411 | dependencies = ( 412 | ); 413 | name = "GAuth-iOS"; 414 | productName = "GoogleAuthenticator-iOS"; 415 | productReference = E505F5B91CA998BD003FD2A4 /* GAuth.framework */; 416 | productType = "com.apple.product-type.framework"; 417 | }; 418 | E53459EE1CA91A72000990A6 /* GAuthExample */ = { 419 | isa = PBXNativeTarget; 420 | buildConfigurationList = E5345A001CA91A72000990A6 /* Build configuration list for PBXNativeTarget "GAuthExample" */; 421 | buildPhases = ( 422 | E53459EB1CA91A72000990A6 /* Sources */, 423 | E53459EC1CA91A72000990A6 /* Frameworks */, 424 | E53459ED1CA91A72000990A6 /* Resources */, 425 | E5B6FE771D0CC98D00B9641D /* Embed Frameworks */, 426 | E5B6FE781D0CCA0B00B9641D /* Copy Frameworks */, 427 | ); 428 | buildRules = ( 429 | ); 430 | dependencies = ( 431 | E5B6FE761D0CC98C00B9641D /* PBXTargetDependency */, 432 | ); 433 | name = GAuthExample; 434 | productName = GoogleAuthenticatorExample; 435 | productReference = E53459EF1CA91A72000990A6 /* GAuth Example.app */; 436 | productType = "com.apple.product-type.application"; 437 | }; 438 | E5B6FE8D1D0ED3BD00B9641D /* GAuthTV Example */ = { 439 | isa = PBXNativeTarget; 440 | buildConfigurationList = E5B6FE9C1D0ED3BE00B9641D /* Build configuration list for PBXNativeTarget "GAuthTV Example" */; 441 | buildPhases = ( 442 | E5B6FE8A1D0ED3BD00B9641D /* Sources */, 443 | E5B6FE8B1D0ED3BD00B9641D /* Frameworks */, 444 | E5B6FE8C1D0ED3BD00B9641D /* Resources */, 445 | E5B6FEC71D0EDC0A00B9641D /* Copy Frameworks */, 446 | E5B6FEDD1D0EDDCC00B9641D /* Embed Frameworks */, 447 | ); 448 | buildRules = ( 449 | ); 450 | dependencies = ( 451 | E5B6FEDC1D0EDDCC00B9641D /* PBXTargetDependency */, 452 | ); 453 | name = "GAuthTV Example"; 454 | productName = "GoogleAuthenticatorTV Example"; 455 | productReference = E5B6FE8E1D0ED3BD00B9641D /* GAuthTV Example.app */; 456 | productType = "com.apple.product-type.application"; 457 | }; 458 | E5B6FF2F1D0EF1F400B9641D /* GAuthTests */ = { 459 | isa = PBXNativeTarget; 460 | buildConfigurationList = E5B6FF381D0EF1F400B9641D /* Build configuration list for PBXNativeTarget "GAuthTests" */; 461 | buildPhases = ( 462 | E5B6FF2C1D0EF1F400B9641D /* Sources */, 463 | E5B6FF2D1D0EF1F400B9641D /* Frameworks */, 464 | E5B6FF2E1D0EF1F400B9641D /* Resources */, 465 | E5B6FF481D0F037400B9641D /* Copy Frameworks */, 466 | ); 467 | buildRules = ( 468 | ); 469 | dependencies = ( 470 | E5B6FF371D0EF1F400B9641D /* PBXTargetDependency */, 471 | ); 472 | name = GAuthTests; 473 | productName = GoogleAuthenticatorTests; 474 | productReference = E5B6FF301D0EF1F400B9641D /* GAuthTests.xctest */; 475 | productType = "com.apple.product-type.bundle.unit-test"; 476 | }; 477 | /* End PBXNativeTarget section */ 478 | 479 | /* Begin PBXProject section */ 480 | E5B77F331CA9121500F8BDD3 /* Project object */ = { 481 | isa = PBXProject; 482 | attributes = { 483 | LastSwiftUpdateCheck = 0730; 484 | LastUpgradeCheck = 0730; 485 | ORGANIZATIONNAME = Touchwonders; 486 | TargetAttributes = { 487 | E505F59E1CA9985F003FD2A4 = { 488 | CreatedOnToolsVersion = 7.3; 489 | }; 490 | E505F5AB1CA9986D003FD2A4 = { 491 | CreatedOnToolsVersion = 7.3; 492 | }; 493 | E505F5B81CA998BD003FD2A4 = { 494 | CreatedOnToolsVersion = 7.3; 495 | }; 496 | E53459EE1CA91A72000990A6 = { 497 | CreatedOnToolsVersion = 7.3; 498 | }; 499 | E5B6FE8D1D0ED3BD00B9641D = { 500 | CreatedOnToolsVersion = 7.3; 501 | }; 502 | E5B6FF2F1D0EF1F400B9641D = { 503 | CreatedOnToolsVersion = 7.3; 504 | }; 505 | }; 506 | }; 507 | buildConfigurationList = E5B77F361CA9121500F8BDD3 /* Build configuration list for PBXProject "GAuth" */; 508 | compatibilityVersion = "Xcode 3.2"; 509 | developmentRegion = English; 510 | hasScannedForEncodings = 0; 511 | knownRegions = ( 512 | en, 513 | Base, 514 | ); 515 | mainGroup = E5B77F321CA9121500F8BDD3; 516 | productRefGroup = E5B77F3D1CA9121500F8BDD3 /* Products */; 517 | projectDirPath = ""; 518 | projectRoot = ""; 519 | targets = ( 520 | E505F59E1CA9985F003FD2A4 /* GAuth-watchOS */, 521 | E505F5AB1CA9986D003FD2A4 /* GAuth-tvOS */, 522 | E505F5B81CA998BD003FD2A4 /* GAuth-iOS */, 523 | E53459EE1CA91A72000990A6 /* GAuthExample */, 524 | E5B6FE8D1D0ED3BD00B9641D /* GAuthTV Example */, 525 | E5B6FF2F1D0EF1F400B9641D /* GAuthTests */, 526 | ); 527 | }; 528 | /* End PBXProject section */ 529 | 530 | /* Begin PBXResourcesBuildPhase section */ 531 | E505F59D1CA9985F003FD2A4 /* Resources */ = { 532 | isa = PBXResourcesBuildPhase; 533 | buildActionMask = 2147483647; 534 | files = ( 535 | ); 536 | runOnlyForDeploymentPostprocessing = 0; 537 | }; 538 | E505F5AA1CA9986D003FD2A4 /* Resources */ = { 539 | isa = PBXResourcesBuildPhase; 540 | buildActionMask = 2147483647; 541 | files = ( 542 | ); 543 | runOnlyForDeploymentPostprocessing = 0; 544 | }; 545 | E505F5B71CA998BD003FD2A4 /* Resources */ = { 546 | isa = PBXResourcesBuildPhase; 547 | buildActionMask = 2147483647; 548 | files = ( 549 | ); 550 | runOnlyForDeploymentPostprocessing = 0; 551 | }; 552 | E53459ED1CA91A72000990A6 /* Resources */ = { 553 | isa = PBXResourcesBuildPhase; 554 | buildActionMask = 2147483647; 555 | files = ( 556 | E53459FC1CA91A72000990A6 /* LaunchScreen.storyboard in Resources */, 557 | E53459F91CA91A72000990A6 /* Assets.xcassets in Resources */, 558 | E53459F71CA91A72000990A6 /* Main.storyboard in Resources */, 559 | ); 560 | runOnlyForDeploymentPostprocessing = 0; 561 | }; 562 | E5B6FE8C1D0ED3BD00B9641D /* Resources */ = { 563 | isa = PBXResourcesBuildPhase; 564 | buildActionMask = 2147483647; 565 | files = ( 566 | E5B6FE981D0ED3BE00B9641D /* Assets.xcassets in Resources */, 567 | E5B6FE961D0ED3BE00B9641D /* Main.storyboard in Resources */, 568 | ); 569 | runOnlyForDeploymentPostprocessing = 0; 570 | }; 571 | E5B6FF2E1D0EF1F400B9641D /* Resources */ = { 572 | isa = PBXResourcesBuildPhase; 573 | buildActionMask = 2147483647; 574 | files = ( 575 | E5B6FF431D0EFAB200B9641D /* authorize-valid.json in Resources */, 576 | ); 577 | runOnlyForDeploymentPostprocessing = 0; 578 | }; 579 | /* End PBXResourcesBuildPhase section */ 580 | 581 | /* Begin PBXShellScriptBuildPhase section */ 582 | E5B6FE781D0CCA0B00B9641D /* Copy Frameworks */ = { 583 | isa = PBXShellScriptBuildPhase; 584 | buildActionMask = 2147483647; 585 | files = ( 586 | ); 587 | inputPaths = ( 588 | "$(SRCROOT)/Carthage/Build/iOS/Result.framework", 589 | "$(SRCROOT)/Carthage/Build/tvOS/OAuthSwift.framework", 590 | ); 591 | name = "Copy Frameworks"; 592 | outputPaths = ( 593 | ); 594 | runOnlyForDeploymentPostprocessing = 0; 595 | shellPath = /bin/sh; 596 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 597 | }; 598 | E5B6FEC71D0EDC0A00B9641D /* Copy Frameworks */ = { 599 | isa = PBXShellScriptBuildPhase; 600 | buildActionMask = 2147483647; 601 | files = ( 602 | ); 603 | inputPaths = ( 604 | "$(SRCROOT)/Carthage/Build/tvOS/Result.framework", 605 | "$(SRCROOT)/Carthage/Build/tvOS/OAuthSwift.framework", 606 | ); 607 | name = "Copy Frameworks"; 608 | outputPaths = ( 609 | ); 610 | runOnlyForDeploymentPostprocessing = 0; 611 | shellPath = /bin/sh; 612 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 613 | }; 614 | E5B6FF481D0F037400B9641D /* Copy Frameworks */ = { 615 | isa = PBXShellScriptBuildPhase; 616 | buildActionMask = 2147483647; 617 | files = ( 618 | ); 619 | inputPaths = ( 620 | "$(SRCROOT)/Carthage/Build/iOS/Nimble.framework", 621 | "$(SRCROOT)/Carthage/Build/iOS/Quick.framework", 622 | "$(SRCROOT)/Carthage/Build/iOS/OHHTTPStubs.framework", 623 | "$(SRCROOT)/Carthage/Build/iOS/Result.framework", 624 | ); 625 | name = "Copy Frameworks"; 626 | outputPaths = ( 627 | ); 628 | runOnlyForDeploymentPostprocessing = 0; 629 | shellPath = /bin/sh; 630 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 631 | }; 632 | /* End PBXShellScriptBuildPhase section */ 633 | 634 | /* Begin PBXSourcesBuildPhase section */ 635 | E505F59A1CA9985F003FD2A4 /* Sources */ = { 636 | isa = PBXSourcesBuildPhase; 637 | buildActionMask = 2147483647; 638 | files = ( 639 | E5B6FE871D0CF7C000B9641D /* DispatchAfter.swift in Sources */, 640 | E5B6FE831D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */, 641 | E505F5CA1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */, 642 | E5B6FE671D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */, 643 | E5B6FE7F1D0CE63C00B9641D /* HTTPClient.swift in Sources */, 644 | E5B6FF451D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */, 645 | E5B6FE7B1D0CCF0400B9641D /* Extensions.swift in Sources */, 646 | ); 647 | runOnlyForDeploymentPostprocessing = 0; 648 | }; 649 | E505F5A71CA9986D003FD2A4 /* Sources */ = { 650 | isa = PBXSourcesBuildPhase; 651 | buildActionMask = 2147483647; 652 | files = ( 653 | E5B6FE881D0CF7C000B9641D /* DispatchAfter.swift in Sources */, 654 | E5B6FE841D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */, 655 | E505F5CB1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */, 656 | E5B6FE681D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */, 657 | E5B6FE801D0CE63C00B9641D /* HTTPClient.swift in Sources */, 658 | E5B6FF461D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */, 659 | E5B6FE7C1D0CCF0400B9641D /* Extensions.swift in Sources */, 660 | ); 661 | runOnlyForDeploymentPostprocessing = 0; 662 | }; 663 | E505F5B41CA998BD003FD2A4 /* Sources */ = { 664 | isa = PBXSourcesBuildPhase; 665 | buildActionMask = 2147483647; 666 | files = ( 667 | E5B6FE891D0CF7C000B9641D /* DispatchAfter.swift in Sources */, 668 | E5B6FE851D0CE8B000B9641D /* DeviceAuthorizationToken.swift in Sources */, 669 | E505F5CC1CA99914003FD2A4 /* GoogleAuthenticatorClient.swift in Sources */, 670 | E5B6FE691D0CBF1100B9641D /* GoogleAuthenticatorError.swift in Sources */, 671 | E5B6FE811D0CE63C00B9641D /* HTTPClient.swift in Sources */, 672 | E5B6FF471D0F000F00B9641D /* OAuthIntegrationProtocols.swift in Sources */, 673 | E5B6FE7D1D0CCF0400B9641D /* Extensions.swift in Sources */, 674 | ); 675 | runOnlyForDeploymentPostprocessing = 0; 676 | }; 677 | E53459EB1CA91A72000990A6 /* Sources */ = { 678 | isa = PBXSourcesBuildPhase; 679 | buildActionMask = 2147483647; 680 | files = ( 681 | E53459F41CA91A72000990A6 /* ViewController.swift in Sources */, 682 | E53459F21CA91A72000990A6 /* AppDelegate.swift in Sources */, 683 | ); 684 | runOnlyForDeploymentPostprocessing = 0; 685 | }; 686 | E5B6FE8A1D0ED3BD00B9641D /* Sources */ = { 687 | isa = PBXSourcesBuildPhase; 688 | buildActionMask = 2147483647; 689 | files = ( 690 | E5B6FE931D0ED3BE00B9641D /* ViewController.swift in Sources */, 691 | E5B6FE911D0ED3BE00B9641D /* AppDelegate.swift in Sources */, 692 | ); 693 | runOnlyForDeploymentPostprocessing = 0; 694 | }; 695 | E5B6FF2C1D0EF1F400B9641D /* Sources */ = { 696 | isa = PBXSourcesBuildPhase; 697 | buildActionMask = 2147483647; 698 | files = ( 699 | E5B6FF331D0EF1F400B9641D /* GoogleAuthenticatorClientSpec.swift in Sources */, 700 | E5DAAE8A1D1049780092169A /* GoogleAuthenticatorSpec.swift in Sources */, 701 | ); 702 | runOnlyForDeploymentPostprocessing = 0; 703 | }; 704 | /* End PBXSourcesBuildPhase section */ 705 | 706 | /* Begin PBXTargetDependency section */ 707 | E5B6FE761D0CC98C00B9641D /* PBXTargetDependency */ = { 708 | isa = PBXTargetDependency; 709 | target = E505F5B81CA998BD003FD2A4 /* GAuth-iOS */; 710 | targetProxy = E5B6FE751D0CC98C00B9641D /* PBXContainerItemProxy */; 711 | }; 712 | E5B6FEDC1D0EDDCC00B9641D /* PBXTargetDependency */ = { 713 | isa = PBXTargetDependency; 714 | target = E505F5AB1CA9986D003FD2A4 /* GAuth-tvOS */; 715 | targetProxy = E5B6FEDB1D0EDDCC00B9641D /* PBXContainerItemProxy */; 716 | }; 717 | E5B6FF371D0EF1F400B9641D /* PBXTargetDependency */ = { 718 | isa = PBXTargetDependency; 719 | target = E505F5B81CA998BD003FD2A4 /* GAuth-iOS */; 720 | targetProxy = E5B6FF361D0EF1F400B9641D /* PBXContainerItemProxy */; 721 | }; 722 | /* End PBXTargetDependency section */ 723 | 724 | /* Begin PBXVariantGroup section */ 725 | E53459F51CA91A72000990A6 /* Main.storyboard */ = { 726 | isa = PBXVariantGroup; 727 | children = ( 728 | E53459F61CA91A72000990A6 /* Base */, 729 | ); 730 | name = Main.storyboard; 731 | sourceTree = ""; 732 | }; 733 | E53459FA1CA91A72000990A6 /* LaunchScreen.storyboard */ = { 734 | isa = PBXVariantGroup; 735 | children = ( 736 | E53459FB1CA91A72000990A6 /* Base */, 737 | ); 738 | name = LaunchScreen.storyboard; 739 | sourceTree = ""; 740 | }; 741 | E5B6FE941D0ED3BE00B9641D /* Main.storyboard */ = { 742 | isa = PBXVariantGroup; 743 | children = ( 744 | E5B6FE951D0ED3BE00B9641D /* Base */, 745 | ); 746 | name = Main.storyboard; 747 | sourceTree = ""; 748 | }; 749 | /* End PBXVariantGroup section */ 750 | 751 | /* Begin XCBuildConfiguration section */ 752 | E505F5A41CA9985F003FD2A4 /* Debug */ = { 753 | isa = XCBuildConfiguration; 754 | buildSettings = { 755 | APPLICATION_EXTENSION_API_ONLY = YES; 756 | DEFINES_MODULE = YES; 757 | DYLIB_COMPATIBILITY_VERSION = 1; 758 | DYLIB_CURRENT_VERSION = 1; 759 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 760 | FRAMEWORK_SEARCH_PATHS = ( 761 | "$(inherited)", 762 | "$(PROJECT_DIR)/Carthage/Build/watchOS", 763 | ); 764 | GCC_PREPROCESSOR_DEFINITIONS = ( 765 | "DEBUG=1", 766 | "$(inherited)", 767 | ); 768 | INFOPLIST_FILE = "Sources/Info-watchOS.plist"; 769 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 770 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 771 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-watchOS"; 772 | PRODUCT_NAME = GAuth; 773 | SDKROOT = watchos2.2; 774 | SKIP_INSTALL = YES; 775 | TARGETED_DEVICE_FAMILY = 4; 776 | WATCHOS_DEPLOYMENT_TARGET = 2.2; 777 | }; 778 | name = Debug; 779 | }; 780 | E505F5A51CA9985F003FD2A4 /* Release */ = { 781 | isa = XCBuildConfiguration; 782 | buildSettings = { 783 | APPLICATION_EXTENSION_API_ONLY = YES; 784 | DEFINES_MODULE = YES; 785 | DYLIB_COMPATIBILITY_VERSION = 1; 786 | DYLIB_CURRENT_VERSION = 1; 787 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 788 | FRAMEWORK_SEARCH_PATHS = ( 789 | "$(inherited)", 790 | "$(PROJECT_DIR)/Carthage/Build/watchOS", 791 | ); 792 | INFOPLIST_FILE = "Sources/Info-watchOS.plist"; 793 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 794 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 795 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-watchOS"; 796 | PRODUCT_NAME = GAuth; 797 | SDKROOT = watchos2.2; 798 | SKIP_INSTALL = YES; 799 | TARGETED_DEVICE_FAMILY = 4; 800 | WATCHOS_DEPLOYMENT_TARGET = 2.2; 801 | }; 802 | name = Release; 803 | }; 804 | E505F5B21CA9986D003FD2A4 /* Debug */ = { 805 | isa = XCBuildConfiguration; 806 | buildSettings = { 807 | APPLICATION_EXTENSION_API_ONLY = NO; 808 | DEFINES_MODULE = YES; 809 | DYLIB_COMPATIBILITY_VERSION = 1; 810 | DYLIB_CURRENT_VERSION = 1; 811 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 812 | FRAMEWORK_SEARCH_PATHS = ( 813 | "$(inherited)", 814 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 815 | ); 816 | GCC_PREPROCESSOR_DEFINITIONS = ( 817 | "DEBUG=1", 818 | "$(inherited)", 819 | ); 820 | INFOPLIST_FILE = "Sources/Info-tvOS.plist"; 821 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 822 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 823 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-tvOS"; 824 | PRODUCT_NAME = GAuth; 825 | SDKROOT = appletvos9.2; 826 | SKIP_INSTALL = YES; 827 | TARGETED_DEVICE_FAMILY = 3; 828 | TVOS_DEPLOYMENT_TARGET = 9.0; 829 | }; 830 | name = Debug; 831 | }; 832 | E505F5B31CA9986D003FD2A4 /* Release */ = { 833 | isa = XCBuildConfiguration; 834 | buildSettings = { 835 | APPLICATION_EXTENSION_API_ONLY = NO; 836 | DEFINES_MODULE = YES; 837 | DYLIB_COMPATIBILITY_VERSION = 1; 838 | DYLIB_CURRENT_VERSION = 1; 839 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 840 | FRAMEWORK_SEARCH_PATHS = ( 841 | "$(inherited)", 842 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 843 | ); 844 | INFOPLIST_FILE = "Sources/Info-tvOS.plist"; 845 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 846 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 847 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-tvOS"; 848 | PRODUCT_NAME = GAuth; 849 | SDKROOT = appletvos9.2; 850 | SKIP_INSTALL = YES; 851 | TARGETED_DEVICE_FAMILY = 3; 852 | TVOS_DEPLOYMENT_TARGET = 9.0; 853 | }; 854 | name = Release; 855 | }; 856 | E505F5C31CA998BE003FD2A4 /* Debug */ = { 857 | isa = XCBuildConfiguration; 858 | buildSettings = { 859 | APPLICATION_EXTENSION_API_ONLY = NO; 860 | DEFINES_MODULE = YES; 861 | DYLIB_COMPATIBILITY_VERSION = 1; 862 | DYLIB_CURRENT_VERSION = 1; 863 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 864 | FRAMEWORK_SEARCH_PATHS = ( 865 | "$(inherited)", 866 | "$(PROJECT_DIR)/Carthage/Build/iOS", 867 | ); 868 | GCC_PREPROCESSOR_DEFINITIONS = ( 869 | "DEBUG=1", 870 | "$(inherited)", 871 | ); 872 | INFOPLIST_FILE = "Sources/Info-iOS.plist"; 873 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 874 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 875 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 876 | OTHER_SWIFT_FLAGS = ""; 877 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-iOS"; 878 | PRODUCT_NAME = GAuth; 879 | SDKROOT = iphoneos9.3; 880 | SKIP_INSTALL = YES; 881 | }; 882 | name = Debug; 883 | }; 884 | E505F5C41CA998BE003FD2A4 /* Release */ = { 885 | isa = XCBuildConfiguration; 886 | buildSettings = { 887 | APPLICATION_EXTENSION_API_ONLY = NO; 888 | DEFINES_MODULE = YES; 889 | DYLIB_COMPATIBILITY_VERSION = 1; 890 | DYLIB_CURRENT_VERSION = 1; 891 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 892 | FRAMEWORK_SEARCH_PATHS = ( 893 | "$(inherited)", 894 | "$(PROJECT_DIR)/Carthage/Build/iOS", 895 | ); 896 | GCC_PREPROCESSOR_DEFINITIONS = ""; 897 | INFOPLIST_FILE = "Sources/Info-iOS.plist"; 898 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 899 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 900 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 901 | OTHER_SWIFT_FLAGS = ""; 902 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GAuth-iOS"; 903 | PRODUCT_NAME = GAuth; 904 | SDKROOT = iphoneos9.3; 905 | SKIP_INSTALL = YES; 906 | }; 907 | name = Release; 908 | }; 909 | E53459FE1CA91A72000990A6 /* Debug */ = { 910 | isa = XCBuildConfiguration; 911 | buildSettings = { 912 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 913 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 914 | FRAMEWORK_SEARCH_PATHS = ( 915 | "$(inherited)", 916 | "$(PROJECT_DIR)/Carthage/Build/iOS", 917 | ); 918 | INFOPLIST_FILE = "GAuth Example/Info.plist"; 919 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 920 | PRODUCT_BUNDLE_IDENTIFIER = com.touchwonders.gauthexample; 921 | PRODUCT_NAME = "GAuth Example"; 922 | }; 923 | name = Debug; 924 | }; 925 | E53459FF1CA91A72000990A6 /* Release */ = { 926 | isa = XCBuildConfiguration; 927 | buildSettings = { 928 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 929 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 930 | FRAMEWORK_SEARCH_PATHS = ( 931 | "$(inherited)", 932 | "$(PROJECT_DIR)/Carthage/Build/iOS", 933 | ); 934 | INFOPLIST_FILE = "GAuth Example/Info.plist"; 935 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 936 | PRODUCT_BUNDLE_IDENTIFIER = com.touchwonders.gauthexample; 937 | PRODUCT_NAME = "GAuth Example"; 938 | }; 939 | name = Release; 940 | }; 941 | E5B6FE9A1D0ED3BE00B9641D /* Debug */ = { 942 | isa = XCBuildConfiguration; 943 | buildSettings = { 944 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 945 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 946 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 947 | FRAMEWORK_SEARCH_PATHS = ( 948 | "$(inherited)", 949 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 950 | ); 951 | INFOPLIST_FILE = "GAuth tvOS Example/Info.plist"; 952 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 953 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GoogleAuthenticatorTV-Example"; 954 | PRODUCT_NAME = "GAuthTV Example"; 955 | SDKROOT = appletvos; 956 | TARGETED_DEVICE_FAMILY = 3; 957 | TVOS_DEPLOYMENT_TARGET = 9.2; 958 | }; 959 | name = Debug; 960 | }; 961 | E5B6FE9B1D0ED3BE00B9641D /* Release */ = { 962 | isa = XCBuildConfiguration; 963 | buildSettings = { 964 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 965 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 966 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 967 | FRAMEWORK_SEARCH_PATHS = ( 968 | "$(inherited)", 969 | "$(PROJECT_DIR)/Carthage/Build/tvOS", 970 | ); 971 | INFOPLIST_FILE = "GAuth tvOS Example/Info.plist"; 972 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 973 | PRODUCT_BUNDLE_IDENTIFIER = "com.touchwonders.GoogleAuthenticatorTV-Example"; 974 | PRODUCT_NAME = "GAuthTV Example"; 975 | SDKROOT = appletvos; 976 | TARGETED_DEVICE_FAMILY = 3; 977 | TVOS_DEPLOYMENT_TARGET = 9.2; 978 | }; 979 | name = Release; 980 | }; 981 | E5B6FF391D0EF1F400B9641D /* Debug */ = { 982 | isa = XCBuildConfiguration; 983 | buildSettings = { 984 | FRAMEWORK_SEARCH_PATHS = ( 985 | "$(inherited)", 986 | "$(PROJECT_DIR)/Carthage/Build/iOS", 987 | ); 988 | INFOPLIST_FILE = GAuthTests/Info.plist; 989 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 990 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 991 | PRODUCT_BUNDLE_IDENTIFIER = com.touchwonders.GoogleAuthenticatorTests; 992 | PRODUCT_NAME = GAuthTests; 993 | SDKROOT = iphoneos; 994 | }; 995 | name = Debug; 996 | }; 997 | E5B6FF3A1D0EF1F400B9641D /* Release */ = { 998 | isa = XCBuildConfiguration; 999 | buildSettings = { 1000 | FRAMEWORK_SEARCH_PATHS = ( 1001 | "$(inherited)", 1002 | "$(PROJECT_DIR)/Carthage/Build/iOS", 1003 | ); 1004 | INFOPLIST_FILE = GAuthTests/Info.plist; 1005 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 1006 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 1007 | PRODUCT_BUNDLE_IDENTIFIER = com.touchwonders.GoogleAuthenticatorTests; 1008 | PRODUCT_NAME = GAuthTests; 1009 | SDKROOT = iphoneos; 1010 | }; 1011 | name = Release; 1012 | }; 1013 | E5B77F4E1CA9121500F8BDD3 /* Debug */ = { 1014 | isa = XCBuildConfiguration; 1015 | buildSettings = { 1016 | ALWAYS_SEARCH_USER_PATHS = NO; 1017 | APPLICATION_EXTENSION_API_ONLY = YES; 1018 | CLANG_ANALYZER_NONNULL = YES; 1019 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1020 | CLANG_CXX_LIBRARY = "libc++"; 1021 | CLANG_ENABLE_MODULES = YES; 1022 | CLANG_ENABLE_OBJC_ARC = YES; 1023 | CLANG_WARN_BOOL_CONVERSION = YES; 1024 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1025 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1026 | CLANG_WARN_EMPTY_BODY = YES; 1027 | CLANG_WARN_ENUM_CONVERSION = YES; 1028 | CLANG_WARN_INT_CONVERSION = YES; 1029 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1030 | CLANG_WARN_UNREACHABLE_CODE = YES; 1031 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1032 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1033 | COPY_PHASE_STRIP = NO; 1034 | CURRENT_PROJECT_VERSION = 1; 1035 | DEBUG_INFORMATION_FORMAT = dwarf; 1036 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1037 | ENABLE_TESTABILITY = YES; 1038 | GCC_C_LANGUAGE_STANDARD = gnu99; 1039 | GCC_DYNAMIC_NO_PIC = NO; 1040 | GCC_NO_COMMON_BLOCKS = YES; 1041 | GCC_OPTIMIZATION_LEVEL = 0; 1042 | GCC_PREPROCESSOR_DEFINITIONS = ( 1043 | "DEBUG=1", 1044 | "$(inherited)", 1045 | ); 1046 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1047 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1048 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1049 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1050 | GCC_WARN_UNUSED_FUNCTION = YES; 1051 | GCC_WARN_UNUSED_VARIABLE = YES; 1052 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1053 | MTL_ENABLE_DEBUG_INFO = YES; 1054 | ONLY_ACTIVE_ARCH = YES; 1055 | OTHER_SWIFT_FLAGS = ""; 1056 | SDKROOT = iphoneos9.3; 1057 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 1058 | TARGETED_DEVICE_FAMILY = "1,2"; 1059 | VERSIONING_SYSTEM = "apple-generic"; 1060 | VERSION_INFO_PREFIX = ""; 1061 | }; 1062 | name = Debug; 1063 | }; 1064 | E5B77F4F1CA9121500F8BDD3 /* Release */ = { 1065 | isa = XCBuildConfiguration; 1066 | buildSettings = { 1067 | ALWAYS_SEARCH_USER_PATHS = NO; 1068 | APPLICATION_EXTENSION_API_ONLY = YES; 1069 | CLANG_ANALYZER_NONNULL = YES; 1070 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 1071 | CLANG_CXX_LIBRARY = "libc++"; 1072 | CLANG_ENABLE_MODULES = YES; 1073 | CLANG_ENABLE_OBJC_ARC = YES; 1074 | CLANG_WARN_BOOL_CONVERSION = YES; 1075 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1076 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1077 | CLANG_WARN_EMPTY_BODY = YES; 1078 | CLANG_WARN_ENUM_CONVERSION = YES; 1079 | CLANG_WARN_INT_CONVERSION = YES; 1080 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1081 | CLANG_WARN_UNREACHABLE_CODE = YES; 1082 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1083 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 1084 | COPY_PHASE_STRIP = NO; 1085 | CURRENT_PROJECT_VERSION = 1; 1086 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1087 | ENABLE_NS_ASSERTIONS = NO; 1088 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1089 | GCC_C_LANGUAGE_STANDARD = gnu99; 1090 | GCC_NO_COMMON_BLOCKS = YES; 1091 | GCC_PREPROCESSOR_DEFINITIONS = ""; 1092 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1093 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1094 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1095 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1096 | GCC_WARN_UNUSED_FUNCTION = YES; 1097 | GCC_WARN_UNUSED_VARIABLE = YES; 1098 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 1099 | MTL_ENABLE_DEBUG_INFO = NO; 1100 | OTHER_SWIFT_FLAGS = ""; 1101 | SDKROOT = iphoneos9.3; 1102 | TARGETED_DEVICE_FAMILY = "1,2"; 1103 | VALIDATE_PRODUCT = YES; 1104 | VERSIONING_SYSTEM = "apple-generic"; 1105 | VERSION_INFO_PREFIX = ""; 1106 | }; 1107 | name = Release; 1108 | }; 1109 | /* End XCBuildConfiguration section */ 1110 | 1111 | /* Begin XCConfigurationList section */ 1112 | E505F5A61CA9985F003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-watchOS" */ = { 1113 | isa = XCConfigurationList; 1114 | buildConfigurations = ( 1115 | E505F5A41CA9985F003FD2A4 /* Debug */, 1116 | E505F5A51CA9985F003FD2A4 /* Release */, 1117 | ); 1118 | defaultConfigurationIsVisible = 0; 1119 | defaultConfigurationName = Release; 1120 | }; 1121 | E505F5B11CA9986D003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-tvOS" */ = { 1122 | isa = XCConfigurationList; 1123 | buildConfigurations = ( 1124 | E505F5B21CA9986D003FD2A4 /* Debug */, 1125 | E505F5B31CA9986D003FD2A4 /* Release */, 1126 | ); 1127 | defaultConfigurationIsVisible = 0; 1128 | defaultConfigurationName = Release; 1129 | }; 1130 | E505F5C21CA998BE003FD2A4 /* Build configuration list for PBXNativeTarget "GAuth-iOS" */ = { 1131 | isa = XCConfigurationList; 1132 | buildConfigurations = ( 1133 | E505F5C31CA998BE003FD2A4 /* Debug */, 1134 | E505F5C41CA998BE003FD2A4 /* Release */, 1135 | ); 1136 | defaultConfigurationIsVisible = 0; 1137 | defaultConfigurationName = Release; 1138 | }; 1139 | E5345A001CA91A72000990A6 /* Build configuration list for PBXNativeTarget "GAuthExample" */ = { 1140 | isa = XCConfigurationList; 1141 | buildConfigurations = ( 1142 | E53459FE1CA91A72000990A6 /* Debug */, 1143 | E53459FF1CA91A72000990A6 /* Release */, 1144 | ); 1145 | defaultConfigurationIsVisible = 0; 1146 | defaultConfigurationName = Release; 1147 | }; 1148 | E5B6FE9C1D0ED3BE00B9641D /* Build configuration list for PBXNativeTarget "GAuthTV Example" */ = { 1149 | isa = XCConfigurationList; 1150 | buildConfigurations = ( 1151 | E5B6FE9A1D0ED3BE00B9641D /* Debug */, 1152 | E5B6FE9B1D0ED3BE00B9641D /* Release */, 1153 | ); 1154 | defaultConfigurationIsVisible = 0; 1155 | defaultConfigurationName = Release; 1156 | }; 1157 | E5B6FF381D0EF1F400B9641D /* Build configuration list for PBXNativeTarget "GAuthTests" */ = { 1158 | isa = XCConfigurationList; 1159 | buildConfigurations = ( 1160 | E5B6FF391D0EF1F400B9641D /* Debug */, 1161 | E5B6FF3A1D0EF1F400B9641D /* Release */, 1162 | ); 1163 | defaultConfigurationIsVisible = 0; 1164 | defaultConfigurationName = Release; 1165 | }; 1166 | E5B77F361CA9121500F8BDD3 /* Build configuration list for PBXProject "GAuth" */ = { 1167 | isa = XCConfigurationList; 1168 | buildConfigurations = ( 1169 | E5B77F4E1CA9121500F8BDD3 /* Debug */, 1170 | E5B77F4F1CA9121500F8BDD3 /* Release */, 1171 | ); 1172 | defaultConfigurationIsVisible = 0; 1173 | defaultConfigurationName = Release; 1174 | }; 1175 | /* End XCConfigurationList section */ 1176 | }; 1177 | rootObject = E5B77F331CA9121500F8BDD3 /* Project object */; 1178 | } 1179 | -------------------------------------------------------------------------------- /GAuth.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GAuth.xcodeproj/xcshareddata/xcschemes/GAuth-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /GAuth.xcodeproj/xcshareddata/xcschemes/GAuth-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /GAuth.xcodeproj/xcshareddata/xcschemes/GAuth-watchOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /GAuth.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GAuthTests/GoogleAuthenticatorClientSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleAuthenticatorClientSpec.swift 3 | // GoogleAuthenticatorSpec 4 | // 5 | // Created by Fabio Milano on 13/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Nimble 10 | import Quick 11 | import OHHTTPStubs 12 | import Result 13 | 14 | @testable import GAuth 15 | 16 | class GoogleAuthenticatorClientSpec: QuickSpec { 17 | 18 | let bundle = NSBundle(forClass: GoogleAuthenticatorClient.self) 19 | 20 | override func spec(){ 21 | describe("-authenticateRequest") { 22 | context("when successfully authenticate a NSURLRequest") { 23 | var authenticator: GoogleAuthenticatorClient? 24 | 25 | it("the request gets properly authenticated with valid access token") { 26 | authenticator = GoogleAuthenticatorClient(oauthClient: OAuthMockClient(tokenFeatures: TokenFeatures.Valid)) 27 | 28 | let originalRequest = NSURLRequest(URL: NSURL(string: "http://googleauthenticator.touchwonders.com")!) 29 | 30 | waitUntil(action: { done in 31 | authenticator!.authenticateRequest(originalRequest, completion: { (result) in 32 | switch result { 33 | case .Success(let authenticatedRequest): 34 | expect(originalRequest.HTTPMethod).to(equal(authenticatedRequest.HTTPMethod)) 35 | expect(originalRequest.HTTPBody).to(beNil()) 36 | expect(originalRequest.cachePolicy).to(equal(authenticatedRequest.cachePolicy)) 37 | expect(originalRequest.HTTPShouldHandleCookies).to(equal(authenticatedRequest.HTTPShouldHandleCookies)) 38 | expect(originalRequest.HTTPBodyStream).to(beNil()) 39 | expect(originalRequest.URL).to(equal(authenticatedRequest.URL)) 40 | expect(authenticatedRequest.valueForHTTPHeaderField("Authorization")).toNot(beNil()) 41 | case .Failure(let error): 42 | switch error { 43 | default: 44 | fail("Impossible to authorize the request") 45 | } 46 | } 47 | 48 | done() 49 | }) 50 | }) 51 | } 52 | 53 | it("the request get properly authenticated by first refreshing an expired token"){ 54 | authenticator = GoogleAuthenticatorClient(oauthClient: OAuthMockClient(tokenFeatures: TokenFeatures.Expired)) 55 | 56 | let originalRequest = NSURLRequest(URL: NSURL(string: "http://googleauthenticator.touchwonders.com")!) 57 | 58 | waitUntil(action: { (done) in 59 | authenticator!.authenticateRequest(originalRequest, completion: { (result) in 60 | switch result { 61 | case .Success(let authenticatedRequest): 62 | expect(originalRequest.HTTPMethod).to(equal(authenticatedRequest.HTTPMethod)) 63 | expect(originalRequest.HTTPBody).to(beNil()) 64 | expect(originalRequest.cachePolicy).to(equal(authenticatedRequest.cachePolicy)) 65 | expect(originalRequest.HTTPShouldHandleCookies).to(equal(authenticatedRequest.HTTPShouldHandleCookies)) 66 | expect(originalRequest.HTTPBodyStream).to(beNil()) 67 | expect(originalRequest.URL).to(equal(authenticatedRequest.URL)) 68 | expect(authenticatedRequest.valueForHTTPHeaderField("Authorization")).toNot(beNil()) 69 | case .Failure(let error): 70 | switch error { 71 | default: 72 | fail("Impossible to authorize the request") 73 | } 74 | } 75 | 76 | done() 77 | }) 78 | }) 79 | } 80 | 81 | } 82 | } 83 | 84 | describe("-pollAccessToken") { 85 | context("when successfully retrieve a new access token") { 86 | let authenticator = GoogleAuthenticatorClient(oauthClient: OAuthMockClient(tokenFeatures: TokenFeatures.Valid)) 87 | 88 | 89 | it("authorizes the user") { 90 | waitUntil(action: { done in 91 | authenticator.pollAccessToken("DEVICE_TOKEN", retryInterval: 0.5, completion: { result in 92 | switch result { 93 | case .Success( _): 94 | done() 95 | case .Failure(let error): 96 | fail("Impossible to authorize user: \(error)") 97 | } 98 | }) 99 | }) 100 | } 101 | 102 | it("authorizes the user after one retry") { 103 | // Make sure first request will fail with an authorization pending error 104 | authenticator.invalidateClientNextRequestWithError(GoogleAuthenticatorError.AuthorizationPending) 105 | 106 | waitUntil(timeout: 2.0, action: { done in 107 | authenticator.pollAccessToken("DEVICE_TOKEN", retryInterval: 0.5, completion: { result in 108 | switch result { 109 | case .Success( _): 110 | done() 111 | case .Failure(let error): 112 | fail("Impossible to authorize user: \(error)") 113 | } 114 | }) 115 | }) 116 | } 117 | } 118 | } 119 | 120 | describe("Helpers") { 121 | context("parameters") { 122 | let authenticator = GoogleAuthenticatorClient(oauthClient: OAuthMockClient(tokenFeatures: TokenFeatures.Valid)) 123 | 124 | it("for device verification request") { 125 | let parameters = authenticator.parametersForDeviceVerification() 126 | expect(parameters["client_id"]).to(equal("CLIENT_ID")) 127 | expect(parameters["scope"]).to(equal("test_scope")) 128 | } 129 | it("for device authorization request") { 130 | let parameters = authenticator.parametersForDeviceAuthorization("TEST_CODE") 131 | expect(parameters["client_id"]).to(equal("CLIENT_ID")) 132 | expect(parameters["client_secret"]).to(equal("CLIENT_SECRET")) 133 | expect(parameters["code"]).to(equal("TEST_CODE")) 134 | expect(parameters["grant_type"]).to(equal("http://oauth.net/grant_type/device/1.0")) 135 | } 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /GAuthTests/GoogleAuthenticatorSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleAuthenticatorSpec.swift 3 | // GoogleAuthenticator 4 | // 5 | // Created by Fabio Milano on 14/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Result 11 | 12 | @testable import GAuth 13 | 14 | public struct TokenMock { 15 | public let accessToken: String 16 | 17 | public let refreshToken: String? 18 | 19 | public let expiresIn: NSTimeInterval? 20 | 21 | public let isExpired: Bool 22 | 23 | public let isValid: Bool 24 | 25 | public let scopes: [String]? 26 | 27 | static func expiredToken() -> TokenMock { 28 | return TokenMock(accessToken: "ACCESS_TOKEN", refreshToken: "REFRESH_TOKEN", expiresIn: 3600, isExpired: true, isValid: false, scopes: ["test_scope"]) 29 | } 30 | 31 | static func validToken() -> TokenMock { 32 | return TokenMock(accessToken: "ACCESS_TOKEN", refreshToken: "REFRESH_TOKEN", expiresIn: 3600, isExpired: false, isValid: true, scopes: ["test_scope"]) 33 | } 34 | } 35 | 36 | enum TokenFeatures { 37 | case Valid 38 | case Expired 39 | } 40 | 41 | public class OAuthMockClient { 42 | 43 | /// The client ID. 44 | public let clientID: String 45 | 46 | /// The client secret. 47 | public let clientSecret: String 48 | 49 | /// The authorize URL. 50 | public let authorizeURL: NSURL 51 | 52 | /// The token URL. 53 | public let tokenURL: NSURL? 54 | 55 | /// The redirect URL. 56 | public let redirectURL: NSURL? 57 | 58 | public let scopes: [String] 59 | 60 | public var token: TokenMock? 61 | 62 | init(tokenFeatures: TokenFeatures, clientID: String = "CLIENT_ID", clientSecret: String = "CLIENT_SECRET", 63 | authorizeURL: NSURL = NSURL(string: "http://authorizeurl.touchwonders.com")!, tokenURL: NSURL = NSURL(string: "http://tokenurl.touchwonders.com")!, 64 | redirectURL: NSURL = NSURL(string: "http://redirecturl.touchwonders.com")!, 65 | scopes: [String] = ["test_scope"]) { 66 | self.clientID = clientID 67 | self.clientSecret = clientSecret 68 | self.authorizeURL = authorizeURL 69 | self.tokenURL = tokenURL 70 | self.redirectURL = redirectURL 71 | self.scopes = scopes 72 | 73 | switch tokenFeatures { 74 | case .Valid: 75 | self.token = TokenMock.validToken() 76 | case .Expired: 77 | self.token = TokenMock.expiredToken() 78 | } 79 | } 80 | 81 | var nextOAuthClientError: GoogleAuthenticatorError? 82 | } 83 | 84 | extension TokenMock: AuthorizedToken { } 85 | 86 | extension GoogleAuthenticatorError: GoogleAuthenticatorErrorAdapter { 87 | public var googleAuthenticatorError: GoogleAuthenticatorError { 88 | return self 89 | } 90 | } 91 | 92 | extension GoogleAuthenticatorClient { 93 | public func invalidateClientNextRequestWithError(error: GoogleAuthenticatorError) -> Void { 94 | let client = oauthClient as! OAuthMockClient 95 | client.nextOAuthClientError = GoogleAuthenticatorError.AuthorizationPending 96 | } 97 | } 98 | 99 | extension OAuthMockClient: GoogleAuthenticatorOAuthClient { 100 | public typealias OAuthClientType = OAuthMockClient 101 | public typealias TokenType = TokenMock 102 | public typealias Failure = GoogleAuthenticatorError 103 | 104 | public static func Google(clientID clientID: String, clientSecret: String, bundleIdentifier: String, scope: GoogleServiceScope) -> OAuthMockClient { 105 | return OAuthMockClient(tokenFeatures: TokenFeatures.Valid) 106 | } 107 | 108 | public func googleAuthenticatorRefreshToken(completion: Result -> Void) { 109 | 110 | if let nextOAuthClientError = nextOAuthClientError { 111 | self.nextOAuthClientError = nil 112 | completion(.Failure(nextOAuthClientError)) 113 | return 114 | } 115 | 116 | self.token = TokenMock.validToken() 117 | completion(.Success(token!)) 118 | } 119 | 120 | public func googleAuthenticatorAuthorize(completion: Result -> Void) { 121 | 122 | if let nextOAuthClientError = nextOAuthClientError { 123 | self.nextOAuthClientError = nil 124 | completion(.Failure(nextOAuthClientError)) 125 | return 126 | } 127 | 128 | completion(.Success(token!)) 129 | } 130 | 131 | public func googleAuthenticatorAuthorizeDeviceCode(deviceCode: String, completion: Result -> Void) { 132 | 133 | if let nextOAuthClientError = nextOAuthClientError { 134 | self.nextOAuthClientError = nil 135 | completion(.Failure(nextOAuthClientError)) 136 | return 137 | } 138 | 139 | completion(.Success(token!)) 140 | } 141 | } -------------------------------------------------------------------------------- /GAuthTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /GAuthTests/JSON Responses/authorize-valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "THE_STUB_ACCESS_TOKEN", 3 | "expires_in": 3600, 4 | "token_type": "bearer", 5 | "scope": "https://www.googleapis.com/auth/analytics.readonly", 6 | "refresh_token": "THE_STUB_REFRESH_TOKEN" 7 | } -------------------------------------------------------------------------------- /GAuthTests/JSON Responses/oauth-request-valid.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "THE_STUB_ACCESS_TOKEN", 3 | "expires_in": 3600, 4 | "token_type": "bearer", 5 | "scope": "https://www.googleapis.com/auth/analytics.readonly", 6 | "refresh_token": "THE_STUB_REFRESH_TOKEN" 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Fabio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Twitter](https://img.shields.io/badge/twitter-@iamfabiomilano-blue.svg?style=flat)](http://twitter.com/iamfabiomilano) 2 | [![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 3 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/GAuth.svg)](https://img.shields.io/cocoapods/v/GAuth.svg) 4 | [![Platform](https://img.shields.io/cocoapods/p/GAuth.svg?style=flat)](http://cocoadocs.org/docsets/GAuth) 5 | 6 | # GAuth 7 | 8 | GAuth is small framework that makes authentication to Google Services easier by properly implementing the OAuth authentication flow described in [Google's Developers website](https://developers.google.com/identity/protocols/OAuth2InstalledApp). 9 | 10 | GAuth is not about to provide yet another Swift OAuth client but to easily integrate authentication to Google Services for the OAuth client already used in your project. 11 | 12 | # Cloning the repo 13 | To give `GAuth` a shot follow this steps: 14 | 15 | 1. Clone the repo. 16 | 2. Open the terminal and move to the `GAuth` folder. 17 | 3. To setup all required dependencies run `make setup`. 18 | 4. Open the `GAuth.xcworkspace`, build and run. 19 | 20 | # Installation 21 | 22 | Add via [CocoaPods](https://cocoapods.org/) by adding this to your Podfile: 23 | 24 | ``` 25 | pod 'GAuth' 26 | ``` 27 | 28 | If using [Carthage](https://github.com/Carthage/Carthage), add following line into your Cartfile: 29 | 30 | ``` 31 | github "fabiomassimo/GAuth" 32 | ``` 33 | 34 | # How to use it 35 | 36 | Integrate GAuth by making your OAuthClient ([OAuthSwift](https://github.com/OAuthSwift/OAuthSwift), [SwiftyOAuth](https://github.com/delba/SwiftyOAuth)) conform to `GoogleAuthenticatorOAuthClient`. 37 | 38 | Finally authorize your device via the `GoogleAuthenticatorClient` by passing the proper OAuth client. 39 | 40 | See the example project (using [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift) to see how it works in details. 41 | 42 | # Support for Limited Input Device Applications 43 | 44 | GAuth cares to support all platforms therefore it also implements the [Device Authentication Flow](https://tools.ietf.org/html/draft-ietf-oauth-v2-01#section-3.5.3) for devices that can not support the standard authorization flows (i.e. tvOS which does not include WebKit framework). 45 | 46 | # Roadmap 47 | 48 | - [x] Include code example 49 | - [ ] Add more Google Service Scope. 50 | - [ ] Add Travis CI/Circle CI to the repo 51 | - [ ] Add support for Swift 3 52 | 53 | # Credits 54 | 55 | GAuth's primary author is Fabio Milano, iOS Engineer at [Touchwonders](http://www.touchwonders.com). 56 | 57 | GAuth was inspired from the need of having a simple framework that could support all type of platforms and easily integrate with an existing OAuth client. 58 | 59 | # License 60 | 61 | GAuth is available under the MIT license. See the LICENSE file for more info. -------------------------------------------------------------------------------- /Sources/G-Auth.h: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleAuthenticator-iOS.h 3 | // GoogleAuthenticator-iOS 4 | // 5 | // Created by Fabio Milano on 28/03/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for GoogleAuthenticator-iOS. 12 | FOUNDATION_EXPORT double GAuthVersionNumber; 13 | 14 | //! Project version string for GoogleAuthenticator-iOS. 15 | FOUNDATION_EXPORT const unsigned char GAuthVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/GoogleAuthenticatorClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleAuthenticator.swift 3 | // GoogleAnalyticsReader 4 | // 5 | // Created by Fabio Milano on 05/02/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Result 11 | 12 | /// The Google Authenticator Client is responsible to provide an easy integration with Google authentication process for you. 13 | public final class GoogleAuthenticatorClient { 14 | 15 | /// The client ID used to initialize the authenticator 16 | public var clientID: String { 17 | return oauthClient.clientID 18 | } 19 | 20 | /// The client secret used to initialize the authenticator 21 | public var clientSecret: String { 22 | return oauthClient.clientSecret 23 | } 24 | 25 | /// The currently available token 26 | public var token: AuthorizedToken? { 27 | get { 28 | return oauthClient.token 29 | } 30 | } 31 | 32 | public var scope: GoogleServiceScope { 33 | return GoogleServiceScope(scopes: oauthClient.scopes) 34 | } 35 | 36 | /// The OAuth Client used to support the OAuth 2.0 specifications and requirements. 37 | internal let oauthClient: T 38 | 39 | /** 40 | Convenience initializer 41 | 42 | - parameter oauthClient: The OAuth Client inistantiation to use to complete the initialization. 43 | 44 | - returns: A fully equipped Google Authenticator Client ready to use. 45 | */ 46 | public required init(oauthClient: T) { 47 | self.oauthClient = oauthClient 48 | } 49 | 50 | /** 51 | The designated initializer. 52 | 53 | - parameter consumerKey: The consumer key. 54 | - parameter consumerSecret: The consumer secret. 55 | - parameter bundleIdentifier: The bundler identifier used to register you app on Goolge Developer console. 56 | - parameter scope: The Google Scopes required to initiailize the authentication with Google Services. 57 | 58 | - returns: A fully equipped Google Authenticator Client ready for action. 59 | */ 60 | public convenience init(consumerKey: String, consumerSecret: String, bundleIdentifier: String, scope:GoogleServiceScope) { 61 | let oauthClient = T.Google(clientID: consumerKey, clientSecret: consumerSecret, bundleIdentifier: bundleIdentifier, scope: scope) as! T 62 | self.init(oauthClient: oauthClient) 63 | } 64 | 65 | // MARK: Public methods 66 | 67 | public func authorizeDevice(verify:(verificationUrl: NSURL, userCode: String) -> Void, completion: Result -> Void) { 68 | let params = parametersForDeviceVerification() 69 | 70 | HTTP.POST(AuthenticationConstants.DeviceVerificationUrl.URL, parameters: params) { [weak self] resultJSON in 71 | guard let _ = self else { 72 | return 73 | } 74 | 75 | switch resultJSON { 76 | case .Success(let json): 77 | guard let deviceAuthorizationToken = DeviceAuthorizationToken(json: json) else { 78 | completion(.Failure(GoogleAuthenticatorError.InvalidAccessToken)) 79 | return 80 | } 81 | 82 | dispatch_async(dispatch_get_main_queue(), { 83 | verify(verificationUrl: deviceAuthorizationToken.verificationUrl, userCode: deviceAuthorizationToken.userCode) 84 | }) 85 | 86 | self!.pollAccessToken(deviceAuthorizationToken.deviceCode, retryInterval:deviceAuthorizationToken.retryInterval, completion:{ (result) in 87 | switch result { 88 | case .Success(): 89 | completion(.Success()) 90 | case .Failure(let error): 91 | completion(.Failure(error)) 92 | } 93 | }) 94 | case .Failure(let error): 95 | if let description = error.userInfo[NSLocalizedDescriptionKey] as? String { 96 | completion(.Failure(GoogleAuthenticatorError.DeviceVerificationFailed(description))) 97 | } 98 | } 99 | } 100 | } 101 | 102 | #if os(iOS) 103 | /** 104 | Every Google Authenticator needs to be authorized in order to make signed request. 105 | If no available tokens have been stored before, the hostViewController is used to present a standard WebView which guides the user to insert his credentials in order to kickoff the OAuth 2 dance to finally retrieve the required tokens. 106 | Otherwise, the authorization is skipped and available tokens are used to sign the requests. 107 | - parameter hostViewController: The host view controller used to present the credentials input. 108 | */ 109 | public func authorize(completion: Result -> Void) { 110 | 111 | oauthClient.googleAuthenticatorAuthorize { (result) in 112 | switch result { 113 | case .Success( _): 114 | completion(.Success()) 115 | case .Failure(let error): 116 | completion(.Failure(error.googleAuthenticatorError)) 117 | } 118 | } 119 | } 120 | #endif 121 | 122 | // MARK: client methods 123 | 124 | /** 125 | - returns: `true` if current authenticator is already authorized (access_token and refresh_token available). `false` otherwise. 126 | */ 127 | public func isAuthorized() -> Bool { 128 | guard let token = oauthClient.token else { 129 | return false 130 | } 131 | 132 | return !token.isExpired && !token.accessToken.isEmpty 133 | } 134 | 135 | /// Alters the given request by adding authentication, if possible. 136 | /// 137 | /// In case of an expired access token and the presence of a refresh token, 138 | /// automatically tries to refresh the access token. If refreshing the 139 | /// access token fails, the access token is cleared. 140 | /// 141 | /// **Note:** If the access token must be refreshed, network I/O is 142 | /// performed. 143 | /// 144 | /// **Note:** The completion closure may be invoked on any thread. 145 | /// 146 | /// - parameter request: An unauthenticated NSURLRequest. 147 | /// - parameter completion: A callback to invoke with the authenticated request. 148 | public func authenticateRequest(request: NSURLRequest, completion: Result -> ()) { 149 | if let token = token { 150 | if token.isExpired { 151 | // Expired token. Let's refresh it. 152 | oauthClient.googleAuthenticatorRefreshToken({ [weak self] result in 153 | switch result { 154 | case .Success( _): 155 | self?.authenticateRequest(request, completion: completion) 156 | case .Failure(let error): 157 | completion(Result.Failure(error.googleAuthenticatorError)) 158 | } 159 | }) 160 | } else { 161 | let mutableRequest = request.mutableCopy() as! NSMutableURLRequest 162 | mutableRequest.setAccessToken(token.accessToken) 163 | completion(Result.Success(mutableRequest)) 164 | } 165 | } else { 166 | // No token available. The client must start an authentication process properly through -authorize method 167 | completion(Result.Failure(GoogleAuthenticatorError.InvalidAccessToken)) 168 | } 169 | } 170 | } 171 | 172 | extension GoogleAuthenticatorClient { 173 | func parametersForDeviceVerification() -> [String: String] { 174 | return ["client_id": clientID, "scope": scope.string() ] 175 | } 176 | 177 | func parametersForDeviceAuthorization(userCode: String) -> [String: String] { 178 | return ["client_id": clientID, "client_secret": clientSecret, "code": userCode, "grant_type": "http://oauth.net/grant_type/device/1.0"] 179 | } 180 | 181 | func pollAccessToken(deviceCode: String, retryInterval: NSTimeInterval, completion: Result -> Void) -> Void { 182 | oauthClient.googleAuthenticatorAuthorizeDeviceCode(deviceCode, completion: { (result) in 183 | switch result { 184 | case .Success( _): 185 | completion(.Success()) 186 | case .Failure (let error): 187 | 188 | switch error.googleAuthenticatorError { 189 | case .AuthorizationPending: 190 | delay(retryInterval, closure: { [weak self] in 191 | guard let _ = self else { 192 | return 193 | } 194 | 195 | self!.pollAccessToken(deviceCode, retryInterval: retryInterval, completion: completion) 196 | }) 197 | default: 198 | completion(.Failure(error.googleAuthenticatorError)) 199 | } 200 | } 201 | }) 202 | } 203 | } 204 | 205 | /** List of all available Google scopes supported by the Google Authenticator 206 | Get full scopes list at: https://developers.google.com/identity/protocols/googlescopes 207 | 208 | - GoogleAnalyticsRead: View your Google Analytics data. 209 | - Custom(String): Define a custom scope. 210 | - Collection([String]): Define a collection of scopes as an array of `String` 211 | */ 212 | public enum GoogleServiceScope { 213 | case GoogleAnalyticsRead 214 | case Custom(String) 215 | case Collection([String]) 216 | 217 | public func string() -> String { 218 | switch self { 219 | case .GoogleAnalyticsRead: 220 | return "https://www.googleapis.com/auth/analytics.readonly" 221 | case Custom(let scopeString): 222 | return scopeString 223 | case Collection(let scopeCollection): 224 | return scopeCollection.joinWithSeparator(" ") 225 | } 226 | } 227 | 228 | init(scopeString: String) { 229 | switch scopeString { 230 | case "https://www.googleapis.com/auth/analytics.readonly": 231 | self = .GoogleAnalyticsRead 232 | default: 233 | self = .Custom(scopeString) 234 | } 235 | } 236 | 237 | init(scopes: [String]) { 238 | self = .Collection(scopes) 239 | } 240 | 241 | public func isEqualToScope(scope: GoogleServiceScope) -> Bool { 242 | return self.string() == scope.string() 243 | } 244 | } -------------------------------------------------------------------------------- /Sources/Info-iOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.2.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Info-tvOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.2.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Info-watchOS.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.2.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Sources/Models/DeviceAuthorizationToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserCode.swift 3 | // GoogleAuthenticator 4 | // 5 | // Created by Fabio Milano on 12/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // EXAMPLE 12 | //{ 13 | // "device_code" : "THE DEVICE CODE", 14 | // "user_code" : "THE USER CODE", 15 | // "verification_url" : "https://www.google.com/device", 16 | // "expires_in" : 1800, 17 | // "interval" : 5 18 | //} 19 | 20 | /** 21 | * A DeviceAuthorizationToken struct represents the response object when starting an authentication via device token. 22 | */ 23 | internal struct DeviceAuthorizationToken { 24 | 25 | /// The device code related to the authorization request. 26 | let deviceCode: String 27 | 28 | /// The user code. This should be shown to the user to complete the authorization process. 29 | let userCode: String 30 | 31 | /// The verification URL to which the user should connect to in order to complete the authorization process. 32 | let verificationUrl: NSURL 33 | 34 | /// Expiration of the user code expressend in NSTimeInterval 35 | let expiresIn: NSTimeInterval 36 | 37 | /// The retry interval to use when polling the authorization against Google 38 | let retryInterval: NSTimeInterval 39 | 40 | init?(json: [String: AnyObject]) { 41 | guard let deviceCode = json[UserCodeJSONKey.DeviceCode.rawValue] as? String, 42 | verificationUrl = json[UserCodeJSONKey.VerificationUrl.rawValue] as? String, 43 | userCode = json[UserCodeJSONKey.UserCode.rawValue] as? String, 44 | expiresIn = json[UserCodeJSONKey.ExpiresIn.rawValue] as? NSTimeInterval, 45 | retryInterval = json[UserCodeJSONKey.RetryInterval.rawValue] as? NSTimeInterval else { 46 | return nil 47 | } 48 | 49 | self.deviceCode = deviceCode 50 | self.userCode = userCode 51 | self.verificationUrl = NSURL(string: verificationUrl)! 52 | self.expiresIn = expiresIn 53 | self.retryInterval = retryInterval 54 | } 55 | } 56 | 57 | private enum UserCodeJSONKey: String { 58 | case DeviceCode = "device_code" 59 | case VerificationUrl = "verification_url" 60 | case UserCode = "user_code" 61 | case ExpiresIn = "expires_in" 62 | case RetryInterval = "interval" 63 | } -------------------------------------------------------------------------------- /Sources/Models/GoogleAuthenticatorError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleAuthenticatorError.swift 3 | // GoogleAuthenticator 4 | // 5 | // Created by Fabio Milano on 11/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | * The designated representation for an error 13 | */ 14 | public enum GoogleAuthenticatorError: ErrorType { 15 | /// Error caused during the authorization flow 16 | case AuthorizationError(String) 17 | 18 | /// The current token is invalid. The user should rissue another autorization process. 19 | case InvalidAccessToken 20 | 21 | /// The authorization process is pending user input. 22 | case AuthorizationPending 23 | 24 | /// The device verification flow failed. Retry is adviced. 25 | case DeviceVerificationFailed(String) 26 | } -------------------------------------------------------------------------------- /Sources/Models/OAuthIntegrationProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuthIntegrationProtocol.swift 3 | // GoogleAuthenticator 4 | // 5 | // Created by Fabio Milano on 13/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | import Result 12 | 13 | /** 14 | * Defines the requirements for any type that represents an Access Token, such that it can be used by the `GoogleAuthenticatorClient` 15 | */ 16 | public protocol AuthorizedToken { 17 | 18 | /// The access token. 19 | var accessToken: String { get } 20 | 21 | /// The refresh token. 22 | var refreshToken: String? { get } 23 | 24 | /// The expiration time for current access token in seconds. 25 | var expiresIn: NSTimeInterval? { get } 26 | 27 | /// Convenience getter to know if current token has expired. 28 | var isExpired: Bool { get } 29 | } 30 | 31 | /** 32 | * Defines the requirements for an error type to be processable by the `GoogleAuthenticatorClient` 33 | */ 34 | public protocol GoogleAuthenticatorErrorAdapter: ErrorType { 35 | /// The representation of the error as GoogleAuthenticatorError type. 36 | var googleAuthenticatorError: GoogleAuthenticatorError { get } 37 | } 38 | 39 | /** 40 | * Defines the requirements for an OAuth client type to be used to initialize a `GoogleAuthenticatorClient` 41 | */ 42 | public protocol GoogleAuthenticatorOAuthClient { 43 | /// The type for the OAuth Client that the authenticator requires to handle OAuth requests and specifications 44 | associatedtype OAuthClientType 45 | 46 | /// The type used to represent a token retrieved via the OAuth Client 47 | associatedtype TokenType: AuthorizedToken 48 | 49 | /// The failure type that is used by the OAuth Client. Additionally, this type, must conform to the protocol needed to evaluate the error in the Google Authenticator implementation. 50 | associatedtype Failure: GoogleAuthenticatorErrorAdapter 51 | 52 | /// The client ID. 53 | var clientID: String { get } 54 | 55 | /// The client secret. 56 | var clientSecret: String { get } 57 | 58 | /// The authorize URL. 59 | var authorizeURL: NSURL { get } 60 | 61 | /// The token URL. 62 | var tokenURL: NSURL? { get } 63 | 64 | /// The redirect URL. 65 | var redirectURL: NSURL? { get } 66 | 67 | /// The token structure used by the OAuth client 68 | var token: TokenType? { get } 69 | 70 | /// The requested scopes. 71 | var scopes: [String] { get } 72 | 73 | /** 74 | The designated initializer for the OAuth Client encapsulate by the Google Authenticator Client 75 | 76 | - Parameters: 77 | - clientID: a.k.a. The consumer id 78 | - clientSecret: a.k.a. The consumer secret 79 | - bundleIdentifier: The bundle identifier of the app is used to build the proper callback via deep linking to the app 80 | - scope: The scope to authorize. Complete list available at `https://developers.google.com/identity/protocols/googlescopes` 81 | */ 82 | static func Google(clientID clientID: String, clientSecret: String, bundleIdentifier: String, scope: GoogleServiceScope) -> OAuthClientType 83 | 84 | #if os(iOS) 85 | /** 86 | Request access token. 87 | This method is called by the authenticator to kick off the OAuth 2.0 authentication process with its OAuthClient. 88 | 89 | - Parameters: 90 | - completion: The completion block. Takes a Result as parameter. 91 | - Success: Takes just issued access token as value 92 | - Failure: The encountered failure. 93 | */ 94 | func googleAuthenticatorAuthorize(completion: Result -> Void) 95 | #endif 96 | 97 | /** 98 | Refresh the available access token. 99 | This method is called by the authenticator when the access token provided by the OAuth client has expired and requires to be refreshed. 100 | 101 | - Parameters: 102 | - completion: The completion block. Takes a `Result` as parameter. 103 | - Success: Takes the a `refreshed` access token as value 104 | - Failure: The encountered failure. 105 | */ 106 | func googleAuthenticatorRefreshToken(completion: Result -> Void) 107 | 108 | /** 109 | Request an access token via device code. For more details see `https://developers.google.com/identity/protocols/OAuth2ForDevices` 110 | This method is called by the authenticator when the access token is requested from device with limited capabilities. 111 | 112 | - Parameters: 113 | - completion: The completion block. Takes a `Result` as parameter. 114 | - Success: Takes the access token as value 115 | - Failure: The encountered failure. 116 | */ 117 | func googleAuthenticatorAuthorizeDeviceCode(deviceCode: String, completion: Result -> Void) 118 | } 119 | 120 | /** 121 | Struct constant required to handle the authentication process with Google 122 | 123 | - AuthorizeUrl: The authorization URL used when requesting the authorization code (used to retrieve the oauth tokens) 124 | - AccessTokensUrl: The access token URL used to retrieve the OAuth tokens: access_token and refresh_token 125 | - CallbackPostfix: The string to append to the bundle identiifier to compose the callback URL used in the OAuth 2.0 authentication process. 126 | - DeviceVerificationUrl: The endpoint to use to kickoff the OAuth 2.0 authorization via devices with limited capabilities. 127 | */ 128 | public enum AuthenticationConstants: String { 129 | case AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth" 130 | case AccessTokensUrl = "https://www.googleapis.com/oauth2/v4/token" 131 | case CallbackPostfix = ":/urn:ietf:wg:oauth:2.0:oob" 132 | case DeviceVerificationUrl = "https://accounts.google.com/o/oauth2/device/code" 133 | 134 | /// A convenience getter to retrienve the URL representation for every enum value. 135 | public var URL: NSURL { 136 | return NSURL(string: self.rawValue)! 137 | } 138 | } -------------------------------------------------------------------------------- /Sources/Utilities/DispatchAfter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchAfter.swift 3 | // 4 | // Created by Nikita Took on 08/04/15. 5 | // Copyright (c) 2015. All rights reserved. 6 | // 7 | // inspired by implementation of Matt Neuburg (http://stackoverflow.com/users/341994/matt) 8 | // 9 | 10 | import Foundation 11 | 12 | public func delay(aDelay:NSTimeInterval, closure: () -> Void) { 13 | 14 | delay(aDelay, queue: dispatch_get_main_queue(), closure: closure) 15 | 16 | } 17 | 18 | public func delay(aDelay:NSTimeInterval, queue: dispatch_queue_t!, closure: () -> Void) { 19 | 20 | let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(aDelay * Double(NSEC_PER_SEC))) 21 | dispatch_after(delayTime, queue, closure) 22 | 23 | } 24 | 25 | public extension Int { 26 | var second: NSTimeInterval { return NSTimeInterval(self) } 27 | var seconds: NSTimeInterval { return NSTimeInterval(self) } 28 | var minute: NSTimeInterval { return NSTimeInterval(self * 60) } 29 | var minutes: NSTimeInterval { return NSTimeInterval(self * 60) } 30 | var hour: NSTimeInterval { return NSTimeInterval(self * 3600) } 31 | var hours: NSTimeInterval { return NSTimeInterval(self * 3600) } 32 | } 33 | 34 | public extension Double { 35 | var second: NSTimeInterval { return self } 36 | var seconds: NSTimeInterval { return self } 37 | var minute: NSTimeInterval { return self * 60 } 38 | var minutes: NSTimeInterval { return self * 60 } 39 | var hour: NSTimeInterval { return self * 3600 } 40 | var hours: NSTimeInterval { return self * 3600 } 41 | } -------------------------------------------------------------------------------- /Sources/Utilities/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // GoogleAuthenticator 4 | // 5 | // Created by Fabio Milano on 12/06/16. 6 | // Copyright © 2016 Touchwonders. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSMutableURLRequest { 12 | public func setAccessToken(accessToken: String) -> Void { 13 | self.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") 14 | } 15 | } -------------------------------------------------------------------------------- /Sources/Utilities/HTTPClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTP.swift 3 | // 4 | // Copyright (c) 2016 Damien (http://delba.io) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | // SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | import Result 27 | 28 | typealias JSON = [String: AnyObject] 29 | 30 | public let HTTPErrorDomain = "HTTPErrorDomain" 31 | 32 | internal enum HTTPErrorCode: Int { 33 | case NoDataFound = 1 34 | case JSONDeserializationError = 2 35 | } 36 | 37 | internal struct HTTP { 38 | static func POST(URL: NSURL, parameters: [String: String], completion: Result -> Void) { 39 | let request = NSMutableURLRequest(URL: URL) 40 | 41 | request.setValue("application/json", forHTTPHeaderField: "Accept") 42 | 43 | request.HTTPMethod = "POST" 44 | 45 | request.HTTPBody = parameters.map { "\($0)=\($1)" } 46 | .joinWithSeparator("&") 47 | .dataUsingEncoding(NSUTF8StringEncoding) 48 | 49 | let session = NSURLSession.sharedSession() 50 | 51 | let task = session.dataTaskWithRequest(request) { data, response, error in 52 | if let error = error { 53 | completion(.Failure(error)) 54 | return 55 | } 56 | 57 | guard let data = data else { 58 | let userInfo = [ NSLocalizedDescriptionKey : "No data found" ] 59 | let error = NSError(domain: HTTPErrorDomain, code: HTTPErrorCode.NoDataFound.rawValue, userInfo: userInfo) 60 | completion(.Failure(error)) 61 | return 62 | } 63 | 64 | do { 65 | let object = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) 66 | if let dictionary = object as? JSON { 67 | completion(.Success(dictionary)) 68 | } else { 69 | let userInfo = [ NSLocalizedDescriptionKey : "Cannot parse response" ] 70 | let error = NSError(domain: HTTPErrorDomain, code: HTTPErrorCode.JSONDeserializationError.rawValue, userInfo: userInfo) 71 | completion(.Failure(error)) 72 | } 73 | } catch { 74 | completion(.Failure(error as NSError)) 75 | } 76 | } 77 | 78 | task.resume() 79 | } 80 | } -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | CARTHAGE := $(shell command -v carthage) 2 | 3 | setup: 4 | ifeq ("$(CARTHAGE)","") 5 | $(error ${\n}Carthage is not installed.${\n}See https://github.com/Carthage/Carthage for install instructions) 6 | endif 7 | carthage bootstrap --platform iOS --no-use-binaries 8 | 9 | define \n 10 | 11 | 12 | endef --------------------------------------------------------------------------------