├── .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 | [](http://cocoapods.org/pods/Simplicity)
4 | [](http://cocoapods.org/pods/Simplicity)
5 | [](http://cocoapods.org/pods/Simplicity) [](https://codebeat.co/projects/github-com-simplicitymobile-simplicity)
6 | [](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 |
--------------------------------------------------------------------------------