Skip to content

Commit 9f51bc2

Browse files
committed
add: httpx
1 parent adbff05 commit 9f51bc2

File tree

2 files changed

+253
-8
lines changed

2 files changed

+253
-8
lines changed

githubapps/__init__.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,14 @@
99
__author__ = 'RTa-technology'
1010
__license__ = 'MIT'
1111
__copyright__ = 'Copyright 2022 by the authors and contributors (see AUTHORS)'
12-
__version__ = '0.1.0'
12+
__version__ = '0.1.1'
1313

1414
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
1515

16-
from .adapter import Auth, Authentication
16+
from .adapter import Authentication, RequestsAuth, AiohttpAuth
1717

1818
from .errors import (BadRequest, Forbidden, HTTPException, InternalServerError,
1919
NotFound, PayloadTooLarge, QuotaExceeded,
2020
ServiceUnavailable, TooManyRequests, URITooLong)
2121

22-
__all__ = [
23-
'Adapter',
24-
'Auth'
25-
]
22+
__all__ = ['Authentication', 'RequestsAuth', 'AiohttpAuth']

githubapps/adapter.py

Lines changed: 250 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import jwt
55
import aiohttp
66
import datetime
7+
import asyncio
78
import requests
9+
import httpx
810
import re
911
import urllib.parse
1012
from cryptography.hazmat.backends import default_backend
@@ -15,7 +17,7 @@
1517
NotFound, PayloadTooLarge, QuotaExceeded,
1618
ServiceUnavailable, TooManyRequests, URITooLong)
1719

18-
__all__ = ['Authentication', 'Auth']
20+
__all__ = ['Authentication', 'RequestsAuth', 'AiohttpAuth']
1921

2022

2123
class Authentication(metaclass=ABCMeta):
@@ -80,7 +82,7 @@ def _check_status(self, status_code, response, data) -> Union[dict, list]:
8082
raise HTTPException(response, message)
8183

8284

