b53ef86ef8
git-subtree-dir: utils_v2 git-subtree-split: 7f273565196085feb05ee3328aa2e80d3d721fc3
244 lines
9.4 KiB
Python
244 lines
9.4 KiB
Python
"""
|
|
|
|
AUTHOR:
|
|
|
|
Khushal P Soonderji
|
|
|
|
DATE:
|
|
|
|
Wednesday, 30th Oct., 2024.
|
|
|
|
OBJECTIVE:
|
|
|
|
To provide a data model for describing the tokens to be used for Google's APIs.
|
|
|
|
REFERENCES:
|
|
|
|
N/A
|
|
|
|
DOWNLOADS:
|
|
|
|
N/A
|
|
|
|
"""
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** IMPORT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# To make sibling directories accessible for imports:
|
|
import sys
|
|
sys.path.append(".")
|
|
sys.path.append("..")
|
|
|
|
# For making data behaviour_models:
|
|
from pydantic import BaseModel, Field, field_validator, model_validator, AwareDatetime
|
|
from typing import Optional, Literal, Union, Dict, List, Any
|
|
|
|
# Related to Google:
|
|
from google.auth.transport.requests import Request
|
|
from google.oauth2.credentials import Credentials
|
|
|
|
# My utils:
|
|
from utils_v2.string import json
|
|
from utils_v2.string import regex
|
|
from utils_v2.date_time import date_time
|
|
|
|
# To work with date and time:
|
|
import datetime
|
|
import dateparser
|
|
|
|
# To make API calls:
|
|
import httpx
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MACROS / ONE-TIME INIT ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# --- Nothing Yet
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** VARIABLES ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
# --- Nothing Yet
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** FUNCTIONS ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
class GoogleAuthTokens(BaseModel):
|
|
|
|
accessToken: str = Field(description = "the main 'bearer' token")
|
|
refreshToken: str = Field(description = "token to be used to refresh the access token")
|
|
expiresAt: AwareDatetime = Field(description = "the time (utc) at which the token will expire")
|
|
scopes: List[str] = Field(description = "the list of permissions", default = [])
|
|
email: str | None = Field(description = "the email id of the user", default = None)
|
|
displayName: str | None = Field(description = "the display name of the user", default = None)
|
|
displayPictureUrl: str | None = Field(description = "the url to the display picture of the user", default = None)
|
|
|
|
# ┏┓ ┏•
|
|
# ┃ ┏┓┏┓╋┓┏┓
|
|
# ┗┛┗┛┛┗┛┗┗┫
|
|
# ┛
|
|
|
|
class Config:
|
|
extra = "allow"
|
|
|
|
# ┓┏ ┓• ┓ •
|
|
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
|
|
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
|
|
|
|
@field_validator("expiresAt", mode = "before")
|
|
def parse_dates(cls, value):
|
|
if not isinstance(value, datetime.datetime):
|
|
parsed = date_time.parse_date_time(value, date_formats = ["%Y%m%d", "%Y-%m-%d"])
|
|
value = parsed if isinstance(parsed, datetime.datetime) else dateparser.parse(value)
|
|
if isinstance(value, datetime.datetime): value = date_time.to_timezone(value, date_time.TIMEZONE_UTC)
|
|
return value
|
|
|
|
# ┏┓ •
|
|
# ┃┃┏┓┏┓┏┓┏┓┏┓╋┓┏┓┏
|
|
# ┣┛┛ ┗┛┣┛┗ ┛ ┗┗┗ ┛
|
|
# ┛
|
|
|
|
@property
|
|
def client_user_id(self):
|
|
return {"email": self.email}
|
|
|
|
@property
|
|
def expired(self):
|
|
return True if date_time.get_current_utc_date_time() >= self.expiresAt else False
|
|
|
|
@property
|
|
def ttl(self):
|
|
return (self.expiresAt - date_time.get_current_utc_date_time()).total_seconds()
|
|
|
|
# ┏┓ ┏┓
|
|
# ┃ ┓┏┏╋┏┓┏┳┓ ┣ ┓┏┏┓┏┏
|
|
# ┗┛┗┻┛┗┗┛┛┗┗ ┻ ┗┻┛┗┗┛
|
|
|
|
def refresh(
|
|
self,
|
|
client_id: str,
|
|
client_secret: str,
|
|
force_refresh: bool = False
|
|
) -> bool:
|
|
|
|
"""
|
|
Synchronously (blocking) refreshes the existing access tokens in place.
|
|
:param client_id: The id of the client app (OAuth JSON) for which these tokens were granted.
|
|
:param client_secret: The secret of the client app (OAuth JSON) for which these tokens were granted.
|
|
:param force_refresh: Whether you want to refresh the token even if it hasn't expired.
|
|
:return: True if refreshed, else False.
|
|
"""
|
|
|
|
# Start by assuming failure:
|
|
success = False
|
|
|
|
try:
|
|
|
|
# Go ahead only if either the token has expired,
|
|
# or the user has asked to forcefully refresh the tokens:
|
|
if self.expired or force_refresh:
|
|
|
|
# Create the credentials:
|
|
credentials = Credentials.from_authorized_user_info(
|
|
info = {
|
|
"client_id": client_id,
|
|
"client_secret": client_secret,
|
|
"refresh_token": self.refreshToken,
|
|
"expires_at": self.expiresAt
|
|
}
|
|
)
|
|
|
|
# Request a refresh:
|
|
credentials.refresh(Request())
|
|
|
|
# Note down the new credentials:
|
|
if credentials.token != self.accessToken:
|
|
success = True
|
|
self.accessToken = credentials.token
|
|
self.refreshToken = credentials.refresh_token
|
|
self.expiresAt = date_time.as_if_timezone(credentials.expiry, timezone = date_time.TIMEZONE_UTC)
|
|
|
|
# In case something goes wrong:
|
|
except Exception as exception:
|
|
success = False
|
|
|
|
# Done here:
|
|
return success
|
|
|
|
async def arefresh(
|
|
self,
|
|
http_client: httpx.AsyncClient,
|
|
client_id: str,
|
|
client_secret: str,
|
|
force_refresh: bool = False
|
|
) -> bool:
|
|
|
|
"""
|
|
Asynchronously refreshes the existing access tokens in place.
|
|
:param http_client: The HTTP client to use to make the refresh request.
|
|
:param client_id: The id of the client (OAuth JSON) for which these tokens were granted.
|
|
:param client_secret: The secret of the client (OAuth JSON) for which these tokens were granted.
|
|
:param force_refresh: Whether you want to refresh the token even if it hasn't expired.
|
|
:return: True if refreshed, else False.
|
|
"""
|
|
|
|
# Currently we don't really know how to refresh tokens through low-level API calls,
|
|
# so we will pass on the intent to the regular, synchronous function.
|
|
return self.refresh(
|
|
client_id = client_id,
|
|
client_secret = client_secret,
|
|
force_refresh = force_refresh
|
|
)
|
|
|
|
def has_scopes(self, scopes: List[str]) -> bool:
|
|
|
|
"""
|
|
Checks if all the specified scopes were granted.
|
|
:param scopes: The list of scopes to check. These are the permissions you need.
|
|
:return: True if all specified scoped are present, else False.
|
|
"""
|
|
|
|
# Start by assuming success:
|
|
has_scopes = True
|
|
|
|
# Now loop through the needed scopes and check:
|
|
for scope in scopes:
|
|
if scope not in self.scopes:
|
|
has_scopes = False
|
|
break
|
|
|
|
# Done here:
|
|
return has_scopes
|
|
|
|
|
|
# *****************************************************************************************************************
|
|
# ***** ****
|
|
# *** MAIN PROGRAM ***
|
|
# ***** ****
|
|
# *****************************************************************************************************************
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
pass
|