├── .clang-format ├── .codecov.yml ├── .github_changelog_generator ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cartfile ├── Cartfile.resolved ├── Configurations ├── ParseTwitterTestApplication.xcconfig ├── ParseTwitterUtils-Tests.xcconfig ├── ParseTwitterUtils-iOS-Dynamic.xcconfig ├── ParseTwitterUtils-iOS.xcconfig └── Shared ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── PATENTS ├── ParseTwitterUtils.podspec ├── ParseTwitterUtils.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── ParseTwitterUtils-iOS-Dynamic.xcscheme │ └── ParseTwitterUtils-iOS.xcscheme ├── ParseTwitterUtils.xcworkspace └── contents.xcworkspacedata ├── ParseTwitterUtils ├── Internal │ ├── Dialog │ │ ├── PFOAuth1FlowDialog.h │ │ └── PFOAuth1FlowDialog.m │ ├── OAuthCore │ │ ├── PF_OAuthCore.h │ │ └── PF_OAuthCore.m │ ├── PFTwitterAlertView.h │ ├── PFTwitterAlertView.m │ ├── PFTwitterAuthenticationProvider.h │ ├── PFTwitterAuthenticationProvider.m │ ├── PFTwitterLocalization.h │ ├── PFTwitterPrivateUtilities.h │ ├── PFTwitterPrivateUtilities.m │ ├── PFTwitterUtils_Private.h │ └── PF_Twitter_Private.h ├── PFTwitterUtils.h ├── PFTwitterUtils.m ├── PF_Twitter.h ├── PF_Twitter.m └── ParseTwitterUtils.h ├── Podfile ├── Podfile.lock ├── README.md ├── Rakefile ├── Resources ├── Info.plist └── en.lproj │ └── ParseTwitterUtils.strings ├── Tests ├── Other │ ├── PFTwitterTestMacros.h │ └── TestCase │ │ ├── PFTwitterTestCase.h │ │ └── PFTwitterTestCase.m ├── Resources │ └── Info.plist ├── TestApplication │ ├── Classes │ │ └── main.m │ └── Resources │ │ └── Info.plist └── Unit │ ├── OAuth1FlowDialogTests.m │ ├── OAuthCoreTests.m │ ├── TwitterAuthenticationProviderTests.m │ ├── TwitterTests.m │ └── TwitterUtilsTests.m └── third_party_licenses.txt /.clang-format: -------------------------------------------------------------------------------- 1 | Vendor/xctoolchain/.clang-format -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ignore: 3 | - Tests/* 4 | status: 5 | project: 6 | default: 7 | target: 50 8 | patch: off 9 | changes: off 10 | comment: off 11 | -------------------------------------------------------------------------------- /.github_changelog_generator: -------------------------------------------------------------------------------- 1 | add_issues_wo_labels=false 2 | exclude_labels=duplicate,question,wontfix,discussion,needs more info,backend,off topic,product usage 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | 7 | *.mode1v3 8 | *.mode2v3 9 | 10 | *.xcodeproj/xcuserdata/*.xcuserdatad 11 | 12 | *.xccheckout 13 | *.xcscmblueprint 14 | *.xcuserdatad 15 | 16 | Pods 17 | 18 | DerivedData 19 | build 20 | 21 | Vendor/Parse.framework 22 | Vendor/Bolts.framework 23 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Vendor/xctoolchain"] 2 | path = Vendor/xctoolchain 3 | url = https://github.com/ParsePlatform/xctoolchain.git 4 | [submodule "Carthage/Checkouts/Bolts-ObjC"] 5 | path = Carthage/Checkouts/Bolts-ObjC 6 | url = https://github.com/BoltsFramework/Bolts-ObjC.git 7 | [submodule "Carthage/Checkouts/Parse-SDK-iOS-OSX"] 8 | path = Carthage/Checkouts/Parse-SDK-iOS-OSX 9 | url = https://github.com/ParsePlatform/Parse-SDK-iOS-OSX.git 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | language: objective-c 5 | os: osx 6 | osx_image: xcode7.3 7 | env: 8 | global: 9 | - LC_CTYPE=en_US.UTF-8 10 | - LANG=en_US.UTF-8 11 | matrix: 12 | - TEST_TYPE=iOS 13 | - TEST_TYPE=Package 14 | - TEST_TYPE=CocoaPods 15 | - TEST_TYPE=Carthage 16 | install: 17 | - | 18 | bundle install 19 | pod repo update --silent 20 | pod install 21 | script: 22 | - | 23 | if [ "$TEST_TYPE" = iOS ]; then 24 | set -o pipefail 25 | xcodebuild test -workspace ParseTwitterUtils.xcworkspace -sdk iphonesimulator -scheme ParseTwitterUtils-iOS -configuration Debug -destination "platform=iOS Simulator,name=iPhone 4s" -destination "platform=iOS Simulator,name=iPhone 6s" GCC_INSTRUMENT_PROGRAM_FLOW_ARCS=YES GCC_GENERATE_TEST_COVERAGE_FILES=YES | xcpretty 26 | elif [ "$TEST_TYPE" = Package ]; then 27 | bundle exec rake package:frameworks 28 | elif [ "$TEST_TYPE" = CocoaPods ]; then 29 | pod lib lint ParseTwitterUtils.podspec 30 | pod lib lint --use-libraries ParseTwitterUtils.podspec 31 | fi 32 | after_success: 33 | - | 34 | if [ "$TEST_TYPE" = iOS ]; then 35 | bash <(curl -s https://codecov.io/bash) 36 | fi 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [1.11.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.11.0) (2016-06-29) 4 | [Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.10.0...1.11.0) 5 | 6 | **Implemented enhancements:** 7 | 8 | - Add support for Carthage. [\#33](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/33) ([nlutsenko](https://github.com/nlutsenko)) 9 | - Fix analyzer and build warnings. [\#32](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/32) ([nlutsenko](https://github.com/nlutsenko)) 10 | - Update all dependencies and project to Xcode 7.3. [\#28](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/28) ([nlutsenko](https://github.com/nlutsenko)) 11 | - Add support for using `PF_Twitter` without a linking or logging into a Parse account [\#27](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/27) ([zadr](https://github.com/zadr)) 12 | - Removed `Parse.clientKey` requirement for initializing ParseTwitterUtils. [\#26](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/26) ([aphex3k](https://github.com/aphex3k)) 13 | 14 | **Merged pull requests:** 15 | 16 | - Update CocoaPods and RubyGems dependencies. [\#31](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/31) ([nlutsenko](https://github.com/nlutsenko)) 17 | 18 | ## [1.10.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.10.0) (2016-01-08) 19 | [Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.9.1...1.10.0) 20 | 21 | **Implemented enhancements:** 22 | 23 | - Remove all usages of PF\_GENERIC macro. [\#19](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/19) ([nlutsenko](https://github.com/nlutsenko)) 24 | - Add custom packaging into a precompiled binary via Rakefile. [\#16](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/16) ([nlutsenko](https://github.com/nlutsenko)) 25 | - Update all of the public documentation to new style. [\#15](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/15) ([nlutsenko](https://github.com/nlutsenko)) 26 | 27 | **Merged pull requests:** 28 | 29 | - ParseTwitterUtils 1.10.0 [\#21](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/21) ([nlutsenko](https://github.com/nlutsenko)) 30 | - Update project to Xcode 7.2. [\#20](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/20) ([nlutsenko](https://github.com/nlutsenko)) 31 | - Use Xcode 7.2 for Travis-CI. [\#17](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/17) ([nlutsenko](https://github.com/nlutsenko)) 32 | - Update xctoolchain to latest. [\#14](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/14) ([richardjrossiii](https://github.com/richardjrossiii)) 33 | 34 | ## [1.9.1](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.9.1) (2015-11-18) 35 | [Full Changelog](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/compare/1.9.0...1.9.1) 36 | 37 | **Implemented enhancements:** 38 | 39 | - Fix warnings in PFOAuth1FlowDialog. [\#10](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/10) ([nlutsenko](https://github.com/nlutsenko)) 40 | - Remove all usage of custom nullability macros. [\#8](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/8) ([nlutsenko](https://github.com/nlutsenko)) 41 | - Update everything to use Xcode 7.1. [\#6](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/6) ([nlutsenko](https://github.com/nlutsenko)) 42 | - Added safeguard assertion for initialization of PFTwitterUtils before Parse is initialized. [\#4](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/4) ([nlutsenko](https://github.com/nlutsenko)) 43 | - Include and automate localized strings generation. [\#2](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/2) ([nlutsenko](https://github.com/nlutsenko)) 44 | 45 | **Merged pull requests:** 46 | 47 | - ParseTwitterUtils 1.9.1 [\#13](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/13) ([nlutsenko](https://github.com/nlutsenko)) 48 | - Update Gemfile. [\#9](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/9) ([nlutsenko](https://github.com/nlutsenko)) 49 | - Update xctoolchain to latest. [\#5](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/5) ([nlutsenko](https://github.com/nlutsenko)) 50 | - Use shared things from xctoolchain. [\#3](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/pull/3) ([nlutsenko](https://github.com/nlutsenko)) 51 | 52 | ## [1.9.0](https://github.com/ParsePlatform/ParseTwitterUtils-iOS/tree/1.9.0) (2015-10-08) 53 | 54 | 55 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Parse Twitter Utils for iOS 2 | We want to make contributing to this project as easy and transparent as possible. 3 | 4 | ## Code of Conduct 5 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please read [the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated. 6 | 7 | ## Our Development Process 8 | Most of our work will be done in public directly on GitHub. There may be changes done through our internal source control, but it will be rare and only as needed. 9 | 10 | ### `master` is unsafe 11 | Our goal is to keep `master` stable, but there may be changes that your application may not be compatible with. We'll do our best to publicize any breaking changes, but try to use our specific releases in any production environment. 12 | 13 | ### Pull Requests 14 | We actively welcome your pull requests. When we get one, we'll run some Parse-specific integration tests on it first. From here, we'll need to get a core member to sign off on the changes and then merge the pull request. For API changes we may need to fix internal uses, which could cause some delay. We'll do our best to provide updates and feedback throughout the process. 15 | 16 | 1. Fork the repo and create your branch from `master`. 17 | 4. Add unit tests for any new code you add. 18 | 3. If you've changed APIs, update the documentation. 19 | 4. Ensure the test suite passes. 20 | 5. Make sure your code lints. 21 | 6. If you haven't already, complete the Contributor License Agreement ("CLA"). 22 | 23 | ### Contributor License Agreement ("CLA") 24 | In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. 25 | 26 | Complete your CLA here: 27 | 28 | ## Bugs 29 | Although we try to keep developing on Parse easy, you still may run into some issues. General questions should be asked on [Google Groups][google-group], technical questions should be asked on [Stack Overflow][stack-overflow], and for everything else we'll be using GitHub issues. 30 | 31 | ### Known Issues 32 | We use GitHub issues to track public bugs. We will keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new issue, try to make sure your problem doesn't already exist. 33 | 34 | ### Reporting New Issues 35 | Not all issues are SDK issues. If you're unsure whether your bug is with the SDK or backend, you can test to see if it reproduces with our [REST API][rest-api] and [Parse API Console][parse-api-console]. If it does, you can report backend bugs [here][bug-reports]. 36 | 37 | To view the REST API network requests issued by the Parse SDK, please check out our [Network Debugging Tool][network-debugging-tool]. 38 | 39 | Details are key. The more information you provide us the easier it'll be for us to debug and the faster you'll receive a fix. Some examples of useful tidbits: 40 | 41 | * A description. What did you expect to happen and what actually happened? Why do you think that was wrong? 42 | * A simple unit test that fails. Refer [here][tests-dir] for examples of existing unit tests. See our [README](README.md#usage) for how to run unit tests. You can submit a pull request with your failing unit test so that our CI verifies that the test fails. 43 | * What version does this reproduce on? What version did it last work on? 44 | * [Stacktrace or GTFO][stacktrace-or-gtfo]. In all honesty, full stacktraces with line numbers make a happy developer. 45 | * Anything else you find relevant. 46 | 47 | 48 | ### Security Bugs 49 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. 50 | 51 | ## Style Guide 52 | We're still working on providing a code style for your IDE and getting a linter on GitHub, but for now try to keep the following: 53 | 54 | * Most importantly, match the existing code style as much as possible. 55 | * Try to keep lines under 120 characters, if possible. 56 | 57 | ## License 58 | By contributing to Parse Twitter Utils for iOS, you agree that your contributions will be licensed under its license. 59 | 60 | [google-group]: https://groups.google.com/forum/#!forum/parse-developers 61 | [stack-overflow]: http://stackoverflow.com/tags/parse.com 62 | [bug-reports]: https://www.parse.com/help#report 63 | [rest-api]: https://www.parse.com/docs/rest/guide 64 | [parse-api-console]: http://blog.parse.com/announcements/introducing-the-parse-api-console/ 65 | [network-debugging-tool]: https://github.com/ParsePlatform/Parse-SDK-iOS-OSX/wiki/Network-Debug-Tool 66 | [stacktrace-or-gtfo]: http://i.imgur.com/jacoj.jpg 67 | [tests-dir]: /Tests/Unit/ 68 | -------------------------------------------------------------------------------- /Cartfile: -------------------------------------------------------------------------------- 1 | github "ParsePlatform/Parse-SDK-iOS-OSX" ~> 1.13 2 | github "BoltsFramework/Bolts-ObjC" ~> 1.7 3 | -------------------------------------------------------------------------------- /Cartfile.resolved: -------------------------------------------------------------------------------- 1 | github "BoltsFramework/Bolts-ObjC" "1.8.4" 2 | github "ParsePlatform/Parse-SDK-iOS-OSX" "1.14.0" 3 | -------------------------------------------------------------------------------- /Configurations/ParseTwitterTestApplication.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-present, Parse, LLC. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/Application.xcconfig" 12 | 13 | PRODUCT_NAME = ParseTwitterTestApplication 14 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.testapplication 15 | 16 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 17 | 18 | INFOPLIST_FILE = $(SRCROOT)/Tests/TestApplication/Resources/Info.plist 19 | -------------------------------------------------------------------------------- /Configurations/ParseTwitterUtils-Tests.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-present, Parse, LLC. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/LogicTests.xcconfig" 12 | #include "Pods/Target Support Files/Pods-ParseTwitterUtils-Tests/Pods-ParseTwitterUtils-Tests.debug.xcconfig" 13 | 14 | PRODUCT_NAME = ParseTwitterUtils-Tests 15 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 16 | 17 | FRAMEWORK_SEARCH_PATHS = $(inherited) $(BUILT_PRODUCTS_DIR)/OCMock 18 | 19 | INFOPLIST_FILE = $(PROJECT_DIR)/Tests/Resources/Info.plist 20 | 21 | TEST_HOST = $(BUILT_PRODUCTS_DIR)/ParseTwitterTestApplication.app/ParseTwitterTestApplication 22 | -------------------------------------------------------------------------------- /Configurations/ParseTwitterUtils-iOS-Dynamic.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-present, Parse, LLC. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/DynamicFramework.xcconfig" 12 | 13 | PRODUCT_NAME = ParseTwitterUtils 14 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.ios 15 | 16 | IPHONEOS_DEPLOYMENT_TARGET = 8.0 17 | 18 | INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist 19 | 20 | // TODO: (nlutsenko) Cleanup source code so we can safely ignore local variable shadow warnings. 21 | GCC_WARN_SHADOW = NO 22 | 23 | -------------------------------------------------------------------------------- /Configurations/ParseTwitterUtils-iOS.xcconfig: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015-present, Parse, LLC. 3 | // All rights reserved. 4 | // 5 | // This source code is licensed under the BSD-style license found in the 6 | // LICENSE file in the root directory of this source tree. An additional grant 7 | // of patent rights can be found in the PATENTS file in the same directory. 8 | // 9 | 10 | #include "Shared/Platform/iOS.xcconfig" 11 | #include "Shared/Product/StaticFramework.xcconfig" 12 | 13 | PRODUCT_NAME = ParseTwitterUtils 14 | PRODUCT_BUNDLE_IDENTIFIER = com.parse.twitterutils.ios 15 | 16 | INFOPLIST_FILE = $(SRCROOT)/Resources/Info.plist 17 | 18 | // TODO: (nlutsenko) Cleanup source code so we can safely ignore local variable shadow warnings. 19 | GCC_WARN_SHADOW = NO 20 | 21 | -------------------------------------------------------------------------------- /Configurations/Shared: -------------------------------------------------------------------------------- 1 | ../Vendor/xctoolchain/Configurations/ -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'naturally' 4 | gem 'rake' 5 | gem 'xcpretty' 6 | gem 'cocoapods' 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (4.2.6) 5 | i18n (~> 0.7) 6 | json (~> 1.7, >= 1.7.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.0) 11 | cocoapods (1.0.1) 12 | activesupport (>= 4.0.2) 13 | claide (>= 1.0.0, < 2.0) 14 | cocoapods-core (= 1.0.1) 15 | cocoapods-deintegrate (>= 1.0.0, < 2.0) 16 | cocoapods-downloader (>= 1.0.0, < 2.0) 17 | cocoapods-plugins (>= 1.0.0, < 2.0) 18 | cocoapods-search (>= 1.0.0, < 2.0) 19 | cocoapods-stats (>= 1.0.0, < 2.0) 20 | cocoapods-trunk (>= 1.0.0, < 2.0) 21 | cocoapods-try (>= 1.0.0, < 2.0) 22 | colored (~> 1.2) 23 | escape (~> 0.0.4) 24 | fourflusher (~> 0.3.0) 25 | molinillo (~> 0.4.5) 26 | nap (~> 1.0) 27 | xcodeproj (>= 1.1.0, < 2.0) 28 | cocoapods-core (1.0.1) 29 | activesupport (>= 4.0.2) 30 | fuzzy_match (~> 2.0.4) 31 | nap (~> 1.0) 32 | cocoapods-deintegrate (1.0.0) 33 | cocoapods-downloader (1.0.1) 34 | cocoapods-plugins (1.0.0) 35 | nap 36 | cocoapods-search (1.0.0) 37 | cocoapods-stats (1.0.0) 38 | cocoapods-trunk (1.0.0) 39 | nap (>= 0.8, < 2.0) 40 | netrc (= 0.7.8) 41 | cocoapods-try (1.0.0) 42 | colored (1.2) 43 | escape (0.0.4) 44 | fourflusher (0.3.2) 45 | fuzzy_match (2.0.4) 46 | i18n (0.7.0) 47 | json (1.8.3) 48 | minitest (5.9.0) 49 | molinillo (0.4.5) 50 | nap (1.1.0) 51 | naturally (2.1.0) 52 | netrc (0.7.8) 53 | rake (11.2.2) 54 | rouge (1.11.1) 55 | thread_safe (0.3.5) 56 | tzinfo (1.2.2) 57 | thread_safe (~> 0.1) 58 | xcodeproj (1.1.0) 59 | activesupport (>= 3) 60 | claide (>= 1.0.0, < 2.0) 61 | colored (~> 1.2) 62 | xcpretty (0.2.2) 63 | rouge (~> 1.8) 64 | 65 | PLATFORMS 66 | ruby 67 | 68 | DEPENDENCIES 69 | cocoapods 70 | naturally 71 | rake 72 | xcpretty 73 | 74 | BUNDLED WITH 75 | 1.12.3 76 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For Parse Twitter Utils for iOS software 4 | 5 | Copyright (c) 2015-present, Parse, LLC. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Parse nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | ----- 33 | 34 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. 35 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the Parse Twitter Utils for iOS software distributed by Parse, LLC. 4 | 5 | Parse, LLC. ("Parse") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Parse’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Parse or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Parse or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Parse or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Parse that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | 35 | ----- 36 | 37 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code. 38 | -------------------------------------------------------------------------------- /ParseTwitterUtils.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'ParseTwitterUtils' 3 | s.version = '1.11.0' 4 | s.license = { :type => 'BSD' } 5 | s.homepage = 'http://parseplatform.org/' 6 | s.summary = 'Parse is a complete technology stack to power your app\'s backend.' 7 | s.authors = 'Parse Community' 8 | s.social_media_url = 'https://twitter.com/ParsePlatform' 9 | 10 | s.source = { :git => "https://github.com/ParsePlatform/ParseTwitterUtils-iOS.git", :tag => s.version.to_s } 11 | 12 | s.platform = :ios 13 | s.ios.deployment_target = '7.0' 14 | s.requires_arc = true 15 | 16 | s.public_header_files = 'ParseTwitterUtils/*.h' 17 | s.source_files = 'ParseTwitterUtils/**/*.{h,m}' 18 | s.resources = 'Resources/en.lproj' 19 | 20 | s.frameworks = 'AudioToolbox', 21 | 'CFNetwork', 22 | 'CoreGraphics', 23 | 'CoreLocation', 24 | 'QuartzCore', 25 | 'Security', 26 | 'StoreKit', 27 | 'SystemConfiguration' 28 | s.weak_frameworks = 'Accounts', 29 | 'Social' 30 | s.libraries = 'z', 'sqlite3' 31 | 32 | s.dependency 'Bolts/Tasks', '~> 1.7' 33 | s.dependency 'Parse', '~> 1.13' 34 | end 35 | -------------------------------------------------------------------------------- /ParseTwitterUtils.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS-Dynamic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ParseTwitterUtils.xcodeproj/xcshareddata/xcschemes/ParseTwitterUtils-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 93 | 94 | 100 | 101 | 102 | 103 | 104 | 105 | 111 | 112 | 118 | 119 | 120 | 121 | 123 | 124 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /ParseTwitterUtils.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | @class PFOAuth1FlowDialog; 14 | 15 | @protocol PFOAuth1FlowDialogDataSource 16 | 17 | /** 18 | Asks if a link touched by a user should be opened in an external browser. 19 | 20 | If a user touches a link, the default behavior is to open the link in the Safari browser, 21 | which will cause your app to quit. You may want to prevent this from happening, open the link 22 | in your own internal browser, or perhaps warn the user that they are about to leave your app. 23 | If so, implement this method on your delegate and return NO. If you warn the user, you 24 | should hold onto the URL and once you have received their acknowledgement open the URL yourself 25 | using [[UIApplication sharedApplication] openURL:]. 26 | */ 27 | - (BOOL)dialog:(PFOAuth1FlowDialog *)dialog shouldOpenURLInExternalBrowser:(NSURL *)url; 28 | 29 | @end 30 | 31 | typedef void (^PFOAuth1FlowDialogCompletion)(BOOL succeeded, NSURL *url, NSError *error); 32 | 33 | /** 34 | To allow for greater mockability, this protocol exposes all of the methods implemented by PFOAuth1FlowDialog. 35 | */ 36 | @protocol PFOAuth1FlowDialogInterface 37 | 38 | @property (nonatomic, weak) id dataSource; 39 | @property (nonatomic, strong) PFOAuth1FlowDialogCompletion completion; 40 | 41 | @property (nonatomic, copy) NSDictionary *queryParameters; 42 | @property (nonatomic, copy) NSString *redirectURLPrefix; 43 | 44 | /** 45 | The title that is shown in the header atop the view. 46 | */ 47 | @property (nonatomic, copy) NSString *title; 48 | 49 | + (instancetype)dialogWithURL:(NSURL *)url queryParameters:(NSDictionary *)queryParameters; 50 | 51 | /** 52 | The view will be added to the top of the current key window. 53 | */ 54 | - (void)showAnimated:(BOOL)animated; 55 | 56 | /** 57 | Hides the view. 58 | This method does not call the completion block. 59 | */ 60 | - (void)dismissAnimated:(BOOL)animated; 61 | 62 | /** 63 | Displays a URL in the dialog. 64 | */ 65 | - (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters; 66 | 67 | @end 68 | 69 | @interface PFOAuth1FlowDialog : UIView { 70 | @public 71 | // Ensures that UI elements behind the dialog are disabled. 72 | UIView *_modalBackgroundView; 73 | 74 | NSURL *_baseURL; 75 | NSURL *_loadingURL; 76 | 77 | UILabel *_titleLabel; 78 | UIButton *_closeButton; 79 | UIWebView *_webView; 80 | UIActivityIndicatorView *_activityIndicator; 81 | 82 | UIInterfaceOrientation _orientation; 83 | BOOL _showingKeyboard; 84 | } 85 | 86 | - (instancetype)initWithURL:(NSURL *)url queryParameters:(NSDictionary *)parameters; 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/Dialog/PFOAuth1FlowDialog.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFOAuth1FlowDialog.h" 11 | 12 | #import 13 | 14 | @implementation PFOAuth1FlowDialog 15 | 16 | @synthesize dataSource = _dataSource; 17 | @synthesize completion = _completion; 18 | 19 | @synthesize queryParameters = _queryParameters; 20 | @synthesize redirectURLPrefix = _redirectURLPrefix; 21 | 22 | static NSString *const PFOAuth1FlowDialogDefaultTitle = @"Connect to Service"; 23 | 24 | static const CGFloat PFOAuth1FlowDialogBorderGreyColorComponents[4] = {0.3f, 0.3f, 0.3f, 0.8f}; 25 | static const CGFloat PFOAuth1FlowDialogBorderBlackColorComponents[4] = {0.3f, 0.3f, 0.3f, 1.0f}; 26 | 27 | static const NSTimeInterval PFOAuth1FlowDialogAnimationDuration = 0.3; 28 | 29 | static const UIEdgeInsets PFOAuth1FlowDialogContentInsets = { 30 | .top = 10.0f, 31 | .left = 10.0f, 32 | .bottom = 10.0f, 33 | .right = 10.0f, 34 | }; 35 | 36 | static const UIEdgeInsets PFOAuth1FlowDialogTitleInsets = {.top = 4.0f, .left = 8.0f, .bottom = 4.0f, .right = 8.0f}; 37 | 38 | static const CGFloat PFOAuth1FlowDialogScreenInset = 10.0f; 39 | 40 | static BOOL PFOAuth1FlowDialogScreenHasAutomaticRotation() { 41 | static BOOL automaticRotation; 42 | static dispatch_once_t onceToken; 43 | dispatch_once(&onceToken, ^{ 44 | automaticRotation = [[UIScreen mainScreen] respondsToSelector:NSSelectorFromString(@"coordinateSpace")]; 45 | }); 46 | return automaticRotation; 47 | } 48 | 49 | static BOOL PFOAuth1FlowDialogIsDevicePad() { 50 | static BOOL isPad; 51 | static dispatch_once_t onceToken; 52 | dispatch_once(&onceToken, ^{ 53 | isPad = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad); 54 | }); 55 | return isPad; 56 | } 57 | 58 | static CGFloat PFTFloatRound(CGFloat value, NSRoundingMode mode) { 59 | switch (mode) { 60 | case NSRoundPlain: 61 | case NSRoundBankers: 62 | #if CGFLOAT_IS_DOUBLE 63 | value = round(value); 64 | #else 65 | value = roundf(value); 66 | #endif 67 | case NSRoundDown: 68 | #if CGFLOAT_IS_DOUBLE 69 | value = floor(value); 70 | #else 71 | value = floorf(value); 72 | #endif 73 | case NSRoundUp: 74 | #if CGFLOAT_IS_DOUBLE 75 | value = ceil(value); 76 | #else 77 | value = ceilf(value); 78 | #endif 79 | default: break; 80 | } 81 | return value; 82 | } 83 | 84 | #pragma mark - 85 | #pragma mark Class 86 | 87 | + (void)_fillRect:(CGRect)rect withColorComponents:(const CGFloat *)colorComponents radius:(CGFloat)radius { 88 | CGContextRef context = UIGraphicsGetCurrentContext(); 89 | 90 | if (colorComponents) { 91 | CGContextSaveGState(context); 92 | CGContextSetFillColor(context, colorComponents); 93 | if (radius != 0.0f) { 94 | UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; 95 | CGContextAddPath(context, [bezierPath CGPath]); 96 | CGContextFillPath(context); 97 | } else { 98 | CGContextFillRect(context, rect); 99 | } 100 | CGContextRestoreGState(context); 101 | } 102 | } 103 | 104 | + (void)_strokeRect:(CGRect)rect withColorComponents:(const CGFloat *)strokeColor { 105 | CGContextRef context = UIGraphicsGetCurrentContext(); 106 | 107 | CGContextSaveGState(context); 108 | { 109 | CGContextSetStrokeColor(context, strokeColor); 110 | CGContextSetLineWidth(context, 1.0f); 111 | CGContextStrokeRect(context, rect); 112 | } 113 | CGContextRestoreGState(context); 114 | } 115 | 116 | + (NSURL *)_urlFromBaseURL:(NSURL *)baseURL queryParameters:(NSDictionary *)params { 117 | if ([params count] > 0) { 118 | NSMutableArray *parameterPairs = [NSMutableArray array]; 119 | [params enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 120 | CFStringRef escapedString = CFURLCreateStringByAddingPercentEscapes( 121 | NULL, /* allocator */ 122 | (CFStringRef)obj, 123 | NULL, /* charactersToLeaveUnescaped */ 124 | (CFStringRef)@"!*'();:@&=+$,/?%#[]", 125 | kCFStringEncodingUTF8); 126 | [parameterPairs addObject:[NSString stringWithFormat:@"%@=%@", key, CFBridgingRelease(escapedString)]]; 127 | }]; 128 | 129 | NSString *query = [parameterPairs componentsJoinedByString:@"&"]; 130 | NSString *url = [NSString stringWithFormat:@"%@?%@", [baseURL absoluteString], query]; 131 | 132 | return [NSURL URLWithString:url]; 133 | } 134 | 135 | return baseURL; 136 | } 137 | 138 | + (UIImage *)_closeButtonImage { 139 | CGRect imageRect = CGRectZero; 140 | imageRect.size = CGSizeMake(30.0f, 30.0f); 141 | 142 | UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0.0f); 143 | CGContextRef context = UIGraphicsGetCurrentContext(); 144 | 145 | CGRect outerRingRect = CGRectInset(imageRect, 2.0f, 2.0f); 146 | 147 | [[UIColor whiteColor] set]; 148 | CGContextFillEllipseInRect(context, outerRingRect); 149 | 150 | CGRect innerRingRect = CGRectInset(outerRingRect, 2.0f, 2.0f); 151 | 152 | [[UIColor blackColor] set]; 153 | CGContextFillEllipseInRect(context, innerRingRect); 154 | 155 | CGRect crossRect = CGRectInset(innerRingRect, 6.0f, 6.0f); 156 | 157 | CGContextBeginPath(context); 158 | 159 | [[UIColor whiteColor] setStroke]; 160 | CGContextSetLineWidth(context, 3.0f); 161 | 162 | CGContextMoveToPoint(context, CGRectGetMinX(crossRect), CGRectGetMinY(crossRect)); 163 | CGContextAddLineToPoint(context, CGRectGetMaxX(crossRect), CGRectGetMaxY(crossRect)); 164 | 165 | CGContextMoveToPoint(context, CGRectGetMaxX(crossRect), CGRectGetMinY(crossRect)); 166 | CGContextAddLineToPoint(context, CGRectGetMinX(crossRect), CGRectGetMaxY(crossRect)); 167 | 168 | CGContextStrokePath(context); 169 | 170 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 171 | UIGraphicsEndImageContext(); 172 | 173 | return image; 174 | } 175 | 176 | #pragma mark - 177 | #pragma mark Init 178 | 179 | - (instancetype)init { 180 | self = [super initWithFrame:CGRectZero]; 181 | if (self) { 182 | self.backgroundColor = [UIColor clearColor]; 183 | self.autoresizesSubviews = YES; 184 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 185 | self.contentMode = UIViewContentModeRedraw; 186 | 187 | _orientation = UIInterfaceOrientationPortrait; 188 | 189 | _closeButton = [UIButton buttonWithType:UIButtonTypeCustom]; 190 | _closeButton.showsTouchWhenHighlighted = YES; 191 | _closeButton.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin); 192 | [_closeButton setImage:[[self class] _closeButtonImage] forState:UIControlStateNormal]; 193 | [_closeButton addTarget:self 194 | action:@selector(_cancelButtonAction) 195 | forControlEvents:UIControlEventTouchUpInside]; 196 | [self addSubview:_closeButton]; 197 | 198 | CGFloat titleLabelFontSize = (PFOAuth1FlowDialogIsDevicePad() ? 18.0f : 14.0f); 199 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 200 | _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; 201 | _titleLabel.backgroundColor = [UIColor clearColor]; 202 | _titleLabel.text = PFOAuth1FlowDialogDefaultTitle; 203 | _titleLabel.textColor = [UIColor whiteColor]; 204 | _titleLabel.font = [UIFont boldSystemFontOfSize:titleLabelFontSize]; 205 | [self addSubview:_titleLabel]; 206 | 207 | _webView = [[UIWebView alloc] initWithFrame:CGRectZero]; 208 | _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 209 | _webView.delegate = self; 210 | [self addSubview:_webView]; 211 | 212 | _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: 213 | UIActivityIndicatorViewStyleWhiteLarge]; 214 | _activityIndicator.color = [UIColor grayColor]; 215 | _activityIndicator.autoresizingMask = (UIViewAutoresizingFlexibleTopMargin | 216 | UIViewAutoresizingFlexibleBottomMargin | 217 | UIViewAutoresizingFlexibleLeftMargin | 218 | UIViewAutoresizingFlexibleRightMargin); 219 | [self addSubview:_activityIndicator]; 220 | 221 | _modalBackgroundView = [[UIView alloc] init]; 222 | } 223 | return self; 224 | } 225 | 226 | - (instancetype)initWithURL:(NSURL *)url queryParameters:(NSDictionary *)parameters { 227 | self = [self init]; 228 | if (self) { 229 | _baseURL = url; 230 | _queryParameters = [parameters mutableCopy]; 231 | } 232 | return self; 233 | } 234 | 235 | + (instancetype)dialogWithURL:(NSURL *)url queryParameters:(NSDictionary *)queryParameters { 236 | return [[self alloc] initWithURL:url queryParameters:queryParameters]; 237 | } 238 | 239 | #pragma mark - 240 | #pragma mark Dealloc 241 | 242 | - (void)dealloc { 243 | _webView.delegate = nil; 244 | 245 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 246 | } 247 | 248 | #pragma mark - 249 | #pragma mark UIView 250 | 251 | - (void)drawRect:(CGRect)rect { 252 | [super drawRect:rect]; 253 | 254 | [[self class] _fillRect:self.bounds withColorComponents:PFOAuth1FlowDialogBorderGreyColorComponents radius:10.0f]; 255 | [[self class] _strokeRect:_webView.frame withColorComponents:PFOAuth1FlowDialogBorderBlackColorComponents]; 256 | } 257 | 258 | - (void)layoutSubviews { 259 | [super layoutSubviews]; 260 | 261 | const CGRect bounds = self.bounds; 262 | const CGRect contentRect = UIEdgeInsetsInsetRect(bounds, PFOAuth1FlowDialogContentInsets); 263 | 264 | CGRect titleLabelBoundingRect = UIEdgeInsetsInsetRect(contentRect, PFOAuth1FlowDialogTitleInsets); 265 | CGSize titleLabelSize = [_titleLabel sizeThatFits:titleLabelBoundingRect.size]; 266 | titleLabelBoundingRect.size.width = (CGRectGetMaxX(contentRect) - 267 | PFOAuth1FlowDialogTitleInsets.right - 268 | titleLabelSize.height); 269 | titleLabelSize = [_titleLabel sizeThatFits:titleLabelBoundingRect.size]; 270 | 271 | CGRect titleLabelFrame = titleLabelBoundingRect; 272 | titleLabelFrame.size.height = titleLabelSize.height; 273 | titleLabelFrame.size.width = CGRectGetWidth(titleLabelBoundingRect); 274 | _titleLabel.frame = titleLabelFrame; 275 | 276 | CGRect closeButtonFrame = contentRect; 277 | closeButtonFrame.size.height = (CGRectGetHeight(titleLabelFrame) + 278 | PFOAuth1FlowDialogTitleInsets.top + 279 | PFOAuth1FlowDialogTitleInsets.bottom); 280 | closeButtonFrame.size.width = CGRectGetHeight(closeButtonFrame); 281 | closeButtonFrame.origin.x = CGRectGetMaxX(contentRect) - CGRectGetWidth(closeButtonFrame); 282 | _closeButton.frame = closeButtonFrame; 283 | 284 | CGRect webViewFrame = contentRect; 285 | if (!_showingKeyboard || PFOAuth1FlowDialogIsDevicePad() || UIInterfaceOrientationIsPortrait(_orientation)) { 286 | webViewFrame.origin.y = CGRectGetMaxY(titleLabelFrame) + PFOAuth1FlowDialogTitleInsets.bottom; 287 | webViewFrame.size.height = CGRectGetMaxY(contentRect) - CGRectGetMinY(webViewFrame); 288 | } 289 | _webView.frame = webViewFrame; 290 | 291 | [_activityIndicator sizeToFit]; 292 | _activityIndicator.center = _webView.center; 293 | } 294 | 295 | #pragma mark - 296 | #pragma mark Accessors 297 | 298 | - (NSString *)title { 299 | return _titleLabel.text; 300 | } 301 | 302 | - (void)setTitle:(NSString *)title { 303 | _titleLabel.text = title; 304 | 305 | [self setNeedsLayout]; 306 | } 307 | 308 | #pragma mark - 309 | #pragma mark Present / Dismiss 310 | 311 | - (void)showAnimated:(BOOL)animated { 312 | [self load]; 313 | [self _sizeToFitOrientation]; 314 | 315 | [_activityIndicator startAnimating]; 316 | 317 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 318 | _modalBackgroundView.frame = window.bounds; 319 | [_modalBackgroundView addSubview:self]; 320 | [window addSubview:_modalBackgroundView]; 321 | 322 | CGAffineTransform transform = [self _transformForOrientation:_orientation]; 323 | if (animated) { 324 | self.transform = CGAffineTransformScale(transform, 0.001f, 0.001f); 325 | 326 | NSTimeInterval animationStepDuration = PFOAuth1FlowDialogAnimationDuration / 2.0f; 327 | 328 | [UIView animateWithDuration:animationStepDuration 329 | animations:^{ 330 | self.transform = CGAffineTransformScale(transform, 1.1f, 1.1f); 331 | } 332 | completion:^(BOOL finished) { 333 | [UIView animateWithDuration:animationStepDuration 334 | animations:^{ 335 | self.transform = CGAffineTransformScale(transform, 0.9f, 0.9f); 336 | } 337 | completion:^(BOOL finished) { 338 | [UIView animateWithDuration:animationStepDuration 339 | animations:^{ 340 | self.transform = transform; 341 | }]; 342 | }]; 343 | 344 | }]; 345 | } else { 346 | self.transform = transform; 347 | } 348 | 349 | [self _addObservers]; 350 | } 351 | 352 | - (void)dismissAnimated:(BOOL)animated { 353 | _loadingURL = nil; 354 | 355 | __weak typeof(self) wself = self; 356 | dispatch_block_t completionBlock = ^{ 357 | __strong typeof(wself) sself = wself; 358 | [sself _removeObservers]; 359 | [sself removeFromSuperview]; 360 | [_modalBackgroundView removeFromSuperview]; 361 | }; 362 | 363 | if (animated) { 364 | [UIView animateWithDuration:PFOAuth1FlowDialogAnimationDuration 365 | animations:^{ 366 | typeof(wself) sself = wself; 367 | sself.alpha = 0.0f; 368 | } 369 | completion:^(BOOL finished) { 370 | completionBlock(); 371 | }]; 372 | } else { 373 | completionBlock(); 374 | } 375 | } 376 | 377 | - (void)_dismissWithSuccess:(BOOL)success url:(NSURL *)url error:(NSError *)error { 378 | if (!self.completion) { 379 | return; 380 | } 381 | 382 | PFOAuth1FlowDialogCompletion completion = self.completion; 383 | self.completion = nil; 384 | 385 | dispatch_async(dispatch_get_main_queue(), ^{ 386 | completion(success, url, error); 387 | }); 388 | 389 | [self dismissAnimated:YES]; 390 | } 391 | 392 | - (void)_cancelButtonAction { 393 | [self _dismissWithSuccess:NO url:nil error:nil]; 394 | } 395 | 396 | #pragma mark - 397 | #pragma mark Public 398 | 399 | - (void)load { 400 | [self loadURL:_baseURL queryParameters:self.queryParameters]; 401 | } 402 | 403 | - (void)loadURL:(NSURL *)url queryParameters:(NSDictionary *)parameters { 404 | _loadingURL = [[self class] _urlFromBaseURL:url queryParameters:parameters]; 405 | 406 | NSURLRequest *request = [NSURLRequest requestWithURL:_loadingURL]; 407 | [_webView loadRequest:request]; 408 | } 409 | 410 | #pragma mark - 411 | #pragma mark UIWebViewDelegate 412 | 413 | - (BOOL)webView:(UIWebView *)webView 414 | shouldStartLoadWithRequest:(NSURLRequest *)request 415 | navigationType:(UIWebViewNavigationType)navigationType { 416 | NSURL *url = request.URL; 417 | 418 | if ([url.absoluteString hasPrefix:self.redirectURLPrefix]) { 419 | [self _dismissWithSuccess:YES url:url error:nil]; 420 | return NO; 421 | } else if ([_loadingURL isEqual:url]) { 422 | return YES; 423 | } else if (navigationType == UIWebViewNavigationTypeLinkClicked) { 424 | if ([self.dataSource dialog:self shouldOpenURLInExternalBrowser:url]) { 425 | [[UIApplication sharedApplication] openURL:url]; 426 | } else { 427 | return YES; 428 | } 429 | } 430 | 431 | return YES; 432 | } 433 | 434 | - (void)webViewDidStartLoad:(UIWebView *)webView { 435 | [[PFNetworkActivityIndicatorManager sharedManager] incrementActivityCount]; 436 | } 437 | 438 | - (void)webViewDidFinishLoad:(UIWebView *)webView { 439 | [[PFNetworkActivityIndicatorManager sharedManager] decrementActivityCount]; 440 | 441 | [_activityIndicator stopAnimating]; 442 | self.title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"]; 443 | } 444 | 445 | - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 446 | [[PFNetworkActivityIndicatorManager sharedManager] decrementActivityCount]; 447 | 448 | // 102 == WebKitErrorFrameLoadInterruptedByPolicyChange 449 | if (!([error.domain isEqualToString:@"WebKitErrorDomain"] && error.code == 102)) { 450 | [self _dismissWithSuccess:NO url:nil error:error]; 451 | } 452 | } 453 | 454 | #pragma mark - 455 | #pragma mark Observers 456 | 457 | - (void)_addObservers { 458 | [[NSNotificationCenter defaultCenter] addObserver:self 459 | selector:@selector(_deviceOrientationDidChange:) 460 | name:UIDeviceOrientationDidChangeNotification 461 | object:nil]; 462 | [[NSNotificationCenter defaultCenter] addObserver:self 463 | selector:@selector(_keyboardWillShow:) 464 | name:UIKeyboardWillShowNotification 465 | object:nil]; 466 | [[NSNotificationCenter defaultCenter] addObserver:self 467 | selector:@selector(_keyboardWillHide:) 468 | name:UIKeyboardWillHideNotification 469 | object:nil]; 470 | } 471 | 472 | - (void)_removeObservers { 473 | [[NSNotificationCenter defaultCenter] removeObserver:self 474 | name:UIDeviceOrientationDidChangeNotification 475 | object:nil]; 476 | [[NSNotificationCenter defaultCenter] removeObserver:self 477 | name:UIKeyboardWillShowNotification 478 | object:nil]; 479 | [[NSNotificationCenter defaultCenter] removeObserver:self 480 | name:UIKeyboardWillHideNotification 481 | object:nil]; 482 | } 483 | 484 | #pragma mark - 485 | #pragma mark UIDeviceOrientationDidChangeNotification 486 | 487 | - (void)_deviceOrientationDidChange:(NSNotification *)notification { 488 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 489 | if ([self _shouldRotateToOrientation:orientation]) { 490 | NSTimeInterval duration = [UIApplication sharedApplication].statusBarOrientationAnimationDuration; 491 | [UIView animateWithDuration:duration 492 | animations:^{ 493 | [self _sizeToFitOrientation]; 494 | }]; 495 | } 496 | } 497 | 498 | - (BOOL)_shouldRotateToOrientation:(UIInterfaceOrientation)orientation { 499 | if (orientation == _orientation) { 500 | return NO; 501 | } 502 | 503 | return (orientation == UIDeviceOrientationLandscapeLeft || 504 | orientation == UIDeviceOrientationLandscapeRight || 505 | orientation == UIDeviceOrientationPortrait || 506 | orientation == UIDeviceOrientationPortraitUpsideDown); 507 | } 508 | 509 | - (CGAffineTransform)_transformForOrientation:(UIInterfaceOrientation)orientation { 510 | // No manual rotation required on iOS 8 511 | // There is coordinateSpace method, since iOS 8 512 | if (PFOAuth1FlowDialogScreenHasAutomaticRotation()) { 513 | return CGAffineTransformIdentity; 514 | } 515 | 516 | switch (orientation) { 517 | case UIInterfaceOrientationLandscapeLeft: 518 | return CGAffineTransformMakeRotation((CGFloat)(-M_PI / 2.0f)); 519 | break; 520 | case UIInterfaceOrientationLandscapeRight: 521 | return CGAffineTransformMakeRotation((CGFloat)(M_PI / 2.0f)); 522 | break; 523 | case UIInterfaceOrientationPortraitUpsideDown: 524 | return CGAffineTransformMakeRotation((CGFloat)-M_PI); 525 | break; 526 | case UIInterfaceOrientationPortrait: 527 | case UIInterfaceOrientationUnknown: 528 | default: 529 | break; 530 | } 531 | 532 | return CGAffineTransformIdentity; 533 | } 534 | 535 | - (void)_sizeToFitOrientation { 536 | _orientation = [UIApplication sharedApplication].statusBarOrientation; 537 | CGAffineTransform transform = [self _transformForOrientation:_orientation]; 538 | 539 | CGRect bounds = [UIScreen mainScreen].applicationFrame; 540 | CGRect transformedBounds = CGRectApplyAffineTransform(bounds, transform); 541 | transformedBounds.origin = CGPointZero; 542 | 543 | CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 544 | 545 | CGFloat scale = (PFOAuth1FlowDialogIsDevicePad() ? 0.6f : 1.0f); 546 | 547 | CGFloat width = PFTFloatRound((scale * CGRectGetWidth(transformedBounds)) - PFOAuth1FlowDialogScreenInset * 2.0f, NSRoundDown); 548 | CGFloat height = PFTFloatRound((scale * CGRectGetHeight(transformedBounds)) - PFOAuth1FlowDialogScreenInset * 2.0f, NSRoundDown); 549 | 550 | self.transform = transform; 551 | self.center = center; 552 | self.bounds = CGRectMake(0.0f, 0.0f, width, height); 553 | 554 | [self setNeedsLayout]; 555 | } 556 | 557 | #pragma mark - 558 | #pragma mark UIKeyboardNotifications 559 | 560 | - (void)_keyboardWillShow:(NSNotification *)notification { 561 | _showingKeyboard = YES; 562 | 563 | if (PFOAuth1FlowDialogIsDevicePad()) { 564 | // On the iPad the screen is large enough that we don't need to 565 | // resize the dialog to accomodate the keyboard popping up 566 | return; 567 | } 568 | 569 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 570 | if (UIInterfaceOrientationIsLandscape(orientation)) { 571 | NSDictionary *userInfo = [notification userInfo]; 572 | NSTimeInterval animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 573 | UIViewAnimationCurve animationCurve = [[notification userInfo][UIKeyboardAnimationCurveUserInfoKey] intValue]; 574 | 575 | [UIView animateWithDuration:animationDuration 576 | delay:0.0 577 | options:animationCurve << 16 | UIViewAnimationOptionBeginFromCurrentState 578 | animations:^{ 579 | [self setNeedsLayout]; 580 | [self layoutIfNeeded]; 581 | 582 | [self setNeedsDisplay]; 583 | } 584 | completion:nil]; 585 | } 586 | } 587 | 588 | - (void)_keyboardWillHide:(NSNotification *)notification { 589 | _showingKeyboard = NO; 590 | 591 | if (PFOAuth1FlowDialogIsDevicePad()) { 592 | return; 593 | } 594 | UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; 595 | if (UIInterfaceOrientationIsLandscape(orientation)) { 596 | NSDictionary *userInfo = [notification userInfo]; 597 | NSTimeInterval animationDuration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; 598 | UIViewAnimationCurve animationCurve = [[notification userInfo][UIKeyboardAnimationCurveUserInfoKey] intValue]; 599 | 600 | [UIView animateWithDuration:animationDuration 601 | delay:0.0 602 | options:animationCurve << 16 | UIViewAnimationOptionBeginFromCurrentState 603 | animations:^{ 604 | [self setNeedsLayout]; 605 | [self layoutIfNeeded]; 606 | 607 | [self setNeedsDisplay]; 608 | } 609 | completion:nil]; 610 | } 611 | } 612 | 613 | @end 614 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface PFOAuthConfiguration : NSObject 15 | 16 | @property (nonatomic, strong) NSURL *url; 17 | @property (nonatomic, copy) NSString *method; 18 | @property (nullable, nonatomic, strong) NSData *body; 19 | 20 | @property (nonatomic, copy) NSString *consumerKey; 21 | @property (nonatomic, copy) NSString *consumerSecret; 22 | 23 | @property (nullable, nonatomic, copy) NSString *token; 24 | @property (nullable, nonatomic, copy) NSString *tokenSecret; 25 | 26 | @property (nullable, nonatomic, copy) NSDictionary *additionalParameters; 27 | 28 | @property (nonatomic, copy) NSString *nonce; 29 | @property (nonatomic, strong) NSDate *timestampDate; 30 | 31 | - (instancetype)init NS_UNAVAILABLE; 32 | + (instancetype)new NS_UNAVAILABLE; 33 | 34 | + (instancetype)configurationForURL:(NSURL *)url 35 | method:(NSString *)method 36 | body:(nullable NSData *)body 37 | additionalParameters:(nullable NSDictionary *)additionalParams 38 | consumerKey:(NSString *)consumerKey 39 | consumerSecret:(NSString *)consumerSecret 40 | token:(nullable NSString *)token 41 | tokenSecret:(nullable NSString *)tokenSecret; 42 | 43 | @end 44 | 45 | @interface PFOAuth : NSObject 46 | 47 | + (NSString *)authorizationHeaderFromConfiguration:(PFOAuthConfiguration *)configuration; 48 | 49 | @end 50 | 51 | @interface NSURL (PF_OAuthAdditions) 52 | 53 | + (NSDictionary *)PF_ab_parseURLQueryString:(NSString *)query; 54 | 55 | @end 56 | 57 | @interface NSString (PF_OAuthAdditions) 58 | 59 | - (NSString *)PF_ab_RFC3986EncodedString; 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/OAuthCore/PF_OAuthCore.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PF_OAuthCore.h" 11 | 12 | #import 13 | 14 | static NSData *PF_HMAC_SHA1(NSString *data, NSString *key) { 15 | unsigned char buf[CC_SHA1_DIGEST_LENGTH]; 16 | CCHmac(kCCHmacAlgSHA1, [key UTF8String], [key length], [data UTF8String], [data length], buf); 17 | return [NSData dataWithBytes:buf length:CC_SHA1_DIGEST_LENGTH]; 18 | } 19 | 20 | @implementation PFOAuthConfiguration 21 | 22 | - (instancetype)init { 23 | self = [super init]; 24 | if (!self) return nil; 25 | 26 | _nonce = [[NSUUID UUID] UUIDString]; 27 | _timestampDate = [NSDate date]; 28 | 29 | return self; 30 | } 31 | 32 | + (instancetype)configurationForURL:(NSURL *)url 33 | method:(NSString *)method 34 | body:(nullable NSData *)body 35 | additionalParameters:(nullable NSDictionary *)additionalParams 36 | consumerKey:(NSString *)consumerKey 37 | consumerSecret:(NSString *)consumerSecret 38 | token:(nullable NSString *)token 39 | tokenSecret:(nullable NSString *)tokenSecret { 40 | PFOAuthConfiguration *configuration = [[self alloc] init]; 41 | configuration.url = url; 42 | configuration.method = method; 43 | configuration.body = body; 44 | configuration.additionalParameters = additionalParams; 45 | configuration.consumerKey = consumerKey; 46 | configuration.consumerSecret = consumerSecret; 47 | configuration.token = token; 48 | configuration.tokenSecret = tokenSecret; 49 | return configuration; 50 | } 51 | 52 | @end 53 | 54 | @implementation PFOAuth 55 | 56 | + (NSString *)authorizationHeaderFromConfiguration:(PFOAuthConfiguration *)configuration { 57 | NSString *authTimeStamp = [NSString stringWithFormat:@"%llu", 58 | (unsigned long long)floor([configuration.timestampDate timeIntervalSince1970])]; 59 | NSString *authSignatureMethod = @"HMAC-SHA1"; 60 | NSString *authVersion = @"1.0"; 61 | NSURL *url = configuration.url; 62 | 63 | // Don't use -mutableCopy here, as that will return nil if `additionalParams` is nil. 64 | NSMutableDictionary *oAuthAuthorizationParameters = [NSMutableDictionary dictionaryWithDictionary:configuration.additionalParameters]; 65 | 66 | oAuthAuthorizationParameters[@"oauth_nonce"] = configuration.nonce; 67 | oAuthAuthorizationParameters[@"oauth_timestamp"] = authTimeStamp; 68 | oAuthAuthorizationParameters[@"oauth_signature_method"] = authSignatureMethod; 69 | oAuthAuthorizationParameters[@"oauth_version"] = authVersion; 70 | oAuthAuthorizationParameters[@"oauth_consumer_key"] = configuration.consumerKey; 71 | 72 | if (configuration.token) { 73 | oAuthAuthorizationParameters[@"oauth_token"] = configuration.token; 74 | } 75 | 76 | // get query and body parameters 77 | NSDictionary *additionalQueryParameters = [NSURL PF_ab_parseURLQueryString:[url query]]; 78 | NSDictionary *additionalBodyParameters = nil; 79 | if (configuration.body) { 80 | NSString *string = [[NSString alloc] initWithData:configuration.body encoding:NSUTF8StringEncoding]; 81 | additionalBodyParameters = [NSURL PF_ab_parseURLQueryString:string]; 82 | } 83 | 84 | // combine all parameters 85 | NSMutableDictionary *parameters = [oAuthAuthorizationParameters mutableCopy]; 86 | [parameters addEntriesFromDictionary:additionalQueryParameters]; 87 | if (additionalBodyParameters) { 88 | [parameters addEntriesFromDictionary:additionalBodyParameters]; 89 | } 90 | 91 | NSArray *sortedKeys = [[parameters allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { 92 | return [obj1 compare:obj2] ?: [parameters[obj1] compare:parameters[obj2]]; 93 | }]; 94 | 95 | NSMutableArray *parameterArray = [NSMutableArray array]; 96 | for (NSString *key in sortedKeys) { 97 | [parameterArray addObject:[NSString stringWithFormat:@"%@=%@", key, [parameters[key] PF_ab_RFC3986EncodedString]]]; 98 | } 99 | 100 | NSString *normalizedParameterString = [parameterArray componentsJoinedByString:@"&"]; 101 | NSString *normalizedURLString = [NSString stringWithFormat:@"%@://%@%@", [url scheme], [url host], [url path]]; 102 | 103 | NSString *signatureBaseString = [NSString stringWithFormat:@"%@&%@&%@", 104 | [configuration.method PF_ab_RFC3986EncodedString], 105 | [normalizedURLString PF_ab_RFC3986EncodedString], 106 | [normalizedParameterString PF_ab_RFC3986EncodedString]]; 107 | 108 | NSString *key = [NSString stringWithFormat:@"%@&%@", 109 | [configuration.consumerSecret PF_ab_RFC3986EncodedString], 110 | (configuration.tokenSecret ? [configuration.tokenSecret PF_ab_RFC3986EncodedString] : @"")]; 111 | 112 | NSData *signature = PF_HMAC_SHA1(signatureBaseString, key); 113 | NSString *base64Signature = [signature base64EncodedStringWithOptions:0]; 114 | 115 | oAuthAuthorizationParameters[@"oauth_signature"] = base64Signature; 116 | 117 | NSMutableArray *authorizationHeaderItems = [NSMutableArray array]; 118 | [oAuthAuthorizationParameters enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { 119 | [authorizationHeaderItems addObject:[NSString stringWithFormat:@"%@=\"%@\"", 120 | [key PF_ab_RFC3986EncodedString], 121 | [value PF_ab_RFC3986EncodedString]]]; 122 | }]; 123 | 124 | NSString *authorizationHeaderString = [authorizationHeaderItems componentsJoinedByString:@", "]; 125 | authorizationHeaderString = [NSString stringWithFormat:@"OAuth %@", authorizationHeaderString]; 126 | 127 | return authorizationHeaderString; 128 | } 129 | 130 | @end 131 | 132 | @implementation NSURL (OAuthAdditions) 133 | 134 | + (NSDictionary *)PF_ab_parseURLQueryString:(NSString *)query { 135 | // Use NSURLComponents if available. 136 | if ([NSURLComponents class] != nil && [NSURLComponents instancesRespondToSelector:@selector(queryItems)]) { 137 | NSURLComponents *components = [[NSURLComponents alloc] init]; 138 | [components setQuery:query]; 139 | 140 | NSArray *queryItems = components.queryItems; 141 | 142 | NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithCapacity:components.queryItems.count]; 143 | for (NSURLQueryItem *item in queryItems) { 144 | dictionary[item.name] = [item.value stringByRemovingPercentEncoding]; 145 | } 146 | return dictionary; 147 | } 148 | 149 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 150 | NSArray *pairs = [query componentsSeparatedByString:@"&"]; 151 | for (NSString *pair in pairs) { 152 | NSArray *keyValue = [pair componentsSeparatedByString:@"="]; 153 | if ([keyValue count] == 2) { 154 | NSString *key = [keyValue objectAtIndex:0]; 155 | NSString *value = [keyValue objectAtIndex:1]; 156 | value = [value stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 157 | if (key && value) 158 | [dict setObject:value forKey:key]; 159 | } 160 | } 161 | return [NSDictionary dictionaryWithDictionary:dict]; 162 | } 163 | 164 | @end 165 | 166 | @implementation NSString (OAuthAdditions) 167 | 168 | - (NSString *)PF_ab_RFC3986EncodedString // UTF-8 encodes prior to URL encoding 169 | { 170 | NSMutableString *result = [NSMutableString string]; 171 | const char *p = [self UTF8String]; 172 | unsigned char c; 173 | 174 | for (; (c = *p); p++) { 175 | switch (c) { 176 | case '0' ... '9': 177 | case 'A' ... 'Z': 178 | case 'a' ... 'z': 179 | case '.': 180 | case '-': 181 | case '~': 182 | case '_': 183 | [result appendFormat:@"%c", c]; 184 | break; 185 | default: 186 | [result appendFormat:@"%%%02X", c]; 187 | } 188 | } 189 | return result; 190 | } 191 | 192 | @end 193 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterAlertView.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | typedef void (^PFTwitterAlertViewCompletion)(NSUInteger selectedOtherButtonIndex); 13 | 14 | @interface PFTwitterAlertView : NSObject 15 | 16 | + (void)showAlertWithTitle:(NSString *)title 17 | message:(NSString *)message 18 | cancelButtonTitle:(NSString *)cancelButtonTitle 19 | otherButtonTitles:(NSArray *)otherButtonTitles 20 | completion:(PFTwitterAlertViewCompletion)completion NS_EXTENSION_UNAVAILABLE_IOS(""); 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterAlertView.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterAlertView.h" 11 | 12 | @interface PFTwitterAlertView () 13 | 14 | @property (nonatomic, copy) PFTwitterAlertViewCompletion completion; 15 | 16 | @end 17 | 18 | @implementation PFTwitterAlertView 19 | 20 | ///-------------------------------------- 21 | #pragma mark - Init 22 | ///-------------------------------------- 23 | 24 | + (void)showAlertWithTitle:(NSString *)title 25 | message:(NSString *)message 26 | cancelButtonTitle:(NSString *)cancelButtonTitle 27 | otherButtonTitles:(NSArray *)otherButtonTitles 28 | completion:(PFTwitterAlertViewCompletion)completion { 29 | if ([UIAlertController class] != nil) { 30 | __block UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title 31 | message:message 32 | preferredStyle:UIAlertControllerStyleAlert]; 33 | 34 | void (^alertActionHandler)(UIAlertAction *) = [^(UIAlertAction *action) { 35 | if (completion) { 36 | // This block intentionally retains alertController, and releases it afterwards. 37 | if (action.style == UIAlertActionStyleCancel) { 38 | completion(NSNotFound); 39 | } else { 40 | NSUInteger index = [alertController.actions indexOfObject:action]; 41 | completion(index - 1); 42 | } 43 | } 44 | alertController = nil; 45 | } copy]; 46 | 47 | [alertController addAction:[UIAlertAction actionWithTitle:cancelButtonTitle 48 | style:UIAlertActionStyleCancel 49 | handler:alertActionHandler]]; 50 | 51 | for (NSString *buttonTitle in otherButtonTitles) { 52 | [alertController addAction:[UIAlertAction actionWithTitle:buttonTitle 53 | style:UIAlertActionStyleDefault 54 | handler:alertActionHandler]]; 55 | } 56 | 57 | UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; 58 | UIViewController *viewController = keyWindow.rootViewController; 59 | while (viewController.presentedViewController) { 60 | viewController = viewController.presentedViewController; 61 | } 62 | 63 | [viewController presentViewController:alertController animated:YES completion:nil]; 64 | } else { 65 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 66 | __block PFTwitterAlertView *pfAlertView = [[self alloc] init]; 67 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title 68 | message:message 69 | delegate:nil 70 | cancelButtonTitle:cancelButtonTitle 71 | otherButtonTitles:nil]; 72 | 73 | for (NSString *buttonTitle in otherButtonTitles) { 74 | [alertView addButtonWithTitle:buttonTitle]; 75 | } 76 | 77 | pfAlertView.completion = ^(NSUInteger index) { 78 | if (completion) { 79 | completion(index); 80 | } 81 | 82 | pfAlertView = nil; 83 | }; 84 | 85 | alertView.delegate = pfAlertView; 86 | [alertView show]; 87 | #endif 88 | } 89 | } 90 | 91 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 92 | 93 | ///-------------------------------------- 94 | #pragma mark - UIAlertViewDelegate 95 | ///-------------------------------------- 96 | 97 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 98 | if (self.completion) { 99 | if (buttonIndex == alertView.cancelButtonIndex) { 100 | self.completion(NSNotFound); 101 | } else { 102 | self.completion(buttonIndex - 1); 103 | } 104 | } 105 | } 106 | 107 | #endif 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | @class BFTask<__covariant BFGenericType>; 15 | @class PF_Twitter; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | extern NSString *const PFTwitterUserAuthenticationType; 20 | 21 | @interface PFTwitterAuthenticationProvider : NSObject 22 | 23 | @property (nonatomic, strong, readonly) PF_Twitter *twitter; 24 | 25 | - (instancetype)init NS_UNAVAILABLE; 26 | - (instancetype)initWithTwitter:(PF_Twitter *)twitter NS_DESIGNATED_INITIALIZER; 27 | + (instancetype)providerWithTwitter:(PF_Twitter *)twitter; 28 | 29 | - (BFTask *)authenticateAsync; 30 | 31 | - (NSDictionary *)authDataWithTwitterId:(NSString *)twitterId 32 | screenName:(NSString *)screenName 33 | authToken:(NSString *)authToken 34 | secret:(NSString *)authTokenSecret; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterAuthenticationProvider.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterAuthenticationProvider.h" 11 | 12 | #import 13 | 14 | #import 15 | 16 | #import "PFTwitterPrivateUtilities.h" 17 | #import "PF_Twitter.h" 18 | 19 | NSString *const PFTwitterUserAuthenticationType = @"twitter"; 20 | 21 | static NSString *const _PFTwitterAuthDataIdKey = @"id"; 22 | static NSString *const _PFTwitterAuthDataScreenNameKey = @"screen_name"; 23 | static NSString *const _PFTwitterAuthDataAuthTokenKey = @"auth_token"; 24 | static NSString *const _PFTwitterAuthDataAuthTokenSecretKey = @"auth_token_secret"; 25 | static NSString *const _PFTwitterAuthDataConsumerKeyKey = @"consumer_key"; 26 | static NSString *const _PFTwitterAuthDataConsumerSecretKey = @"consumer_secret"; 27 | 28 | @implementation PFTwitterAuthenticationProvider 29 | 30 | ///-------------------------------------- 31 | #pragma mark - Init 32 | ///-------------------------------------- 33 | 34 | - (instancetype)init { 35 | PFTWConsistencyAssert(NO, @"%@ is not a designated initializer for instances of %@.", 36 | NSStringFromSelector(_cmd), NSStringFromClass([self class])); 37 | return nil; 38 | } 39 | 40 | - (instancetype)initWithTwitter:(PF_Twitter *)twitter { 41 | self = [super init]; 42 | if (!self) return nil; 43 | 44 | _twitter = twitter; 45 | 46 | return self; 47 | } 48 | 49 | + (instancetype)providerWithTwitter:(PF_Twitter *)twitter { 50 | return [[self alloc] initWithTwitter:twitter]; 51 | } 52 | 53 | ///-------------------------------------- 54 | #pragma mark - Auth Data 55 | ///-------------------------------------- 56 | 57 | - (NSDictionary *)authDataWithTwitterId:(NSString *)twitterId 58 | screenName:(NSString *)screenName 59 | authToken:(NSString *)authToken 60 | secret:(NSString *)authTokenSecret { 61 | NSDictionary *authData = @{_PFTwitterAuthDataIdKey : twitterId, 62 | _PFTwitterAuthDataScreenNameKey : screenName, 63 | _PFTwitterAuthDataAuthTokenKey : authToken, 64 | _PFTwitterAuthDataAuthTokenSecretKey : authTokenSecret, 65 | _PFTwitterAuthDataConsumerKeyKey : self.twitter.consumerKey, 66 | _PFTwitterAuthDataConsumerSecretKey : self.twitter.consumerSecret}; 67 | return authData; 68 | } 69 | 70 | ///-------------------------------------- 71 | #pragma mark - Authentication 72 | ///-------------------------------------- 73 | 74 | - (BFTask *)authenticateAsync { 75 | return [[self.twitter authorizeInBackground] continueWithSuccessBlock:^id(BFTask *task) { 76 | NSDictionary *authData = [self authDataWithTwitterId:self.twitter.userId 77 | screenName:self.twitter.screenName 78 | authToken:self.twitter.authToken 79 | secret:self.twitter.authTokenSecret]; 80 | return [BFTask taskWithResult:authData]; 81 | }]; 82 | } 83 | 84 | ///-------------------------------------- 85 | #pragma mark - PFUserAuthenticationDelegate 86 | ///-------------------------------------- 87 | 88 | - (BOOL)restoreAuthenticationWithAuthData:(NSDictionary *)authData { 89 | // If authData is nil, this is an unlink operation, which should succeed. 90 | if (!authData) { 91 | self.twitter.userId = nil; 92 | self.twitter.authToken = nil; 93 | self.twitter.authTokenSecret = nil; 94 | self.twitter.screenName = nil; 95 | return YES; 96 | } 97 | 98 | // Check that the authData contains the required fields, and if so, synchronize. 99 | NSString *userId = authData[_PFTwitterAuthDataIdKey]; 100 | NSString *screenName = authData[_PFTwitterAuthDataScreenNameKey]; 101 | NSString *authToken = authData[_PFTwitterAuthDataAuthTokenKey]; 102 | NSString *authTokenSecret = authData[_PFTwitterAuthDataAuthTokenSecretKey]; 103 | if (userId && screenName && authToken && authTokenSecret) { 104 | self.twitter.userId = userId; 105 | self.twitter.screenName = screenName; 106 | self.twitter.authToken = authToken; 107 | self.twitter.authTokenSecret = authTokenSecret; 108 | return YES; 109 | } 110 | return NO; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterLocalization.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #ifndef PFTwitterLocalization_h 11 | #define PFTwitterLocalization_h 12 | 13 | #define PFTWLocalizedString(key, comment) \ 14 | [[NSBundle bundleForClass:[self class]] localizedStringForKey:key value:nil table:@"ParseTwitterUtils"] 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | #import 15 | 16 | @interface PFTwitterPrivateUtilities : NSObject 17 | 18 | + (void)safePerformSelector:(SEL)selector onTarget:(id)target withObject:(id)object object:(id)anotherObject; 19 | 20 | @end 21 | 22 | @interface BFTask (ParseTwitterUtils) 23 | 24 | - (id)pftw_waitForResult:(NSError **)error; 25 | 26 | //TODO: (nlutsenko) Look into killing this and replacing with generic continueWithBlock: 27 | - (instancetype)pftw_continueAsyncWithBlock:(BFContinuationBlock)block; 28 | 29 | - (instancetype)pftw_continueWithMainThreadUserBlock:(PFUserResultBlock)block; 30 | - (instancetype)pftw_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block; 31 | - (instancetype)pftw_continueWithMainThreadBlock:(BFContinuationBlock)block; 32 | 33 | @end 34 | 35 | /** 36 | Raises an `NSInternalInconsistencyException` if the `condition` does not pass. 37 | Use `description` to supply the way to fix the exception. 38 | */ 39 | #define PFTWConsistencyAssert(condition, description, ...) \ 40 | do { \ 41 | if (!(condition)) { \ 42 | [NSException raise:NSInternalInconsistencyException \ 43 | format:description, ##__VA_ARGS__]; \ 44 | } \ 45 | } while(0) 46 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterPrivateUtilities.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterPrivateUtilities.h" 11 | 12 | #import 13 | 14 | @implementation PFTwitterPrivateUtilities 15 | 16 | + (void)safePerformSelector:(SEL)selector 17 | onTarget:(id)target 18 | withObject:(id)object 19 | object:(id)anotherObject { 20 | if (target == nil || selector == nil || ![target respondsToSelector:selector]) { 21 | return; 22 | } 23 | 24 | #pragma clang diagnostic push 25 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks" 26 | [target performSelector:selector withObject:object withObject:anotherObject]; 27 | #pragma clang diagnostic pop 28 | } 29 | 30 | @end 31 | 32 | @implementation BFTask (ParseTwitterUtils) 33 | 34 | - (id)pftw_waitForResult:(NSError **)error { 35 | [self waitUntilFinished]; 36 | 37 | if (self.cancelled) { 38 | return nil; 39 | } else if (self.error && error) { 40 | *error = self.error; 41 | } 42 | return self.result; 43 | } 44 | 45 | - (instancetype)pftw_continueAsyncWithBlock:(BFContinuationBlock)block { 46 | BFExecutor *executor = [BFExecutor executorWithDispatchQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 47 | return [self continueWithExecutor:executor withBlock:block]; 48 | } 49 | 50 | - (instancetype)pftw_continueWithMainThreadUserBlock:(PFUserResultBlock)block { 51 | return [self pftw_continueWithMainThreadBlock:^id(BFTask *task) { 52 | if (block) { 53 | block(task.result, task.error); 54 | } 55 | return nil; 56 | }]; 57 | } 58 | 59 | - (instancetype)pftw_continueWithMainThreadBooleanBlock:(PFBooleanResultBlock)block { 60 | return [self pftw_continueWithMainThreadBlock:^id(BFTask *task) { 61 | if (block) { 62 | block([task.result boolValue], task.error); 63 | } 64 | return nil; 65 | }]; 66 | } 67 | 68 | - (instancetype)pftw_continueWithMainThreadBlock:(BFContinuationBlock)block { 69 | return [self continueWithExecutor:[BFExecutor mainThreadExecutor] withBlock:block]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PFTwitterUtils_Private.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class PFTwitterAuthenticationProvider; 13 | 14 | @interface PFTwitterUtils () 15 | 16 | + (PFTwitterAuthenticationProvider *)_authenticationProvider; 17 | + (void)_setAuthenticationProvider:(PFTwitterAuthenticationProvider *)provider; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ParseTwitterUtils/Internal/PF_Twitter_Private.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | @class ACAccount; 15 | @class ACAccountStore; 16 | @protocol PFOAuth1FlowDialogInterface; 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | @interface PF_Twitter () 21 | 22 | @property (nonatomic, strong, readonly) ACAccountStore *accountStore; 23 | @property (nonatomic, strong, readonly) NSURLSession *urlSession; 24 | @property (nonatomic, strong, readonly) Class oauthDialogClass; 25 | 26 | - (instancetype)initWithAccountStore:(ACAccountStore *)accountStore 27 | urlSession:(NSURLSession *)urlSession 28 | dialogClass:(Class)dialogClass; 29 | 30 | /** 31 | Obtain access to the local twitter account. 32 | Returns the twitter account if access is obtained. Otherwise, returns null. 33 | */ 34 | - (BFTask *)_getLocalTwitterAccountAsync; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /ParseTwitterUtils/PFTwitterUtils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | #import 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @class BFTask<__covariant BFGenericType>; 18 | @class PF_Twitter; 19 | 20 | /** 21 | The `PFTwitterUtils` class provides utility functions for working with Twitter in a Parse application. 22 | 23 | This class is currently for iOS only. 24 | */ 25 | @interface PFTwitterUtils : NSObject 26 | 27 | ///-------------------------------------- 28 | /// @name Interacting With Twitter 29 | ///-------------------------------------- 30 | 31 | /** 32 | Gets the instance of the `PF_Twitter` object that Parse uses. 33 | 34 | @return An instance of `PF_Twitter` object. 35 | */ 36 | + (nullable PF_Twitter *)twitter; 37 | 38 | /** 39 | Initializes the Twitter singleton. 40 | 41 | @warning You must invoke this in order to use the Twitter functionality in Parse. 42 | 43 | @param consumerKey Your Twitter application's consumer key. 44 | @param consumerSecret Your Twitter application's consumer secret. 45 | */ 46 | + (void)initializeWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret; 47 | 48 | /** 49 | Whether the user has their account linked to Twitter. 50 | 51 | @param user User to check for a Twitter link. The user must be logged in on this device. 52 | 53 | @return `YES` if the user has their account linked to Twitter, otherwise `NO`. 54 | */ 55 | + (BOOL)isLinkedWithUser:(nullable PFUser *)user; 56 | 57 | ///-------------------------------------- 58 | /// @name Logging In & Creating Twitter-Linked Users 59 | ///-------------------------------------- 60 | 61 | /** 62 | *Asynchronously* logs in a user using Twitter. 63 | 64 | This method delegates to Twitter to authenticate the user, 65 | and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. 66 | 67 | @return The task, that encapsulates the work being done. 68 | */ 69 | + (BFTask *)logInInBackground; 70 | 71 | /** 72 | *Asynchronously* logs in a user using Twitter. 73 | 74 | This method delegates to Twitter to authenticate the user, 75 | and then automatically logs in (or creates, in the case where it is a new user) `PFUser`. 76 | 77 | @param block The block to execute. 78 | It should have the following argument signature: `^(PFUser *user, NSError *error)`. 79 | */ 80 | + (void)logInWithBlock:(nullable PFUserResultBlock)block; 81 | 82 | /* 83 | *Asynchronously* Logs in a user using Twitter. 84 | 85 | This method delegates to Twitter to authenticate the user, 86 | and then automatically logs in (or creates, in the case where it is a new user) a `PFUser`. 87 | 88 | @param target Target object for the selector 89 | @param selector The selector that will be called when the asynchrounous request is complete. 90 | It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError **)error`. 91 | */ 92 | + (void)logInWithTarget:(nullable id)target selector:(nullable SEL)selector; 93 | 94 | /** 95 | *Asynchronously* logs in a user using Twitter. 96 | 97 | Allows you to handle user login to Twitter, then provide authentication 98 | data to log in (or create, in the case where it is a new user) the `PFUser`. 99 | 100 | @param twitterId The id of the Twitter user being linked. 101 | @param screenName The screen name of the Twitter user being linked. 102 | @param authToken The auth token for the user's session. 103 | @param authTokenSecret The auth token secret for the user's session. 104 | 105 | @return The task, that encapsulates the work being done. 106 | */ 107 | + (BFTask *)logInWithTwitterIdInBackground:(NSString *)twitterId 108 | screenName:(NSString *)screenName 109 | authToken:(NSString *)authToken 110 | authTokenSecret:(NSString *)authTokenSecret; 111 | 112 | /** 113 | Logs in a user using Twitter. 114 | 115 | Allows you to handle user login to Twitter, then provide authentication data 116 | to log in (or create, in the case where it is a new user) the `PFUser`. 117 | 118 | @param twitterId The id of the Twitter user being linked 119 | @param screenName The screen name of the Twitter user being linked 120 | @param authToken The auth token for the user's session 121 | @param authTokenSecret The auth token secret for the user's session 122 | @param block The block to execute. 123 | It should have the following argument signature: `^(PFUser *user, NSError *error)`. 124 | */ 125 | + (void)logInWithTwitterId:(NSString *)twitterId 126 | screenName:(NSString *)screenName 127 | authToken:(NSString *)authToken 128 | authTokenSecret:(NSString *)authTokenSecret 129 | block:(nullable PFUserResultBlock)block; 130 | 131 | /* 132 | Logs in a user using Twitter. 133 | 134 | Allows you to handle user login to Twitter, then provide authentication data 135 | to log in (or create, in the case where it is a new user) the `PFUser`. 136 | 137 | @param twitterId The id of the Twitter user being linked. 138 | @param screenName The screen name of the Twitter user being linked. 139 | @param authToken The auth token for the user's session. 140 | @param authTokenSecret The auth token secret for the user's session. 141 | @param target Target object for the selector. 142 | @param selector The selector that will be called when the asynchronous request is complete. 143 | It should have the following signature: `(void)callbackWithUser:(PFUser *)user error:(NSError *)error`. 144 | */ 145 | + (void)logInWithTwitterId:(NSString *)twitterId 146 | screenName:(NSString *)screenName 147 | authToken:(NSString *)authToken 148 | authTokenSecret:(NSString *)authTokenSecret 149 | target:(nullable id)target 150 | selector:(nullable SEL)selector; 151 | 152 | ///-------------------------------------- 153 | /// @name Linking Users with Twitter 154 | ///-------------------------------------- 155 | 156 | /** 157 | *Asynchronously* links Twitter to an existing PFUser. 158 | 159 | This method delegates to Twitter to authenticate the user, 160 | and then automatically links the account to the `PFUser`. 161 | 162 | @param user User to link to Twitter. 163 | 164 | @deprecated Please use `[PFTwitterUtils linkUserInBackground:]` instead. 165 | */ 166 | + (void)linkUser:(PFUser *)user PARSE_DEPRECATED("Please use +linkUserInBackground: instead."); 167 | 168 | /** 169 | *Asynchronously* links Twitter to an existing `PFUser`. 170 | 171 | This method delegates to Twitter to authenticate the user, 172 | and then automatically links the account to the `PFUser`. 173 | 174 | @param user User to link to Twitter. 175 | 176 | @return The task, that encapsulates the work being done. 177 | */ 178 | + (BFTask *)linkUserInBackground:(PFUser *)user; 179 | 180 | /** 181 | *Asynchronously* links Twitter to an existing `PFUser`. 182 | 183 | This method delegates to Twitter to authenticate the user, 184 | and then automatically links the account to the `PFUser`. 185 | 186 | @param user User to link to Twitter. 187 | @param block The block to execute. 188 | It should have the following argument signature: `^(BOOL success, NSError *error)`. 189 | */ 190 | + (void)linkUser:(PFUser *)user block:(nullable PFBooleanResultBlock)block; 191 | 192 | /* 193 | *Asynchronously* links Twitter to an existing `PFUser`. 194 | 195 | This method delegates to Twitter to authenticate the user, 196 | and then automatically links the account to the `PFUser`. 197 | 198 | @param user User to link to Twitter. 199 | @param target Target object for the selector 200 | @param selector The selector that will be called when the asynchrounous request is complete. 201 | It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. 202 | */ 203 | + (void)linkUser:(PFUser *)user target:(nullable id)target selector:(nullable SEL)selector; 204 | 205 | /** 206 | *Asynchronously* links Twitter to an existing PFUser asynchronously. 207 | 208 | Allows you to handle user login to Twitter, 209 | then provide authentication data to link the account to the `PFUser`. 210 | 211 | @param user User to link to Twitter. 212 | @param twitterId The id of the Twitter user being linked. 213 | @param screenName The screen name of the Twitter user being linked. 214 | @param authToken The auth token for the user's session. 215 | @param authTokenSecret The auth token secret for the user's session. 216 | @return The task, that encapsulates the work being done. 217 | */ 218 | + (BFTask *)linkUserInBackground:(PFUser *)user 219 | twitterId:(NSString *)twitterId 220 | screenName:(NSString *)screenName 221 | authToken:(NSString *)authToken 222 | authTokenSecret:(NSString *)authTokenSecret; 223 | 224 | /** 225 | *Asynchronously* links Twitter to an existing `PFUser`. 226 | 227 | @discussionAllows you to handle user login to Twitter, 228 | then provide authentication data to link the account to the `PFUser`. 229 | 230 | @param user User to link to Twitter. 231 | @param twitterId The id of the Twitter user being linked. 232 | @param screenName The screen name of the Twitter user being linked. 233 | @param authToken The auth token for the user's session. 234 | @param authTokenSecret The auth token secret for the user's session. 235 | @param block The block to execute. 236 | It should have the following argument signature: `^(BOOL success, NSError *error)`. 237 | */ 238 | + (void)linkUser:(PFUser *)user 239 | twitterId:(NSString *)twitterId 240 | screenName:(NSString *)screenName 241 | authToken:(NSString *)authToken 242 | authTokenSecret:(NSString *)authTokenSecret 243 | block:(nullable PFBooleanResultBlock)block; 244 | 245 | /* 246 | Links Twitter to an existing `PFUser`. 247 | 248 | This method allows you to handle user login to Twitter, 249 | then provide authentication data to link the account to the `PFUser`. 250 | 251 | @param user User to link to Twitter. 252 | @param twitterId The id of the Twitter user being linked. 253 | @param screenName The screen name of the Twitter user being linked. 254 | @param authToken The auth token for the user's session. 255 | @param authTokenSecret The auth token secret for the user's session. 256 | @param target Target object for the selector. 257 | @param selector The selector that will be called when the asynchronous request is complete. 258 | It should have the following signature: `(void)callbackWithResult:(NSNumber *)result error:(NSError *)error`. 259 | */ 260 | + (void)linkUser:(PFUser *)user 261 | twitterId:(NSString *)twitterId 262 | screenName:(NSString *)screenName 263 | authToken:(NSString *)authToken 264 | authTokenSecret:(NSString *)authTokenSecret 265 | target:(nullable id)target 266 | selector:(nullable SEL)selector; 267 | 268 | ///-------------------------------------- 269 | /// @name Unlinking Users from Twitter 270 | ///-------------------------------------- 271 | 272 | /** 273 | *Synchronously* unlinks the `PFUser` from a Twitter account. 274 | 275 | @param user User to unlink from Twitter. 276 | 277 | @return Returns true if the unlink was successful. 278 | */ 279 | + (BOOL)unlinkUser:(PFUser *)user; 280 | 281 | /** 282 | *Synchronously* unlinks the PFUser from a Twitter account. 283 | 284 | @param user User to unlink from Twitter. 285 | @param error Error object to set on error. 286 | 287 | @return Returns `YES` if the unlink was successful, otherwise `NO`. 288 | */ 289 | + (BOOL)unlinkUser:(PFUser *)user error:(NSError **)error; 290 | 291 | /** 292 | Makes an *asynchronous* request to unlink a user from a Twitter account. 293 | 294 | @param user User to unlink from Twitter. 295 | 296 | @return The task, that encapsulates the work being done. 297 | */ 298 | + (BFTask *)unlinkUserInBackground:(PFUser *)user; 299 | 300 | /** 301 | Makes an *asynchronous* request to unlink a user from a Twitter account. 302 | 303 | @param user User to unlink from Twitter. 304 | @param block The block to execute. 305 | It should have the following argument signature: `^(BOOL succeeded, NSError *error)`. 306 | */ 307 | + (void)unlinkUserInBackground:(PFUser *)user block:(nullable PFBooleanResultBlock)block; 308 | 309 | /* 310 | Makes an *asynchronous* request to unlink a user from a Twitter account. 311 | 312 | @param user User to unlink from Twitter 313 | @param target Target object for the selector 314 | @param selector The selector that will be called when the asynchrounous request is complete. 315 | */ 316 | + (void)unlinkUserInBackground:(PFUser *)user target:(nullable id)target selector:(nullable SEL)selector; 317 | 318 | @end 319 | 320 | NS_ASSUME_NONNULL_END 321 | -------------------------------------------------------------------------------- /ParseTwitterUtils/PFTwitterUtils.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterUtils.h" 11 | 12 | #import 13 | #import 14 | 15 | #import 16 | 17 | #import "PFTwitterAuthenticationProvider.h" 18 | #import "PFTwitterPrivateUtilities.h" 19 | #import "PF_Twitter.h" 20 | 21 | @implementation PFTwitterUtils 22 | 23 | ///-------------------------------------- 24 | #pragma mark - Authentication Provider 25 | ///-------------------------------------- 26 | 27 | static PFTwitterAuthenticationProvider *authenticationProvider_; 28 | 29 | + (PFTwitterAuthenticationProvider *)_authenticationProvider { 30 | return authenticationProvider_; 31 | } 32 | 33 | + (void)_setAuthenticationProvider:(PFTwitterAuthenticationProvider *)provider { 34 | authenticationProvider_ = provider; 35 | } 36 | 37 | ///-------------------------------------- 38 | #pragma mark - Initialize 39 | ///-------------------------------------- 40 | 41 | + (void)_assertTwitterInitialized { 42 | PFTWConsistencyAssert([self _authenticationProvider], 43 | @"You must call PFTwitterUtils initializeWithConsumerKey:consumerSecret: to use PFTwitterUtils."); 44 | } 45 | 46 | + (void)_assertParseInitialized { 47 | PFTWConsistencyAssert([Parse getApplicationId], 48 | @"PFTwitterUtils should be initialized after setting up Parse."); 49 | } 50 | 51 | + (void)initializeWithConsumerKey:(NSString *)consumerKey consumerSecret:(NSString *)consumerSecret { 52 | [self _assertParseInitialized]; 53 | if (![self _authenticationProvider]) { 54 | PF_Twitter *twitter = [[PF_Twitter alloc] init]; 55 | twitter.consumerKey = consumerKey; 56 | twitter.consumerSecret = consumerSecret; 57 | 58 | PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; 59 | [PFUser registerAuthenticationDelegate:provider forAuthType:PFTwitterUserAuthenticationType]; 60 | 61 | [self _setAuthenticationProvider:provider]; 62 | } 63 | } 64 | 65 | + (PF_Twitter *)twitter { 66 | return [self _authenticationProvider].twitter; 67 | } 68 | 69 | + (BOOL)isLinkedWithUser:(PFUser *)user { 70 | return [user isLinkedWithAuthType:PFTwitterUserAuthenticationType]; 71 | } 72 | 73 | + (BOOL)unlinkUser:(PFUser *)user { 74 | return [self unlinkUser:user error:nil]; 75 | } 76 | 77 | + (BOOL)unlinkUser:(PFUser *)user error:(NSError **)error { 78 | return [[[self unlinkUserInBackground:user] pftw_waitForResult:error] boolValue]; 79 | } 80 | 81 | + (BFTask *)unlinkUserInBackground:(PFUser *)user { 82 | [self _assertTwitterInitialized]; 83 | return [user unlinkWithAuthTypeInBackground:PFTwitterUserAuthenticationType]; 84 | } 85 | 86 | + (void)unlinkUserInBackground:(PFUser *)user block:(PFBooleanResultBlock)block { 87 | [[self unlinkUserInBackground:user] pftw_continueWithMainThreadBooleanBlock:block]; 88 | } 89 | 90 | + (void)unlinkUserInBackground:(PFUser *)user target:(id)target selector:(SEL)selector { 91 | [PFTwitterUtils unlinkUserInBackground:user block:^(BOOL succeeded, NSError *error) { 92 | [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:@(succeeded) object:error]; 93 | }]; 94 | } 95 | 96 | + (void)linkUser:(PFUser *)user { 97 | // This is misnamed `*InBackground` method. Left as is for backward compatability. 98 | [self linkUserInBackground:user]; 99 | } 100 | 101 | + (BFTask *)linkUserInBackground:(PFUser *)user { 102 | [self _assertTwitterInitialized]; 103 | 104 | PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; 105 | return [[provider authenticateAsync] continueWithSuccessBlock:^id(BFTask *task) { 106 | return [user linkWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:task.result]; 107 | }]; 108 | } 109 | 110 | + (void)linkUser:(PFUser *)user block:(PFBooleanResultBlock)block { 111 | [[self linkUserInBackground:user] pftw_continueWithMainThreadBooleanBlock:block]; 112 | } 113 | 114 | + (void)linkUser:(PFUser *)user target:(id)target selector:(SEL)selector { 115 | [PFTwitterUtils linkUser:user block:^(BOOL succeeded, NSError *error) { 116 | [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:@(succeeded) object:error]; 117 | }]; 118 | } 119 | 120 | + (BFTask *)linkUserInBackground:(PFUser *)user 121 | twitterId:(NSString *)twitterId 122 | screenName:(NSString *)screenName 123 | authToken:(NSString *)authToken 124 | authTokenSecret:(NSString *)authTokenSecret { 125 | [self _assertTwitterInitialized]; 126 | 127 | PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; 128 | NSDictionary *authData = [provider authDataWithTwitterId:twitterId 129 | screenName:screenName 130 | authToken:authToken 131 | secret:authTokenSecret]; 132 | return [user linkWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:authData]; 133 | } 134 | 135 | + (void)linkUser:(PFUser *)user 136 | twitterId:(NSString *)twitterId 137 | screenName:(NSString *)screenName 138 | authToken:(NSString *)authToken 139 | authTokenSecret:(NSString *)authTokenSecret 140 | block:(PFBooleanResultBlock)block { 141 | [[self linkUserInBackground:user 142 | twitterId:twitterId 143 | screenName:screenName 144 | authToken:authToken 145 | authTokenSecret:authTokenSecret] pftw_continueWithMainThreadBooleanBlock:block]; 146 | } 147 | 148 | + (void)linkUser:(PFUser *)user 149 | twitterId:(NSString *)twitterId 150 | screenName:(NSString *)screenName 151 | authToken:(NSString *)authToken 152 | authTokenSecret:(NSString *)authTokenSecret 153 | target:(id)target 154 | selector:(SEL)selector { 155 | [PFTwitterUtils linkUser:user 156 | twitterId:twitterId 157 | screenName:screenName 158 | authToken:authToken 159 | authTokenSecret:authTokenSecret 160 | block:^(BOOL succeeded, NSError *error) { 161 | [PFTwitterPrivateUtilities safePerformSelector:selector 162 | onTarget:target 163 | withObject:@(succeeded) 164 | object:error]; 165 | }]; 166 | } 167 | 168 | + (BFTask *)logInInBackground { 169 | [self _assertTwitterInitialized]; 170 | 171 | PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; 172 | return [[provider authenticateAsync] continueWithSuccessBlock:^id(BFTask *task) { 173 | return [PFUser logInWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:task.result]; 174 | }]; 175 | } 176 | 177 | + (void)logInWithBlock:(PFUserResultBlock)block { 178 | [[self logInInBackground] pftw_continueWithMainThreadUserBlock:block]; 179 | } 180 | 181 | + (void)logInWithTarget:(id)target selector:(SEL)selector { 182 | [self logInWithBlock:^(PFUser *user, NSError *error) { 183 | [PFTwitterPrivateUtilities safePerformSelector:selector onTarget:target withObject:user object:error]; 184 | }]; 185 | } 186 | 187 | + (BFTask *)logInWithTwitterIdInBackground:(NSString *)twitterId 188 | screenName:(NSString *)screenName 189 | authToken:(NSString *)authToken 190 | authTokenSecret:(NSString *)authTokenSecret { 191 | [self _assertTwitterInitialized]; 192 | 193 | PFTwitterAuthenticationProvider *provider = [self _authenticationProvider]; 194 | NSDictionary *authData = [provider authDataWithTwitterId:twitterId 195 | screenName:screenName 196 | authToken:authToken 197 | secret:authTokenSecret]; 198 | return [PFUser logInWithAuthTypeInBackground:PFTwitterUserAuthenticationType authData:authData]; 199 | } 200 | 201 | + (void)logInWithTwitterId:(NSString *)twitterId 202 | screenName:(NSString *)screenName 203 | authToken:(NSString *)authToken 204 | authTokenSecret:(NSString *)authTokenSecret 205 | block:(PFUserResultBlock)block { 206 | [[self logInWithTwitterIdInBackground:twitterId 207 | screenName:screenName 208 | authToken:authToken 209 | authTokenSecret:authTokenSecret] pftw_continueWithMainThreadUserBlock:block]; 210 | } 211 | 212 | + (void)logInWithTwitterId:(NSString *)twitterId 213 | screenName:(NSString *)screenName 214 | authToken:(NSString *)authToken 215 | authTokenSecret:(NSString *)authTokenSecret 216 | target:(id)target 217 | selector:(SEL)selector { 218 | [self logInWithTwitterId:twitterId 219 | screenName:screenName 220 | authToken:authToken 221 | authTokenSecret:authTokenSecret 222 | block:^(PFUser *user, NSError *error) { 223 | [PFTwitterPrivateUtilities safePerformSelector:selector 224 | onTarget:target 225 | withObject:user 226 | object:error]; 227 | }]; 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /ParseTwitterUtils/PF_Twitter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @class BFTask<__covariant BFGenericType>; 17 | 18 | /** 19 | The `PF_Twitter` class is a simple interface for interacting with the Twitter REST API, 20 | automating sign-in and OAuth signing of requests against the API. 21 | */ 22 | @interface PF_Twitter : NSObject 23 | 24 | /** 25 | Initializes an instance of `PF_Twitter` configured to access device Twitter accounts with Accounts.framework, 26 | and remote access to Twitter accounts - and, if no accounts are found locally - through a built-in webview. 27 | 28 | After setting `consumerKey` and `consumerSecret`, authorization to Twitter accounts can be requested with 29 | `authorizeInBackground`, and then revoked with its opposite, `deauthorizeInBackground`. 30 | */ 31 | - (instancetype)init; 32 | 33 | /** 34 | Consumer key of the application that is used to authorize with Twitter. 35 | */ 36 | @property (nullable, nonatomic, copy) NSString *consumerKey; 37 | 38 | /** 39 | Consumer secret of the application that is used to authorize with Twitter. 40 | */ 41 | @property (nullable, nonatomic, copy) NSString *consumerSecret; 42 | 43 | /** 44 | Auth token for the current user. 45 | */ 46 | @property (nullable, nonatomic, copy) NSString *authToken; 47 | 48 | /** 49 | Auth token secret for the current user. 50 | */ 51 | @property (nullable, nonatomic, copy) NSString *authTokenSecret; 52 | 53 | /** 54 | Twitter user id of the currently signed in user. 55 | */ 56 | @property (nullable, nonatomic, copy) NSString *userId; 57 | 58 | /** 59 | Twitter screen name of the currently signed in user. 60 | */ 61 | @property (nullable, nonatomic, copy) NSString *screenName; 62 | 63 | /** 64 | Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties 65 | if the Twitter user grants permission to the application. 66 | 67 | @return The task, that encapsulates the work being done. 68 | */ 69 | - (BFTask *)authorizeInBackground; 70 | 71 | /** 72 | Invalidates an OAuth token for the current user, if the Twitter user has granted permission to the 73 | current application. 74 | 75 | @return The task, that encapsulates the work being done. 76 | */ 77 | - (BFTask *)deauthorizeInBackground; 78 | 79 | /** 80 | Displays an auth dialog and populates the authToken, authTokenSecret, userId, and screenName properties 81 | if the Twitter user grants permission to the application. 82 | 83 | @param success Invoked upon successful authorization. 84 | @param failure Invoked upon an error occurring in the authorization process. 85 | @param cancel Invoked when the user cancels authorization. 86 | */ 87 | - (void)authorizeWithSuccess:(nullable void (^)(void))success 88 | failure:(nullable void (^)(NSError *__nullable error))failure 89 | cancel:(nullable void (^)(void))cancel; 90 | 91 | /** 92 | Adds a 3-legged OAuth signature to an `NSMutableURLRequest` based 93 | upon the properties set for the Twitter object. 94 | 95 | Use this function to sign requests being made to the Twitter API. 96 | 97 | @param request Request to sign. 98 | */ 99 | - (void)signRequest:(nullable NSMutableURLRequest *)request; 100 | 101 | @end 102 | 103 | NS_ASSUME_NONNULL_END 104 | -------------------------------------------------------------------------------- /ParseTwitterUtils/PF_Twitter.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PF_Twitter.h" 11 | #import "PF_Twitter_Private.h" 12 | 13 | #import 14 | #import 15 | #import 16 | 17 | #import 18 | #import 19 | 20 | #import 21 | 22 | #import "PFOAuth1FlowDialog.h" 23 | #import "PFTwitterAlertView.h" 24 | #import "PFTwitterPrivateUtilities.h" 25 | #import "PF_OAuthCore.h" 26 | #import "PFTwitterLocalization.h" 27 | 28 | @implementation PF_Twitter 29 | 30 | ///-------------------------------------- 31 | #pragma mark - Init 32 | ///-------------------------------------- 33 | 34 | - (instancetype)init { 35 | return [self initWithAccountStore:[[ACAccountStore alloc] init] 36 | urlSession:[NSURLSession sharedSession] 37 | dialogClass:[PFOAuth1FlowDialog class]]; 38 | } 39 | 40 | - (instancetype)initWithAccountStore:(ACAccountStore *)accountStore 41 | urlSession:(NSURLSession *)urlSession 42 | dialogClass:(Class)dialogClass { 43 | self = [super init]; 44 | if (!self) return nil; 45 | 46 | _accountStore = accountStore; 47 | _urlSession = urlSession; 48 | _oauthDialogClass = dialogClass; 49 | 50 | PFTWConsistencyAssert(_oauthDialogClass == nil || 51 | [(id)_oauthDialogClass conformsToProtocol:@protocol(PFOAuth1FlowDialogInterface)], 52 | @"OAuth Dialog class must conform to the Dialog Interface protocol!"); 53 | 54 | return self; 55 | } 56 | 57 | ///-------------------------------------- 58 | #pragma mark - Authorize 59 | ///-------------------------------------- 60 | 61 | - (BFTask *)authorizeInBackground { 62 | if (self.consumerKey.length == 0 || self.consumerSecret.length == 0) { 63 | //TODO: (nlutsenko) This doesn't look right, maybe we should add additional error code? 64 | return [BFTask taskWithError:[NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]]; 65 | } 66 | 67 | return [[self _performReverseAuthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { 68 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 69 | dispatch_async(dispatch_get_main_queue(), ^{ 70 | // if reverse auth was successful then return 71 | if (task.cancelled) { 72 | [source cancel]; 73 | return; 74 | } else if (!task.error && !task.result) { 75 | source.result = nil; 76 | return; 77 | } 78 | 79 | // fallback to the webview auth 80 | [[self _performWebViewAuthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { 81 | NSError *error = task.error; 82 | if (task.cancelled) { 83 | [source cancel]; 84 | } else if (!error) { 85 | [source setResult:task.result]; 86 | } else { 87 | [source setError:error]; 88 | } 89 | return nil; 90 | }]; 91 | }); 92 | return source.task; 93 | }]; 94 | } 95 | - (BFTask *)deauthorizeInBackground { 96 | if (self.consumerKey.length == 0 || self.consumerSecret.length == 0) { 97 | //TODO: (nlutsenko) This doesn't look right, maybe we should add additional error code? 98 | return [BFTask taskWithError:[NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]]; 99 | } 100 | 101 | return [[self _performDeauthAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { 102 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 103 | if (task.cancelled) { 104 | [source cancel]; 105 | } else if (!task.error && !task.result) { 106 | source.result = nil; 107 | } else if (task.error) { 108 | [source trySetError:task.error]; 109 | } else if (task.result) { 110 | [self setLoginResultValues:nil]; 111 | 112 | [source trySetResult:task.result]; 113 | } 114 | return source.task; 115 | }]; 116 | } 117 | 118 | - (void)authorizeWithSuccess:(void (^)(void))success 119 | failure:(void (^)(NSError *error))failure 120 | cancel:(void (^)(void))cancel { 121 | [[self authorizeInBackground] continueWithExecutor:[BFExecutor mainThreadExecutor] 122 | withBlock:^id(BFTask *task) { 123 | if (task.error) { 124 | failure(task.error); 125 | } else if (task.cancelled) { 126 | cancel(); 127 | } else { 128 | success(); 129 | } 130 | return nil; 131 | }]; 132 | } 133 | 134 | // Displays the web view dialog 135 | - (BFTask *)_showWebViewDialogAsync:(NSString *)requestToken requestSecret:(NSString *)requestSecret { 136 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 137 | 138 | static NSString *twitterAuthURLString = @"https://api.twitter.com/oauth/authenticate"; 139 | 140 | PFOAuth1FlowDialog *dialog = [_oauthDialogClass dialogWithURL:[NSURL URLWithString:twitterAuthURLString] 141 | queryParameters:@{ @"oauth_token" : requestToken }]; 142 | dialog.redirectURLPrefix = @"http://twitter-oauth.callback"; 143 | dialog.completion = ^(BOOL succeeded, NSURL *url, NSError *error) { 144 | // In case of error 145 | if (error) { 146 | source.error = error; 147 | return; 148 | } 149 | // In case the dialog was cancelled 150 | if (!succeeded) { 151 | [source cancel]; 152 | return; 153 | } 154 | 155 | // Handle URL received. 156 | NSDictionary *authQueryParams = [NSURL PF_ab_parseURLQueryString:[url query]]; 157 | NSString *verifier = [authQueryParams objectForKey:@"oauth_verifier"]; 158 | NSString *token = [authQueryParams objectForKey:@"oauth_token"]; 159 | 160 | [[self _getAccessTokenForWebAuthAsync:verifier requestSecret:requestSecret token:token] 161 | pftw_continueAsyncWithBlock:^id (BFTask *task) { 162 | NSError *error = task.error; 163 | if (!error) { 164 | NSDictionary *accessResult = (NSDictionary*) task.result; 165 | [self setLoginResultValues:accessResult]; 166 | source.result = nil; 167 | } else { 168 | source.error = error; 169 | } 170 | return nil; 171 | }]; 172 | }; 173 | [dialog showAnimated:YES]; 174 | 175 | return source.task; 176 | } 177 | 178 | /** 179 | Get the request token for the authentication. This is the first step in auth. 180 | if isReverseAuth is YES then get the request token for reverse auth mode. Otherwise, get the request token for web auth mode. 181 | */ 182 | - (BFTask *)_getRequestTokenAsync:(BOOL)isReverseAuth { 183 | NSURL *url = [NSURL URLWithString:@"https://api.twitter.com/oauth/request_token"]; 184 | NSMutableDictionary *params = nil; 185 | NSData *body = nil; 186 | 187 | if (isReverseAuth) { 188 | body = [[NSString stringWithFormat:@"x_auth_mode=%@", @"reverse_auth"] dataUsingEncoding:NSUTF8StringEncoding]; 189 | } else { 190 | params = [NSMutableDictionary dictionary]; 191 | [params setObject:@"http://twitter-oauth.callback" forKey:@"oauth_callback"]; 192 | } 193 | 194 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:url 195 | method:@"POST" 196 | body:body 197 | additionalParameters:params 198 | consumerKey:_consumerKey 199 | consumerSecret:_consumerSecret 200 | token:nil 201 | tokenSecret:nil]; 202 | NSString *header = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 203 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 204 | [request setHTTPMethod:@"POST"]; 205 | [request addValue:header forHTTPHeaderField:@"Authorization"]; 206 | [request setHTTPBody:body]; 207 | 208 | BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; 209 | [[self.urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 210 | if (error) { 211 | [taskCompletionSource trySetError:error]; 212 | } else { 213 | [taskCompletionSource trySetResult:data]; 214 | } 215 | }] resume]; 216 | return taskCompletionSource.task; 217 | } 218 | 219 | // Get the access token for web authentication 220 | - (BFTask *)_getAccessTokenForWebAuthAsync:(NSString *)verifier 221 | requestSecret:(NSString *)requestSecret 222 | token:(NSString *)token { 223 | NSURL *accessTokenURL = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"]; 224 | NSData *body = [[NSString stringWithFormat:@"oauth_verifier=%@", verifier] dataUsingEncoding:NSUTF8StringEncoding]; 225 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:accessTokenURL 226 | method:@"POST" 227 | body:body 228 | additionalParameters:nil 229 | consumerKey:_consumerKey 230 | consumerSecret:_consumerSecret 231 | token:token 232 | tokenSecret:requestSecret]; 233 | NSString *accessTokenHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 234 | NSMutableURLRequest *accessRequest = [NSMutableURLRequest requestWithURL:accessTokenURL]; 235 | [accessRequest setHTTPMethod:@"POST"]; 236 | [accessRequest addValue:accessTokenHeader forHTTPHeaderField:@"Authorization"]; 237 | [accessRequest setHTTPBody:body]; 238 | 239 | BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; 240 | [[self.urlSession dataTaskWithRequest:accessRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 241 | if (error) { 242 | [taskCompletionSource trySetError:error]; 243 | } else { 244 | NSString *accessResponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 245 | NSDictionary *accessResponseValues = [NSURL PF_ab_parseURLQueryString:accessResponseString]; 246 | [taskCompletionSource trySetResult:accessResponseValues]; 247 | } 248 | }] resume]; 249 | return taskCompletionSource.task; 250 | } 251 | 252 | /** 253 | Get the access token for reverse authentication. 254 | If the Task is successful then, Task result is dictionary containing logged in user's Auth token, Screenname and other attributes. 255 | */ 256 | - (BFTask *)_getAccessTokenForReverseAuthAsync:(NSString *)signedReverseAuthSignature 257 | localTwitterAccount:(ACAccount *)localTwitterAccount { 258 | 259 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 260 | if (!signedReverseAuthSignature || 261 | [signedReverseAuthSignature length] == 0 || 262 | !localTwitterAccount) { 263 | 264 | source.error = [NSError errorWithDomain:PFParseErrorDomain code:1 userInfo:nil]; 265 | return source.task; 266 | } 267 | 268 | NSDictionary *params = @{ @"x_reverse_auth_parameters" : signedReverseAuthSignature, 269 | @"x_reverse_auth_target" : _consumerKey }; 270 | 271 | NSURL *accessTokenUrl = [NSURL URLWithString:@"https://api.twitter.com/oauth/access_token"]; 272 | SLRequest *accessRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter 273 | requestMethod:SLRequestMethodPOST 274 | URL:accessTokenUrl 275 | parameters:params]; 276 | 277 | [accessRequest setAccount:localTwitterAccount]; 278 | [accessRequest performRequestWithHandler:^(NSData *data, NSHTTPURLResponse *urlResponse, NSError *error) { 279 | if (error) { 280 | [source setError:error]; 281 | } else { 282 | NSString *accessResponseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 283 | NSDictionary *accessResponseValues = [NSURL PF_ab_parseURLQueryString:accessResponseString]; 284 | [source setResult:accessResponseValues]; 285 | } 286 | }]; 287 | 288 | return source.task; 289 | } 290 | 291 | // Set the result parameters from the data returned om succesful login 292 | - (void)setLoginResultValues:(NSDictionary *)resultData { 293 | self.authToken = [resultData objectForKey:@"oauth_token"]; 294 | self.authTokenSecret = [resultData objectForKey:@"oauth_token_secret"]; 295 | self.userId = [resultData objectForKey:@"user_id"]; 296 | self.screenName = [resultData objectForKey:@"screen_name"]; 297 | } 298 | 299 | // Performs the Reverse auth for the the twitter account setup on the device. 300 | - (BFTask *)_performReverseAuthAsync { 301 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 302 | 303 | // get permission to access the account if its setup and available. 304 | [[self _getLocalTwitterAccountAsync] pftw_continueAsyncWithBlock:^id(BFTask *task) { 305 | 306 | if (task.error) { 307 | source.error = task.error; 308 | return source.task; 309 | } 310 | 311 | if (task.cancelled) { 312 | [source cancel]; 313 | return source.task; 314 | } 315 | 316 | dispatch_async(dispatch_get_main_queue(), ^{ 317 | ACAccount *localTwitterAccount = (ACAccount*) task.result; 318 | 319 | if(!localTwitterAccount) { 320 | source.error = [NSError errorWithDomain:PFParseErrorDomain code:2 userInfo:nil]; 321 | return; 322 | } 323 | 324 | // continue with reverse auth since its permitted 325 | [[self _getRequestTokenAsync:YES] pftw_continueAsyncWithBlock:^id(BFTask *task) { 326 | if (task.error) { 327 | source.error = task.error; 328 | return source.task; 329 | } 330 | dispatch_async(dispatch_get_main_queue(), ^{ 331 | NSString *requestTokenResponse = [[NSString alloc] initWithData:task.result encoding:NSUTF8StringEncoding]; 332 | 333 | [[self _getAccessTokenForReverseAuthAsync:requestTokenResponse 334 | localTwitterAccount:localTwitterAccount] pftw_continueAsyncWithBlock:^id(BFTask *task) { 335 | NSError *error = task.error; 336 | if (!error) { 337 | NSDictionary *accessResult = (NSDictionary*) task.result; 338 | [self setLoginResultValues:accessResult]; 339 | source.result = nil; 340 | } else { 341 | source.error = task.error; 342 | } 343 | return nil; 344 | }]; 345 | }); 346 | return nil; 347 | }]; 348 | 349 | }); 350 | return nil; 351 | }]; 352 | 353 | return source.task; 354 | } 355 | 356 | - (BFTask *)_performWebViewAuthAsync { 357 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 358 | 359 | [[self _getRequestTokenAsync:NO] pftw_continueAsyncWithBlock:^id(BFTask *task) { 360 | if (task.error || task.isCancelled) { 361 | if (task.error) { 362 | [source setError:task.error]; 363 | } else { 364 | [source cancel]; 365 | } 366 | return task; 367 | } 368 | 369 | dispatch_async(dispatch_get_main_queue(), ^{ 370 | NSString *requestTokenResponse = [[NSString alloc] initWithData:task.result encoding:NSUTF8StringEncoding]; 371 | 372 | NSDictionary *requestTokenParsed = [NSURL PF_ab_parseURLQueryString:requestTokenResponse]; 373 | NSString *requestToken = requestTokenParsed[@"oauth_token"]; 374 | NSString *requestSecret = requestTokenParsed[@"oauth_token_secret"]; 375 | 376 | // If one of these is missing, then we failed to get a token. 377 | if (!requestToken || !requestSecret) { 378 | NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: @"Failed to request authorization token from Twitter." }; 379 | NSError *error = [NSError errorWithDomain:PFParseErrorDomain code:2 userInfo:userInfo]; 380 | [source setError:error]; 381 | return; 382 | } 383 | 384 | // show the webview dialog for auth token 385 | [[self _showWebViewDialogAsync:requestToken 386 | requestSecret:requestSecret] pftw_continueAsyncWithBlock:^id(BFTask *task) { 387 | NSError *error = task.error; 388 | if (task.isCancelled) { 389 | [source cancel]; 390 | } else if (!error) { 391 | [source setResult:task.result]; 392 | } else { 393 | [source setError:error]; 394 | } 395 | return nil; 396 | }]; 397 | }); 398 | return nil; 399 | }]; 400 | 401 | return source.task; 402 | } 403 | 404 | - (BFTask *)_getLocalTwitterAccountAsync { 405 | BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; 406 | 407 | // If no twitter accounts present in the system, then no need to ask for permission to the user 408 | if (![SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter]) { 409 | [source setResult:nil]; 410 | return source.task; 411 | } 412 | 413 | ACAccountType *twitterType = [_accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]; 414 | [_accountStore requestAccessToAccountsWithType:twitterType options:nil completion:^(BOOL granted, NSError *error) { 415 | if (error) { 416 | [source setError:error]; 417 | return; 418 | } 419 | 420 | if (!granted) { 421 | [source setResult:nil]; 422 | return; 423 | } 424 | 425 | NSArray *accounts = [_accountStore accountsWithAccountType:twitterType]; 426 | 427 | // No accounts - provide an empty result 428 | if ([accounts count] == 0) { 429 | [source setResult:nil]; 430 | return; 431 | } 432 | 433 | // Finish if there is only 1 account 434 | if ([accounts count] == 1) { 435 | [source setResult:accounts[0]]; 436 | return; 437 | } 438 | 439 | NSArray *usernames = [accounts valueForKey:@"accountDescription"]; 440 | 441 | // Call async on the main thread, as the completion isn't executed on the main thread 442 | dispatch_async(dispatch_get_main_queue(), ^{ 443 | [PFTwitterAlertView showAlertWithTitle:PFTWLocalizedString(@"Select a Twitter Account", @"Select a Twitter Account") 444 | message:nil 445 | cancelButtonTitle:PFTWLocalizedString(@"Cancel", @"Cancel") 446 | otherButtonTitles:usernames 447 | completion:^(NSUInteger buttonIndex) { 448 | if (buttonIndex == NSNotFound) { 449 | [source cancel]; 450 | } else { 451 | ACAccount *account = accounts[buttonIndex]; 452 | [source setResult:account]; 453 | } 454 | }]; 455 | }); 456 | }]; 457 | 458 | return source.task; 459 | } 460 | 461 | - (BFTask *)_performDeauthAsync { 462 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 463 | request.URL = [NSURL URLWithString:@"https://api.twitter.com/oauth2/invalidate_token"]; 464 | request.HTTPMethod = @"POST"; 465 | 466 | [self signRequest:request]; 467 | 468 | BFTaskCompletionSource *taskCompletionSource = [BFTaskCompletionSource taskCompletionSource]; 469 | 470 | [[self.urlSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 471 | if (error) { 472 | [taskCompletionSource trySetError:error]; 473 | } else { 474 | [taskCompletionSource trySetResult:data]; 475 | } 476 | }] resume]; 477 | 478 | return taskCompletionSource.task; 479 | } 480 | 481 | ///-------------------------------------- 482 | #pragma mark - Sign Request 483 | ///-------------------------------------- 484 | 485 | - (void)signRequest:(NSMutableURLRequest *)request { 486 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:request.URL 487 | method:request.HTTPMethod ?: @"GET" 488 | body:request.HTTPBody 489 | additionalParameters:nil 490 | consumerKey:_consumerKey 491 | consumerSecret:_consumerSecret 492 | token:_authToken 493 | tokenSecret:_authTokenSecret]; 494 | NSString *header = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 495 | [request addValue:header forHTTPHeaderField:@"Authorization"]; 496 | } 497 | 498 | @end 499 | -------------------------------------------------------------------------------- /ParseTwitterUtils/ParseTwitterUtils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | platform :ios, '7.0' 3 | use_frameworks! 4 | 5 | target 'ParseTwitterUtils-Tests' do 6 | pod 'OCMock' 7 | end 8 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - OCMock (3.3.1) 3 | 4 | DEPENDENCIES: 5 | - OCMock 6 | 7 | SPEC CHECKSUMS: 8 | OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22 9 | 10 | PODFILE CHECKSUM: 156592e0521c4d42b735a52d7e92198e26878c21 11 | 12 | COCOAPODS: 1.0.1 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ARCHIVE] ParseTwitterUtils 2 | 3 | # ParseTwitterUtils has moved to [Parse-SDK-iOS-OSX](https://github.com/parse-community/Parse-SDK-iOS-OSX) 4 | 5 | ## Migration 6 | 7 | ### CocoaPods 8 | 9 | Update your `Podfile`: 10 | 11 | ```ruby 12 | pod 'Parse/TwitterUtils' 13 | ``` 14 | ### Carthage 15 | 16 | Update your `Cartfile`: 17 | 18 | ``` 19 | github "ParsePlatform/Parse-SDK-iOS-OSX" 20 | ``` 21 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2015-present, Parse, LLC. 3 | # All rights reserved. 4 | # 5 | # This source code is licensed under the BSD-style license found in the 6 | # LICENSE file in the root directory of this source tree. An additional grant 7 | # of patent rights can be found in the PATENTS file in the same directory. 8 | # 9 | 10 | require_relative 'Vendor/xctoolchain/Scripts/xctask/build_task' 11 | require_relative 'Vendor/xctoolchain/Scripts/xctask/build_framework_task' 12 | 13 | script_folder = File.expand_path(File.dirname(__FILE__)) 14 | build_folder = File.join(script_folder, 'build') 15 | release_folder = File.join(build_folder, 'release') 16 | 17 | xcworkspace_name = 'ParseTwitterUtils.xcworkspace' 18 | framework_name = 'ParseTwitterUtils.framework' 19 | 20 | namespace :build do 21 | desc 'Build iOS framework.' 22 | task :ios do 23 | task = XCTask::BuildFrameworkTask.new do |t| 24 | t.directory = script_folder 25 | t.build_directory = build_folder 26 | t.framework_type = XCTask::FrameworkType::IOS 27 | t.framework_name = framework_name 28 | 29 | t.workspace = xcworkspace_name 30 | t.scheme = 'ParseTwitterUtils-iOS' 31 | t.configuration = 'Release' 32 | end 33 | result = task.execute 34 | unless result 35 | puts 'Failed to build iOS Framework.' 36 | exit(1) 37 | end 38 | end 39 | end 40 | 41 | namespace :package do 42 | ios_package_name = 'ParseTwitterUtils-iOS.zip' 43 | 44 | desc 'Build and package all frameworks' 45 | task :frameworks do 46 | rm_rf build_folder, :verbose => false 47 | mkdir_p build_folder, :verbose => false 48 | 49 | Rake::Task['build:ios'].invoke 50 | ios_framework_path = File.join(build_folder, framework_name) 51 | make_package(release_folder, [ios_framework_path], ios_package_name) 52 | end 53 | 54 | def make_package(target_path, items, archive_name) 55 | temp_folder = File.join(target_path, 'tmp') 56 | `mkdir -p #{temp_folder}` 57 | 58 | item_list = '' 59 | items.each do |item| 60 | `cp -R #{item} #{temp_folder}` 61 | 62 | file_name = File.basename(item) 63 | item_list << " #{file_name}" 64 | end 65 | 66 | archive_path = File.join(target_path, archive_name) 67 | `cd #{temp_folder}; zip -r --symlinks #{archive_path} #{item_list}` 68 | rm_rf temp_folder 69 | puts "Release archive created: #{File.join(target_path, archive_name)}" 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ParseTwitterUtils 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | FMWK 15 | CFBundleShortVersionString 16 | 1.11.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleSupportedPlatforms 20 | 21 | iPhoneSimulator 22 | iPhoneOS 23 | 24 | CFBundleVersion 25 | 1.11.0 26 | MinimumOSVersion 27 | 6.0 28 | 29 | 30 | -------------------------------------------------------------------------------- /Resources/en.lproj/ParseTwitterUtils.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parse-community/ParseTwitterUtils-iOS/7464c2f8294fd21d0a6e80bc8fd1343591aa4eb7/Resources/en.lproj/ParseTwitterUtils.strings -------------------------------------------------------------------------------- /Tests/Other/PFTwitterTestMacros.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #ifndef PFTwitterTestMacros_h 11 | #define PFTwitterTestMacros_h 12 | 13 | /** 14 | To prevent retain cycles by OCMock, this macro allows us to capture a weak reference to return from a stubbed method. 15 | */ 16 | #define andReturnWeak(variable) _andDo( \ 17 | ({ \ 18 | __weak typeof(variable) variable ## _weak = (variable); \ 19 | ^(NSInvocation *invocation) { \ 20 | __autoreleasing typeof(variable) variable ## _block = variable ## _weak; \ 21 | [invocation setReturnValue:&(variable ## _block)]; \ 22 | }; \ 23 | }) \ 24 | ) 25 | 26 | #endif /* PFTwitterTestMacros_h */ 27 | -------------------------------------------------------------------------------- /Tests/Other/TestCase/PFTwitterTestCase.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @import XCTest; 13 | 14 | @interface PFTwitterTestCase : XCTestCase 15 | 16 | ///-------------------------------------- 17 | /// @name XCTestCase 18 | ///-------------------------------------- 19 | 20 | - (void)setUp NS_REQUIRES_SUPER; 21 | - (void)tearDown NS_REQUIRES_SUPER; 22 | 23 | ///-------------------------------------- 24 | /// @name Expectations 25 | ///-------------------------------------- 26 | 27 | - (XCTestExpectation *)currentSelectorTestExpectation; 28 | - (void)waitForTestExpectations; 29 | 30 | ///-------------------------------------- 31 | /// @name Mocks 32 | ///-------------------------------------- 33 | 34 | - (void)registerMockObject:(id)mockObject; 35 | 36 | @end 37 | 38 | #define _PFRegisterMock(mockObject) [self registerMockObject:mockObject] 39 | #define _PFMockShim(method, args...) ({ id mock = method(args); _PFRegisterMock(mock); mock; }) 40 | #define _PFOCMockWarning _Pragma("GCC warning \"Please use PF mocking methods instead of OCMock ones.\"") 41 | 42 | #define _PFStrictClassMock(kls) [OCMockObject mockForClass:kls] 43 | #define _PFClassMock(kls) [OCMockObject niceMockForClass:kls] 44 | #define _PFStrictProtocolMock(proto) [OCMockObject mockForProtocol:proto] 45 | #define _PFProtocolMock(proto) [OCMockObject niceMockForProtocol:proto] 46 | #define _PFPartialMock(obj) [OCMockObject partialMockForObject:obj] 47 | 48 | #define PFStrictClassMock(...) _PFMockShim(_PFStrictClassMock, __VA_ARGS__) 49 | #define PFClassMock(...) _PFMockShim(_PFClassMock, __VA_ARGS__) 50 | #define PFStrictProtocolMock(...) _PFMockShim(_PFStrictProtocolMock, __VA_ARGS__) 51 | #define PFProtocolMock(...) _PFMockShim(_PFProtocolMock, __VA_ARGS__) 52 | #define PFPartialMock(...) _PFMockShim(_PFPartialMock, __VA_ARGS__) 53 | 54 | #undef OCMStrictClassMock 55 | #undef OCMClassMock 56 | #undef OCMStrictProtocolMock 57 | #undef OCMProtocolMock 58 | #undef OCMPartialMock 59 | 60 | #define OCMStrictClassMock _PFOCMockWarning _PFStrictClassMock 61 | #define OCMClassMock _PFOCMockWarning _PFClassMock 62 | #define OCMStrictProtocolMock _PFOCMockWarning _PFStrictProtocolMock 63 | #define OCMProtocolMock _PFOCMockWarning _PFProtocolMock 64 | #define OCMPartialMock _PFOCMockWarning _PFPartialMock 65 | 66 | #define PFAssertIsKindOfClass(a1, a2, description...) \ 67 | XCTAssertTrue([a1 isKindOfClass:[a2 class]], ## description) 68 | 69 | #define PFAssertNotKindOfClass(a1, a2, description...) \ 70 | XCTAssertFalse([a1 isKindOfClass:[a2 class]], ## description) 71 | 72 | #define PFAssertThrowsInconsistencyException(expression, ...) \ 73 | XCTAssertThrowsSpecificNamed(expression, NSException, NSInternalInconsistencyException, __VA_ARGS__) 74 | 75 | #define PFAssertThrowsInvalidArgumentException(expression, ...) \ 76 | XCTAssertThrowsSpecificNamed(expression, NSException, NSInvalidArgumentException, __VA_ARGS__) 77 | 78 | #define PFAssertStringContains(a, b) XCTAssertTrue([(a) rangeOfString:(b)].location != NSNotFound) 79 | -------------------------------------------------------------------------------- /Tests/Other/TestCase/PFTwitterTestCase.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterTestCase.h" 11 | 12 | @implementation PFTwitterTestCase { 13 | NSMutableArray *_mocks; 14 | dispatch_queue_t _mockQueue; 15 | } 16 | 17 | ///-------------------------------------- 18 | #pragma mark - XCTestCase 19 | ///-------------------------------------- 20 | 21 | - (void)setUp { 22 | [super setUp]; 23 | 24 | _mocks = [[NSMutableArray alloc] init]; 25 | _mockQueue = dispatch_queue_create("com.parse.tests.mock.queue", DISPATCH_QUEUE_SERIAL); 26 | } 27 | 28 | - (void)tearDown { 29 | dispatch_sync(_mockQueue, ^{ 30 | [_mocks makeObjectsPerformSelector:@selector(stopMocking)]; 31 | }); 32 | 33 | _mocks = nil; 34 | _mockQueue = nil; 35 | 36 | [super tearDown]; 37 | } 38 | 39 | ///-------------------------------------- 40 | #pragma mark - Helpers 41 | ///-------------------------------------- 42 | 43 | - (XCTestExpectation *)currentSelectorTestExpectation { 44 | NSInvocation *invocation = self.invocation; 45 | NSString *selectorName = invocation ? NSStringFromSelector(invocation.selector) : @"testExpectation"; 46 | return [self expectationWithDescription:selectorName]; 47 | } 48 | 49 | - (void)waitForTestExpectations { 50 | [self waitForExpectationsWithTimeout:10.0 handler:nil]; 51 | } 52 | 53 | ///-------------------------------------- 54 | #pragma mark - Mock Registration 55 | ///-------------------------------------- 56 | 57 | - (void)registerMockObject:(id)mockObject { 58 | dispatch_sync(_mockQueue, ^{ 59 | [_mocks addObject:mockObject]; 60 | }); 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Tests/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Tests/TestApplication/Classes/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @interface AppDelegate : NSObject 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].applicationFrame]; 20 | window.rootViewController = [[UIViewController alloc] init]; 21 | [window makeKeyAndVisible]; 22 | return YES; 23 | } 24 | 25 | @end 26 | 27 | int main(int argc, char * argv[]) { 28 | @autoreleasepool { 29 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/TestApplication/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIcons 10 | 11 | CFBundleIcons~ipad 12 | 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | $(PRODUCT_NAME) 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Tests/Unit/OAuth1FlowDialogTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFOAuth1FlowDialog.h" 11 | #import "PFTwitterTestCase.h" 12 | 13 | @interface UIActivityIndicatorView (Private) 14 | 15 | - (void)_generateImages; 16 | 17 | @end 18 | 19 | @interface OAuth1FlowDialogTests : PFTwitterTestCase 20 | @end 21 | 22 | @interface UIDevice (Yolo) 23 | 24 | - (void)setOrientation:(UIDeviceOrientation)orientation animated:(BOOL)animated; 25 | 26 | @end 27 | 28 | @implementation OAuth1FlowDialogTests 29 | 30 | ///-------------------------------------- 31 | #pragma mark - Tests 32 | ///-------------------------------------- 33 | 34 | - (void)testConstructors { 35 | NSURL *exampleURL = [NSURL URLWithString:@"http://foo.bar"]; 36 | NSDictionary *parameters = @{ @"a" : @"b" }; 37 | 38 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:exampleURL 39 | queryParameters:parameters]; 40 | XCTAssertNotNil(flowDialog); 41 | XCTAssertEqualObjects(flowDialog.queryParameters, parameters); 42 | XCTAssertEqualObjects(flowDialog->_baseURL, exampleURL); 43 | 44 | flowDialog = [PFOAuth1FlowDialog dialogWithURL:exampleURL queryParameters:parameters]; 45 | XCTAssertNotNil(flowDialog); 46 | XCTAssertEqualObjects(flowDialog.queryParameters, parameters); 47 | XCTAssertEqualObjects(flowDialog->_baseURL, exampleURL); 48 | } 49 | 50 | - (void)testTitle { 51 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; 52 | XCTAssertEqualObjects(flowDialog.title, @"Connect to Service"); 53 | flowDialog.title = @"Bleh"; 54 | XCTAssertEqualObjects(flowDialog.title, @"Bleh"); 55 | } 56 | 57 | - (void)testShow { 58 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; 59 | 60 | [flowDialog showAnimated:NO]; 61 | [flowDialog layoutSubviews]; 62 | [flowDialog dismissAnimated:NO]; 63 | } 64 | 65 | - (void)testKeyboard { 66 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; 67 | [flowDialog showAnimated:NO]; 68 | 69 | NSDictionary *notificationuserInfo = @{ UIKeyboardAnimationDurationUserInfoKey : @0, 70 | UIKeyboardAnimationCurveUserInfoKey : @(UIViewAnimationCurveLinear) }; 71 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification 72 | object:nil 73 | userInfo:notificationuserInfo]; 74 | 75 | [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification 76 | object:nil 77 | userInfo:notificationuserInfo]; 78 | 79 | [flowDialog dismissAnimated:NO]; 80 | } 81 | 82 | - (void)testRotation { 83 | [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]; 84 | [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO]; 85 | 86 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:nil queryParameters:nil]; 87 | 88 | [flowDialog showAnimated:NO]; 89 | CGRect oldBounds = flowDialog.bounds; 90 | 91 | [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeLeft]; 92 | [[UIDevice currentDevice] setOrientation:UIDeviceOrientationLandscapeLeft animated:NO]; 93 | [[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; 94 | 95 | CGRect newBounds = flowDialog.bounds; 96 | XCTAssertFalse(CGRectEqualToRect(oldBounds, newBounds)); 97 | 98 | [[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationPortrait]; 99 | [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO]; 100 | [[NSNotificationCenter defaultCenter] postNotificationName:UIDeviceOrientationDidChangeNotification object:nil]; 101 | 102 | newBounds = flowDialog.bounds; 103 | XCTAssertTrue(CGRectEqualToRect(oldBounds, newBounds)); 104 | 105 | [flowDialog dismissAnimated:NO]; 106 | } 107 | 108 | - (void)testWebViewDelegate { 109 | NSURL *sampleURL = [NSURL URLWithString:@"http://foo.bar"]; 110 | NSURL *successURL = [NSURL URLWithString:@"foo://success"]; 111 | 112 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 113 | PFOAuth1FlowDialog *flowDialog = [[PFOAuth1FlowDialog alloc] initWithURL:sampleURL queryParameters:nil]; 114 | flowDialog.redirectURLPrefix = @"foo://"; 115 | flowDialog.completion = ^(BOOL succeeded, NSURL *url, NSError *error) { 116 | XCTAssertTrue(succeeded); 117 | XCTAssertNil(error); 118 | XCTAssertEqualObjects(url, successURL); 119 | 120 | [expectation fulfill]; 121 | }; 122 | 123 | [flowDialog showAnimated:NO]; 124 | 125 | id webView = PFStrictClassMock([UIWebView class]); 126 | 127 | NSURLRequest *request = [NSURLRequest requestWithURL:sampleURL]; 128 | XCTAssertTrue([flowDialog webView:webView 129 | shouldStartLoadWithRequest:request 130 | navigationType:UIWebViewNavigationTypeOther]); 131 | 132 | [flowDialog webViewDidStartLoad:webView]; 133 | [flowDialog webViewDidFinishLoad:webView]; 134 | 135 | NSURLRequest *successRequest = [NSURLRequest requestWithURL:successURL]; 136 | [flowDialog webView:webView shouldStartLoadWithRequest:successRequest navigationType:UIWebViewNavigationTypeOther]; 137 | 138 | [self waitForTestExpectations]; 139 | } 140 | 141 | @end 142 | -------------------------------------------------------------------------------- /Tests/Unit/OAuthCoreTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import ObjectiveC.runtime; 11 | 12 | #import "PFTwitterTestCase.h" 13 | #import "PF_OAuthCore.h" 14 | 15 | @implementation NSURLComponents (OAuthCoreTests) 16 | 17 | + (Class)class { 18 | return [super class]; 19 | } 20 | 21 | + (Class)_nilClass { 22 | return nil; 23 | } 24 | 25 | @end 26 | 27 | @interface OAuthCoreTests : PFTwitterTestCase 28 | 29 | @property (nonatomic, strong) NSDate *authDate; 30 | @property (nonatomic, copy) NSString *authNonce; 31 | 32 | @end 33 | 34 | @implementation OAuthCoreTests 35 | 36 | ///-------------------------------------- 37 | #pragma mark - Helpers 38 | ///-------------------------------------- 39 | 40 | - (NSURL *)sampleURL { 41 | return [NSURL URLWithString:@"https://localhost/foo/bar"]; 42 | } 43 | 44 | - (NSData *)sampleData { 45 | return [@"sampe=@!value" dataUsingEncoding:NSUTF8StringEncoding]; 46 | } 47 | 48 | - (void)assertAuthHeader:(NSString *)authHeader matchesExpectedSignature:(NSString *)signature { 49 | XCTAssertTrue([authHeader hasPrefix:@"OAuth "]); 50 | 51 | PFAssertStringContains(authHeader, 52 | ([NSString stringWithFormat:@"oauth_timestamp=\"%llu\"", 53 | (unsigned long long)[_authDate timeIntervalSince1970]])); 54 | 55 | PFAssertStringContains(authHeader, ([NSString stringWithFormat:@"oauth_nonce=\"%@\"", _authNonce])); 56 | PFAssertStringContains(authHeader, @"oauth_version=\"1.0\""); 57 | PFAssertStringContains(authHeader, @"oauth_consumer_key=\"consumer_key\""); 58 | PFAssertStringContains(authHeader, @"oauth_signature_method=\"HMAC-SHA1\""); 59 | PFAssertStringContains(authHeader, ([NSString stringWithFormat:@"oauth_signature=\"%@\"", signature])); 60 | } 61 | 62 | ///-------------------------------------- 63 | #pragma mark - XCTestCase 64 | ///-------------------------------------- 65 | 66 | - (void)setUp { 67 | [super setUp]; 68 | 69 | _authDate = [NSDate dateWithTimeIntervalSinceReferenceDate:0.0]; 70 | _authNonce = @"UUID-STRING"; 71 | } 72 | 73 | ///-------------------------------------- 74 | #pragma mark - Tests 75 | ///-------------------------------------- 76 | 77 | - (void)testBasic { 78 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] 79 | method:@"POST" 80 | body:[self sampleData] 81 | additionalParameters:nil 82 | consumerKey:@"consumer_key" 83 | consumerSecret:@"consumer_secret" 84 | token:nil 85 | tokenSecret:nil]; 86 | configuration.nonce = self.authNonce; 87 | configuration.timestampDate = self.authDate; 88 | NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 89 | 90 | [self assertAuthHeader:authHeader matchesExpectedSignature:@"3Nvy4O1Ok3qkeKcjvtv4wtyjc%2FY%3D"]; 91 | } 92 | 93 | - (void)testNoBody { 94 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] 95 | method:@"POST" 96 | body:nil 97 | additionalParameters:nil 98 | consumerKey:@"consumer_key" 99 | consumerSecret:@"consumer_secret" 100 | token:nil 101 | tokenSecret:nil]; 102 | configuration.nonce = self.authNonce; 103 | configuration.timestampDate = self.authDate; 104 | NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 105 | 106 | [self assertAuthHeader:authHeader matchesExpectedSignature:@"rXtmqPIUmMbl4e1%2Bz4JgJUuVIz0%3D"]; 107 | } 108 | 109 | - (void)testWithToken { 110 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] 111 | method:@"POST" 112 | body:nil 113 | additionalParameters:nil 114 | consumerKey:@"consumer_key" 115 | consumerSecret:@"consumer_secret" 116 | token:@"token" 117 | tokenSecret:nil]; 118 | configuration.nonce = self.authNonce; 119 | configuration.timestampDate = self.authDate; 120 | NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 121 | 122 | XCTAssertTrue([authHeader rangeOfString:@"oauth_token=\"token\""].location != NSNotFound); 123 | [self assertAuthHeader:authHeader matchesExpectedSignature:@"iRsvN%2FUCXyzhf3o9tIL0DAX%2F4HY%3D"]; 124 | } 125 | 126 | - (void)testNoNSURLComponents { 127 | // Disable NSURLComponents for a single test. 128 | Method originalMethod = class_getClassMethod([NSURLComponents class], @selector(class)); 129 | Method replacementMethod = class_getClassMethod([NSURLComponents class], @selector(_nilClass)); 130 | method_exchangeImplementations(originalMethod, replacementMethod); 131 | 132 | @try { 133 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:[self sampleURL] 134 | method:@"POST" 135 | body:[self sampleData] 136 | additionalParameters:nil 137 | consumerKey:@"consumer_key" 138 | consumerSecret:@"consumer_secret" 139 | token:nil 140 | tokenSecret:nil]; 141 | configuration.nonce = self.authNonce; 142 | configuration.timestampDate = self.authDate; 143 | NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 144 | 145 | [self assertAuthHeader:authHeader matchesExpectedSignature:@"3Nvy4O1Ok3qkeKcjvtv4wtyjc%2FY%3D"]; 146 | } @finally { 147 | method_exchangeImplementations(originalMethod, replacementMethod); 148 | } 149 | } 150 | 151 | - (void)testWithQuery { 152 | NSURL *url = [NSURL URLWithString:[[[self sampleURL] absoluteString] stringByAppendingString:@"?key=value"]]; 153 | 154 | PFOAuthConfiguration *configuration = [PFOAuthConfiguration configurationForURL:url 155 | method:@"GET" 156 | body:nil 157 | additionalParameters:nil 158 | consumerKey:@"consumer_key" 159 | consumerSecret:@"consumer_secret" 160 | token:nil 161 | tokenSecret:nil]; 162 | configuration.nonce = self.authNonce; 163 | configuration.timestampDate = self.authDate; 164 | NSString *authHeader = [PFOAuth authorizationHeaderFromConfiguration:configuration]; 165 | [self assertAuthHeader:authHeader matchesExpectedSignature:@"LecftA2NX%2FvSD4KakdTFjPZlmc0%3D"]; 166 | } 167 | 168 | @end 169 | -------------------------------------------------------------------------------- /Tests/Unit/TwitterAuthenticationProviderTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import Bolts.BFTask; 11 | 12 | #import "PFTwitterAuthenticationProvider.h" 13 | #import "PFTwitterTestCase.h" 14 | #import "PF_Twitter.h" 15 | 16 | @interface TwitterAuthenticationProviderTests : PFTwitterTestCase 17 | 18 | @end 19 | 20 | @implementation TwitterAuthenticationProviderTests 21 | 22 | ///-------------------------------------- 23 | #pragma mark - Helpers 24 | ///-------------------------------------- 25 | 26 | - (PF_Twitter *)mockedTwitter { 27 | PF_Twitter *twitter = PFStrictClassMock([PF_Twitter class]); 28 | 29 | OCMStub(twitter.consumerKey).andReturn(@"yarr"); 30 | OCMStub(twitter.consumerSecret).andReturn(@"yolo"); 31 | 32 | return twitter; 33 | } 34 | 35 | - (void)assertValidAuthenticationData:(NSDictionary *)authData forTwitter:(PF_Twitter *)twitter { 36 | XCTAssertEqualObjects(authData[@"id"], @"a"); 37 | XCTAssertEqualObjects(authData[@"screen_name"], @"b"); 38 | XCTAssertEqualObjects(authData[@"auth_token"], @"c"); 39 | XCTAssertEqualObjects(authData[@"auth_token_secret"], @"d"); 40 | XCTAssertEqualObjects(authData[@"consumer_key"], twitter.consumerKey); 41 | XCTAssertEqualObjects(authData[@"consumer_secret"], twitter.consumerSecret); 42 | } 43 | 44 | ///-------------------------------------- 45 | #pragma mark - Tests 46 | ///-------------------------------------- 47 | 48 | - (void)testConstructors { 49 | PF_Twitter *twitter = [self mockedTwitter]; 50 | PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; 51 | XCTAssertNotNil(provider); 52 | XCTAssertEqual(provider.twitter, twitter); 53 | 54 | provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; 55 | XCTAssertNotNil(provider); 56 | XCTAssertEqual(provider.twitter, twitter); 57 | 58 | PFAssertThrowsInconsistencyException([PFTwitterAuthenticationProvider new]); 59 | } 60 | 61 | - (void)testAuthData { 62 | PF_Twitter *twitter = [self mockedTwitter]; 63 | PFTwitterAuthenticationProvider *provider = [[PFTwitterAuthenticationProvider alloc] initWithTwitter:twitter]; 64 | 65 | NSDictionary *authData = [provider authDataWithTwitterId:@"a" 66 | screenName:@"b" 67 | authToken:@"c" 68 | secret:@"d"]; 69 | [self assertValidAuthenticationData:authData forTwitter:twitter]; 70 | } 71 | 72 | - (void)testAuthType { 73 | XCTAssertEqualObjects(PFTwitterUserAuthenticationType, @"twitter"); 74 | } 75 | 76 | - (void)testAuthenticateAsync { 77 | PF_Twitter *twitter = [self mockedTwitter]; 78 | 79 | OCMStub(twitter.userId).andReturn(@"a"); 80 | OCMStub(twitter.screenName).andReturn(@"b"); 81 | OCMStub(twitter.authToken).andReturn(@"c"); 82 | OCMStub(twitter.authTokenSecret).andReturn(@"d"); 83 | 84 | BFTask *task = [BFTask taskWithResult:nil]; 85 | OCMStub([twitter authorizeInBackground]).andReturn(task); 86 | 87 | PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; 88 | 89 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 90 | [[provider authenticateAsync] continueWithBlock:^id(BFTask *t) { 91 | NSDictionary *authData = t.result; 92 | XCTAssertNotNil(authData); 93 | [self assertValidAuthenticationData:authData forTwitter:twitter]; 94 | 95 | [expectation fulfill]; 96 | return nil; 97 | }]; 98 | [self waitForTestExpectations]; 99 | } 100 | 101 | - (void)testRestoreAuthentication { 102 | PF_Twitter *twitter = [self mockedTwitter]; 103 | OCMExpect(twitter.userId = @"a"); 104 | OCMExpect(twitter.screenName = @"b"); 105 | OCMExpect(twitter.authToken = @"c"); 106 | OCMExpect(twitter.authTokenSecret = @"d"); 107 | 108 | PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; 109 | 110 | NSDictionary *authData = @{ @"id" : @"a", 111 | @"screen_name" : @"b", 112 | @"auth_token" : @"c", 113 | @"auth_token_secret" : @"d" }; 114 | XCTAssertTrue([provider restoreAuthenticationWithAuthData:authData]); 115 | 116 | OCMVerifyAll((id)twitter); 117 | } 118 | 119 | - (void)testRestoreAuthenticationBadData { 120 | PF_Twitter *twitter = [self mockedTwitter]; 121 | PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; 122 | 123 | NSDictionary *authData = @{ @"bad" : @"data" }; 124 | XCTAssertFalse([provider restoreAuthenticationWithAuthData:authData]); 125 | } 126 | 127 | - (void)testRestoreAuthenticationWithNoData { 128 | PF_Twitter *twitter = [self mockedTwitter]; 129 | OCMExpect(twitter.userId = nil); 130 | OCMExpect(twitter.authToken = nil); 131 | OCMExpect(twitter.authTokenSecret = nil); 132 | OCMExpect(twitter.screenName = nil); 133 | 134 | PFTwitterAuthenticationProvider *provider = [PFTwitterAuthenticationProvider providerWithTwitter:twitter]; 135 | XCTAssertTrue([provider restoreAuthenticationWithAuthData:nil]); 136 | 137 | OCMVerifyAll((id)twitter); 138 | } 139 | 140 | @end 141 | -------------------------------------------------------------------------------- /Tests/Unit/TwitterTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | @import Accounts; 11 | @import Bolts.BFTask; 12 | @import Parse.PFConstants; 13 | @import Social; 14 | 15 | #import "PFOAuth1FlowDialog.h" 16 | #import "PFTwitterAlertView.h" 17 | #import "PFTwitterTestCase.h" 18 | #import "PFTwitterTestMacros.h" 19 | #import "PF_Twitter_Private.h" 20 | 21 | typedef void (^NSURLSessionDataTaskCompletionHandler)(NSData *data, NSURLResponse *response, NSError *error); 22 | 23 | @interface TwitterTests : PFTwitterTestCase 24 | @end 25 | 26 | @implementation TwitterTests 27 | 28 | ///-------------------------------------- 29 | #pragma mark - Tests 30 | ///-------------------------------------- 31 | 32 | - (void)testConstructors { 33 | PF_Twitter *twitter = [[PF_Twitter alloc] init]; 34 | XCTAssertNotNil(twitter); 35 | XCTAssertNotNil(twitter.accountStore); 36 | 37 | ACAccountStore *store = PFStrictClassMock([ACAccountStore class]); 38 | NSURLSession *session = PFStrictClassMock([NSURLSession class]); 39 | id dialogClass = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 40 | twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:dialogClass]; 41 | XCTAssertNotNil(twitter); 42 | XCTAssertEqual(twitter.accountStore, store); 43 | XCTAssertEqual(twitter.urlSession, session); 44 | XCTAssertEqual(twitter.oauthDialogClass, dialogClass); 45 | } 46 | 47 | - (void)testProperties { 48 | PF_Twitter *twitter = [[PF_Twitter alloc] init]; 49 | 50 | XCTAssertNil(twitter.consumerKey); 51 | XCTAssertNil(twitter.consumerKey); 52 | XCTAssertNil(twitter.consumerSecret); 53 | XCTAssertNil(twitter.authToken); 54 | XCTAssertNil(twitter.authTokenSecret); 55 | XCTAssertNil(twitter.userId); 56 | XCTAssertNil(twitter.screenName); 57 | 58 | twitter.consumerKey = @"a"; 59 | XCTAssertEqualObjects(twitter.consumerKey, @"a"); 60 | twitter.consumerSecret = @"b"; 61 | XCTAssertEqualObjects(twitter.consumerSecret, @"b"); 62 | twitter.authToken = @"c"; 63 | XCTAssertEqualObjects(twitter.authToken, @"c"); 64 | twitter.authTokenSecret = @"d"; 65 | XCTAssertEqualObjects(twitter.authTokenSecret, @"d"); 66 | twitter.userId = @"e"; 67 | XCTAssertEqualObjects(twitter.userId, @"e"); 68 | twitter.screenName = @"f"; 69 | XCTAssertEqualObjects(twitter.screenName, @"f"); 70 | } 71 | 72 | - (void)testAuthorizeWithoutRequiredKeys { 73 | id store = PFStrictClassMock([ACAccountStore class]); 74 | NSURLSession *session = PFStrictClassMock([NSURLSession class]); 75 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 76 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; 77 | 78 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 79 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 80 | NSError *error = task.error; 81 | XCTAssertNotNil(error); 82 | XCTAssertEqualObjects(error.domain, PFParseErrorDomain); 83 | //TODO: (nlutsenko) Add code verification when we have proper code reported. 84 | [expectation fulfill]; 85 | return nil; 86 | }]; 87 | [self waitForTestExpectations]; 88 | } 89 | 90 | - (void)testSignRequest { 91 | id store = PFStrictClassMock([ACAccountStore class]); 92 | NSURLSession *session = PFStrictClassMock([NSURLSession class]); 93 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 94 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; 95 | 96 | twitter.consumerKey = @"consumer_key"; 97 | twitter.consumerSecret = @"consumer_secret"; 98 | 99 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; 100 | [twitter signRequest:request]; 101 | 102 | XCTAssertNotNil([request valueForHTTPHeaderField:@"Authorization"]); 103 | } 104 | 105 | - (void)testAuthorizeWithCallbackBlocks { 106 | id store = PFStrictClassMock([ACAccountStore class]); 107 | NSURLSession *session = PFStrictClassMock([NSURLSession class]); 108 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 109 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; 110 | 111 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 112 | [twitter authorizeWithSuccess:^{ 113 | XCTFail(@"Did not expect success!"); 114 | } failure:^(NSError *error) { 115 | [expectation fulfill]; 116 | } cancel:^{ 117 | XCTFail(@"Did not expect cancellation!"); 118 | }]; 119 | 120 | [self waitForTestExpectations]; 121 | } 122 | 123 | - (void)testAuthorizeWithLocalAccountErrorAndNetworkError { 124 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 125 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 126 | id mockedOperationQueue = PFStrictClassMock([NSOperationQueue class]); 127 | id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); 128 | 129 | NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; 130 | 131 | OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); 132 | OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); 133 | 134 | OCMStub([mockedStore requestAccessToAccountsWithType:nil 135 | options:nil 136 | completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 137 | __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; 138 | [invocation getArgument:&handler atIndex:4]; 139 | 140 | handler(NO, expectedError); 141 | }); 142 | 143 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 144 | NSURLRequest *request = obj; 145 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 146 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 147 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 148 | [invocation getArgument:&completionHandler atIndex:3]; 149 | 150 | completionHandler(nil, nil, expectedError); 151 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 152 | 153 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 154 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 155 | urlSession:mockedURLSession 156 | dialogClass:mockedDialog]; 157 | 158 | twitter.consumerKey = @"consumer_key"; 159 | twitter.consumerSecret = @"consumer_secret"; 160 | 161 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 162 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 163 | XCTAssertEqualObjects(task.error, expectedError); 164 | 165 | [expectation fulfill]; 166 | 167 | return nil; 168 | }]; 169 | 170 | [self waitForTestExpectations]; 171 | OCMVerifyAll(mockedOperationQueue); 172 | } 173 | 174 | - (void)testAuthorizeWithoutLocalAccountAndNetworkError { 175 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 176 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 177 | id mockedOperationQueue = PFStrictClassMock([NSOperationQueue class]); 178 | 179 | NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; 180 | 181 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 182 | NSURLRequest *request = obj; 183 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 184 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 185 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 186 | [invocation getArgument:&completionHandler atIndex:3]; 187 | 188 | completionHandler(nil, nil, expectedError); 189 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 190 | 191 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 192 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 193 | urlSession:mockedURLSession 194 | dialogClass:mockedDialog]; 195 | 196 | twitter.consumerKey = @"consumer_key"; 197 | twitter.consumerSecret = @"consumer_secret"; 198 | 199 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 200 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 201 | XCTAssertEqualObjects(task.error, expectedError); 202 | XCTAssertNil(task.result); 203 | 204 | [expectation fulfill]; 205 | 206 | return nil; 207 | }]; 208 | 209 | [self waitForTestExpectations]; 210 | OCMVerifyAll(mockedOperationQueue); 211 | } 212 | 213 | - (void)testAuthorizeWithLocalAccountAndNetworkError { 214 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 215 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 216 | id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); 217 | 218 | OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); 219 | OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); 220 | 221 | OCMStub([mockedStore requestAccessToAccountsWithType:nil 222 | options:nil 223 | completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 224 | __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; 225 | [invocation getArgument:&handler atIndex:4]; 226 | 227 | handler(YES, nil); 228 | }); 229 | 230 | id mockedAccount = PFStrictClassMock([ACAccount class]); 231 | 232 | NSArray *twitterAccounts = @[ mockedAccount ]; 233 | OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); 234 | 235 | NSError *expectedError = [NSError errorWithDomain:PFParseErrorDomain code:1337 userInfo:nil]; 236 | 237 | __block NSURLSessionDataTaskCompletionHandler completionHandler = nil; 238 | [OCMStub([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 239 | NSURLRequest *request = obj; 240 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 241 | }] completionHandler:[OCMArg checkWithBlock:^BOOL(id obj) { 242 | completionHandler = obj; 243 | return (obj != nil); 244 | }]]).andDo(^(NSInvocation *invocation) { 245 | completionHandler(nil, nil, expectedError); 246 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 247 | 248 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 249 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 250 | urlSession:mockedURLSession 251 | dialogClass:mockedDialog]; 252 | 253 | twitter.consumerKey = @"consumer_key"; 254 | twitter.consumerSecret = @"consumer_secret"; 255 | 256 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 257 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 258 | XCTAssertEqualObjects(task.error, expectedError); 259 | [expectation fulfill]; 260 | return nil; 261 | }]; 262 | [self waitForTestExpectations]; 263 | } 264 | 265 | - (void)testAuthorizeWithSingleLocalAccountAndNetworkSuccess { 266 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 267 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 268 | id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); 269 | id mockedSLRequest = PFStrictClassMock([SLRequest class]); 270 | 271 | OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); 272 | OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); 273 | 274 | OCMStub([mockedStore requestAccessToAccountsWithType:nil 275 | options:nil 276 | completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 277 | __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; 278 | [invocation getArgument:&handler atIndex:4]; 279 | 280 | handler(YES, nil); 281 | }); 282 | 283 | id mockedAccount = PFStrictClassMock([ACAccount class]); 284 | OCMStub([mockedAccount accountType]).andReturn(nil); 285 | 286 | NSArray *twitterAccounts = @[ mockedAccount ]; 287 | OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); 288 | 289 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 290 | NSURLRequest *request = obj; 291 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 292 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 293 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 294 | [invocation getArgument:&completionHandler atIndex:3]; 295 | 296 | NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; 297 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 298 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 299 | 300 | __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; 301 | OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter 302 | requestMethod:0 303 | URL:OCMOCK_ANY 304 | parameters:OCMOCK_ANY])) 305 | .andDo(^(NSInvocation *invocation) { 306 | __strong typeof(mockedSLRequest) slRequest = weakSLRequest; 307 | [invocation setReturnValue:&slRequest]; 308 | }); 309 | 310 | OCMStub([mockedSLRequest setAccount:OCMOCK_ANY]); 311 | OCMStub([mockedSLRequest performRequestWithHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 312 | __unsafe_unretained SLRequestHandler requestHandler = nil; 313 | [invocation getArgument:&requestHandler atIndex:2]; 314 | 315 | NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" 316 | @"screen_name=test_name"; 317 | requestHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 318 | }); 319 | 320 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 321 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 322 | urlSession:mockedURLSession 323 | dialogClass:mockedDialog]; 324 | 325 | twitter.consumerKey = @"consumer_key"; 326 | twitter.consumerSecret = @"consumer_secret"; 327 | 328 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 329 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 330 | XCTAssertNil(task.error); 331 | XCTAssertNil(task.result); 332 | 333 | XCTAssertEqualObjects(@"access_token", twitter.authToken); 334 | XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); 335 | XCTAssertEqualObjects(@"test_user", twitter.userId); 336 | XCTAssertEqualObjects(@"test_name", twitter.screenName); 337 | 338 | [expectation fulfill]; 339 | 340 | return nil; 341 | }]; 342 | 343 | [self waitForTestExpectations]; 344 | } 345 | 346 | - (void)testAuthorizeWithMultipleLocalAccountsAndNetworkSuccess { 347 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 348 | id mockedSession = PFStrictClassMock([NSURLSession class]); 349 | id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); 350 | id mockedSLRequest = PFStrictClassMock([SLRequest class]); 351 | id mockedAlertView = PFStrictClassMock([PFTwitterAlertView class]); 352 | 353 | OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); 354 | OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); 355 | 356 | OCMStub([mockedStore requestAccessToAccountsWithType:nil 357 | options:nil 358 | completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 359 | __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; 360 | [invocation getArgument:&handler atIndex:4]; 361 | 362 | handler(YES, nil); 363 | }); 364 | 365 | id mockedAccount = PFStrictClassMock([ACAccount class]); 366 | OCMStub([mockedAccount accountType]).andReturn(nil); 367 | OCMStub([mockedAccount valueForKey:@"accountDescription"]).andReturn(@"An Account"); 368 | 369 | NSArray *twitterAccounts = @[ mockedAccount, mockedAccount ]; 370 | OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); 371 | 372 | [OCMExpect([mockedSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 373 | NSURLRequest *request = obj; 374 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 375 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 376 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 377 | [invocation getArgument:&completionHandler atIndex:3]; 378 | 379 | 380 | NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; 381 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 382 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 383 | 384 | __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; 385 | OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter 386 | requestMethod:0 387 | URL:OCMOCK_ANY 388 | parameters:OCMOCK_ANY])) 389 | .andDo(^(NSInvocation *invocation) { 390 | __strong typeof(mockedSLRequest) slRequest = weakSLRequest; 391 | [invocation setReturnValue:&slRequest]; 392 | }); 393 | 394 | OCMStub([mockedSLRequest setAccount:OCMOCK_ANY]); 395 | OCMStub([mockedSLRequest performRequestWithHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 396 | __unsafe_unretained SLRequestHandler requestHandler = nil; 397 | [invocation getArgument:&requestHandler atIndex:2]; 398 | 399 | NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" 400 | @"screen_name=test_name"; 401 | requestHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 402 | }); 403 | 404 | OCMStub(ClassMethod([mockedAlertView showAlertWithTitle:OCMOCK_ANY 405 | message:nil 406 | cancelButtonTitle:@"Cancel" 407 | otherButtonTitles:[OCMArg checkWithBlock:^BOOL(id obj) { 408 | return [obj count] == 2; 409 | }] 410 | completion:OCMOCK_ANY])).andDo(^(NSInvocation *invocation) { 411 | __unsafe_unretained PFTwitterAlertViewCompletion completionHandler = nil; 412 | [invocation getArgument:&completionHandler atIndex:6]; 413 | 414 | completionHandler(0); 415 | }); 416 | 417 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 418 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 419 | urlSession:mockedSession 420 | dialogClass:mockedDialog]; 421 | 422 | twitter.consumerKey = @"consumer_key"; 423 | twitter.consumerSecret = @"consumer_secret"; 424 | 425 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 426 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 427 | XCTAssertNil(task.error); 428 | XCTAssertNil(task.result); 429 | 430 | XCTAssertEqualObjects(@"access_token", twitter.authToken); 431 | XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); 432 | XCTAssertEqualObjects(@"test_user", twitter.userId); 433 | XCTAssertEqualObjects(@"test_name", twitter.screenName); 434 | 435 | [expectation fulfill]; 436 | 437 | return nil; 438 | }]; 439 | 440 | [self waitForTestExpectations]; 441 | } 442 | 443 | - (void)testAuthorizeWithZeroLocalAccountsAndNetworkSuccess { 444 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 445 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 446 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 447 | id mockedComposeViewController = PFStrictClassMock([SLComposeViewController class]); 448 | id mockedSLRequest = PFStrictClassMock([SLRequest class]); 449 | 450 | OCMStub(ClassMethod([mockedComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])).andReturn(YES); 451 | OCMStub([mockedStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter]).andReturn(nil); 452 | 453 | OCMStub([mockedStore requestAccessToAccountsWithType:nil 454 | options:nil 455 | completion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 456 | __unsafe_unretained ACAccountStoreRequestAccessCompletionHandler handler = nil; 457 | [invocation getArgument:&handler atIndex:4]; 458 | 459 | handler(YES, nil); 460 | }); 461 | 462 | id mockedAccount = PFStrictClassMock([ACAccount class]); 463 | OCMStub([mockedAccount accountType]).andReturn(nil); 464 | 465 | NSArray *twitterAccounts = @[]; 466 | OCMStub([mockedStore accountsWithAccountType:nil]).andReturn(twitterAccounts); 467 | 468 | __weak typeof(mockedSLRequest) weakSLRequest = mockedSLRequest; 469 | OCMStub(ClassMethod([[mockedSLRequest ignoringNonObjectArgs] requestForServiceType:SLServiceTypeTwitter 470 | requestMethod:0 471 | URL:OCMOCK_ANY 472 | parameters:OCMOCK_ANY])) 473 | .andDo(^(NSInvocation *invocation) { 474 | __strong typeof(mockedSLRequest) slRequest = weakSLRequest; 475 | [invocation setReturnValue:&slRequest]; 476 | }); 477 | 478 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 479 | NSURLRequest *request = obj; 480 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 481 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 482 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 483 | [invocation getArgument:&completionHandler atIndex:3]; 484 | 485 | 486 | NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; 487 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 488 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 489 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 490 | NSURLRequest *request = obj; 491 | return [request.URL.lastPathComponent isEqualToString:@"access_token"]; 492 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 493 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 494 | [invocation getArgument:&completionHandler atIndex:3]; 495 | 496 | 497 | NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" 498 | @"screen_name=test_name"; 499 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 500 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 501 | 502 | OCMExpect([mockedDialog dialogWithURL:OCMOCK_ANY queryParameters:OCMOCK_ANY]).andReturnWeak(mockedDialog); 503 | OCMExpect([mockedDialog setRedirectURLPrefix:@"http://twitter-oauth.callback"]); 504 | 505 | __block PFOAuth1FlowDialogCompletion completionHandler = nil; 506 | OCMExpect([mockedDialog setCompletion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 507 | __unsafe_unretained PFOAuth1FlowDialogCompletion newHandler = nil; 508 | [invocation getArgument:&newHandler atIndex:2]; 509 | 510 | completionHandler = [newHandler copy]; 511 | }); 512 | 513 | OCMExpect([[mockedDialog ignoringNonObjectArgs] showAnimated:NO]).andDo(^(NSInvocation *invocation) { 514 | completionHandler( 515 | YES, 516 | [NSURL URLWithString:@"http://twitter-oauth.callback/?oauth_token=sucess_token&oauth_token_secret=success_secret"], 517 | nil 518 | ); 519 | }); 520 | 521 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 522 | urlSession:mockedURLSession 523 | dialogClass:mockedDialog]; 524 | 525 | twitter.consumerKey = @"consumer_key"; 526 | twitter.consumerSecret = @"consumer_secret"; 527 | 528 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 529 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 530 | XCTAssertNil(task.error); 531 | XCTAssertNil(task.result); 532 | 533 | XCTAssertEqualObjects(@"access_token", twitter.authToken); 534 | XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); 535 | XCTAssertEqualObjects(@"test_user", twitter.userId); 536 | XCTAssertEqualObjects(@"test_name", twitter.screenName); 537 | 538 | [expectation fulfill]; 539 | 540 | return nil; 541 | }]; 542 | 543 | [self waitForTestExpectations]; 544 | 545 | OCMVerifyAll(mockedDialog); 546 | OCMVerifyAll(mockedURLSession); 547 | } 548 | 549 | - (void)testAuthorizeWithoutLocalAccountAndNetworkSuccess { 550 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 551 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 552 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 553 | 554 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 555 | NSURLRequest *request = obj; 556 | return [request.URL.lastPathComponent isEqualToString:@"request_token"]; 557 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 558 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 559 | [invocation getArgument:&completionHandler atIndex:3]; 560 | 561 | 562 | NSString *successString = @"oauth_token=request_token&oauth_token_secret=request_secret"; 563 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 564 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 565 | [OCMExpect([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 566 | NSURLRequest *request = obj; 567 | return [request.URL.lastPathComponent isEqualToString:@"access_token"]; 568 | }] completionHandler:[OCMArg isNotNil]]).andDo(^(NSInvocation *invocation) { 569 | __unsafe_unretained NSURLSessionDataTaskCompletionHandler completionHandler = nil; 570 | [invocation getArgument:&completionHandler atIndex:3]; 571 | 572 | 573 | NSString *successString = @"oauth_token=access_token&oauth_token_secret=access_secret&user_id=test_user&" 574 | @"screen_name=test_name"; 575 | completionHandler([successString dataUsingEncoding:NSUTF8StringEncoding], nil, nil); 576 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 577 | 578 | OCMExpect([mockedDialog dialogWithURL:OCMOCK_ANY queryParameters:OCMOCK_ANY]).andReturnWeak(mockedDialog); 579 | OCMExpect([mockedDialog setRedirectURLPrefix:@"http://twitter-oauth.callback"]); 580 | 581 | __block PFOAuth1FlowDialogCompletion completionHandler = nil; 582 | OCMExpect([mockedDialog setCompletion:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { 583 | __unsafe_unretained PFOAuth1FlowDialogCompletion newHandler = nil; 584 | [invocation getArgument:&newHandler atIndex:2]; 585 | 586 | completionHandler = [newHandler copy]; 587 | }); 588 | 589 | OCMExpect([[mockedDialog ignoringNonObjectArgs] showAnimated:NO]).andDo(^(NSInvocation *invocation) { 590 | completionHandler( 591 | YES, 592 | [NSURL URLWithString:@"http://twitter-oauth.callback/?oauth_token=sucess_token&oauth_token_secret=success_secret"], 593 | nil 594 | ); 595 | }); 596 | 597 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore 598 | urlSession:mockedURLSession 599 | dialogClass:mockedDialog]; 600 | 601 | twitter.consumerKey = @"consumer_key"; 602 | twitter.consumerSecret = @"consumer_secret"; 603 | 604 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 605 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 606 | XCTAssertNil(task.error); 607 | XCTAssertNil(task.result); 608 | 609 | XCTAssertEqualObjects(@"access_token", twitter.authToken); 610 | XCTAssertEqualObjects(@"access_secret", twitter.authTokenSecret); 611 | XCTAssertEqualObjects(@"test_user", twitter.userId); 612 | XCTAssertEqualObjects(@"test_name", twitter.screenName); 613 | 614 | [expectation fulfill]; 615 | 616 | return nil; 617 | }]; 618 | 619 | [self waitForTestExpectations]; 620 | 621 | OCMVerifyAll(mockedDialog); 622 | OCMVerifyAll(mockedURLSession); 623 | } 624 | 625 | - (void)testDeauthorizeLoggedOutAccount { 626 | id store = PFStrictClassMock([ACAccountStore class]); 627 | NSURLSession *session = PFStrictClassMock([NSURLSession class]); 628 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 629 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:store urlSession:session dialogClass:mockedDialog]; 630 | 631 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 632 | [[twitter authorizeInBackground] continueWithBlock:^id(BFTask *task) { 633 | NSError *error = task.error; 634 | XCTAssertNotNil(error); 635 | XCTAssertEqualObjects(error.domain, PFParseErrorDomain); 636 | XCTAssertEqual(error.code, 1); 637 | [expectation fulfill]; 638 | return nil; 639 | }]; 640 | [self waitForTestExpectations]; 641 | } 642 | 643 | - (void)testDeauthorizeLoggedInAccount { 644 | id mockedStore = PFStrictClassMock([ACAccountStore class]); 645 | id mockedURLSession = PFStrictClassMock([NSURLSession class]); 646 | 647 | id mockedDialog = PFStrictProtocolMock(@protocol(PFOAuth1FlowDialogInterface)); 648 | PF_Twitter *twitter = [[PF_Twitter alloc] initWithAccountStore:mockedStore urlSession:mockedURLSession dialogClass:mockedDialog]; 649 | twitter.consumerKey = @"consumer_key"; 650 | twitter.consumerSecret = @"consumer_secret"; 651 | twitter.authToken = @"auth_token"; 652 | twitter.authTokenSecret = @"auth_token_secret"; 653 | twitter.userId = @"user_id"; 654 | twitter.screenName = @"screen_name"; 655 | 656 | __block NSURLSessionDataTaskCompletionHandler completionHandler = nil; 657 | [OCMStub([mockedURLSession dataTaskWithRequest:[OCMArg checkWithBlock:^BOOL(id obj) { 658 | NSURLRequest *request = obj; 659 | return [request.URL.lastPathComponent isEqualToString:@"invalidate_token"]; 660 | }] completionHandler:[OCMArg checkWithBlock:^BOOL(id obj) { 661 | completionHandler = obj; 662 | return (obj != nil); 663 | }]]).andDo(^(NSInvocation *invocation) { 664 | completionHandler([NSData data], nil, nil); 665 | }) andReturn:[OCMockObject niceMockForClass:[NSURLSessionDataTask class]]]; 666 | 667 | XCTestExpectation *expectation = [self currentSelectorTestExpectation]; 668 | [[twitter deauthorizeInBackground] continueWithBlock:^id(BFTask *task) { 669 | NSError *error = task.error; 670 | XCTAssertNil(error); 671 | XCTAssertNotNil(task.result); 672 | 673 | XCTAssertNil(twitter.authToken); 674 | XCTAssertNil(twitter.authTokenSecret); 675 | XCTAssertNil(twitter.userId); 676 | XCTAssertNil(twitter.screenName); 677 | 678 | [expectation fulfill]; 679 | return nil; 680 | }]; 681 | [self waitForTestExpectations]; 682 | } 683 | 684 | @end 685 | -------------------------------------------------------------------------------- /Tests/Unit/TwitterUtilsTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Parse, LLC. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "PFTwitterTestCase.h" 11 | #import "PFTwitterUtils_Private.h" 12 | #import "PF_Twitter.h" 13 | 14 | @import Parse; 15 | 16 | ///-------------------------------------- 17 | #pragma mark - Tests 18 | ///-------------------------------------- 19 | 20 | @interface TwitterUtilsTests : PFTwitterTestCase 21 | 22 | @end 23 | 24 | @implementation TwitterUtilsTests 25 | 26 | ///-------------------------------------- 27 | #pragma mark - XCTestCase 28 | ///-------------------------------------- 29 | 30 | - (void)tearDown { 31 | [PFTwitterUtils _setAuthenticationProvider:nil]; 32 | 33 | [super tearDown]; 34 | } 35 | 36 | ///-------------------------------------- 37 | #pragma mark - Tests 38 | ///-------------------------------------- 39 | 40 | - (void)testInitialize { 41 | id parseMock = PFStrictClassMock([Parse class]); 42 | OCMStub([parseMock getApplicationId]).andReturn(@"yolo"); 43 | OCMStub([parseMock getClientKey]).andReturn(@"yarr"); 44 | 45 | id userMock = PFStrictClassMock([PFUser class]); 46 | OCMExpect(ClassMethod([userMock registerAuthenticationDelegate:[OCMArg checkWithBlock:^BOOL(id obj) { 47 | return (obj != nil); 48 | }] forAuthType:@"twitter"])); 49 | 50 | [PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]; 51 | XCTAssertNotNil([PFTwitterUtils twitter]); 52 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); 53 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); 54 | 55 | OCMVerifyAll(userMock); 56 | } 57 | 58 | - (void)testInitializeTwice { 59 | id parseMock = PFStrictClassMock([Parse class]); 60 | OCMStub([parseMock getApplicationId]).andReturn(@"yolo"); 61 | OCMStub([parseMock getClientKey]).andReturn(@"yarr"); 62 | 63 | id userMock = PFStrictClassMock([PFUser class]); 64 | 65 | [PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]; 66 | XCTAssertNotNil([PFTwitterUtils twitter]); 67 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); 68 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); 69 | 70 | [PFTwitterUtils initializeWithConsumerKey:@"b" consumerSecret:@"c"]; 71 | XCTAssertNotNil([PFTwitterUtils twitter]); 72 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerKey, @"a"); 73 | XCTAssertEqualObjects([PFTwitterUtils twitter].consumerSecret, @"b"); 74 | 75 | OCMVerifyAll(userMock); 76 | } 77 | 78 | - (void)testInitializeRequiresParseInitialize { 79 | PFAssertThrowsInconsistencyException([PFTwitterUtils initializeWithConsumerKey:@"a" consumerSecret:@"b"]); 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /third_party_licenses.txt: -------------------------------------------------------------------------------- 1 | THE FOLLOWING SETS FORTH ATTRIBUTION NOTICES FOR THIRD PARTY SOFTWARE THAT MAY BE CONTAINED IN PORTIONS OF THE PARSE PRODUCT. 2 | 3 | ----- 4 | 5 | The following software may be included in this product: OAuthCore. This software contains the following license and notice below: 6 | 7 | Copyright (C) 2012 Loren Brichter 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | 15 | -------------------------------------------------------------------- 16 | 17 | Copyright 2001-2004 Unicode, Inc. 18 | 19 | Disclaimer 20 | 21 | This source code is provided as is by Unicode, Inc. No claims are 22 | made as to fitness for any particular purpose. No warranties of any 23 | kind are expressed or implied. The recipient agrees to determine 24 | applicability of information provided. If this file has been 25 | purchased on magnetic or optical media from Unicode, Inc., the 26 | sole remedy for any claim will be exchange of defective media 27 | within 90 days of receipt. 28 | 29 | Limitations on Rights to Redistribute This Code 30 | 31 | Unicode, Inc. hereby grants the right to freely use the information 32 | supplied in this file in the creation of products supporting the 33 | Unicode Standard, and to make copies of this file in any form 34 | for internal or external distribution as long as this notice 35 | remains attached. 36 | --------------------------------------------------------------------------------