83-
class Auth(Authentication):
85+
class RequestsAuth(Authentication):
8486
"""Researchmap authentication interface.
8587
8688
Parameters
@@ -321,3 +323,249 @@ def get_access_token(self, *, access_token_response: Optional[Union[list, dict]]
321323

322324
def get_usage(self) -> None:
323325
return None
326+
327+
328+
329+
class AiohttpAuth(Authentication):
330+
"""Researchmap authentication interface.
331+
332+
Parameters
333+
----------
334+
app_id: :class:`str`
335+
Client ID.
336+
client_secret: :class:`bytes`
337+
Client secret key.
338+
339+
Keyword Arguments
340+
-----------------
341+
iat: :class:`int`
342+
Issued at [sec].
343+
exp: :class:`int`
344+
Expire at [sec].
345+
trial: :class:`bool`
346+
Trial mode.
347+
"""
348+
349+
@property
350+
def is_trial(self) -> bool:
351+
"""Get trial mode.
352+
353+
Returns
354+
-------
355+
:class:`bool`
356+
Trial mode.
357+
"""
358+
return self.trial
359+
360+
@property
361+
def time_now(self) -> datetime.datetime:
362+
"""Get current time [aware].
363+
364+
Returns
365+
-------
366+
:class:`datetime.datetime`
367+
Current time of UTC.
368+
"""
369+
return self.now
370+
371+
@property
372+
def time_iat(self) -> datetime.datetime:
373+
"""Get issued at time [aware].
374+
375+
Returns
376+
-------
377+
:class:`datetime.datetime`
378+
Issued at time of UTC.
379+
"""
380+
return self.now - datetime.timedelta(seconds=self.iat)
381+
382+
@property
383+
def time_exp(self) -> datetime.datetime:
384+
"""Get expire at time [aware].
385+
386+
Returns
387+
-------
388+
:class:`datetime.datetime`
389+
Expire at time of UTC.
390+
"""
391+
return self.now + datetime.timedelta(seconds=self.exp)
392+
393+
@property
394+
def token(self) -> str:
395+
"""Get token.
396+
397+
Returns
398+
-------
399+
:class:`str`
400+
Token.
401+
402+
Raises
403+
------
404+
:exc:`InvalidToken`
405+
Invalid token.
406+
:class:`json.JSONDecodeError`
407+
JSON decode error.
408+
:class:`requests.exceptions.HTTPError`
409+
HTTP error.
410+
"""
411+
return self.get_access_token()
412+
413+
def gen_jwt(self, *, exp: int = None, iat: int = None) -> bytes:
414+
"""Generate JWT.
415+
416+
Keyword Arguments
417+
-----------------
418+
exp: :class:`int`
419+
Expire at [sec].
420+
iat: :class:`int`
421+
Issued at [sec].
422+
423+
Returns
424+
-------
425+
:class:`bytes`
426+
JWT.
427+
"""
428+
if exp is None:
429+
exp = self.exp
430+
if iat is None:
431+
iat = self.iat
432+
433+
payload = {
434+
"iss": self.app_id,
435+
"iat": self.now - datetime.timedelta(seconds=iat),
436+
"exp": self.now + datetime.timedelta(seconds=exp),
437+
}
438+
_jwt = jwt.encode(payload, self.client_secret,
439+
algorithm=self.algorithm)
440+
return _jwt
441+
442+
def gen_pubkey(self, *, client_secret: str = None) -> bytes:
443+
"""
444+
Generate public key.
445+
446+
Keyword Arguments
447+
-----------------
448+
client_secret: :class:`str`
449+
Client secret key.
450+
451+
Returns
452+
-------
453+
:class:`bytes`
454+
Client public key.
455+
"""
456+
if client_secret is None:
457+
client_secret = self.client_secret
458+
459+
privkey = serialization.load_pem_private_key(
460+
client_secret,
461+
password=None,
462+
backend=default_backend()
463+
)
464+
pubkey = privkey.public_key()
465+
client_public = pubkey.public_bytes(
466+
serialization.Encoding.PEM,
467+
serialization.PublicFormat.SubjectPublicKeyInfo
468+
)
469+
return client_public
470+
471+
def is_authorization(self, *, _jwt: str = None, client_public: str = None) -> bool:
472+
"""Check authorization.
473+
474+
Keyword Arguments
475+
-----------------
476+
_jwt: :class:`str`
477+
JWT.
478+
client_public: :class:`str`
479+
Client public key.
480+
481+
Returns
482+
-------
483+
:class:`bool`
484+
True if authorization.
485+
486+
Raises
487+
------
488+
:class:`jwt.InvalidTokenError`
489+
Invalid JWT.
490+
491+
"""
492+
if _jwt is None:
493+
_jwt = self.gen_jwt()
494+
if client_public is None:
495+
client_public = self.gen_pubkey()
496+
497+
try:
498+
decoded_jwt = jwt.decode(
499+
_jwt, key=client_public, algorithms=self.algorithm)
500+
if decoded_jwt['iss'] == self.app_id:
501+
return True
502+
except:
503+
print("The signature of JWT cannot be verified.")
504+
return False
505+
506+
async def get_access_token_response(self, *, _jwt: bytes = None, **kwargs) -> Optional[Union[list, dict]]:
507+
"""Get access token.
508+
509+
Keyword Arguments
510+
----------
511+
_jwt: :class:`bytes`
512+
JWT.
513+
514+
Returns
515+
-------
516+
Optional[Union[:class:`list`, :class:`dict`]]
517+
Access token.
518+
519+
Raises
520+
------
521+
:exc:`HTTPException`
522+
An unknown HTTP related error occurred, usually when it isn’t 200 or the known incorrect credentials passing status code.
523+
"""
524+
if _jwt is None:
525+
_jwt = self.gen_jwt()
526+
527+
headers = {
528+
'Accept': 'application/vnd.github.v3+json',
529+
'Authorization': 'Bearer {}'.format(_jwt),
530+
}
531+
532+
async with httpx.AsyncClient() as client:
533+
if self.is_authorization():
534+
req_access_token = await client.post(
535+
url=self.endpoint, headers=headers)
536+
try:
537+
data = req_access_token.json()
538+
except json.JSONDecodeError:
539+
print(req_access_token.content)
540+
return self._check_status(req_access_token.status_code, req_access_token, data)
541+
else:
542+
print("Access Token is not valid")
543+
544+
async def get_access_token(self, *, access_token_response: Optional[Union[list, dict]] = None) -> str:
545+
"""Get access token.
546+
547+
Keyword Arguments
548+
----------
549+
access_token_response: :class: Optional[Union[:class:`list`, :class:`dict`]]
550+
Access token response.
551+
552+
Returns
553+
-------
554+
:class:`str`
555+
Access token.
556+
557+
Raises
558+
------
559+
:class:`TypeError`
560+
The type of the argument is not correct.
561+
:exc:`HTTPException`
562+
An unknown HTTP related error occurred, usually when it isn’t 200 or the known incorrect credentials passing status code.
563+
:exc:`InvalidToken`
564+
Invalid token.
565+
"""
566+
if access_token_response is None:
567+
access_token_response = await self.get_access_token_response()
568+
return access_token_response["token"]
569+
570+
def get_usage(self) -> None:
571+
return None

0 commit comments

Comments
 (0)