Source code for pytket.extensions.quantinuum.backends.credential_storage

# Copyright 2020-2024 Quantinuum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from abc import ABC, abstractmethod
from typing import Optional
from datetime import timedelta, datetime, timezone
import jwt
from .config import QuantinuumConfig


class CredentialStorage(ABC):

    """Storage for Quantinuum username and tokens"""

    def __init__(
        self,
        id_token_timedelt: timedelta = timedelta(minutes=55),
        refresh_token_timedelt: timedelta = timedelta(days=29),
    ) -> None:
        self._id_timedelt = id_token_timedelt
        self._refresh_timedelt = refresh_token_timedelt

    @abstractmethod
    def save_refresh_token(self, refresh_token: str) -> None:
        """save refresh token"""

    @abstractmethod
    def save_id_token(self, id_token: str) -> None:
        """save ID token"""

    @abstractmethod
    def save_user_name(self, user_name: str) -> None:
        """save user_name"""

    def save_tokens(self, id_token: str, refresh_token: str) -> None:
        self.save_id_token(id_token)
        self.save_refresh_token(refresh_token)

    @abstractmethod
    def delete_credential(self) -> None:
        """delete credential"""

    @property
    def id_token(self) -> Optional[str]:
        """returns a ID token if valid"""

    @property
    def refresh_token(self) -> Optional[str]:
        """returns a refresh token if valid"""

    @property
    def user_name(self) -> Optional[str]:
        """returns the user name"""


[docs]class MemoryCredentialStorage(CredentialStorage): """In memory credential storage. Intended use is only to store id tokens, refresh tokens and user_name. Password storage is only included for debug purposes.""" def __init__( self, id_token_timedelt: timedelta = timedelta(minutes=55), refresh_token_timedelt: timedelta = timedelta(days=29), ) -> None: super().__init__(id_token_timedelt, refresh_token_timedelt) self._user_name: Optional[str] = None self._password: Optional[str] = None self._id_token: Optional[str] = None self._refresh_token: Optional[str] = None self._id_token_timeout: Optional[datetime] = None self._refresh_token_timeout: Optional[datetime] = None
[docs] def save_user_name(self, user_name: str) -> None: self._user_name = user_name
[docs] def save_refresh_token(self, refresh_token: str) -> None: self._refresh_token = refresh_token self._refresh_token_timeout = ( datetime.now(timezone.utc) + self._refresh_timedelt )
[docs] def save_id_token(self, id_token: str) -> None: self._id_token = id_token self._id_token_timeout = datetime.now(timezone.utc) + self._id_timedelt
@property def id_token(self) -> Optional[str]: if self._id_token is not None: timeout = ( jwt.decode( self._id_token, algorithms=["HS256"], options={"verify_signature": False}, )["exp"] - 60 ) if self._id_token_timeout is not None: timeout = min(timeout, self._id_token_timeout.timestamp()) if datetime.now(timezone.utc).timestamp() > timeout: self._id_token = None return self._id_token @property def refresh_token(self) -> Optional[str]: if self._refresh_token is not None and self._refresh_token_timeout is not None: if datetime.now(timezone.utc) > self._refresh_token_timeout: self._refresh_token = None return self._refresh_token @property def user_name(self) -> Optional[str]: return self._user_name
[docs] def delete_credential(self) -> None: del self._user_name del self._password del self._id_token del self._refresh_token self._user_name = None self._password = None self._id_token = None self._id_token_timeout = None self._refresh_token = None self._refresh_token_timeout = None
[docs]class QuantinuumConfigCredentialStorage(CredentialStorage): """Store tokens in the default pytket configuration file.""" def __init__( self, id_token_timedelt: timedelta = timedelta(minutes=55), refresh_token_timedelt: timedelta = timedelta(days=29), ) -> None: super().__init__(id_token_timedelt, refresh_token_timedelt)
[docs] def save_user_name(self, user_name: str) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.username = user_name hconfig.update_default_config_file()
[docs] def save_refresh_token(self, refresh_token: str) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.refresh_token = refresh_token refresh_token_timeout = datetime.now(timezone.utc) + self._refresh_timedelt hconfig.refresh_token_timeout = refresh_token_timeout.strftime( "%Y-%m-%d %H:%M:%S.%z" ) hconfig.update_default_config_file()
[docs] def save_id_token(self, id_token: str) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.id_token = id_token id_token_timeout = datetime.now(timezone.utc) + self._id_timedelt hconfig.id_token_timeout = id_token_timeout.strftime("%Y-%m-%d %H:%M:%S.%z") hconfig.update_default_config_file()
@property def id_token(self) -> Optional[str]: hconfig = QuantinuumConfig.from_default_config_file() id_token = hconfig.id_token if id_token is not None: timeout = ( jwt.decode( id_token, algorithms=["HS256"], options={"verify_signature": False}, )["exp"] - 60 ) if hconfig.id_token_timeout is not None: id_token_timeout = datetime.strptime( hconfig.id_token_timeout, "%Y-%m-%d %H:%M:%S.%z" ) timeout = min(timeout, id_token_timeout.timestamp()) if datetime.now(timezone.utc).timestamp() > timeout: return None return id_token @property def refresh_token(self) -> Optional[str]: hconfig = QuantinuumConfig.from_default_config_file() refresh_token = hconfig.refresh_token if refresh_token is not None and hconfig.refresh_token_timeout is not None: refresh_token_timeout = datetime.strptime( hconfig.refresh_token_timeout, "%Y-%m-%d %H:%M:%S.%z" ) if datetime.now(timezone.utc) > refresh_token_timeout: return None return refresh_token @property def user_name(self) -> Optional[str]: hconfig = QuantinuumConfig.from_default_config_file() return hconfig.username
[docs] def delete_credential(self) -> None: hconfig = QuantinuumConfig.from_default_config_file() hconfig.username = None hconfig.refresh_token = None hconfig.id_token = None hconfig.refresh_token_timeout = None hconfig.id_token_timeout = None hconfig.update_default_config_file()