Skip to content
This repository has been archived by the owner on Oct 15, 2019. It is now read-only.

Commit

Permalink
refactor(auth): update auth classes from gcloud-aio (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
gunjanswitchco authored and TheKevJames committed Jul 1, 2019
1 parent bf6af90 commit 6feecd0
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 76 deletions.
10 changes: 3 additions & 7 deletions .pre-commit-config.py27.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,19 @@ repos:
args:
- --max-line-length=79
- --ignore-imports=yes
- -d bad-continuation
- -d fixme
- -d import-error
- -d invalid-name
- -d locally-disabled
- -d missing-docstring
- -d too-few-public-methods
- -d too-many-arguments
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.1.6
hooks:
- id: remove-crlf
- id: remove-tabs
- repo: https://github.com/asottile/reorder_python_imports
rev: v1.5.0
hooks:
- id: reorder-python-imports
args: [--py26-plus]
- repo: https://github.com/asottile/yesqa
rev: v0.0.11
hooks:
Expand All @@ -60,5 +57,4 @@ repos:
rev: v1.4.0
hooks:
- id: python-no-eval
- id: python-no-log-warn
- id: python-use-type-annotations
# - id: python-no-log-warn TODO: fix the hook to not catch 'warnings.warn' and uncomment once done
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ repos:
args:
- --max-line-length=79
- --ignore-imports=yes
- -d duplicate-code
- -d fixme
- -d import-error
- -d invalid-name
- -d locally-disabled
- -d missing-docstring
- -d too-few-public-methods
- -d too-many-arguments
- -d useless-object-inheritance # necessary for Python 2 compatibility
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.1.6
Expand Down Expand Up @@ -73,8 +75,7 @@ repos:
rev: v1.4.0
hooks:
- id: python-no-eval
- id: python-no-log-warn
- id: python-use-type-annotations
# - id: python-no-log-warn TODO: fix the hook to not catch 'warnings.warn' and uncomment once done
- id: rst-backticks
- repo: https://github.com/Lucas-C/pre-commit-hooks-markup
rev: v1.0.0
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ RESTful Google Cloud Client Library for Python
==============================================

This project is a collection of Google Cloud client libraries for the REST-only
APIs; its *raison d'être* is to implement a simple `CloudTasks API`_ as well as
APIs; its *raison d'etre* is to implement a simple `CloudTasks API`_ as well as
a more abstract TaskManager.

If you don't need to support Python 2, you probably want to use `gcloud-aio`_,
Expand Down
5 changes: 4 additions & 1 deletion gcloud/rest/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from pkg_resources import get_distribution
__version__ = get_distribution('gcloud-rest').version

from gcloud.rest.auth.iam import IamClient
from gcloud.rest.auth.token import Token
from gcloud.rest.auth.utils import decode
from gcloud.rest.auth.utils import encode


__all__ = ['__version__', 'Token']
__all__ = ['__version__', 'IamClient', 'Token', 'decode', 'encode']
147 changes: 147 additions & 0 deletions gcloud/rest/auth/iam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import json
import threading
from typing import Dict # pylint: disable=unused-import
from typing import List # pylint: disable=unused-import
from typing import Optional # pylint: disable=unused-import
from typing import Union # pylint: disable=unused-import

import requests

from .token import Token
from .token import Type
from .utils import encode


API_ROOT_IAM = 'https://iam.googleapis.com/v1'
API_ROOT_IAM_CREDENTIALS = 'https://iamcredentials.googleapis.com/v1'
SCOPES = ['https://www.googleapis.com/auth/iam']


class IamClient(object):
def __init__(self,
service_file=None, # type: Optional[str]
session=None, # type: Optional[requests.Session]
google_api_lock=None, # type: Optional[threading.RLock]
token=None # type: Optional[Token]
):
# type: (...) -> None
self.session = session
self.google_api_lock = google_api_lock or threading.RLock()
self.token = token or Token(service_file=service_file,
session=session, scopes=SCOPES)

if self.token.token_type != Type.SERVICE_ACCOUNT:
raise TypeError('IAM Credentials Client is only valid for use'
' with Service Accounts')

def headers(self):
# type: () -> Dict[str, str]
token = self.token.get()
return {
'Authorization': 'Bearer {}'.format(token),
}

@property
def service_account_email(self):
# type: () -> Optional[str]
return self.token.service_data.get('client_email')

# https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts.keys/get
def get_public_key(self,
key_id=None, # type: Optional[str]
key=None, # type: Optional[str]
service_account_email=None, # type: Optional[str]
project=None, # type: Optional[str]
session=None, # type: requests.Session
timeout=10 # type: int
):
# type: (...) -> Dict[str, str]
service_account_email = (service_account_email
or self.service_account_email)
project = project or self.token.get_project()

if not key_id and not key:
raise ValueError('get_public_key must have either key_id or key')

if not key:
key = 'projects/{}/serviceAccounts/{}/keys/{}' \
.format(project, service_account_email, key_id)

url = '{}/{}?publicKeyType=TYPE_X509_PEM_FILE'.format(
API_ROOT_IAM, key)
headers = self.headers()

if not self.session:
self.session = requests.Session()

session = session or self.session
with self.google_api_lock:
resp = session.get(url, headers=headers, timeout=timeout)
resp.raise_for_status()
return resp.json()

# https://cloud.google.com/iam/reference/rest/v1/projects.serviceAccounts.keys/list
def list_public_keys(self,
service_account_email=None, # type: Optional[str]
project=None, # type: Optional[str]
session=None, # type: requests.Session
timeout=10 # type: int
):
# type: (...) -> List[Dict[str, str]]
service_account_email = (service_account_email
or self.service_account_email)
project = project or self.token.get_project()

url = ('{}/projects/{}/serviceAccounts/'
'{}/keys').format(API_ROOT_IAM, project, service_account_email)

headers = self.headers()

if not self.session:
self.session = requests.Session()

session = session or self.session
with self.google_api_lock:
resp = session.get(url, headers=headers, timeout=timeout)
resp.raise_for_status()
return resp.json().get('keys', [])

# https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/signBlob
def sign_blob(self,
payload, # type: Optional[Union[str, bytes]]
service_account_email=None, # type: Optional[str]
delegates=None, # type: Optional[list]
session=None, # type: requests.Session
timeout=10 # type: int
):
# type: (...) -> Dict[str, str]
service_account_email = (
service_account_email or self.service_account_email)
if not service_account_email:
raise TypeError('sign_blob must have a valid '
'service_account_email')

resource_name = 'projects/-/serviceAccounts/{}'.format(
service_account_email)
url = '{}/{}:signBlob'.format(API_ROOT_IAM_CREDENTIALS, resource_name)

json_str = json.dumps({
'delegates': delegates or [resource_name],
'payload': encode(payload).decode('utf-8'),
})

headers = self.headers()
headers.update({
'Content-Length': str(len(json_str)),
'Content-Type': 'application/json',
})

if not self.session:
self.session = requests.Session()

session = session or self.session
with self.google_api_lock:
resp = session.post(url, data=json_str,
headers=headers, timeout=timeout)
resp.raise_for_status()
return resp.json()
Loading

0 comments on commit 6feecd0

Please sign in to comment.