otter-service-stdalone 0.1.21__py3-none-any.whl → 0.1.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- otter_service_stdalone/__init__.py +1 -1
- otter_service_stdalone/app.py +27 -13
- otter_service_stdalone/static_templates/403.html +11 -3
- otter_service_stdalone/static_templates/500.html +11 -3
- otter_service_stdalone/user_auth.py +26 -33
- {otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/METADATA +1 -1
- {otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/RECORD +10 -10
- {otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/WHEEL +0 -0
- {otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/entry_points.txt +0 -0
- {otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.1.
|
1
|
+
__version__ = "0.1.23"
|
otter_service_stdalone/app.py
CHANGED
@@ -14,7 +14,7 @@ __UPLOADS__ = "/tmp/uploads"
|
|
14
14
|
log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
|
15
15
|
log_error = f'{os.environ.get("ENVIRONMENT")}-logs'
|
16
16
|
|
17
|
-
|
17
|
+
authorization_states = {} # used to protect against cross-site request forgery attacks.
|
18
18
|
|
19
19
|
|
20
20
|
class HealthHandler(tornado.web.RequestHandler):
|
@@ -34,6 +34,8 @@ class LoginHandler(tornado.web.RequestHandler):
|
|
34
34
|
tornado (tornado.web.RequestHandler): The request handler
|
35
35
|
"""
|
36
36
|
async def get(self):
|
37
|
+
state = str(uuid.uuid4())
|
38
|
+
authorization_states[state] = True
|
37
39
|
await u_auth.handle_authorization(self, state)
|
38
40
|
|
39
41
|
|
@@ -49,12 +51,13 @@ class BaseHandler(tornado.web.RequestHandler):
|
|
49
51
|
|
50
52
|
def write_error(self, status_code, **kwargs):
|
51
53
|
log.write_logs("Http Error", f"{status_code} Error", "", "info", log_error)
|
54
|
+
self.clear_cookie("user")
|
52
55
|
if status_code == 403:
|
53
56
|
self.set_status(403)
|
54
|
-
self.render("static_templates/403.html")
|
57
|
+
self.render("static_templates/403.html", support=f'{os.environ.get("SUPPORT_EMAIL")}')
|
55
58
|
else:
|
56
59
|
self.set_status(500)
|
57
|
-
self.render("static_templates/500.html")
|
60
|
+
self.render("static_templates/500.html", support=f'{os.environ.get("SUPPORT_EMAIL")}')
|
58
61
|
|
59
62
|
|
60
63
|
class GitHubOAuthHandler(BaseHandler):
|
@@ -66,18 +69,29 @@ class GitHubOAuthHandler(BaseHandler):
|
|
66
69
|
async def get(self):
|
67
70
|
code = self.get_argument('code', False)
|
68
71
|
arg_state = self.get_argument('state', False)
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
if is_org_member:
|
74
|
-
self.set_secure_cookie("user", user, expires_days=7)
|
75
|
-
self.redirect("/")
|
76
|
-
else:
|
77
|
-
raise tornado.web.HTTPError(403)
|
78
|
-
else:
|
72
|
+
if arg_state not in authorization_states:
|
73
|
+
m = "UserAuth: GitHubOAuthHandler: Cross-Site Forgery possible - aborting"
|
74
|
+
log.write_logs("Auth Workflow", m, "", "info", log_error)
|
75
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
79
76
|
raise tornado.web.HTTPError(500)
|
80
77
|
|
78
|
+
del authorization_states[arg_state]
|
79
|
+
|
80
|
+
access_token = await u_auth.get_acess_token(code)
|
81
|
+
if access_token is None:
|
82
|
+
raise tornado.web.HTTPError(403)
|
83
|
+
|
84
|
+
user = await u_auth.get_github_username(access_token)
|
85
|
+
if not user:
|
86
|
+
raise tornado.web.HTTPError(403)
|
87
|
+
|
88
|
+
is_org_member = await u_auth.handle_is_org_member(access_token, user)
|
89
|
+
if not is_org_member:
|
90
|
+
raise tornado.web.HTTPError(403)
|
91
|
+
|
92
|
+
self.set_secure_cookie("user", user, expires_days=7)
|
93
|
+
self.redirect("/")
|
94
|
+
|
81
95
|
|
82
96
|
class MainHandler(BaseHandler):
|
83
97
|
"""This is the initial landing page for application
|
@@ -12,9 +12,17 @@
|
|
12
12
|
<div class="container">
|
13
13
|
<h1 class="title">403 Forbidden</h1>
|
14
14
|
<p class="subtitle colored is-3">
|
15
|
-
You do not have permission to access this resource or your authentication needs to be renewed.
|
16
|
-
|
17
|
-
|
15
|
+
You do not have permission to access this resource or your authentication needs to be renewed.
|
16
|
+
<br/>
|
17
|
+
<br/>
|
18
|
+
If you logged in with the incorrect GitHub username you will need to
|
19
|
+
<a target="_blank" style='color:#005b96' href="https://github.com/logout">logout</a> of GitHub first.
|
20
|
+
<br/>
|
21
|
+
<br/>
|
22
|
+
After logging out of GitHub, you can return the application's <a style='color:#005b96' href="/login">login</a> to try again.
|
23
|
+
<br/>
|
24
|
+
<br/>
|
25
|
+
If you need help, please email {{ support }}.
|
18
26
|
</p>
|
19
27
|
</div>
|
20
28
|
</div>
|
@@ -12,9 +12,17 @@
|
|
12
12
|
<div class="container">
|
13
13
|
<h1 class="title">500 Forbidden - Access Problem</h1>
|
14
14
|
<p class="subtitle colored is-3">
|
15
|
-
We
|
16
|
-
|
17
|
-
<
|
15
|
+
We could not establish access to the application for you.
|
16
|
+
<br/>
|
17
|
+
<br/>
|
18
|
+
If you logged in with the incorrect GitHub username you will need to
|
19
|
+
<a target="_blank" style='color:#005b96' href="https://github.com/logout">logout</a> of GitHub first.
|
20
|
+
<br/>
|
21
|
+
<br/>
|
22
|
+
After logging out of GitHub, you can return the application's <a style='color:#005b96' href="/login">login</a> to try again.
|
23
|
+
<br/>
|
24
|
+
<br/>
|
25
|
+
If you need help, please email {{ support }}.
|
18
26
|
</p>
|
19
27
|
</div>
|
20
28
|
</div>
|
@@ -6,7 +6,7 @@ import tornado
|
|
6
6
|
import urllib.parse
|
7
7
|
|
8
8
|
|
9
|
-
|
9
|
+
log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
|
10
10
|
environment_name = os.environ.get("ENVIRONMENT").split("-")[-1]
|
11
11
|
gh_key_path = os.path.join(os.path.dirname(__file__), f"secrets/gh_key.{environment_name}.yaml")
|
12
12
|
github_id = access_sops_keys.get(None, "github_access_id", secrets_file=gh_key_path)
|
@@ -22,19 +22,15 @@ async def handle_authorization(form, state):
|
|
22
22
|
- state (int): random uuid4 number generated to ensure communication between endpoints is
|
23
23
|
not compromised
|
24
24
|
"""
|
25
|
-
log.write_logs("Auth Workflow", "UserAuth: Get: Authorizing", "", "info",
|
25
|
+
log.write_logs("Auth Workflow", "UserAuth: Get: Authorizing", "", "info", log_debug)
|
26
26
|
q_params = f"client_id={github_id}&state={state}&scope=read:org"
|
27
27
|
form.redirect(f'https://github.com/login/oauth/authorize?{q_params}')
|
28
28
|
|
29
29
|
|
30
|
-
async def get_acess_token(
|
30
|
+
async def get_acess_token(code):
|
31
31
|
"""requests and returns the access token or None
|
32
32
|
|
33
33
|
Parameters:
|
34
|
-
- arg_state (int): the state argument being passed on the url string; this will be compared
|
35
|
-
to the state value generated in this file to ensure they are the same!
|
36
|
-
- state (int): random uuid4 number generated to ensure communication between endpoints is
|
37
|
-
not compromised
|
38
34
|
- code (str): the code that is returned from the authorization request
|
39
35
|
|
40
36
|
Returns:
|
@@ -48,7 +44,7 @@ async def get_acess_token(arg_state, state, code):
|
|
48
44
|
'redirect_uri': f"{os.environ.get('GRADER_DNS')}/oauth_callback"
|
49
45
|
}
|
50
46
|
m = "UserAuth: GitHubOAuthHandler: Getting Access Token"
|
51
|
-
log.write_logs("Auth Workflow", m, "", "info",
|
47
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
52
48
|
response = await http_client.fetch(
|
53
49
|
'https://github.com/login/oauth/access_token',
|
54
50
|
method='POST',
|
@@ -56,14 +52,15 @@ async def get_acess_token(arg_state, state, code):
|
|
56
52
|
body=urllib.parse.urlencode(params)
|
57
53
|
)
|
58
54
|
resp = json.loads(response.body.decode())
|
59
|
-
access_token =
|
60
|
-
if
|
61
|
-
access_token =
|
62
|
-
|
63
|
-
|
55
|
+
access_token = None
|
56
|
+
if "access_token" in resp:
|
57
|
+
access_token = resp["access_token"]
|
58
|
+
if access_token is None:
|
59
|
+
m = "UserAuth: GitHubOAuthHandler: Access Token NOT Granted - probably not member"
|
60
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
64
61
|
else:
|
65
62
|
m = "UserAuth: GitHubOAuthHandler: Access Token Granted"
|
66
|
-
log.write_logs("Auth Workflow", m, "", "info",
|
63
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
67
64
|
return access_token
|
68
65
|
|
69
66
|
|
@@ -78,18 +75,18 @@ async def handle_is_org_member(access_token, user):
|
|
78
75
|
Returns:
|
79
76
|
- boolean: True user is in the GH org, False otherwise
|
80
77
|
"""
|
81
|
-
log.write_logs("Auth Workflow", "UserAuth: Get: Check Membership", "", "info",
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
78
|
+
log.write_logs("Auth Workflow", "UserAuth: Get: Check Membership", "", "info", log_debug)
|
79
|
+
org_name = os.environ.get("AUTH_ORG")
|
80
|
+
url = f'https://api.github.com/orgs/{org_name}/members/{user}'
|
81
|
+
headers = {
|
82
|
+
'Authorization': f'token {access_token}',
|
83
|
+
'Accept': 'application/vnd.github.v3+json',
|
84
|
+
}
|
85
|
+
response = requests.get(url, headers=headers)
|
86
|
+
is_member = response.status_code == 204
|
87
|
+
m = f"UserAuth: Get: Check Membership: {is_member}"
|
88
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
89
|
+
return is_member
|
93
90
|
|
94
91
|
|
95
92
|
async def get_github_username(access_token):
|
@@ -103,18 +100,14 @@ async def get_github_username(access_token):
|
|
103
100
|
- str: The username of the authenticated user, or None if not found.
|
104
101
|
"""
|
105
102
|
url = 'https://api.github.com/user'
|
106
|
-
headers = {
|
107
|
-
'Authorization': f'token {access_token}',
|
108
|
-
'Accept': 'application/vnd.github.v3+json',
|
109
|
-
}
|
110
|
-
|
103
|
+
headers = {'Authorization': f'token {access_token}'}
|
111
104
|
response = requests.get(url, headers=headers)
|
112
105
|
if response.status_code == 200:
|
113
106
|
m = "UserAuth: Get: UserName - Success"
|
114
|
-
log.write_logs("Auth Workflow", m, "", "info",
|
107
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
115
108
|
user_info = response.json()
|
116
109
|
return user_info.get('login')
|
117
110
|
else:
|
118
111
|
m = f"UserAuth: Get: UserName - Fail:{response.status_code}"
|
119
|
-
log.write_logs("Auth Workflow", m, "", "info",
|
112
|
+
log.write_logs("Auth Workflow", m, "", "info", log_debug)
|
120
113
|
return None
|
@@ -1,19 +1,19 @@
|
|
1
|
-
otter_service_stdalone/__init__.py,sha256=
|
1
|
+
otter_service_stdalone/__init__.py,sha256=0byemO6n6WCv41u9vBG2AIsOkVbxLvok7puvwy8EhfU,23
|
2
2
|
otter_service_stdalone/access_sops_keys.py,sha256=nboU5aZ84Elrm5vO0dMgpIF5LLcnecpNAwpxKvj6DvU,2129
|
3
|
-
otter_service_stdalone/app.py,sha256=
|
3
|
+
otter_service_stdalone/app.py,sha256=IRcDHh37N_oaaxcUwn5T0oN3bvIeh_NegLsGrHr0B7k,10300
|
4
4
|
otter_service_stdalone/fs_logging.py,sha256=IKFZkc5TmpI6G3vTYFAU9jDjQ-GT5aRxk8kdZ0h0kJE,2390
|
5
5
|
otter_service_stdalone/grade_notebooks.py,sha256=DiIW7oIwq2TROHFiR0A9qqVCoFzd3QhZXxqgWZtCDk8,4498
|
6
6
|
otter_service_stdalone/index.html,sha256=QbSQs31OZhWlCQFE5vvJOlNh-JHEJ3PZPgR4GukzrCA,6032
|
7
7
|
otter_service_stdalone/upload_handle.py,sha256=NB6isuLrLkUCPetUA3ugUlKKpAYw4nvDBVmxpzvgcE8,5157
|
8
|
-
otter_service_stdalone/user_auth.py,sha256=
|
8
|
+
otter_service_stdalone/user_auth.py,sha256=o78ymv9jhi_KvO0Z6C68_F8-KM_7Zeph8XjyouaNGRY,4342
|
9
9
|
otter_service_stdalone/secrets/gh_key.dev.yaml,sha256=ORUVDu8SDcv0OE2ThwROppeg7y8oLkJJbPTCMn0s5l0,1138
|
10
10
|
otter_service_stdalone/secrets/gh_key.local.yaml,sha256=NtPXXyGf1iSgJ9Oa2ahvIEf_fcmflB3dwd3LWyCgxis,1138
|
11
11
|
otter_service_stdalone/secrets/gh_key.prod.yaml,sha256=6vgLqHzDp8cVAOJlpSXGDTUjSI6EyCb6f1-SSVG2rqw,1138
|
12
12
|
otter_service_stdalone/secrets/gh_key.staging.yaml,sha256=cKVqj4dcwuz2LhXwMNQy_1skF8XCVQOX2diXNjAFJXg,1138
|
13
|
-
otter_service_stdalone/static_templates/403.html,sha256=
|
14
|
-
otter_service_stdalone/static_templates/500.html,sha256=
|
15
|
-
otter_service_stdalone-0.1.
|
16
|
-
otter_service_stdalone-0.1.
|
17
|
-
otter_service_stdalone-0.1.
|
18
|
-
otter_service_stdalone-0.1.
|
19
|
-
otter_service_stdalone-0.1.
|
13
|
+
otter_service_stdalone/static_templates/403.html,sha256=7eO3XQsEkl4nF8PEeFkLwCzGBfdZ3kkkeu_Kgpgbh0k,1440
|
14
|
+
otter_service_stdalone/static_templates/500.html,sha256=t6DeEMp8piSWyBToHb_JpTrw3GStAHFrozlmeuXyamg,1421
|
15
|
+
otter_service_stdalone-0.1.23.dist-info/METADATA,sha256=Ri8YhADG_r7oqujXbrtDby9oB6pckzVvDMn1HyWgJiQ,1345
|
16
|
+
otter_service_stdalone-0.1.23.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
17
|
+
otter_service_stdalone-0.1.23.dist-info/entry_points.txt,sha256=cx447chuIEl8ly9jEoF5-2xNhaKsWcIMDdhUMH_00qQ,75
|
18
|
+
otter_service_stdalone-0.1.23.dist-info/top_level.txt,sha256=6UP22fD4OhbLt23E01v8Kvjn44vPRbyTIg_GqMYL-Ng,23
|
19
|
+
otter_service_stdalone-0.1.23.dist-info/RECORD,,
|
File without changes
|
{otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/entry_points.txt
RENAMED
File without changes
|
{otter_service_stdalone-0.1.21.dist-info → otter_service_stdalone-0.1.23.dist-info}/top_level.txt
RENAMED
File without changes
|