def get_repos(self, account, page_url=None, sess=None, refresh=True): if not page_url: page_url = self.api_repos_path.format( user_id=urlquote(account.user_id), user_name=urlquote(account.user_name or ''), ) r = self.api_get(account.domain, page_url, sess=sess) repos, count, pages_urls = self.api_paginator(r, self.api_parser(r)) repos = [ self.extract_repo_info(repo, account.domain) for repo in repos ] if repos and repos[0].owner_id != account.user_id: # https://hackerone.com/reports/452920 if not refresh: raise TokenExpiredError() from liberapay.models.account_elsewhere import UnableToRefreshAccount try: account = account.refresh_user_info() except UnableToRefreshAccount: raise TokenExpiredError() # Note: we can't pass the page_url below, because it contains the old user_name return self.get_repos(account, page_url=None, sess=sess, refresh=False) if count == -1 and hasattr(self, 'x_repos_count'): count = self.x_repos_count(None, account.extra_info, -1) return repos, count, pages_urls
def get_repos(self, account, page_url=None, sess=None, refresh=True): if not page_url: page_url = self.api_repos_path.format( user_id=urlquote(account.user_id), user_name=urlquote(account.user_name or ''), ) if not getattr(self.api_repos_path, 'use_session', True): sess = None r = self.api_get(account.domain, page_url, sess=sess) repos, count, pages_urls = self.api_paginator(r, self.api_parser(r)) repos = [self.extract_repo_info(repo) for repo in repos] if '{user_id}' in self.api_repos_path: for repo in repos: if repo.owner_id is None: repo.owner_id = account.user_id elif '{user_name}' in self.api_repos_path and repos and repos[ 0].owner_id != account.user_id: # https://hackerone.com/reports/452920 if not refresh: raise TokenExpiredError() from liberapay.models.account_elsewhere import UnableToRefreshAccount try: account = account.refresh_user_info() except (ElsewhereError, UnableToRefreshAccount): raise TokenExpiredError() # Note: we can't pass the page_url below, because it contains the old user_name return self.get_repos(account, page_url=None, sess=sess, refresh=False) return repos, count, pages_urls
def test_request_refresh_fail(self, mock_request, mock_refresh_token): """Test making a request when token fails to be refreshed.""" mock_request.side_effect = [TokenExpiredError(), TokenExpiredError()] mock_refresh_token.return_value = None with self.assertRaises(TokenExpiredError): self.client.get('https://getfilehub.com/api/endpoint/')
def request(self, path, method='get', data=None, files=None, **kwargs): """ General method for submitting an API request :param path: the API request path :param method: the http request method that should be used :param data: dictionary of request data that should be used for post/put requests :param files: dictionary of file information when the API accepts file uploads as input :param kwargs: optional keyword arguments to be relayed along as request parameters :return: """ if data and not files: headers = self.json_headers request_data = json.dumps(data) else: headers = self.base_headers request_data = data request_url = "{0}{1}".format(self.url_base, path) request_method = getattr(self.api_client, method) try: response = request_method(request_url, data=request_data, files=files, params=kwargs, headers=headers) self.status_code = response.status_code if self.status_code == 401: # if TokenExpiredError is not raised but it should have been, we'll raise it explicitly here # https://github.com/oauthlib/oauthlib/pull/506 could cause this code path to be followed. # this special handling can likely be removed once https://github.com/oauthlib/oauthlib/pull/506 # rolls into a new oauthlib release raise TokenExpiredError( 'Access Token has expired. Please, re-authenticate. ' 'Use auto_refresh=True to have your client auto refresh') return response.json() except (TokenUpdated, TokenExpiredError): if self.auto_refresh: # Re-fetch token and try request again self.fetch_access_token(self.code) return request_method(request_url, data=request_data, files=files, params=kwargs, headers=headers).json() else: self.status_code = 401 # UNAUTHORIZED raise TokenExpiredError( 'Access Token has expired. Please, re-authenticate. ' 'Use auto_refresh=True to have your client auto refresh')
def test_request_refresh(self, mock_request, mock_refresh_token): """Test making a request when token needs to be refreshed.""" callback = Mock() response = Mock(spec=requests.Response) response.status_code = 200 mock_request.side_effect = [TokenExpiredError(), response] new_token = { 'access_token': '789', 'refresh_token': '456', 'expires_in': '3600', } mock_refresh_token.return_value = new_token client = Client( client_id='abc123', client_secret='1234', uid='abcd', url='https://getfilehub.com/', refresh_token_callback=callback, ) r = client.get('https://getfilehub.com/api/endpoint/') self.assertEqual(200, r.status_code) self.assertTrue(callback.called) self.assertEqual(new_token, callback.call_args[0][0])
def test_given_request_return_oauth_token_expired_error_then_except_sequoia_token_expired_error_is_raised( self, mock_oauth_request, mock_basic_auth): mock_basic_auth.return_value = 'MyBasicAuth' mock_oauth_request.side_effect = TokenExpiredError('Token expired') auth = ClientGrantAuth('user', 'pass', 'http://identity') with pytest.raises(error.TokenExpiredError): auth.session.request('GET', 'http://mydata')
def get_url(*args): """ Method that simulates the get. When the response is 401 it means that token expired and the dispatcher requires another token """ res = MagicMock() if args[1].startswith(CONSENT_MANAGER_URI) and self.counter == 0: self.counter += 1 raise TokenExpiredError() else: res.status_code = 200 return res
def _post(self, endpoint, data): """Get data as dictionary from an endpoint.""" res = self._request("post", endpoint, data=json.dumps(data)) if not res.content: return {} try: res = res.json() except json.JSONDecodeError: raise ValueError("Cannot parse {} as JSON".format(res)) if "error" in res: if "code" in res['error'] and res['error']['code'] == 401: raise TokenExpiredError() else: raise SDMError(res["error"]) return res
def test_access_token_refreshed_for_token_expired(self): """ Tests that, when the response is 401 (Unauthorized), another token is created and the call is perfomed again """ with patch('hgw_common.models.OAuth2Session', MockOAuth2Session): MockOAuth2Session.RESPONSES = [TokenExpiredError(), 200] proxy = OAuth2SessionProxy(self.service_url, self.client_id, self.client_secret) m = proxy._session first_token = m.token['access_token'] # m.token['expires_at'] = m.token['expires_at'] - 36001 proxy.get("/fake_url/1/") second_token = m.token['access_token'] self.assertEqual(len(m.get.call_args_list), 2) # Number of calls self.assertEqual(len(m.fetch_token.call_args_list), 2) # Number of calls m.get.assert_has_calls([call('/fake_url/1/'), call('/fake_url/1/')]) self.assertEqual(AccessToken.objects.count(), 1) self.assertNotEquals(first_token, second_token)
def monzoapi_get_response(self, method, endpoint, params=None, data=None): """ Helper method to handle HTTP requests and catch API errors :param method: valid HTTP method :type method: str :param endpoint: API endpoint :type endpoint: str :param params: extra parameters passed with the request :type params: dict :returns: API response :rtype: Response """ url = urljoin(self.api_url, endpoint) try: if method in ['post']: response = getattr(self._session, method)(url, params=params, data=data) elif method in ['put']: response = getattr(self._session, method)(url, data=data) else: response = getattr(self._session, method)(url, params=params) if response.status_code == 401: raise TokenExpiredError() except TokenExpiredError: # For some reason 'requests-oauthlib' automatic token refreshing # doesn't work so we do it here semi-manually self._refresh_oath_token() self._session = OAuth2Session( client_id=self._client_id, token=self._token, ) self._get_response(method, endpoint, params, data) if response.status_code != requests.codes.ok: raise MonzoAPIError("Something went wrong: {}".format(response.json())) return response
def get_url(*args): """ Method that simulates the get. When the response is 401 it means that token expired and the dispatcher requires another token """ res = MagicMock() if args[1].startswith(HGW_FRONTEND_URI) and self.counter == 0: self.counter += 1 raise TokenExpiredError() else: res.status_code = 200 if args[1].startswith(CONSENT_MANAGER_URI): # simulates the consent manager with minimum data just to arrive to the point of # getting the hgw_frontend token res.json.return_value = { 'destination': DESTINATION, 'status': 'AC' } return res
def make_request(self, method, url, *args, **kwargs): """ Make HTTP(S) request. Make best effort to detect token expiry. Two mechanisms are used for expiry detection: 1) `oauth_provider`: check expiration datetime on the token itself. This is baked into the call to 3rd party's `super().request`. Other tokens are not guaranteed to contain this information. 2) Salesforce: interpret 400 status code along with `invalid_grant` error as token expiry Raises: TokenExpiredError: upon token expiry detection """ # 1 oauth_provider resp = super(OAuth2Client, self).request(method, url, *args, **kwargs) # 2 salesforce if self.app.authorization_grant_type == Application.GRANT_JWT_BEARER and is_invalid_jwt_grant(resp): raise TokenExpiredError(description="400 status code received in JWT flow. Assuming expired token.") return resp
def request(self, path, method='get', data=None, files=None, **kwargs): """ General method for submitting an API request :param path: the API request path :param method: the http request method that should be used :param data: dictionary of request data that should be used for post/put requests :param files: dictionary of file information when the API accepts file uploads as input :param kwargs: optional keyword arguments to be relayed along as request parameters :return: """ if data and not files: headers = self.json_headers request_data = json.dumps(data) else: headers = self.base_headers request_data = data request_url = "{0}{1}".format(self.url_base, path) request_method = getattr(self.api_client, method) try: response = request_method(request_url, data=request_data, files=files, params=kwargs, headers=headers) self.status_code = response.status_code return response.json() except (TokenUpdated, TokenExpiredError): if self.auto_refresh: # Re-fetch token and try request again self.fetch_access_token(self.code) return request_method(request_url, data=request_data, files=files, params=kwargs, headers=headers).json() else: self.status_code = 401 # UNAUTHORIZED raise TokenExpiredError( 'Access Token has expired. Please, re-authenticate. ' 'Use auto_refresh=True to have your client auto refresh')
def authorize(self, auth): """""" # check for access token (dict) if isinstance(auth, dict): # check if access token is still valid expires_at = datetime.fromtimestamp(auth.get("expires_at")) now = datetime.now() if expires_at > now: self.access_token = auth self.session.headers[ "Authorization"] = f"Bearer {self.access_token.get('access_token')}" self.metadata = self._metadata() return "Authorized!" else: raise TokenExpiredError("Access token expired!") # check for client credentials (tuple) if isinstance(auth, tuple): client_id, client_secret = auth # fetch new access token token_url = f"{self.base_url}/oauth/access_token/" auth = HTTPBasicAuth(client_id, client_secret) client = BackendApplicationClient(client_id=client_id) session = OAuth2Session(client=client) token_dict = session.fetch_token(token_url=token_url, auth=auth) self.access_token = token_dict self.session.headers[ "Authorization"] = f"Bearer {self.access_token.get('access_token')}" self.metadata = self._metadata() return "Authorized!" else: raise InvalidClientError( "You must provide a valid access token file or client credentials." )
rsps.add(responses.POST, re.compile(".+google.+"), status=200 ) with login_disabled_app.test_client() as client: response = client.get( "/auth/logout" ) assert response.status_code == 302 @pytest.mark.parametrize( "error", [ (TokenExpiredError()), (HTTPException()), (Exception()) ] ) def test_logout_controller_token_exception(monkeypatch, mocker, login_disabled_app, error): storage = MemoryStorage({"access_token": "fake-token"}) monkeypatch.setattr(bplogin, "storage", storage) mocker.patch("flask_login.utils._get_user", return_value=MagicMock(provider="google", autospec=True)) with responses.RequestsMock() as rsps: rsps.add(responses.POST, re.compile(".+google.+"), body=error ) with login_disabled_app.test_client() as client:
def __handle_expired_token(self, response): if ("error" in response and response["error"] == "EXPIRED TOKEN"): raise TokenExpiredError(response)