{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/comments/26048559","id":26048559,"diff_hunk":"@@ -0,0 +1,125 @@\n+\"\"\"\n+Test utilities for OAuth access token exchange\n+\"\"\"\n+import json\n+\n+import httpretty\n+import provider.constants\n+from provider.oauth2.models import Client\n+from social.apps.django_app.default.models import UserSocialAuth\n+\n+from student.tests.factories import UserFactory\n+\n+\n+class AccessTokenExchangeTestMixin(object):\n+    \"\"\"\n+    A mixin to define test cases for access token exchange. The following\n+    methods must be implemented by subclasses:\n+    * _assert_error(data, expected_error, expected_error_description)\n+    * _assert_success(data, expected_scopes)\n+    \"\"\"\n+    def setUp(self):\n+        super(AccessTokenExchangeTestMixin, self).setUp()\n+\n+        self.client_id = \"test_client_id\"\n+        self.oauth_client = Client.objects.create(\n+            client_id=self.client_id,\n+            client_type=provider.constants.PUBLIC\n+        )\n+        self.social_uid = \"test_social_uid\"\n+        self.user = UserFactory()\n+        UserSocialAuth.objects.create(user=self.user, provider=self.BACKEND, uid=self.social_uid)\n+        self.access_token = \"test_access_token\"\n+        # Initialize to minimal data\n+        self.data = {\n+            \"access_token\": self.access_token,\n+            \"client_id\": self.client_id,\n+        }\n+\n+    def _setup_user_response(self, success):\n+        \"\"\"\n+        Register a mock response for the third party user information endpoint;\n+        success indicates whether the response status code should be 200 or 400\n+        \"\"\"\n+        if success:\n+            status = 200\n+            body = json.dumps({self.UID_FIELD: self.social_uid})\n+        else:\n+            status = 400\n+            body = json.dumps({})\n+        httpretty.register_uri(\n+            httpretty.GET,\n+            self.USER_URL,\n+            body=body,\n+            status=status,\n+            content_type=\"application/json\"\n+        )\n+\n+    def _assert_error(self, _data, _expected_error, _expected_error_description):\n+        \"\"\"\n+        Given request data, execute a test and check that the expected error\n+        was returned (along with any other appropriate assertions).\n+        \"\"\"\n+        raise NotImplementedError()\n+\n+    def _assert_success(self, data, expected_scopes):\n+        \"\"\"\n+        Given request data, execute a test and check that the expected scopes\n+        were returned (along with any other appropriate assertions).\n+        \"\"\"\n+        raise NotImplementedError()\n+\n+    def test_minimal(self):\n+        self._setup_user_response(success=True)\n+        self._assert_success(self.data, expected_scopes=[])\n+\n+    def test_scopes(self):\n+        self._setup_user_response(success=True)\n+        self.data[\"scope\"] = \"profile email\"\n+        self._assert_success(self.data, expected_scopes=[\"profile\", \"email\"])\n+\n+    def test_missing_fields(self):\n+        for field in [\"access_token\", \"client_id\"]:\n+            data = dict(self.data)\n+            del data[field]\n+            self._assert_error(data, \"invalid_request\", \"{} is required\".format(field))\n+\n+    def test_invalid_client(self):\n+        self.data[\"client_id\"] = \"nonexistent_client\"\n+        self._assert_error(\n+            self.data,\n+            \"invalid_client\",\n+            \"nonexistent_client is not a valid client_id\"\n+        )\n+\n+    def test_confidential_client(self):\n+        self.oauth_client.client_type = provider.constants.CONFIDENTIAL\n+        self.oauth_client.save()\n+        self._assert_error(\n+            self.data,\n+            \"unauthorized_client\",\n+            \"test_client_id is not a public client\"\n+        )\n+\n+    def test_invalid_acess_token(self):\n+        self._setup_user_response(success=False)\n+        self._assert_error(self.data, \"invalid_grant\", \"access_token is not valid\")\n+\n+    def test_no_linked_user(self):\n+        UserSocialAuth.objects.all().delete()\n+        self._setup_user_response(success=True)\n+        self._assert_error(self.data, \"invalid_grant\", \"access_token is not valid\")\n+\n+\n+class AccessTokenExchangeMixinFacebook(object):\n+    \"\"\"Tests access token exchange with the Facebook backend\"\"\"\n+    BACKEND = \"facebook\"\n+    USER_URL = \"https://graph.facebook.com/me\"\n+    UID_FIELD = \"id\"\n+\n+\n+class AccessTokenExchangeMixinGoogle(object):\n+    \"\"\"Tests access token exchange with the Google backend\"\"\"\n+    BACKEND = \"google-oauth2\"\n+    USER_URL = \"https://www.googleapis.com/oauth2/v1/userinfo\"\n+    UID_FIELD = \"email\"","path":"common/djangoapps/oauth_exchange/tests/utils.py","position":125,"original_position":125,"commit_id":"6374243d8575975a420c137834719616aa0c90ac","original_commit_id":"6374243d8575975a420c137834719616aa0c90ac","user":{"login":"gwprice","id":4027165,"avatar_url":"https://avatars.githubusercontent.com/u/4027165?v=3","gravatar_id":"","url":"https://api.github.com/users/gwprice","html_url":"https://github.com/gwprice","followers_url":"https://api.github.com/users/gwprice/followers","following_url":"https://api.github.com/users/gwprice/following{/other_user}","gists_url":"https://api.github.com/users/gwprice/gists{/gist_id}","starred_url":"https://api.github.com/users/gwprice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gwprice/subscriptions","organizations_url":"https://api.github.com/users/gwprice/orgs","repos_url":"https://api.github.com/users/gwprice/repos","events_url":"https://api.github.com/users/gwprice/events{/privacy}","received_events_url":"https://api.github.com/users/gwprice/received_events","type":"User","site_admin":false},"body":"This indicates which field in the provider's response is to be treated as the unique identifier for that user.","created_at":"2015-03-09T16:03:13Z","updated_at":"2015-03-09T16:03:13Z","html_url":"https://github.com/edx/edx-platform/pull/7256#discussion_r26048559","pull_request_url":"https://api.github.com/repos/edx/edx-platform/pulls/7256","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments/26048559"},"html":{"href":"https://github.com/edx/edx-platform/pull/7256#discussion_r26048559"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7256"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/7256","id":30671786,"html_url":"https://github.com/edx/edx-platform/pull/7256","diff_url":"https://github.com/edx/edx-platform/pull/7256.diff","patch_url":"https://github.com/edx/edx-platform/pull/7256.patch","issue_url":"https://api.github.com/repos/edx/edx-platform/issues/7256","number":7256,"state":"open","locked":false,"title":"Add an endpoint to exchange OAuth access tokens","user":{"login":"gwprice","id":4027165,"avatar_url":"https://avatars.githubusercontent.com/u/4027165?v=3","gravatar_id":"","url":"https://api.github.com/users/gwprice","html_url":"https://github.com/gwprice","followers_url":"https://api.github.com/users/gwprice/followers","following_url":"https://api.github.com/users/gwprice/following{/other_user}","gists_url":"https://api.github.com/users/gwprice/gists{/gist_id}","starred_url":"https://api.github.com/users/gwprice/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gwprice/subscriptions","organizations_url":"https://api.github.com/users/gwprice/orgs","repos_url":"https://api.github.com/users/gwprice/repos","events_url":"https://api.github.com/users/gwprice/events{/privacy}","received_events_url":"https://api.github.com/users/gwprice/received_events","type":"User","site_admin":false},"body":"This allows the holder of a third-party access token (e.g. from Google\r\nor Facebook) to get a first-party access token for the edX account\r\nlinked to the given access token.\r\n\r\n\r\n@nasthagiri @BenjiLee please review. I still need to write a few more tests, but I'm posting the PR now so we can get the review started. Please feel free to provide suggestions on alternative URL and param names. Also, I'm not totally happy with the mixin mess that I have created for defining the tests. If you have ideas on simplifying that, I'd love to hear them. Alternatively, I might test just for one third party instead of two; I like the thoroughness, but I'm not sure it's worth the complexity it introduces.","created_at":"2015-03-06T19:23:22Z","updated_at":"2015-03-09T16:03:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"af80d581cf3ede6240e9eb7f36b318e0b4ff1a6e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-platform/pulls/7256/commits","review_comments_url":"https://api.github.com/repos/edx/edx-platform/pulls/7256/comments","review_comment_url":"https://api.github.com/repos/edx/edx-platform/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/edx-platform/issues/7256/comments","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/6374243d8575975a420c137834719616aa0c90ac","head":{"label":"edx:gprice/oauth-exchange","ref":"gprice/oauth-exchange","sha":"6374243d8575975a420c137834719616aa0c90ac","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":10391073,"name":"edx-platform","full_name":"edx/edx-platform","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-platform","description":"Open edX, the edX platform that powers http://edx.org","fork":false,"url":"https://api.github.com/repos/edx/edx-platform","forks_url":"https://api.github.com/repos/edx/edx-platform/forks","keys_url":"https://api.github.com/repos/edx/edx-platform/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-platform/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-platform/teams","hooks_url":"https://api.github.com/repos/edx/edx-platform/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-platform/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-platform/events","assignees_url":"https://api.github.com/repos/edx/edx-platform/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-platform/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-platform/tags","blobs_url":"https://api.github.com/repos/edx/edx-platform/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-platform/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-platform/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-platform/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-platform/languages","stargazers_url":"https://api.github.com/repos/edx/edx-platform/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-platform/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-platform/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-platform/subscription","commits_url":"https://api.github.com/repos/edx/edx-platform/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-platform/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-platform/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-platform/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-platform/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-platform/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-platform/merges","archive_url":"https://api.github.com/repos/edx/edx-platform/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-platform/downloads","issues_url":"https://api.github.com/repos/edx/edx-platform/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-platform/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-platform/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-platform/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-platform/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-platform/releases{/id}","created_at":"2013-05-30T20:20:38Z","updated_at":"2015-03-07T17:51:09Z","pushed_at":"2015-03-09T15:43:49Z","git_url":"git://github.com/edx/edx-platform.git","ssh_url":"git@github.com:edx/edx-platform.git","clone_url":"https://github.com/edx/edx-platform.git","svn_url":"https://github.com/edx/edx-platform","homepage":"http://openedx.org/","size":1835930,"stargazers_count":1778,"watchers_count":1778,"language":"Gettext Catalog","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1019,"mirror_url":null,"open_issues_count":144,"forks":1019,"open_issues":144,"watchers":1778,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"3604e2dd97002ed29f901022b488ef85c34938f0","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":10391073,"name":"edx-platform","full_name":"edx/edx-platform","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-platform","description":"Open edX, the edX platform that powers http://edx.org","fork":false,"url":"https://api.github.com/repos/edx/edx-platform","forks_url":"https://api.github.com/repos/edx/edx-platform/forks","keys_url":"https://api.github.com/repos/edx/edx-platform/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-platform/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-platform/teams","hooks_url":"https://api.github.com/repos/edx/edx-platform/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-platform/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-platform/events","assignees_url":"https://api.github.com/repos/edx/edx-platform/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-platform/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-platform/tags","blobs_url":"https://api.github.com/repos/edx/edx-platform/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-platform/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-platform/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-platform/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-platform/languages","stargazers_url":"https://api.github.com/repos/edx/edx-platform/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-platform/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-platform/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-platform/subscription","commits_url":"https://api.github.com/repos/edx/edx-platform/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-platform/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-platform/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-platform/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-platform/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-platform/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-platform/merges","archive_url":"https://api.github.com/repos/edx/edx-platform/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-platform/downloads","issues_url":"https://api.github.com/repos/edx/edx-platform/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-platform/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-platform/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-platform/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-platform/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-platform/releases{/id}","created_at":"2013-05-30T20:20:38Z","updated_at":"2015-03-07T17:51:09Z","pushed_at":"2015-03-09T15:43:49Z","git_url":"git://github.com/edx/edx-platform.git","ssh_url":"git@github.com:edx/edx-platform.git","clone_url":"https://github.com/edx/edx-platform.git","svn_url":"https://github.com/edx/edx-platform","homepage":"http://openedx.org/","size":1835930,"stargazers_count":1778,"watchers_count":1778,"language":"Gettext Catalog","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1019,"mirror_url":null,"open_issues_count":144,"forks":1019,"open_issues":144,"watchers":1778,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7256"},"html":{"href":"https://github.com/edx/edx-platform/pull/7256"},"issue":{"href":"https://api.github.com/repos/edx/edx-platform/issues/7256"},"comments":{"href":"https://api.github.com/repos/edx/edx-platform/issues/7256/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7256/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7256/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-platform/statuses/6374243d8575975a420c137834719616aa0c90ac"}}}}