def expire_token(self, token): """ Given a token, makes a request to the authentication server to expire it immediately. This is considered a responsible way to log out a user. If you simply remove the session your application has for the user without expiring their token, the user is not _really_ logged out. :param token: The OAuth token you wish to expire :type token: str :returns: If the expiration attempt succeeded. :rtype: bool :raises ApiError: If the expiration attempt failed. """ r = requests.post(self._login_uri("/oauth/token/expire"), data={ "client_id": self.client_id, "client_secret": self.client_secret, "token": token, }) if r.status_code != 200: raise ApiError("Failed to expire token!", r) return True
def set_thumbnail(self, thumbnail): """ Sets the thumbnail for this OAuth Client. If thumbnail is bytes, uploads it as a png. Otherwise, assumes thumbnail is a path to the thumbnail and reads it in as bytes before uploading. """ headers = { "Authorization": "token {}".format(self._client.token), "Content-type": "image/png", } # TODO this check needs to be smarter - python2 doesn't do it right if not isinstance(thumbnail, bytes): with open(thumbnail, 'rb') as f: thumbnail = f.read() result = requests.put('{}/{}/thumbnail'.format( self._client.base_url, OAuthClient.api_endpoint.format(id=self.id)), headers=headers, data=thumbnail) if not result.status_code == 200: errors = [] j = result.json() if 'errors' in j: errors = [e['reason'] for e in j['errors']] raise ApiError('{}: {}'.format(result.status_code, errors), json=j) return True
def upload_attachment(self, attachment): content = None with open(attachment) as f: content = f.read() if not content: raise ValueError('Nothing to upload!') headers = { "Authorization": "token {}".format(self._client.token), "Content-type": "multipart/form-data", } result = requests.post('{}{}/attachments'.format(self._client.base_url, SupportTicket.api_endpoint.format(id=self.id)), headers=headers, files=content) if not result.status_code == 200: errors = [] j = result.json() if 'errors' in j: errors = [ e['reason'] for e in j['errors'] ] raise ApiError('{}: {}'.format(result.status_code, errors), json=j) return True
def _api_call(self, endpoint, model=None, method=None, data=None, filters=None): """ Makes a call to the linode api. Data should only be given if the method is POST or PUT, and should be a dictionary """ if not self.token: raise RuntimeError("You do not have an API token!") if not method: raise ValueError("Method is required for API calls!") if model: endpoint = endpoint.format(**vars(model)) url = '{}{}'.format(self.base_url, endpoint) headers = { 'Authorization': "Bearer {}".format(self.token), 'Content-Type': 'application/json', 'User-Agent': self._user_agent, } if filters: headers['X-Filter'] = json.dumps(filters) body = None if data is not None: body = json.dumps(data) response = method(url, headers=headers, data=body) warning = response.headers.get('Warning', None) if warning: logger.warning('Received warning from server: {}'.format(warning)) if 399 < response.status_code < 600: j = None error_msg = '{}: '.format(response.status_code) try: j = response.json() if 'errors' in j.keys(): for e in j['errors']: error_msg += '{}; '.format(e['reason']) \ if 'reason' in e.keys() else '' except: pass raise ApiError(error_msg, status=response.status_code, json=j) if response.status_code != 204: j = response.json() else: j = None # handle no response body return j
def finish_oauth(self, code): """ Given an OAuth Exchange Code, completes the OAuth exchange with the authentication server. This should be called once the user has already been directed to the login_uri, and has been sent back after successfully authenticating. For example, in `Flask`_, this might be implemented as a route like this:: @app.route("/oauth-redirect") def oauth_redirect(): exchange_code = request.args.get("code") login_client = LinodeLoginClient(client_id, client_secret) token, scopes = login_client.finish_oauth(exchange_code) # store the user's OAuth token in their session for later use # and mark that they are logged in. return redirect("/") .. _Flask: http://flask.pocoo.org :param code: The OAuth Exchange Code returned from the authentication server in the query string. :type code: str :returns: The new OAuth token, and a list of scopes the token has, when the token expires, and a refresh token that can generate a new valid token when this one is expired. :rtype: tuple(str, list) :raise ApiError: If the OAuth exchange fails. """ r = requests.post(self._login_uri("/oauth/token"), data={ "code": code, "client_id": self.client_id, "client_secret": self.client_secret }) if r.status_code != 200: raise ApiError("OAuth token exchange failed", status=r.status_code, json=r.json()) token = r.json()["access_token"] scopes = OAuthScopes.parse(r.json()["scopes"]) expiry = datetime.now() + timedelta(seconds=r.json()['expires_in']) refresh_token = r.json()['refresh_token'] return token, scopes, expiry, refresh_token
def thumbnail(self, dump_to=None): """ This returns binary data that represents a 128x128 image. If dump_to is given, attempts to write the image to a file at the given location. """ headers = {"Authorization": "token {}".format(self._client.token)} result = requests.get('{}/{}/thumbnail'.format( self._client.base_url, OAuthClient.api_endpoint.format(id=self.id)), headers=headers) if not result.status_code == 200: raise ApiError('No thumbnail found for OAuthClient {}'.format( self.id)) if dump_to: with open(dump_to, 'wb+') as f: f.write(result.content) return result.content
def refresh_oauth_token(self, refresh_token): """ Some tokens are generated with refresh tokens (namely tokens generated through an OAuth Exchange). These tokens may be renewed, or "refreshed", with the auth server, generating a new OAuth Token with a new (later) expiry. This method handles refreshing an OAuth Token using the refresh token that was generated at the time of its issuance, and returns a new OAuth token and refresh token for the same client and user. :param refresh_token: The refresh token returned for the OAuth Token we are renewing. :type refresh_token: str :returns: The new OAuth token, and a list of scopes the token has, when the token expires, and a refresh token that can generate a new valid token when this one is expired. :rtype: tuple(str, list) :raise ApiError: If the refresh fails.. """ r = requests.post(self._login_uri("/oauth/token"), data={ "grant_type": "refresh_token", "client_id": self.client_id, "client_secret": self.client_secret, "refresh_token": refresh_token, }) if r.status_code != 200: raise ApiError("Refresh failed", r) token = r.json()["access_token"] scopes = OAuthScopes.parse(r.json()["scopes"]) expiry = datetime.now() + timedelta(seconds=r.json()['expires_in']) refresh_token = r.json()['refresh_token'] return token, scopes, expiry, refresh_token