From 1617633e12380a8c2eb95269a3658c18df0a2d4b Mon Sep 17 00:00:00 2001 From: Baghirov Feyruz Date: Thu, 24 Oct 2024 13:41:22 +0200 Subject: [PATCH] pkce implemented --- .../codechecker_server/api/authentication.py | 97 +++++++------------ ...36d79ba_create_table_for_oauth_sessions.py | 22 +++-- web/server/vue-cli/src/views/Login.vue | 8 +- .../functional/authentication/oauth_server.py | 7 +- .../authentication/test_authentication.py | 10 +- 5 files changed, 61 insertions(+), 83 deletions(-) diff --git a/web/server/codechecker_server/api/authentication.py b/web/server/codechecker_server/api/authentication.py index 46bdf6965a..e6f5556c3b 100644 --- a/web/server/codechecker_server/api/authentication.py +++ b/web/server/codechecker_server/api/authentication.py @@ -185,20 +185,19 @@ def insertDataOauth(self, state, code_verifier, provider): LOG.debug("State inserted into the database") oauth_data_id = session.query(OAuthSession) \ - .filter(OAuthSession.state == new_state.state - and - OAuthSession.expires_at == new_state.expires_at - and - OAuth2Session.code_verifier == new_state.code_verifier - and - OAuthSession.provider == new_state.provider - ) \ - .first().id + .filter( + OAuthSession.state == new_state.state + and + OAuthSession.expires_at == new_state.expires_at + and + OAuthSession.code_verifier == new_state.code_verifier + and + OAuthSession.provider == new_state.provider + ).first().id LOG.debug("FETCHED STATE ID") - LOG.debug(f"State {state} inserted successfully") - LOG.debug(f"State {state[0]} inserted successfully.") - LOG.info(f"code verifier has been inserted sucessfully {code_verifier}") + LOG.debug("State %s inserted successfully", state) + LOG.debug("verifier %s inserted sucessfully ", code_verifier) return oauth_data_id except sqlite3.Error as e: LOG.error(f"An error occurred: {e}") # added here re1move @@ -228,11 +227,8 @@ def createLink(self, provider): authorization_uri = oauth_config["oauth_authorization_uri"] redirect_uri = oauth_config["oauth_redirect_uri"] # code verifier for PKCE - stored_code_verifier = generate_token(48) - token_url = oauth_config["oauth_token_uri"] + pkce_verifier = generate_token(48) - # generated state and verifyer - LOG.info(f"State: {stored_state} and code verifier: {stored_code_verifier}") # Create an OAuth2Session instance session = OAuth2Session( client_id, @@ -248,24 +244,19 @@ def createLink(self, provider): authorization_uri, nonce=nonce, state=stored_state, - code_verifier=stored_code_verifier + code_verifier=pkce_verifier ) - # print("stored code_verifyer", stored_code_verifier) - # print(url) - # toekn = session.fetch_token( - # url=token_url, - # authorization_response=url, - # code_verifier=stored_code_verifier - # ) - # print("REASFISIGFJRG", toekn) + # Save the state and nonce to the database - oauth_data_id = self.insertDataOauth(state, stored_code_verifier, provider) + oauth_data_id = self.insertDataOauth(state=state, + code_verifier=pkce_verifier, + provider=provider) if not oauth_data_id: raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED, "OAuth data insertion failed.") - LOG.debug(f"State {state} inserted successfully with ID {oauth_data_id}") + LOG.debug("State inserted successfully with ID %s", oauth_data_id) return url + "&oauth_data_id=" + str(oauth_data_id) @timeit @@ -294,14 +285,13 @@ def performLogin(self, auth_method, auth_string): msg) elif auth_method == "oauth": - print(auth_string) provider, url = auth_string.split("@") url_new = urlparse(url) parsed_query = parse_qs(url_new.query) code = parsed_query.get("code")[0] state = parsed_query.get("state")[0] - code_challenge = parsed_query.get("code_challenge")[0] - code_challenge_method = parsed_query.get("code_challenge_method")[0] + cc = parsed_query.get("code_challenge")[0] + cc_method = parsed_query.get("code_challenge_method")[0] # code_verifier = parsed_query.get("code_verifier")[0] oauth_data_id = parsed_query.get("oauth_data_id")[0] # for code verifier from created link and original @@ -309,19 +299,14 @@ def performLogin(self, auth_method, auth_string): state_db = None provider_db = None with DBSession(self.__config_db) as session: - state_db, code_verifier_db, provider_db = session.query(OAuthSession.state, - OAuthSession.code_verifier, - OAuthSession.provider - ) \ - .filter(OAuthSession.id == oauth_data_id) \ - .first() - print("**************************") - print(state_db) - print(code_verifier_db) - print(provider_db) - print("**************************") - # if state_db != state or code_verifier_db != code_verifier or provider_db != provider: - if state_db != state or provider_db != provider or not code_verifier_db: + state_db, code_verifier_db, provider_db = \ + session.query(OAuthSession.state, + OAuthSession.code_verifier, + OAuthSession.provider) \ + .filter(OAuthSession.id == oauth_data_id) \ + .first() + + if str(state_db) != state or str(provider_db) != provider: LOG.error("State code mismatch.") raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED, @@ -359,42 +344,30 @@ def performLogin(self, auth_method, auth_string): raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED, "OAuth2Session creation failed.") - # fetch url that is not used anywhere for the purpose - # of adding code_verifier to the instance of OAuth2Session - # url_notused, state_notused = session.create_authorization_url( - # url=token_url, - # state=state_db, - # code_verifier=code_verifier_db - # ) - # print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&") - # print(url_notused) - # print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&") # FIXME: This is a workaround for the Microsoft OAuth2 provider # which doesn't correctly fetch the code from url. - # print("#############################") - # print(url_new) - # print("#############################") + # the workaround is to construct the url manually + url = url_new.scheme + "://" + url_new.netloc + url_new.path + \ "?code=" + code + "&state=" + state + \ - "&code_challenge=" + code_challenge + \ - "&code_challenge_method=" +code_challenge_method + "&code_challenge=" + cc + \ + "&code_challenge_method=" + cc_method LOG.info("URL has been constructed successfully") - print(url) token = None try: + # code_verifier_db is not supported for github provider + # if it will be fixed the code should adjust automatically token = session.fetch_token( url=token_url, - #replaced url with url_notused , subject to change!!!!!!🚧🚧🚧 authorization_response=url, - code_verifier="GGGGGHGFHT") + code_verifier=code_verifier_db) except Exception as ex: LOG.error("Token fetch failed: %s", str(ex)) raise codechecker_api_shared.ttypes.RequestFailed( codechecker_api_shared.ttypes.ErrorCode.AUTH_DENIED, "Token fetch failed.") - print(token) LOG.info("Token fetched successfully for provider: %s", provider) user_info = None diff --git a/web/server/codechecker_server/migrations/config/versions/b523b36d79ba_create_table_for_oauth_sessions.py b/web/server/codechecker_server/migrations/config/versions/b523b36d79ba_create_table_for_oauth_sessions.py index d664f74c87..e64ad1ed34 100644 --- a/web/server/codechecker_server/migrations/config/versions/b523b36d79ba_create_table_for_oauth_sessions.py +++ b/web/server/codechecker_server/migrations/config/versions/b523b36d79ba_create_table_for_oauth_sessions.py @@ -11,8 +11,6 @@ from alembic import op import sqlalchemy as sa - - # Revision identifiers, used by Alembic. revision = 'b523b36d79ba' down_revision = '00099e8bc212' @@ -23,15 +21,19 @@ def upgrade(): LOG = getLogger("migration/config") # ### commands auto generated by Alembic - please adjust! ### - op.create_table('oauth_sessions', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('provider', sa.String(), nullable=False), - sa.Column('state', sa.String(), nullable=False), - sa.Column('code_verifier', sa.String(), nullable=False), - sa.Column('expires_at', sa.DateTime(), nullable=True), - sa.PrimaryKeyConstraint('id', name=op.f('pk_oauth_sessions')) + op.create_table( + 'oauth_sessions', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('provider', sa.String(), nullable=False), + sa.Column('state', sa.String(), nullable=False), + sa.Column('code_verifier', sa.String(), nullable=False), + sa.Column('expires_at', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id', name=op.f('pk_oauth_sessions')) + ) + op.add_column( + 'auth_sessions', + sa.Column('access_token', sa.String(), nullable=True) ) - op.add_column('auth_sessions', sa.Column('access_token', sa.String(), nullable=True)) # ### end Alembic commands ### diff --git a/web/server/vue-cli/src/views/Login.vue b/web/server/vue-cli/src/views/Login.vue index 701d6988a4..ff28ae9c4b 100644 --- a/web/server/vue-cli/src/views/Login.vue +++ b/web/server/vue-cli/src/views/Login.vue @@ -143,8 +143,7 @@ export default { valid: false, providers: [], dialog: false, - on: false, - url: null + on: false }; }, @@ -216,10 +215,6 @@ export default { const baseMethod = `${baseUrlOAuthId}&code_challenge_method=${method}`; const fullUrl = `${baseMethod}&code_challenge=${code_challenge}`; - // const url = `https://example.com` + - // `/oauth2/token?grant_type=authorization_code&code=${url.code}` + - // `&redirect_uri=${baseUrlOAuthId}`; - this.$store .dispatch(LOGIN, { type: "oauth", @@ -264,7 +259,6 @@ export default { })); }).then(url => { if (url) { - this.url = url; this.success = false; this.error = false; const params = new URLSearchParams(url); diff --git a/web/tests/functional/authentication/oauth_server.py b/web/tests/functional/authentication/oauth_server.py index c0db635a30..578fbb291a 100644 --- a/web/tests/functional/authentication/oauth_server.py +++ b/web/tests/functional/authentication/oauth_server.py @@ -88,9 +88,14 @@ def login_tester(self): state = params['state'] code = query_result['code'] oauth_data_id = params['oauth_data_id'] + code_challenge = params['code_challenge'] + ccm = params['code_challenge_method'] return self.show_json({"code": code, "state": state, - "oauth_data_id": oauth_data_id}) + "oauth_data_id": oauth_data_id, + "code_challenge": code_challenge, + "code_challenge_method": ccm}) + return self.show_rejection("Invalid credentials") except IndexError: return self.show_rejection("Invalid query parameters") diff --git a/web/tests/functional/authentication/test_authentication.py b/web/tests/functional/authentication/test_authentication.py index 3f207921f1..950e1243a0 100644 --- a/web/tests/functional/authentication/test_authentication.py +++ b/web/tests/functional/authentication/test_authentication.py @@ -198,10 +198,14 @@ def try_login(self, provider, username, password): raise RequestFailed(data['error']) link = link.split('?')[0] - code, state, oauth_data_id = data['code'], data['state'], \ - data['oauth_data_id'] + code, state, oauth_data_id, code_challenge, code_challenge_method = \ + data['code'], data['state'], data['oauth_data_id'], \ + data['code_challenge'], \ + data['code_challenge_method'] auth_string = f"{link}?code={code}&state={state}" \ - f"&oauth_data_id={oauth_data_id}" + f"&oauth_data_id={oauth_data_id}" \ + f"&code_challenge_method={code_challenge_method}" \ + f"&code_challenge={code_challenge}" self.session_token = auth_client.performLogin( "oauth", provider + "@" + auth_string)