|
4 | 4 | import jwt
|
5 | 5 | import aiohttp
|
6 | 6 | import datetime
|
| 7 | +import asyncio |
7 | 8 | import requests
|
| 9 | +import httpx |
8 | 10 | import re
|
9 | 11 | import urllib.parse
|
10 | 12 | from cryptography.hazmat.backends import default_backend
|
|
15 | 17 | NotFound, PayloadTooLarge, QuotaExceeded,
|
16 | 18 | ServiceUnavailable, TooManyRequests, URITooLong)
|
17 | 19 |
|
18 |
| -__all__ = ['Authentication', 'Auth'] |
| 20 | +__all__ = ['Authentication', 'RequestsAuth', 'AiohttpAuth'] |
19 | 21 |
|
20 | 22 |
|
21 | 23 | class Authentication(metaclass=ABCMeta):
|
@@ -80,7 +82,7 @@ def _check_status(self, status_code, response, data) -> Union[dict, list]:
|
80 | 82 | raise HTTPException(response, message)
|
81 | 83 |
|
82 | 84 |
|
83 |
| -class Auth(Authentication): |
| 85 | +class RequestsAuth(Authentication): |
84 | 86 | """Researchmap authentication interface.
|
85 | 87 |
|
86 | 88 | Parameters
|
@@ -321,3 +323,249 @@ def get_access_token(self, *, access_token_response: Optional[Union[list, dict]]
|
321 | 323 |
|
322 | 324 | def get_usage(self) -> None:
|
323 | 325 | 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