-
-
Notifications
You must be signed in to change notification settings - Fork 551
/
Copy pathgithub.py
149 lines (119 loc) · 5.08 KB
/
github.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""
Github OAuth2 backend, docs at:
https://python-social-auth.readthedocs.io/en/latest/backends/github.html
"""
from urllib.parse import urljoin
from requests import HTTPError
from ..exceptions import AuthFailed
from .oauth import BaseOAuth2
class GithubOAuth2(BaseOAuth2):
"""Github OAuth authentication backend"""
name = "github"
API_URL = "https://api.github.com/"
AUTHORIZATION_URL = "https://github.com/login/oauth/authorize"
ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token"
ACCESS_TOKEN_METHOD = "POST"
SCOPE_SEPARATOR = ","
REDIRECT_STATE = False
STATE_PARAMETER = True
SEND_USER_AGENT = True
EXTRA_DATA = [
("id", "id"),
("expires_in", "expires"),
("login", "login"),
("refresh_token", "refresh_token"),
]
def api_url(self) -> str:
return self.API_URL
def get_user_details(self, response):
"""Return user details from Github account"""
fullname, first_name, last_name = self.get_user_names(response.get("name"))
return {
"username": response.get("login"),
"email": response.get("email") or "",
"fullname": fullname,
"first_name": first_name,
"last_name": last_name,
}
def user_data(self, access_token, *args, **kwargs):
"""Loads user data from service"""
data = self._user_data(access_token)
if not data.get("email"):
try:
emails = self._user_data(access_token, "/emails")
except (HTTPError, ValueError, TypeError):
emails = []
if emails:
email = emails[0]
primary_emails = [
e for e in emails if not isinstance(e, dict) or e.get("primary")
]
if primary_emails:
email = primary_emails[0]
if isinstance(email, dict):
email = email.get("email", "")
data["email"] = email
return data
def _user_data(self, access_token, path=None):
url = urljoin(self.api_url(), "user{}".format(path or ""))
return self.get_json(url, headers={"Authorization": f"token {access_token}"})
class GithubMemberOAuth2(GithubOAuth2):
no_member_string = ""
def user_data(self, access_token, *args, **kwargs):
"""Loads user data from service"""
user_data = super().user_data(access_token, *args, **kwargs)
headers = {"Authorization": f"token {access_token}"}
try:
self.request(self.member_url(user_data), headers=headers)
except HTTPError as err:
# if the user is a member of the organization, response code
# will be 204, see http://bit.ly/ZS6vFl
if err.response.status_code != 204:
raise AuthFailed(self, "User doesn't belong to the organization")
return user_data
def member_url(self, user_data):
raise NotImplementedError("Implement in subclass")
class GithubOrganizationOAuth2(GithubMemberOAuth2):
"""Github OAuth2 authentication backend for organizations"""
name = "github-org"
no_member_string = "User doesn't belong to the organization"
def member_url(self, user_data):
return urljoin(
self.api_url(),
"orgs/{org}/members/{username}".format(
org=self.setting("NAME"), username=user_data.get("login")
),
)
class GithubTeamOAuth2(GithubMemberOAuth2):
"""Github OAuth2 authentication backend for teams"""
name = "github-team"
no_member_string = "User doesn't belong to the team"
def member_url(self, user_data):
return urljoin(
self.api_url(),
"teams/{team_id}/members/{username}".format(
team_id=self.setting("ID"), username=user_data.get("login")
),
)
class GithubAppAuth(GithubOAuth2):
"""GitHub App OAuth authentication backend"""
name = "github-app"
def validate_state(self):
"""
Scenario 1: user clicks an icon/button on your website and initiates
social login. This works exacltly like standard OAuth and we
have `state` and `redirect_uri`.
Scenario 2: user starts from http://github.com/apps/your-app and clicks
'Install & Authorize' button! They still get a temporary
`code` (used to fetch `access_token`) but there's no `state`
or `redirect_uri` here.
Note: Scenario 2 only happens when your GitHub App is configured
with `Request user authorization (OAuth) during installation`
turned on! This causes GitHub to redirect the person back to
`/complete/github/`. If the above setting is turned off then
GitHub will redirect to another URL called Setup URL and the
person may need to login first before they can continue!
"""
if self.data.get("installation_id") and self.data.get("setup_action"):
return None
return super().validate_state()