-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into master-github
- Loading branch information
Showing
62 changed files
with
1,756 additions
and
679 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,4 @@ checkstyle.txt | |
.env | ||
.direnv | ||
.envrc | ||
.elasticbeanstalk/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from rest_framework import serializers | ||
|
||
|
||
class ErrorSerializer(serializers.Serializer): | ||
message = serializers.CharField() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
class GithubError(Exception): | ||
pass | ||
|
||
|
||
class GoogleError(Exception): | ||
pass | ||
|
||
|
||
class OAuthError(Exception): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import requests | ||
from django.conf import settings | ||
from requests import RequestException | ||
|
||
from custom_auth.oauth.exceptions import GithubError | ||
from custom_auth.oauth.helpers.github_helpers import convert_response_data_to_dictionary, get_first_and_last_name | ||
from util.logging import get_logger | ||
|
||
GITHUB_API_URL = "https://api.github.com" | ||
GITHUB_OAUTH_URL = "https://github.com/login/oauth" | ||
|
||
NON_200_ERROR_MESSAGE = "Github returned {} status code when getting an access token." | ||
|
||
logger = get_logger(__name__) | ||
|
||
|
||
class GithubUser: | ||
def __init__(self, code: str, client_id: str = None, client_secret: str = None): | ||
self.client_id = client_id or settings.GITHUB_CLIENT_ID | ||
self.client_secret = client_secret or settings.GITHUB_CLIENT_SECRET | ||
|
||
self.access_token = self._get_access_token(code) | ||
self.headers = { | ||
"Authorization": f"token {self.access_token}" | ||
} | ||
|
||
def _get_access_token(self, code) -> str: | ||
data = { | ||
"code": code, | ||
"client_id": self.client_id, | ||
"client_secret": self.client_secret | ||
} | ||
response = requests.post(f"{GITHUB_OAUTH_URL}/access_token", data=data) | ||
|
||
if response.status_code != 200: | ||
raise GithubError(NON_200_ERROR_MESSAGE.format(response.status_code)) | ||
|
||
response_json = convert_response_data_to_dictionary(response.text) | ||
if "error" in response_json: | ||
error_message = response_json["error_description"].replace("+", " ") | ||
raise GithubError(error_message) | ||
|
||
return response_json["access_token"] | ||
|
||
def get_user_info(self) -> dict: | ||
# TODO: use threads? | ||
try: | ||
return { | ||
**self._get_user_name_and_id(), | ||
"email": self._get_primary_email() | ||
} | ||
except RequestException: | ||
raise GithubError("Failed to communicate with the Github API.") | ||
|
||
def _get_user_name_and_id(self): | ||
user_response = requests.get(f"{GITHUB_API_URL}/user", headers=self.headers) | ||
user_response_json = user_response.json() | ||
full_name = user_response_json.get("name") | ||
first_name, last_name = get_first_and_last_name(full_name) if full_name else ["", ""] | ||
return { | ||
"first_name": first_name, | ||
"last_name": last_name, | ||
"github_user_id": user_response_json.get("id") | ||
} | ||
|
||
def _get_primary_email(self): | ||
emails_response = requests.get(f"{GITHUB_API_URL}/user/emails", headers=self.headers) | ||
|
||
# response from github should be a list of dictionaries, this will find the first entry that is both verified | ||
# and marked as primary (there should only be one). | ||
primary_email_data = next( | ||
filter(lambda email_data: email_data["primary"] and email_data["verified"], emails_response.json()), None | ||
) | ||
|
||
if not primary_email_data: | ||
raise GithubError("User does not have a verified email address with Github.") | ||
|
||
return primary_email_data["email"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,27 @@ | ||
import requests | ||
from requests import RequestException | ||
from rest_framework import status | ||
|
||
from custom_auth.oauth.exceptions import GoogleError | ||
|
||
USER_INFO_URL = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&" | ||
NON_200_ERROR_MESSAGE = "Google returned {} status code when getting an access token." | ||
|
||
|
||
def get_user_info(access_token): | ||
headers = {"Authorization": f"Bearer {access_token}"} | ||
response = requests.get(USER_INFO_URL, headers=headers) | ||
response_json = response.json() | ||
return { | ||
"email": response_json["email"], | ||
"first_name": response_json.get("given_name", ""), | ||
"last_name": response_json.get("family_name", ""), | ||
"google_user_id": response_json["id"] | ||
} | ||
try: | ||
headers = {"Authorization": f"Bearer {access_token}"} | ||
response = requests.get(USER_INFO_URL, headers=headers) | ||
|
||
if response.status_code != status.HTTP_200_OK: | ||
raise GoogleError(NON_200_ERROR_MESSAGE.format(response.status_code)) | ||
|
||
response_json = response.json() | ||
return { | ||
"email": response_json["email"], | ||
"first_name": response_json.get("given_name", ""), | ||
"last_name": response_json.get("family_name", ""), | ||
"google_user_id": response_json["id"] | ||
} | ||
except RequestException: | ||
raise GoogleError("Failed to communicate with the Google API.") |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from custom_auth.oauth.exceptions import GithubError | ||
from util.logging import get_logger | ||
|
||
logger = get_logger(__name__) | ||
|
||
|
||
def convert_response_data_to_dictionary(text: str) -> dict: | ||
try: | ||
response_data = {} | ||
for key, value in [param.split("=") for param in text.split("&")]: | ||
response_data[key] = value | ||
return response_data | ||
except ValueError: | ||
logger.warning("Malformed data received from Github (%s)" % text) | ||
raise GithubError("Malformed data received from Github") | ||
|
||
|
||
def get_first_and_last_name(full_name: str) -> list: | ||
if not full_name: | ||
return ["", ""] | ||
|
||
names = full_name.strip().split(" ") | ||
return names if len(names) == 2 else [full_name, ""] |
Oops, something went wrong.