Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GitLab] Implement refresh_user() to keep auth_state updated #490

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 58 additions & 8 deletions oauthenticator/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Custom Authenticator to use GitLab OAuth with JupyterHub
"""
import os
import time
import warnings
from urllib.parse import quote

Expand Down Expand Up @@ -128,7 +129,54 @@ async def authenticate(self, handler, data=None):
grant_type="authorization_code",
redirect_uri=self.get_callback_url(handler),
)
return await self._oauth_call(handler, params, data)

async def refresh_user(self, user, handler=None):

# Renew the Access Token with a valid Refresh Token
#
# Without that custom configuration, the Gitlab access token gets
# outdated while the user can still connect to JupyterHub, that leads to
# forbidden Git interactions with Gitlab within Notebook.
#
# See:
# - https://github.com/gitlabhq/gitlabhq/blob/HEAD/doc/api/oauth2.md
# - https://github.com/jupyterhub/oauthenticator/pull/490

auth_state = await user.get_auth_state()
if not auth_state:
self.log.info(
"No auth_state found for user %s refresh, full authentication needed",
user,
)
return False

# In seconds, ex : 1607635748
created_at = auth_state.get('created_at', 0)
# In seconds, ex : 7200
expires_in = auth_state.get('expires_in', 0)
is_expired = created_at + expires_in - time.time() < 0
if not is_expired:
# Access token still valid, function returns True
self.log.info(
"access_token still valid for user %s, refresh skipped",
user,
)
return True

# GitLab specifies a POST request yet requires URL parameters
params = dict(
client_id=self.client_id,
client_secret=self.client_secret,
grant_type="refresh_token",
refresh_token=auth_state['refresh_token'],
)
return await self._oauth_call(handler, params)

async def _oauth_call(self, handler, params, data=None):
"""
Common logic shared by authenticate() and refresh_user()
"""
validate_server_cert = self.validate_server_cert

url = url_concat("%s/oauth/token" % self.gitlab_url, params)
Expand All @@ -141,8 +189,8 @@ async def authenticate(self, handler, data=None):
body='', # Body is required for a POST...
)

resp_json = await self.fetch(req, label="getting access token")
access_token = resp_json['access_token']
oauth_resp = await self.fetch(req, label="getting access token")
access_token = oauth_resp['access_token']

# memoize gitlab version for class lifetime
if self.gitlab_version is None:
Expand All @@ -156,11 +204,10 @@ async def authenticate(self, handler, data=None):
validate_cert=validate_server_cert,
headers=_api_headers(access_token),
)
resp_json = await self.fetch(req, label="getting gitlab user")
gitlab_user = await self.fetch(req, label="getting gitlab user")

username = resp_json["username"]
user_id = resp_json["id"]
is_admin = resp_json.get("is_admin", False)
username = gitlab_user["username"]
user_id = gitlab_user["id"]

# Check if user is a member of any allowed groups or projects.
# These checks are performed here, as it requires `access_token`.
Expand Down Expand Up @@ -189,10 +236,13 @@ async def authenticate(self, handler, data=None):
):
return {
'name': username,
'auth_state': {'access_token': access_token, 'gitlab_user': resp_json},
'auth_state': {**oauth_resp, **{'gitlab_user': gitlab_user}},
}
else:
self.log.warning("%s not in group or project allowed list", username)
self.log.warning(
"%s not in group or project allowed list",
username,
)
return None

async def _get_gitlab_version(self, access_token):
Expand Down