otter-service-stdalone 0.1.21__tar.gz → 0.1.23__tar.gz

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.
Files changed (25) hide show
  1. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/PKG-INFO +1 -1
  2. otter_service_stdalone-0.1.23/src/otter_service_stdalone/__init__.py +1 -0
  3. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/app.py +27 -13
  4. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/static_templates/403.html +11 -3
  5. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/static_templates/500.html +11 -3
  6. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/user_auth.py +26 -33
  7. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone.egg-info/PKG-INFO +1 -1
  8. otter_service_stdalone-0.1.21/src/otter_service_stdalone/__init__.py +0 -1
  9. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/README.md +0 -0
  10. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/pyproject.toml +0 -0
  11. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/setup.cfg +0 -0
  12. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/access_sops_keys.py +0 -0
  13. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/fs_logging.py +0 -0
  14. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/grade_notebooks.py +0 -0
  15. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/index.html +0 -0
  16. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/secrets/gh_key.dev.yaml +0 -0
  17. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/secrets/gh_key.local.yaml +0 -0
  18. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/secrets/gh_key.prod.yaml +0 -0
  19. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/secrets/gh_key.staging.yaml +0 -0
  20. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone/upload_handle.py +0 -0
  21. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone.egg-info/SOURCES.txt +0 -0
  22. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone.egg-info/dependency_links.txt +0 -0
  23. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone.egg-info/entry_points.txt +0 -0
  24. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/src/otter_service_stdalone.egg-info/top_level.txt +0 -0
  25. {otter_service_stdalone-0.1.21 → otter_service_stdalone-0.1.23}/tests/test_upload_handle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otter_service_stdalone
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Grading Service for Instructors using Otter Grader
5
5
  Home-page: https://github.com/sean-morris/otter-service-stdalone
6
6
  Author: Sean Morris
@@ -0,0 +1 @@
1
+ __version__ = "0.1.23"
@@ -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
- state = str(uuid.uuid4()) # used to protect against cross-site request forgery attacks.
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
- access_token = await u_auth.get_acess_token(arg_state, state, code)
70
- if access_token:
71
- user = await u_auth.get_github_username(access_token)
72
- is_org_member = await u_auth.handle_is_org_member(access_token, user)
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
- Please <a style='color:#005b96' href="/login">login</a> to try again or
17
- email sean.smorris@berkeley.edu for help.
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 are unable to establish access for you.
16
- Please email: sean.smorris@berkeley.edu or try
17
- <a style='color:#005b96' href="/login">logging</a> in again.
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
- log_coll = f'{os.environ.get("ENVIRONMENT")}-debug'
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", log_coll)
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(arg_state, state, code):
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", log_coll)
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 = resp['access_token']
60
- if arg_state != state:
61
- access_token = None
62
- m = "UserAuth: GitHubOAuthHandler: Cross-Site Forgery possible - aborting"
63
- log.write_logs("Auth Workflow", m, "", "info", log_coll)
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", log_coll)
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", log_coll)
82
- if user:
83
- org_name = os.environ.get("AUTH_ORG")
84
- url = f'https://api.github.com/orgs/{org_name}/members/{user}'
85
- headers = {
86
- 'Authorization': f'token {access_token}',
87
- 'Accept': 'application/vnd.github.v3+json',
88
- }
89
- response = requests.get(url, headers=headers)
90
- return response.status_code == 204
91
- else:
92
- return False
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", log_coll)
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", log_coll)
112
+ log.write_logs("Auth Workflow", m, "", "info", log_debug)
120
113
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otter_service_stdalone
3
- Version: 0.1.21
3
+ Version: 0.1.23
4
4
  Summary: Grading Service for Instructors using Otter Grader
5
5
  Home-page: https://github.com/sean-morris/otter-service-stdalone
6
6
  Author: Sean Morris
@@ -1 +0,0 @@
1
- __version__ = "0.1.21"