├── .swift-version ├── Simplicity.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcshareddata │ └── xcschemes │ │ └── Simplicity.xcscheme └── project.pbxproj ├── Simplicity ├── Simplicity.h ├── Info.plist ├── LoginProvider.swift ├── LoginError.swift ├── LoginProviders │ ├── VKontakte.swift │ ├── Facebook.swift │ └── Google.swift ├── Simplicity.swift ├── Helpers.swift ├── OAuth2Error.swift └── OAuth2.swift ├── Simplicity.podspec ├── .gitignore ├── README.md └── LICENSE /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /Simplicity.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Simplicity/Simplicity.h: -------------------------------------------------------------------------------- 1 | // 2 | // Simplicity.h 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/10/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Simplicity. 12 | FOUNDATION_EXPORT double SimplicityVersionNumber; 13 | 14 | //! Project version string for Simplicity. 15 | FOUNDATION_EXPORT const unsigned char SimplicityVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Simplicity/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.2 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Simplicity.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint Simplicity.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | 6 | Pod::Spec.new do |s| 7 | s.name = "Simplicity" 8 | s.version = "2.0.1" 9 | s.summary = "A simple way to login with Facebook or Google on iOS" 10 | 11 | s.description = <<-DESC 12 | Simplicity is a simple way to implement Facebook and Google login in your iOS and OS X apps. 13 | 14 | Simplicity can be easily extended to support other external login providers, including OAuth2, OpenID, SAML, and other custom protocols, and will support more in the future. We always appreciate pull requests! 15 | 16 | DESC 17 | 18 | s.homepage = "https://github.com/SimplicityMobile/Simplicity" 19 | s.license = 'Apache 2.0' 20 | s.author = { "Edward Jiang" => "edward@stormpath.com" } 21 | s.source = { :git => "https://github.com/SimplicityMobile/Simplicity.git", :tag => s.version.to_s } 22 | s.social_media_url = 'https://twitter.com/EdwardStarcraft' 23 | 24 | s.ios.deployment_target = '8.0' 25 | 26 | s.source_files = 'Simplicity/**/*.swift' 27 | 28 | end 29 | -------------------------------------------------------------------------------- /Simplicity/LoginProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoginProvider.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/10/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | A LoginProvider represents an external login provider that needs to be opened. 13 | */ 14 | public protocol LoginProvider { 15 | /// The URL to redirect to when beginning the login process 16 | var authorizationURL: URL { get } 17 | 18 | /// The URL Scheme that this LoginProvider is bound to. 19 | var urlScheme: String { get } 20 | 21 | /** 22 | Called when the external login provider links back into the application 23 | with a URL Scheme matching the login provider's URL Scheme. 24 | 25 | - parameters: 26 | - url: The URL that triggered that AppDelegate's link handler 27 | - callback: A callback that returns with an access token or NSError. 28 | */ 29 | func linkHandler(_ url: URL, callback: @escaping ExternalLoginCallback) 30 | } 31 | 32 | public extension LoginProvider { 33 | func login(_ callback: @escaping ExternalLoginCallback) { 34 | Simplicity.login(self, callback: callback) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Simplicity/LoginError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimplicityError.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/17/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | An error produced by a LoginProvider on redirecting back to the app. Error 13 | domain is "Simplicity" 14 | */ 15 | public class LoginError: NSError { 16 | /// An error that should never happen. If seen, please open a GitHub issue. 17 | public static let InternalSDKError = LoginError(code: 0, description: "Internal SDK Error") 18 | 19 | /** 20 | Initializer for LoginError 21 | 22 | - parameters: 23 | - code: Error code for the error 24 | - description: Localized description of the error. 25 | */ 26 | public init(code: Int, description: String) { 27 | var userInfo = [String: Any]() 28 | userInfo[NSLocalizedDescriptionKey] = description 29 | 30 | super.init(domain: "Simplicity", code: code, userInfo: userInfo) 31 | } 32 | 33 | /// Unimplemented stub since NSError implements requires this init method. 34 | public required init?(coder aDecoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Simplicity/LoginProviders/VKontakte.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VKontakte.swift 3 | // Simplicity 4 | // 5 | // Created by Andrey Toropchin on 14.07.16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | /** 10 | Class implementing VKontakte (VK.com) implicit grant flow. 11 | 12 | ## Using VKontakte in your app. 13 | 14 | To get started, you first need to [create an application](https://vk.com/dev/) with VKontakte. 15 | After registering your app, go into your client settings page. 16 | Set App Bundle ID for iOS to your App Bundle in Xcode -> Target -> Bundle Identifier (e.g. com.developer.applicationName) 17 | 18 | Finally, open up your App's Xcode project and go to the project's 19 | info tab. Under "URL Types", add a new entry, and in the URL schemes form 20 | field, type in `vk[CLIENT_ID_HERE]`. Then, you can initiate the login 21 | screen by calling: 22 | ``` 23 | Simplicity.login(VKontakte()) { (accessToken, error) in 24 | // Insert code here 25 | } 26 | ``` 27 | */ 28 | 29 | public class VKontakte: OAuth2 { 30 | 31 | public init() { 32 | guard let urlScheme = Helpers.registeredURLSchemes(filter: {$0.hasPrefix("vk")}).first, 33 | let range = urlScheme.range(of: "\\d+", options: .regularExpression) else { 34 | preconditionFailure("You must configure your VK URL Scheme to use VK login.") 35 | } 36 | let clientId = urlScheme.substring(with: range) 37 | let authorizationEndpoint = URL(string: "https://oauth.vk.com/authorize")! 38 | let redirectEndpoint = URL(string: urlScheme + "://authorize")! 39 | 40 | super.init(clientId: clientId, authorizationEndpoint: authorizationEndpoint, redirectEndpoint: redirectEndpoint, grantType: .Implicit) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xcuserstate 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /Simplicity/Simplicity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Simplicity.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/10/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SafariServices 11 | 12 | /// Callback handler after an external login completes. 13 | public typealias ExternalLoginCallback = (String?, NSError?) -> Void 14 | 15 | /** 16 | Simplicity is a framework for authenticating with external providers on iOS. 17 | */ 18 | public final class Simplicity { 19 | private static var currentLoginProvider: LoginProvider? 20 | private static var callback: ExternalLoginCallback? 21 | private static var safari: UIViewController? 22 | 23 | /** 24 | Begin the login flow by redirecting to the LoginProvider's website. 25 | 26 | - parameters: 27 | - loginProvider: The login provider object configured to be used. 28 | - callback: A callback with the access token, or a SimplicityError. 29 | */ 30 | public static func login(_ loginProvider: LoginProvider, callback: @escaping ExternalLoginCallback) { 31 | self.currentLoginProvider = loginProvider 32 | self.callback = callback 33 | 34 | presentSafariView(loginProvider.authorizationURL) 35 | } 36 | 37 | /// Deep link handler (iOS9) 38 | public static func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any]) -> Bool { 39 | safari?.dismiss(animated: true, completion: nil) 40 | guard let callback = callback, url.scheme == currentLoginProvider?.urlScheme else { 41 | return false 42 | } 43 | currentLoginProvider?.linkHandler(url, callback: callback) 44 | currentLoginProvider = nil 45 | 46 | return true 47 | } 48 | 49 | /// Deep link handler ( Bool { 51 | return self.application(application, open: url, options: [UIApplicationOpenURLOptionsKey: Any]()) 52 | } 53 | 54 | private static func presentSafariView(_ url: URL) { 55 | if #available(iOS 9, *) { 56 | safari = SFSafariViewController(url: url) 57 | var topController = UIApplication.shared.keyWindow?.rootViewController 58 | while let vc = topController?.presentedViewController { 59 | topController = vc 60 | } 61 | topController?.present(safari!, animated: true, completion: nil) 62 | } else { 63 | UIApplication.shared.openURL(url) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Simplicity/LoginProviders/Facebook.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Facebook.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/10/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Class implementing Facebook login's mobile implicit grant flow. 13 | 14 | ## Using Facebook Login in your app. 15 | 16 | To get started, you first need to [register an 17 | application](https://developers.facebook.com/?advanced_app_create=true) with 18 | Facebook. After registering your app, go into your app dashboard's settings 19 | page. Click "Add Platform", and fill in your Bundle ID, and turn "Single Sign 20 | On" on. 21 | 22 | Finally, open up your App's Xcode project and go to the project's info tab. 23 | Under "URL Types", add a new entry, and in the URL schemes form field, type in 24 | `fb[APP_ID_HERE]`, replacing `[APP_ID_HERE]` with your Facebook App ID. 25 | 26 | Then, you can initiate the login screen by calling: 27 | 28 | ``` 29 | Simplicity.login(Facebook()) { (accessToken, error) in 30 | // Insert code here 31 | } 32 | ``` 33 | */ 34 | public class Facebook: OAuth2 { 35 | /// Facebook Auth Type 36 | public var authType = FacebookAuthType.None 37 | 38 | /// An array with query string parameters for the authorization URL. 39 | override public var authorizationURLParameters: [String : String?] { 40 | var result = super.authorizationURLParameters 41 | result["auth_type"] = authType.rawValue 42 | return result 43 | } 44 | 45 | /** 46 | Initializes the Facebook login object. Auto configures based on the URL 47 | scheme you have in your app. 48 | */ 49 | public init() { 50 | // Search for URL Scheme, error if not there 51 | 52 | guard let urlScheme = Helpers.registeredURLSchemes(filter: {$0.hasPrefix("fb")}).first, 53 | let range = urlScheme.range(of: "\\d+", options: .regularExpression) else { 54 | preconditionFailure("You must configure your Facebook URL Scheme to use Facebook login.") 55 | } 56 | let clientId = urlScheme.substring(with: range) 57 | let authorizationEndpoint = URL(string: "https://www.facebook.com/dialog/oauth")! 58 | let redirectEndpoint = URL(string: urlScheme + "://authorize")! 59 | 60 | super.init(clientId: clientId, authorizationEndpoint: authorizationEndpoint, redirectEndpoint: redirectEndpoint, grantType: .Implicit) 61 | } 62 | } 63 | 64 | /** 65 | Facebook supports an OAuth extension that allows you to do additional things 66 | with its login page. 67 | */ 68 | public enum FacebookAuthType: String { 69 | /** 70 | Re-requests permissions from the user. Otherwise they will login (but 71 | still with declined scopes) 72 | */ 73 | case Rerequest = "rerequest", 74 | 75 | /// Asks the user to type in their password again. 76 | Reauthenticate = "reauthenticate", 77 | 78 | /// None 79 | None = "" 80 | } 81 | -------------------------------------------------------------------------------- /Simplicity/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/10/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class Helpers { 12 | /** 13 | Returns a list of URL Schemes that match the filter closure. 14 | 15 | - parameters: 16 | - closure: A closure returning true / false as to whether or not the URL 17 | Scheme matches the filter 18 | - returns: A list of URL Schemes that match the filter closure. 19 | */ 20 | static func registeredURLSchemes(filter closure: (String) -> Bool) -> [String] { 21 | guard let urlTypes = Bundle.main.infoDictionary?["CFBundleURLTypes"] as? [[String: AnyObject]] else { 22 | return [String]() 23 | } 24 | 25 | // Convert the complex dictionary into an array of URL schemes 26 | let urlSchemes = urlTypes.flatMap({($0["CFBundleURLSchemes"] as? [String])?.first }) 27 | 28 | return urlSchemes.flatMap({closure($0) ? $0 : nil}) 29 | } 30 | 31 | /** 32 | Converts a dictionary into a query string. 33 | 34 | - parameters: 35 | - parts: A dictionary of parameters to put in a query string. 36 | - returns: A query string 37 | */ 38 | static func queryString(_ parts: [String: String?]) -> String? { 39 | return parts.flatMap { key, value -> String? in 40 | if let value = value { 41 | return key + "=" + value 42 | } else { 43 | return nil 44 | } 45 | }.joined(separator: "&").addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) 46 | } 47 | } 48 | 49 | extension URL { 50 | /// Dictionary with key/value pairs from the URL fragment 51 | var fragmentDictionary: [String: String] { 52 | return dictionaryFromFormEncodedString(fragment) 53 | } 54 | 55 | /// Dictionary with key/value pairs from the URL query string 56 | var queryDictionary: [String: String] { 57 | return dictionaryFromFormEncodedString(query) 58 | } 59 | 60 | var fragmentAndQueryDictionary: [String: String] { 61 | var result = fragmentDictionary 62 | queryDictionary.forEach { (key, value) in 63 | result[key] = value 64 | } 65 | return result 66 | } 67 | 68 | private func dictionaryFromFormEncodedString(_ input: String?) -> [String: String] { 69 | var result = [String: String]() 70 | 71 | guard let input = input else { 72 | return result 73 | } 74 | let inputPairs = input.components(separatedBy: "&") 75 | 76 | for pair in inputPairs { 77 | let split = pair.components(separatedBy: "=") 78 | if split.count == 2 { 79 | if let key = split[0].removingPercentEncoding, let value = split[1].removingPercentEncoding { 80 | result[key] = value 81 | } 82 | } 83 | } 84 | return result 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Simplicity.xcodeproj/xcshareddata/xcschemes/Simplicity.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 | -------------------------------------------------------------------------------- /Simplicity/OAuth2Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth2Error.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/17/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | An OAuth 2 Error response. Subclass of LoginError. 13 | Error codes subject to change, so initialize a OAuth2ErrorCode enum with the 14 | raw value of the error code to check. 15 | */ 16 | public class OAuth2Error: LoginError { 17 | /// A mapping of OAuth 2 Error strings to OAuth2ErrorCode enum. 18 | public static let mapping: [String: OAuth2ErrorCode] = [ "invalid_request": .invalidRequest, 19 | "unauthorized_client": .unauthorizedClient, 20 | "access_denied": .accessDenied, 21 | "unsupported_response_type": .unsupportedResponseType, 22 | "invalid_scope": .invalidScope, 23 | "server_error": .serverError, 24 | "temporarily_unavailable": .temporarilyUnavailable ] 25 | 26 | /** 27 | Constructs a OAuth 2 error object from an OAuth response. 28 | 29 | - parameters: 30 | - callbackParameters: A dictionary of OAuth 2 Error response parameters. 31 | - returns: OAuth2Error object. 32 | */ 33 | public class func error(_ callbackParameters: [String: String]) -> LoginError? { 34 | let errorCode = mapping[callbackParameters["error"] ?? ""] 35 | 36 | if let errorCode = errorCode { 37 | let errorDescription = callbackParameters["error_description"]?.removingPercentEncoding?.replacingOccurrences(of: "+", with: " ") ?? errorCode.description 38 | 39 | return OAuth2Error(code: errorCode.rawValue, description: errorDescription) 40 | } else { 41 | return nil 42 | } 43 | } 44 | } 45 | 46 | /// OAuth 2 Error codes 47 | public enum OAuth2ErrorCode: Int, CustomStringConvertible { 48 | /** 49 | The request is missing a required parameter. This is usually programmer 50 | error, and should be filed as a GitHub issue. 51 | */ 52 | case invalidRequest = 100, 53 | 54 | /// The client ID is not authorized to make this request. 55 | unauthorizedClient, 56 | 57 | /// The user or OAuth server denied this request. 58 | accessDenied, 59 | 60 | /// The grant type requested is not supported. This is programmer error. 61 | unsupportedResponseType, 62 | 63 | /// A scope requested is invalid. 64 | invalidScope, 65 | 66 | /// The authorization server is currently experiencing an error. 67 | serverError, 68 | 69 | /// The authorization server is currently unavailable. 70 | temporarilyUnavailable 71 | 72 | /// User readable default error message 73 | public var description: String { 74 | switch self { 75 | case .invalidRequest: 76 | return "The OAuth request is missing a required parameter" 77 | case .unauthorizedClient: 78 | return "The client ID is not authorized to make this request" 79 | case .accessDenied: 80 | return "You denied the login request" 81 | case .unsupportedResponseType: 82 | return "The grant type requested is not supported" 83 | case .invalidScope: 84 | return "A scope requested is invalid" 85 | case .serverError: 86 | return "The login server experienced an internal error" 87 | case .temporarilyUnavailable: 88 | return "The login server is temporarily unavailable. Please try again later. " 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Simplicity/LoginProviders/Google.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Google.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/18/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Class implementing Google login's mobile OAuth 2 flow. 13 | 14 | ## Using Google Login in your app. 15 | 16 | To get started, you first need to [register an 17 | application](https://console.developers.google.com/project) with Google. Click 18 | "Enable and Manage APIs", and then the credentials tab. Create two sets of 19 | OAuth Client IDs, one as "Web Application", and one as "iOS". 20 | 21 | Finally, open up your App's Xcode project and go to the project's info tab. 22 | Under "URL Types", add a new entry, and in the URL schemes form field, type in 23 | your Google iOS Client's `iOS URL scheme` from the Google Developer Console. 24 | 25 | Then, you can initiate the login screen by calling: 26 | 27 | ``` 28 | Simplicity.login(Google()) { (accessToken, error) in 29 | // Insert code here 30 | } 31 | ``` 32 | */ 33 | public class Google: OAuth2 { 34 | 35 | /** 36 | Initializes the Google login object. Auto configures based on the URL 37 | scheme you have in your app. Uses default scopes of 'email profile' to 38 | match the Google SDK. 39 | */ 40 | public init() { 41 | guard let urlScheme = Helpers.registeredURLSchemes(filter: {$0.hasPrefix("com.googleusercontent.apps.")}).first else { 42 | preconditionFailure("You must configure your Google URL Scheme to use Google login.") 43 | } 44 | 45 | let appId = urlScheme.components(separatedBy: ".").reversed().joined(separator: ".") 46 | let authorizationEndpoint = URL(string: "https://accounts.google.com/o/oauth2/auth")! 47 | let redirectionEndpoint = URL(string: "\(urlScheme):/oauth2callback")! 48 | 49 | super.init(clientId: appId, authorizationEndpoint: authorizationEndpoint, redirectEndpoint: redirectionEndpoint, grantType: .AuthorizationCode) 50 | self.scopes = ["email", "profile"] 51 | } 52 | 53 | /** 54 | Handles the resulting link from the OAuth Redirect 55 | 56 | - parameters: 57 | - url: The OAuth redirect URL 58 | - callback: A callback that returns with an access token or NSError. 59 | */ 60 | override public func linkHandler(_ url: URL, callback: @escaping ExternalLoginCallback) { 61 | guard let authorizationCode = url.queryDictionary["code"], url.queryDictionary["state"] == state else { 62 | if let error = OAuth2Error.error(url.queryDictionary) ?? OAuth2Error.error(url.queryDictionary) { 63 | callback(nil, error) 64 | } else { 65 | callback(nil, LoginError.InternalSDKError) 66 | } 67 | return 68 | } 69 | exchangeCodeForAccessToken(authorizationCode, callback: callback) 70 | } 71 | 72 | private func exchangeCodeForAccessToken(_ authorizationCode: String, callback: @escaping ExternalLoginCallback) { 73 | let session = URLSession(configuration: URLSessionConfiguration.ephemeral) 74 | let url = URL(string: "https://www.googleapis.com/oauth2/v4/token")! 75 | 76 | let requestParams: [String: String?] = ["client_id": clientId, 77 | "code": authorizationCode, 78 | "grant_type": "authorization_code", 79 | "redirect_uri": authorizationURLParameters["redirect_uri"] ?? nil] 80 | 81 | var request = URLRequest(url: url) 82 | request.httpMethod = "POST" 83 | request.httpBody = Helpers.queryString(requestParams)?.data(using: String.Encoding.utf8) 84 | 85 | let task = session.dataTask(with: request) { (data, response, error) -> Void in 86 | guard let data = data, let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any], let accessToken = json["access_token"] as? String else { 87 | callback(nil, LoginError.InternalSDKError) // This request should not fail. 88 | return 89 | } 90 | callback(accessToken, nil) 91 | } 92 | task.resume() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Simplicity/OAuth2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OAuth2.swift 3 | // Simplicity 4 | // 5 | // Created by Edward Jiang on 5/17/16. 6 | // Copyright © 2016 Stormpath. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Base implementation of a basic OAuth 2 provider. Only supports Implicit grant 13 | types, but is overridable to support custom grant types. 14 | 15 | You should never implement the AuthorizationCode grant type unless you're sure 16 | that the API Key Secret used is not important for anything else. Otherwise, 17 | it's a security concern to leave it in your app as it can be sniffed and used 18 | for malicious purposes. 19 | */ 20 | public class OAuth2: LoginProvider { 21 | /// A set of OAuth 2 Scopes to request from the login provider. 22 | public final var scopes = Set() 23 | 24 | /// The URL Scheme registered by the app. 25 | public final var urlScheme: String { 26 | return redirectEndpoint.scheme! 27 | } 28 | 29 | /// The state used to prevent CSRF attacks with bad access tokens. 30 | public final let state = String(arc4random_uniform(10000000)) 31 | 32 | /// The OAuth 2 Client ID 33 | public final let clientId: String 34 | 35 | /// The OAuth 2 Grant Type 36 | public final let grantType: OAuth2GrantType 37 | 38 | /// The OAuth 2 authorization endpoint 39 | public final let authorizationEndpoint: URL 40 | 41 | /// The OAuth 2 redirection endpoint 42 | public final let redirectEndpoint: URL 43 | 44 | /** 45 | An array with query string parameters for the authorization URL. 46 | Override this to pass custom data to the OAuth provider. 47 | */ 48 | public var authorizationURLParameters: [String: String?] { 49 | guard grantType != .Custom else { 50 | preconditionFailure("Custom Grant Type Not Supported") 51 | } 52 | return ["client_id": clientId, 53 | "redirect_uri": redirectEndpoint.absoluteString, 54 | "response_type": grantType.rawValue, 55 | "scope": scopes.joined(separator: " "), 56 | "state": state] 57 | } 58 | 59 | /// The authorization URL to start the OAuth flow. 60 | public var authorizationURL: URL { 61 | guard grantType != .Custom else { 62 | preconditionFailure("Custom Grant Type Not Supported") 63 | } 64 | 65 | var url = URLComponents(url: authorizationEndpoint, resolvingAgainstBaseURL: false)! 66 | 67 | url.queryItems = authorizationURLParameters.flatMap({key, value -> URLQueryItem? in 68 | return value != nil ? URLQueryItem(name: key, value: value) : nil 69 | }) 70 | 71 | return url.url! 72 | } 73 | 74 | /** 75 | Handles the resulting link from the OAuth Redirect 76 | 77 | - parameters: 78 | - url: The OAuth redirect URL 79 | - callback: A callback that returns with an access token or NSError. 80 | */ 81 | public func linkHandler(_ url: URL, callback: @escaping ExternalLoginCallback) { 82 | switch grantType { 83 | case .AuthorizationCode: 84 | preconditionFailure("Authorization Code Grant Type Not Supported") 85 | case .Implicit: 86 | // Get the access token, and check that the state is the same 87 | guard let accessToken = url.fragmentDictionary["access_token"], url.fragmentAndQueryDictionary["state"] == state else { 88 | /** 89 | Facebook's mobile implicit grant type returns errors as 90 | query. Don't think it's a huge issue to be liberal in looking 91 | for errors, so will check both. 92 | */ 93 | if let error = OAuth2Error.error(url.fragmentAndQueryDictionary) { 94 | callback(nil, error) 95 | } else { 96 | callback(nil, LoginError.InternalSDKError) 97 | } 98 | return 99 | } 100 | 101 | callback(accessToken, nil) 102 | case .Custom: 103 | preconditionFailure("Custom Grant Type Not Supported") 104 | } 105 | } 106 | 107 | /** 108 | Creates a generic OAuth 2 Login Provider. 109 | 110 | - parameters: 111 | - clientId: The OAuth Client ID 112 | - authorizationEndpoint: The OAuth Provider's Authorization Endpoint. The 113 | application will redirect to this endpoint to start the login flow. 114 | - redirectEndpoint: The redirect URI passed to the provider. 115 | - grantType: The OAuth 2 Grant Type 116 | */ 117 | public init(clientId: String, authorizationEndpoint: URL, redirectEndpoint: URL, grantType: OAuth2GrantType) { 118 | self.grantType = grantType 119 | self.clientId = clientId 120 | self.authorizationEndpoint = authorizationEndpoint 121 | self.redirectEndpoint = redirectEndpoint 122 | 123 | if Helpers.registeredURLSchemes(filter: {$0 == self.urlScheme}).count != 1 { 124 | preconditionFailure("You must register your URL Scheme in Info.plist.") 125 | } 126 | } 127 | } 128 | 129 | /// The OAuth 2 Grant Type 130 | public enum OAuth2GrantType: String { 131 | /// Authorization Code Grant Type 132 | case AuthorizationCode = "code", 133 | 134 | /// Implicit Grant Type 135 | Implicit = "token", 136 | 137 | /// Custom Grant Type 138 | Custom = "" 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplicity 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/Simplicity.svg?style=flat)](http://cocoapods.org/pods/Simplicity) 4 | [![License](https://img.shields.io/cocoapods/l/Simplicity.svg?style=flat)](http://cocoapods.org/pods/Simplicity) 5 | [![Platform](https://img.shields.io/cocoapods/p/Simplicity.svg?style=flat)](http://cocoapods.org/pods/Simplicity) [![codebeat badge](https://codebeat.co/badges/be32bb87-36e8-47e3-9324-5eae153a4d6d)](https://codebeat.co/projects/github-com-simplicitymobile-simplicity) 6 | [![Slack Status](https://talkstormpath.shipit.xyz/badge.svg)](https://talkstormpath.shipit.xyz) 7 | 8 | Simplicity is a simple way to implement Facebook and Google login in your iOS apps. 9 | 10 | Simplicity can be easily extended to support other external login providers, including OAuth2, OpenID, SAML, and other custom protocols, and will support more in the future. We always appreciate pull requests! 11 | 12 | ## Why use Simplicity? 13 | 14 | Facebook and Google's SDKs are heavyweight, and take time to set up and use. You can use Simplicity and only have to manage one SDK for logging in with an external provider in your app. Simplicity adds just 200KB to your app's binary, compared to 5.4MB when using the Facebook & Google SDKs. 15 | 16 | Simplicity is also extensible, and already supports other login providers, like VKontakte (the largest European social network) and generic OAuth providers. 17 | 18 | Logging in with Simplicity is as easy as: 19 | 20 | ```Swift 21 | Simplicity.login(Facebook()) { (accessToken, error) in 22 | // Handle access token here 23 | } 24 | ``` 25 | 26 | ## Stormpath 27 | 28 | Simplicity is maintained by [Stormpath](https://stormpath.com), an API service for authentication, authorization, and user management. If you're building a backend API for your app, consider using Stormpath to help you implement a secure REST API. Read our tutorial on how to [build a REST API for your mobile apps using Node.js](https://stormpath.com/blog/tutorial-build-rest-api-mobile-apps-using-node-js). 29 | 30 | ## Installation 31 | 32 | Requires XCode 8+ / Swift 3+ 33 | 34 | To install Simplicity, we use [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: 35 | 36 | ```ruby 37 | pod 'Simplicity' 38 | ``` 39 | 40 | **Carthage** 41 | 42 | To use Simplicity with [Carthage](https://github.com/Carthage/Carthage), specify it in your `Cartfile`: 43 | 44 | ```ogdl 45 | github "SimplicityMobile/Simplicity" 46 | ``` 47 | 48 | **Swift 2** 49 | 50 | Older versions of Simplicity support Swift 2.3 (Xcode 8) or Swift 2.2 (Xcode 7). 51 | 52 | * Swift 2.3 support is on branch [`swift2.3`](https://github.com/SimplicityMobile/Simplicity/tree/swift2.3) 53 | * Swift 2.2 support is on version [`1.x`](https://github.com/SimplicityMobile/Simplicity/tree/1.0.2) 54 | 55 | ### Add the link handlers to the AppDelegate 56 | 57 | When a user finishes their log in flow, Facebook or Google will redirect back into the app. Simplicity will listen for the access token or error. You need to add the following lines of code to `AppDelegate.swift`: 58 | 59 | ```Swift 60 | import Simplicity 61 | 62 | func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey: Any]) -> Bool { 63 | return Simplicity.application(app, open: url, options: options) 64 | } 65 | 66 | func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool { 67 | return Simplicity.application(application, open: url, sourceApplication: sourceApplication, annotation: annotation) 68 | } 69 | ``` 70 | 71 | # Usage 72 | 73 | Simplicity is very flexible and supports a number of configuration options for your login providers. To view, please see the [full API docs on CocoaDocs](http://cocoadocs.org/docsets/Simplicity/). 74 | 75 | ## Using Facebook Login 76 | 77 | To get started, you first need to [register an application](https://developers.facebook.com/?advanced_app_create=true) with Facebook. After registering your app, go into your app dashboard's settings page. Click "Add Platform", and fill in your Bundle ID, and turn "Single Sign On" on. 78 | 79 | Finally, open up your App's Xcode project and go to the project's info tab. Under "URL Types", add a new entry, and in the URL schemes form field, type in `fb[APP_ID_HERE]`, replacing `[APP_ID_HERE]` with your Facebook App ID. 80 | 81 | Then, you can initiate the login screen by calling: 82 | 83 | ```Swift 84 | Simplicity.login(Facebook()) { (accessToken, error) in 85 | // Handle access token here 86 | } 87 | ``` 88 | 89 | By request, you can also call `.login` on any `LoginProvider`: 90 | 91 | ```Swift 92 | Facebook().login { (accessToken, error) in 93 | // Handle access token here 94 | } 95 | ``` 96 | 97 | ## Using Google Login 98 | 99 | To get started, you first need to [register an application](https://console.developers.google.com/project) with Google. Click "Enable and Manage APIs", and then the [credentials tab](https://console.developers.google.com/apis/credentials). Create an OAuth Client ID for "iOS". 100 | 101 | Next, open up your App's Xcode project and go to the project's info tab. Under "URL Types", add a new entry, and in the URL schemes form field, type in your Google iOS Client's `iOS URL scheme` from the Google Developer Console. 102 | 103 | Then, you can initiate the login screen by calling: 104 | 105 | ```Swift 106 | Simplicity.login(Google()) { (accessToken, error) in 107 | // Handle access token here 108 | } 109 | ``` 110 | 111 | ## Using VKontakte Login 112 | 113 | To get started, you first need to [create an application](https://vk.com/dev/) with VKontakte. 114 | After registering your app, go into your client settings page. 115 | Set App Bundle ID for iOS to your App Bundle in Xcode -> Target -> Bundle Identifier (e.g. com.developer.applicationName) 116 | 117 | Finally, open up your App's Xcode project and go to the project's 118 | info tab. Under "URL Types", add a new entry, and in the URL schemes form 119 | field, type in `vk[CLIENT_ID_HERE]`. Then, you can initiate the login 120 | screen by calling: 121 | 122 | ``` 123 | Simplicity.login(VKontakte()) { (accessToken, error) in 124 | // Handle access token here 125 | } 126 | ``` 127 | 128 | ## Generic OAuth Provider 129 | 130 | Simplicity supports any OAuth provider that implements the Implicit grant type. 131 | 132 | ```Swift 133 | let provider = OAuth2(clientId: clientId, authorizationEndpoint: authorizationEndpoint, redirectEndpoint: redirectEndpoint, grantType: .Implicit) 134 | 135 | Simplicity.login(provider) { (accessToken, error) in 136 | // Handle access token here 137 | } 138 | ``` 139 | 140 | ## Requesting Scopes for OAuth Providers 141 | 142 | If you need custom scopes, you can modify the Facebook or Google object to get them. 143 | 144 | ```Swift 145 | let facebook = Facebook() 146 | facebook.scopes = ["public_profile", "email", "user_friends"] 147 | 148 | Simplicity.login(facebook) { (accessToken, error) in 149 | // Handle access token here 150 | } 151 | ``` 152 | 153 | ## Twitter, LinkedIn, and GitHub 154 | 155 | We can't implement Twitter, GitHub, LinkedIn, Slack, or other login types because we can't do authorization_code grants without a client secret. Client secrets are fundamentally insecure on mobile clients, so we need to create a companion server to help with the authentication request. 156 | 157 | If this is something you'd like to see, please +1 or follow this [GitHub Issue to create a companion server](https://github.com/SimplicityMobile/Simplicity/issues/1) so I know that there's demand for this. 158 | 159 | ## Other External Login Providers 160 | 161 | Want another external login provider implemented? Please [open a GitHub issue](https://github.com/SimplicityMobile/Simplicity/issues) so I know it's in demand, or consider contributing to this project! 162 | 163 | ## Contributing 164 | 165 | Please send a pull request with your new LoginProvider implemented. LoginProviders should try to autoconfigure where possible. 166 | 167 | ## License 168 | 169 | Simplicity is available under the Apache 2.0 license. See the LICENSE file for more info. 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Simplicity.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A132748E1D3750DC002ED68B /* VKontakte.swift in Sources */ = {isa = PBXBuildFile; fileRef = A132748D1D3750DC002ED68B /* VKontakte.swift */; }; 11 | DF74EC341CE2A8BB008F16BF /* Simplicity.h in Headers */ = {isa = PBXBuildFile; fileRef = DF74EC331CE2A8BB008F16BF /* Simplicity.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | DF74EC3F1CE2A943008F16BF /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = DF74EC3E1CE2A943008F16BF /* LICENSE */; }; 13 | DF74EC411CE2AC2F008F16BF /* LoginProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF74EC401CE2AC2F008F16BF /* LoginProvider.swift */; }; 14 | DF74EC451CE2AC54008F16BF /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF74EC441CE2AC54008F16BF /* Helpers.swift */; }; 15 | DF74EC471CE2AC6F008F16BF /* Simplicity.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF74EC461CE2AC6F008F16BF /* Simplicity.swift */; }; 16 | DF74EC4A1CE2ACF0008F16BF /* Facebook.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF74EC491CE2ACF0008F16BF /* Facebook.swift */; }; 17 | DF74EC4C1CE2AD19008F16BF /* Simplicity.podspec in Resources */ = {isa = PBXBuildFile; fileRef = DF74EC4B1CE2AD19008F16BF /* Simplicity.podspec */; }; 18 | DFB3897F1CEBAA57001DDCCE /* LoginError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB3897E1CEBAA57001DDCCE /* LoginError.swift */; }; 19 | DFB389811CEBAA98001DDCCE /* OAuth2Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB389801CEBAA98001DDCCE /* OAuth2Error.swift */; }; 20 | DFB389861CECFBFC001DDCCE /* Google.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB389851CECFBFC001DDCCE /* Google.swift */; }; 21 | DFB389881CED3667001DDCCE /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFB389871CED3667001DDCCE /* OAuth2.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | A132748D1D3750DC002ED68B /* VKontakte.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VKontakte.swift; path = LoginProviders/VKontakte.swift; sourceTree = ""; }; 26 | DF74EC301CE2A8BB008F16BF /* Simplicity.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Simplicity.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | DF74EC331CE2A8BB008F16BF /* Simplicity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Simplicity.h; sourceTree = ""; }; 28 | DF74EC351CE2A8BB008F16BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | DF74EC3B1CE2A919008F16BF /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 30 | DF74EC3C1CE2A936008F16BF /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 31 | DF74EC3E1CE2A943008F16BF /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 32 | DF74EC401CE2AC2F008F16BF /* LoginProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginProvider.swift; sourceTree = ""; }; 33 | DF74EC441CE2AC54008F16BF /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; 34 | DF74EC461CE2AC6F008F16BF /* Simplicity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Simplicity.swift; sourceTree = ""; }; 35 | DF74EC491CE2ACF0008F16BF /* Facebook.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Facebook.swift; path = LoginProviders/Facebook.swift; sourceTree = ""; }; 36 | DF74EC4B1CE2AD19008F16BF /* Simplicity.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Simplicity.podspec; sourceTree = ""; }; 37 | DFB3897E1CEBAA57001DDCCE /* LoginError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginError.swift; sourceTree = ""; }; 38 | DFB389801CEBAA98001DDCCE /* OAuth2Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2Error.swift; sourceTree = ""; }; 39 | DFB389851CECFBFC001DDCCE /* Google.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Google.swift; path = LoginProviders/Google.swift; sourceTree = ""; }; 40 | DFB389871CED3667001DDCCE /* OAuth2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | DF74EC2C1CE2A8BB008F16BF /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | DF74EC261CE2A8BB008F16BF = { 55 | isa = PBXGroup; 56 | children = ( 57 | DF74EC4D1CE2AE4E008F16BF /* Root Directory */, 58 | DF74EC321CE2A8BB008F16BF /* Simplicity */, 59 | DF74EC311CE2A8BB008F16BF /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | DF74EC311CE2A8BB008F16BF /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | DF74EC301CE2A8BB008F16BF /* Simplicity.framework */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | DF74EC321CE2A8BB008F16BF /* Simplicity */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | DFB389821CEBC706001DDCCE /* Base */, 75 | DF74EC481CE2ACB8008F16BF /* LoginProviders */, 76 | DF74EC331CE2A8BB008F16BF /* Simplicity.h */, 77 | DF74EC351CE2A8BB008F16BF /* Info.plist */, 78 | DF74EC461CE2AC6F008F16BF /* Simplicity.swift */, 79 | ); 80 | path = Simplicity; 81 | sourceTree = ""; 82 | }; 83 | DF74EC481CE2ACB8008F16BF /* LoginProviders */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | DF74EC491CE2ACF0008F16BF /* Facebook.swift */, 87 | DFB389851CECFBFC001DDCCE /* Google.swift */, 88 | A132748D1D3750DC002ED68B /* VKontakte.swift */, 89 | ); 90 | name = LoginProviders; 91 | sourceTree = ""; 92 | }; 93 | DF74EC4D1CE2AE4E008F16BF /* Root Directory */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | DF74EC4B1CE2AD19008F16BF /* Simplicity.podspec */, 97 | DF74EC3E1CE2A943008F16BF /* LICENSE */, 98 | DF74EC3C1CE2A936008F16BF /* README.md */, 99 | DF74EC3B1CE2A919008F16BF /* .gitignore */, 100 | ); 101 | name = "Root Directory"; 102 | sourceTree = ""; 103 | }; 104 | DFB389821CEBC706001DDCCE /* Base */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | DF74EC401CE2AC2F008F16BF /* LoginProvider.swift */, 108 | DF74EC441CE2AC54008F16BF /* Helpers.swift */, 109 | DFB3897E1CEBAA57001DDCCE /* LoginError.swift */, 110 | DFB389871CED3667001DDCCE /* OAuth2.swift */, 111 | DFB389801CEBAA98001DDCCE /* OAuth2Error.swift */, 112 | ); 113 | name = Base; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXHeadersBuildPhase section */ 119 | DF74EC2D1CE2A8BB008F16BF /* Headers */ = { 120 | isa = PBXHeadersBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | DF74EC341CE2A8BB008F16BF /* Simplicity.h in Headers */, 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXHeadersBuildPhase section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | DF74EC2F1CE2A8BB008F16BF /* Simplicity */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = DF74EC381CE2A8BB008F16BF /* Build configuration list for PBXNativeTarget "Simplicity" */; 133 | buildPhases = ( 134 | DF74EC2B1CE2A8BB008F16BF /* Sources */, 135 | DF74EC2C1CE2A8BB008F16BF /* Frameworks */, 136 | DF74EC2D1CE2A8BB008F16BF /* Headers */, 137 | DF74EC2E1CE2A8BB008F16BF /* Resources */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = Simplicity; 144 | productName = Simplicity; 145 | productReference = DF74EC301CE2A8BB008F16BF /* Simplicity.framework */; 146 | productType = "com.apple.product-type.framework"; 147 | }; 148 | /* End PBXNativeTarget section */ 149 | 150 | /* Begin PBXProject section */ 151 | DF74EC271CE2A8BB008F16BF /* Project object */ = { 152 | isa = PBXProject; 153 | attributes = { 154 | LastUpgradeCheck = 0800; 155 | ORGANIZATIONNAME = Stormpath; 156 | TargetAttributes = { 157 | DF74EC2F1CE2A8BB008F16BF = { 158 | CreatedOnToolsVersion = 7.3.1; 159 | LastSwiftMigration = 0800; 160 | }; 161 | }; 162 | }; 163 | buildConfigurationList = DF74EC2A1CE2A8BB008F16BF /* Build configuration list for PBXProject "Simplicity" */; 164 | compatibilityVersion = "Xcode 3.2"; 165 | developmentRegion = English; 166 | hasScannedForEncodings = 0; 167 | knownRegions = ( 168 | en, 169 | ); 170 | mainGroup = DF74EC261CE2A8BB008F16BF; 171 | productRefGroup = DF74EC311CE2A8BB008F16BF /* Products */; 172 | projectDirPath = ""; 173 | projectRoot = ""; 174 | targets = ( 175 | DF74EC2F1CE2A8BB008F16BF /* Simplicity */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | DF74EC2E1CE2A8BB008F16BF /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | DF74EC3F1CE2A943008F16BF /* LICENSE in Resources */, 186 | DF74EC4C1CE2AD19008F16BF /* Simplicity.podspec in Resources */, 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | }; 190 | /* End PBXResourcesBuildPhase section */ 191 | 192 | /* Begin PBXSourcesBuildPhase section */ 193 | DF74EC2B1CE2A8BB008F16BF /* Sources */ = { 194 | isa = PBXSourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | DF74EC411CE2AC2F008F16BF /* LoginProvider.swift in Sources */, 198 | A132748E1D3750DC002ED68B /* VKontakte.swift in Sources */, 199 | DFB389861CECFBFC001DDCCE /* Google.swift in Sources */, 200 | DFB389881CED3667001DDCCE /* OAuth2.swift in Sources */, 201 | DF74EC451CE2AC54008F16BF /* Helpers.swift in Sources */, 202 | DF74EC4A1CE2ACF0008F16BF /* Facebook.swift in Sources */, 203 | DFB389811CEBAA98001DDCCE /* OAuth2Error.swift in Sources */, 204 | DFB3897F1CEBAA57001DDCCE /* LoginError.swift in Sources */, 205 | DF74EC471CE2AC6F008F16BF /* Simplicity.swift in Sources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | /* End PBXSourcesBuildPhase section */ 210 | 211 | /* Begin XCBuildConfiguration section */ 212 | DF74EC361CE2A8BB008F16BF /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | ALWAYS_SEARCH_USER_PATHS = NO; 216 | CLANG_ANALYZER_NONNULL = YES; 217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 218 | CLANG_CXX_LIBRARY = "libc++"; 219 | CLANG_ENABLE_MODULES = YES; 220 | CLANG_ENABLE_OBJC_ARC = YES; 221 | CLANG_WARN_BOOL_CONVERSION = YES; 222 | CLANG_WARN_CONSTANT_CONVERSION = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_EMPTY_BODY = YES; 225 | CLANG_WARN_ENUM_CONVERSION = YES; 226 | CLANG_WARN_INFINITE_RECURSION = YES; 227 | CLANG_WARN_INT_CONVERSION = YES; 228 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 229 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 230 | CLANG_WARN_UNREACHABLE_CODE = YES; 231 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 232 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 233 | COPY_PHASE_STRIP = NO; 234 | CURRENT_PROJECT_VERSION = 1; 235 | DEBUG_INFORMATION_FORMAT = dwarf; 236 | ENABLE_STRICT_OBJC_MSGSEND = YES; 237 | ENABLE_TESTABILITY = YES; 238 | GCC_C_LANGUAGE_STANDARD = gnu99; 239 | GCC_DYNAMIC_NO_PIC = NO; 240 | GCC_NO_COMMON_BLOCKS = YES; 241 | GCC_OPTIMIZATION_LEVEL = 0; 242 | GCC_PREPROCESSOR_DEFINITIONS = ( 243 | "DEBUG=1", 244 | "$(inherited)", 245 | ); 246 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 247 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 248 | GCC_WARN_UNDECLARED_SELECTOR = YES; 249 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 250 | GCC_WARN_UNUSED_FUNCTION = YES; 251 | GCC_WARN_UNUSED_VARIABLE = YES; 252 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 253 | MTL_ENABLE_DEBUG_INFO = YES; 254 | ONLY_ACTIVE_ARCH = YES; 255 | SDKROOT = iphoneos; 256 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 257 | SWIFT_VERSION = 3.0; 258 | TARGETED_DEVICE_FAMILY = "1,2"; 259 | VERSIONING_SYSTEM = "apple-generic"; 260 | VERSION_INFO_PREFIX = ""; 261 | }; 262 | name = Debug; 263 | }; 264 | DF74EC371CE2A8BB008F16BF /* Release */ = { 265 | isa = XCBuildConfiguration; 266 | buildSettings = { 267 | ALWAYS_SEARCH_USER_PATHS = NO; 268 | CLANG_ANALYZER_NONNULL = YES; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | CURRENT_PROJECT_VERSION = 1; 287 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 288 | ENABLE_NS_ASSERTIONS = NO; 289 | ENABLE_STRICT_OBJC_MSGSEND = YES; 290 | GCC_C_LANGUAGE_STANDARD = gnu99; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | SDKROOT = iphoneos; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 302 | SWIFT_VERSION = 3.0; 303 | TARGETED_DEVICE_FAMILY = "1,2"; 304 | VALIDATE_PRODUCT = YES; 305 | VERSIONING_SYSTEM = "apple-generic"; 306 | VERSION_INFO_PREFIX = ""; 307 | }; 308 | name = Release; 309 | }; 310 | DF74EC391CE2A8BB008F16BF /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | CLANG_ENABLE_MODULES = YES; 314 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 315 | DEFINES_MODULE = YES; 316 | DYLIB_COMPATIBILITY_VERSION = 1; 317 | DYLIB_CURRENT_VERSION = 1; 318 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 319 | INFOPLIST_FILE = Simplicity/Info.plist; 320 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 321 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 322 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 323 | PRODUCT_BUNDLE_IDENTIFIER = com.stormpath.Simplicity; 324 | PRODUCT_NAME = "$(TARGET_NAME)"; 325 | SKIP_INSTALL = YES; 326 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 327 | SWIFT_VERSION = 3.0; 328 | }; 329 | name = Debug; 330 | }; 331 | DF74EC3A1CE2A8BB008F16BF /* Release */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | CLANG_ENABLE_MODULES = YES; 335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 336 | DEFINES_MODULE = YES; 337 | DYLIB_COMPATIBILITY_VERSION = 1; 338 | DYLIB_CURRENT_VERSION = 1; 339 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 340 | INFOPLIST_FILE = Simplicity/Info.plist; 341 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 342 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 343 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 344 | PRODUCT_BUNDLE_IDENTIFIER = com.stormpath.Simplicity; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SKIP_INSTALL = YES; 347 | SWIFT_VERSION = 3.0; 348 | }; 349 | name = Release; 350 | }; 351 | /* End XCBuildConfiguration section */ 352 | 353 | /* Begin XCConfigurationList section */ 354 | DF74EC2A1CE2A8BB008F16BF /* Build configuration list for PBXProject "Simplicity" */ = { 355 | isa = XCConfigurationList; 356 | buildConfigurations = ( 357 | DF74EC361CE2A8BB008F16BF /* Debug */, 358 | DF74EC371CE2A8BB008F16BF /* Release */, 359 | ); 360 | defaultConfigurationIsVisible = 0; 361 | defaultConfigurationName = Release; 362 | }; 363 | DF74EC381CE2A8BB008F16BF /* Build configuration list for PBXNativeTarget "Simplicity" */ = { 364 | isa = XCConfigurationList; 365 | buildConfigurations = ( 366 | DF74EC391CE2A8BB008F16BF /* Debug */, 367 | DF74EC3A1CE2A8BB008F16BF /* Release */, 368 | ); 369 | defaultConfigurationIsVisible = 0; 370 | defaultConfigurationName = Release; 371 | }; 372 | /* End XCConfigurationList section */ 373 | }; 374 | rootObject = DF74EC271CE2A8BB008F16BF /* Project object */; 375 | } 376 | --------------------------------------------------------------------------------