Skip to content

Commit f24718f

Browse files
authored
Adds support for expiry of bigquery access token. (Kaggle#501)
* Adds support for expiry of bigquery access token. * Updates expiry of access token in tests. * Sets expiry to a datetime instead of seconds to expiry. * Add more typing information for kaggle_secrets.py
1 parent 3adbbe3 commit f24718f

File tree

4 files changed

+28
-8
lines changed

4 files changed

+28
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.pyc
22
.idea/
33
.vscode
4+
.mypy_cache

patches/kaggle_gcp.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class KaggleKernelCredentials(credentials.Credentials):
1616
def refresh(self, request):
1717
try:
1818
client = UserSecretsClient()
19-
self.token = client.get_bigquery_access_token()
19+
self.token, self.expiry = client.get_bigquery_access_token()
2020
except Exception as e:
2121
raise RefreshError('Unable to refresh access token.') from e
2222

patches/kaggle_secrets.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import json
88
import os
99
import urllib.request
10+
from urllib.error import HTTPError
11+
from typing import Tuple, Optional
12+
from datetime import datetime, timedelta
1013

1114
_KAGGLE_DEFAULT_URL_BASE = "https://www.kaggle.com"
1215
_KAGGLE_URL_BASE_ENV_VAR_NAME = "KAGGLE_URL_BASE"
@@ -37,7 +40,7 @@ def __init__(self):
3740
f'but none found in environment variable {_KAGGLE_USER_SECRETS_TOKEN_ENV_VAR_NAME}')
3841
self.headers = {'Content-type': 'application/json'}
3942

40-
def _make_post_request(self, data):
43+
def _make_post_request(self, data: dict) -> dict:
4144
url = f'{self.url_base}{self.GET_USER_SECRET_ENDPOINT}'
4245
request_body = dict(data)
4346
request_body['JWE'] = self.jwt_token
@@ -50,18 +53,29 @@ def _make_post_request(self, data):
5053
raise BackendError(
5154
'Unexpected response from the service.')
5255
return response_json['result']
53-
except urllib.error.HTTPError as e:
56+
except HTTPError as e:
5457
if e.code == 401 or e.code == 403:
5558
raise CredentialError(f'Service responded with error code {e.code}.'
5659
' Please ensure you have access to the resource.') from e
5760
raise BackendError('Unexpected response from the service.') from e
5861

59-
def get_bigquery_access_token(self):
62+
def get_bigquery_access_token(self) -> Tuple[str, Optional[datetime]]:
63+
"""Retrieves BigQuery access token information from the UserSecrets service.
64+
65+
This returns the token for the current kernel as well as its expiry (abs time) if it
66+
is present.
67+
Example usage:
68+
client = UserSecretsClient()
69+
token, expiry = client.get_bigquery_access_token()
70+
"""
6071
request_body = {
6172
'Target': self.BIGQUERY_TARGET_VALUE
6273
}
6374
response_json = self._make_post_request(request_body)
6475
if 'secret' not in response_json:
6576
raise BackendError(
6677
'Unexpected response from the service.')
67-
return response_json['secret']
78+
# Optionally return expiry if it is set.
79+
expiresInSeconds = response_json.get('expiresInSeconds')
80+
expiry = datetime.utcnow() + timedelta(seconds=expiresInSeconds) if expiresInSeconds else None
81+
return response_json['secret'], expiry

tests/test_user_secrets.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from http.server import BaseHTTPRequestHandler, HTTPServer
66
from test.support import EnvironmentVarGuard
77
from urllib.parse import urlparse
8+
from datetime import datetime, timedelta
9+
import mock
810

911
from google.auth.exceptions import DefaultCredentialsError
1012
from google.cloud import bigquery
@@ -50,7 +52,7 @@ def set_request(self):
5052

5153
def get_response(self):
5254
if success:
53-
return {'result': {'secret': secret, 'secretType': 'refreshToken', 'secretProvider': 'google'}, 'wasSuccessful': "true"}
55+
return {'result': {'secret': secret, 'secretType': 'refreshToken', 'secretProvider': 'google', 'expiresInSeconds': 3600}, 'wasSuccessful': "true"}
5456
else:
5557
return {'wasSuccessful': "false"}
5658

@@ -82,13 +84,16 @@ def test_no_token_fails(self):
8284
with self.assertRaises(CredentialError):
8385
client = UserSecretsClient()
8486

85-
def test_get_access_token_succeeds(self):
87+
@mock.patch('kaggle_secrets.datetime')
88+
def test_get_access_token_succeeds(self, mock_dt):
8689
secret = '12345'
90+
now = datetime(1993, 4, 24)
91+
mock_dt.utcnow = mock.Mock(return_value=now)
8792

8893
def call_get_access_token():
8994
client = UserSecretsClient()
9095
secret_response = client.get_bigquery_access_token()
91-
self.assertEqual(secret_response, secret)
96+
self.assertEqual(secret_response, (secret, now + timedelta(seconds=3600)))
9297
self._test_client(call_get_access_token,
9398
'/requests/GetUserSecretRequest', {'Target': 1, 'JWE': _TEST_JWT}, secret=secret)
9499

0 commit comments

Comments
 (0)