77import concurrent .futures
88import json
99import os
10+ import base64
11+ import binascii
1012import threading
1113import time
1214import uuid
@@ -96,53 +98,29 @@ def received_activity(self, data):
9698 else :
9799 cla .log .debug ("github_models.received_activity - Ignoring unsupported action: {}" .format (data ["action" ]))
98100
99- def user_from_session (self , request , redirect , redirect_url , state , code ):
101+ def user_from_session (self , request ):
100102 fn = "github_models.user_from_session"
101- cla .log .debug (f"{ fn } - Loading session from request: { request } ..." )
103+ cla .log .debug (f"{ fn } - loading session from request: { request } ..." )
102104 session = self ._get_request_session (request )
103- cla .log .debug (f"{ fn } - redirect: { redirect } , redirect_url: { redirect_url } , state: { state } , code: { code } , session: { session } " )
105+ cla .log .debug (f"{ fn } - session: { session } " )
104106
105- # we can already have token in the session
107+ # We can already have token in the session
106108 if "github_oauth2_token" in session :
107- cla .log .debug (f"{ fn } - Using existing session GitHub OAuth2 token" )
109+ cla .log .debug (f"{ fn } - using existing session GitHub OAuth2 token" )
108110 user = self .get_or_create_user (request )
109- cla .log .debug (f"{ fn } - loaded user { user .to_dict ()} " )
110- return user
111-
112- # if not then we can either request a new OAuth2 GitHub authentication or user code & state from GitHub to create a session
113- if code and state :
114- session_state = None
115- if "github_oauth2_state" in session :
116- session_state = session ["github_oauth2_state" ]
117- cla .log .warning (f"{ fn } - github_oauth2_state in current session: { session_state } " )
111+ if user is None :
112+ cla .log .debug (f"{ fn } - cannot find user, returning HTTP 404 status" )
118113 else :
119- cla .log .warning (f"{ fn } - github_oauth2_state not set in current session" )
120- if session_state and state != session_state :
121- cla .log .warning (f"{ fn } - invalid GitHub OAuth2 state { session_state } expecting { state } " )
122- raise falcon .HTTPBadRequest (f"Invalid OAuth2 state: '{ session_state } ' != '{ state } '" )
123- token_url = cla .conf ["GITHUB_OAUTH_TOKEN_URL" ]
124- client_id = os .environ ["GH_OAUTH_CLIENT_ID" ]
125- client_secret = os .environ ["GH_OAUTH_SECRET" ]
126- try :
127- token = self ._fetch_token (client_id , state , token_url , client_secret , code )
128- except Exception as err :
129- cla .log .warning (f"{ fn } - GitHub OAuth2 error: { err } . Likely bad or expired code." )
130- raise falcon .HTTPBadRequest ("OAuth2 code is invalid or expired" )
131- cla .log .debug (f"{ fn } - oauth2 token received for state { state } : { token } - storing token in session" )
132- session ["github_oauth2_token" ] = token
133- user = self .get_or_create_user (request )
134- cla .log .debug (f"{ fn } - loaded user { user .to_dict ()} " )
114+ cla .log .debug (f"{ fn } - loaded user { user .to_dict ()} returning HTTP 200 status" )
135115 return user
136- else :
137- cla .log .debug (f"{ fn } - No existing GitHub OAuth2 token - building authorization url and state" )
138- authorization_url , new_state = self .get_github_oauth2_redirect_url_and_state (redirect_url )
139- cla .log .debug (f"{ fn } - Obtained GitHub OAuth2 state from authorization - storing state in the session..." )
140- session ["github_oauth2_state" ] = new_state
141- cla .log .debug (f"{ fn } - GitHub OAuth2 request with state { new_state } - sending user to { authorization_url } " )
142- if redirect :
143- raise falcon .HTTPFound (authorization_url )
144- else :
145- return { "redirect_url" : authorization_url }
116+
117+ authorization_url , csrf_token = self .get_authorization_url_and_state (None , None , None , ["user:email" ], state = 'user-from-session' )
118+ cla .log .debug (f"{ fn } - obtained GitHub OAuth2 state from authorization - storing CSRF token in the session..." )
119+ session ["github_oauth2_state" ] = csrf_token
120+ cla .log .debug (f"{ fn } - GitHub OAuth2 request with CSRF token { csrf_token } - sending user to { authorization_url } " )
121+ cla .log .debug (f"{ fn } - redirecting by returning 302 and redirect URL" )
122+ # We must redirect to GitHub OAuth app for authentication, it will return you to /v2/github/installation which will handle returning user data
123+ raise falcon .HTTPFound (authorization_url )
146124
147125 def sign_request (self , installation_id , github_repository_id , change_request_id , request ):
148126 """
@@ -204,26 +182,7 @@ def _get_request_session(self, request) -> dict: # pylint: disable=no-self-use
204182
205183 return session
206184
207- def get_github_oauth2_redirect_url_and_state (self , redirect_uri ):
208- fn = "github_models.get_github_oauth2_redirect_url_and_state"
209- github_oauth_url = cla .conf ["GITHUB_OAUTH_AUTHORIZE_URL" ]
210- github_oauth_client_id = os .environ ["GH_OAUTH_CLIENT_ID" ]
211- if not redirect_uri :
212- redirect_uri = os .environ .get ("CLA_API_BASE" , "" ).strip () + "/v2/user-from-session"
213-
214- scope = ["user:email" ]
215- cla .log .debug (
216- f"{ fn } - Directing user to the github authorization url: { github_oauth_url } via "
217- f"our github installation flow: { redirect_uri } "
218- f"using the github oauth client id: { github_oauth_client_id [0 :5 ]} "
219- f"with scope: { scope } "
220- )
221-
222- return self ._get_authorization_url_and_state (
223- client_id = github_oauth_client_id , redirect_uri = redirect_uri , scope = scope , authorize_url = github_oauth_url
224- )
225-
226- def get_authorization_url_and_state (self , installation_id , github_repository_id , pull_request_number , scope ):
185+ def get_authorization_url_and_state (self , installation_id , github_repository_id , pull_request_number , scope , state = None ):
227186 """
228187 Helper method to get the GitHub OAuth2 authorization URL and state.
229188
@@ -254,14 +213,14 @@ def get_authorization_url_and_state(self, installation_id, github_repository_id,
254213 )
255214
256215 return self ._get_authorization_url_and_state (
257- client_id = github_oauth_client_id , redirect_uri = redirect_uri , scope = scope , authorize_url = github_oauth_url
216+ client_id = github_oauth_client_id , redirect_uri = redirect_uri , scope = scope , authorize_url = github_oauth_url , state = state ,
258217 )
259218
260- def _get_authorization_url_and_state (self , client_id , redirect_uri , scope , authorize_url ):
219+ def _get_authorization_url_and_state (self , client_id , redirect_uri , scope , authorize_url , state = None ):
261220 """
262221 Mockable helper method to do the fetching of the authorization URL and state from GitHub.
263222 """
264- return cla .utils .get_authorization_url_and_state (client_id , redirect_uri , scope , authorize_url )
223+ return cla .utils .get_authorization_url_and_state (client_id , redirect_uri , scope , authorize_url , state )
265224
266225 def oauth2_redirect (self , state , code , request ): # pylint: disable=too-many-arguments
267226 """
@@ -283,8 +242,38 @@ def oauth2_redirect(self, state, code, request): # pylint: disable=too-many-arg
283242 cla .log .warning (f"{ fn } - github_oauth2_state not set in current session" )
284243
285244 if state != session_state :
286- cla .log .warning (f"{ fn } - invalid GitHub OAuth2 state { session_state } expecting { state } " )
287- raise falcon .HTTPBadRequest ("Invalid OAuth2 state" , state )
245+ # Eventually handle user-from-session API callback
246+ try :
247+ state_data = json .loads (base64 .urlsafe_b64decode (state .encode ()).decode ())
248+ except (ValueError , json .JSONDecodeError , binascii .Error ):
249+ cla .log .warning (f"{ fn } - failed to decode state: { state } , error: { err } " )
250+ raise falcon .HTTPBadRequest ("Invalid OAuth2 state" , state )
251+ state_token = state_data ["csrf" ]
252+ value = state_data ["state" ]
253+ if value != "user-from-session" :
254+ cla .log .warning (f"{ fn } - invalid GitHub OAuth2 state { session_state } expecting { state } , value: { value } " )
255+ raise falcon .HTTPBadRequest ("Invalid OAuth2 state" , state )
256+ if state_token != session_state :
257+ cla .log .warning (f"{ fn } - invalid GitHub OAuth2 state { session_state } expecting { state_token } while handling user-from-session callback" )
258+ raise falcon .HTTPBadRequest (f"Invalid OAuth2 state" )
259+ cla .log .debug (f"handling user-from-session callback" )
260+ token_url = cla .conf ["GITHUB_OAUTH_TOKEN_URL" ]
261+ client_id = os .environ ["GH_OAUTH_CLIENT_ID" ]
262+ cla .log .debug (f"{ fn } - using client ID { client_id } " )
263+ client_secret = os .environ ["GH_OAUTH_SECRET" ]
264+ try :
265+ token = self ._fetch_token (client_id , state , token_url , client_secret , code )
266+ except Exception as err :
267+ cla .log .warning (f"{ fn } - GitHub OAuth2 error: { err } . Likely bad or expired code, returning HTTP 404 state." )
268+ raise falcon .HTTPBadRequest ("OAuth2 code is invalid or expired" )
269+ cla .log .debug (f"{ fn } - oauth2 token received for state { state } : { token } - storing token in session" )
270+ session ["github_oauth2_token" ] = token
271+ user = self .get_or_create_user (request )
272+ if user is None :
273+ cla .log .debug (f"{ fn } - cannot find user, returning HTTP 404 status" )
274+ else :
275+ cla .log .debug (f"{ fn } - loaded user { user .to_dict ()} returning HTTP 200 status" )
276+ return user .to_dict ()
288277
289278 # Get session information for this request.
290279 cla .log .debug (f"{ fn } - attempting to fetch OAuth2 token for state { state } " )
@@ -1884,7 +1873,7 @@ def __init__(self, oauth2_token=False):
18841873 def _get_github_client (self , username , token ):
18851874 return MockGitHubClient (username , token )
18861875
1887- def _get_authorization_url_and_state (self , client_id , redirect_uri , scope , authorize_url ):
1876+ def _get_authorization_url_and_state (self , client_id , redirect_uri , scope , authorize_url , state = None ):
18881877 authorization_url = "http://authorization.url"
18891878 state = "random-state-here"
18901879 return authorization_url , state
@@ -1895,7 +1884,7 @@ def _fetch_token(self, client_id, state, token_url, client_secret, code): # pyl
18951884 def _get_request_session (self , request ) -> dict :
18961885 if self .oauth2_token :
18971886 return {
1898- "github_oauth2_token" : "random-token" , # LG: comment this out to see how Mock class woudl attempt to fetch GitHub token using state & code
1887+ "github_oauth2_token" : "random-token" , # LG: comment this out to see how Mock class would attempt to fetch GitHub token using state & code
18991888 "github_oauth2_state" : "random-state" ,
19001889 "github_origin_url" : "http://github/origin/url" ,
19011890 "github_installation_id" : 1 ,
0 commit comments