Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Authenticators Factory #794

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from

Conversation

giovannialbero1992
Copy link
Contributor

Description

Based on the discussion #690

Related to issue #(issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas

@giovannialbero1992 giovannialbero1992 changed the base branch from main to develop April 26, 2024 22:53
@giovannialbero1992 giovannialbero1992 marked this pull request as draft April 26, 2024 22:53
) -> None | str:
"""Authenticate endpoint.

Check the provided key is available in API keys list.
Copy link
Contributor

@sambarza sambarza Apr 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note about of the doc. The check_ws_with_authorizator doesn't know how the authenticator will do the authorization check, do you agree?

@lucagobbi
Copy link
Contributor

Hi @giovannialbero1992 I think you are doing a great job, this is really flexible and easy to implement. I propose also to have a generic abstract class from which custom authorizators should inherit, since is_allowed and is_allowed_ws methods should be the same for every implementation:

class Auth(ABC):
    @abstractmethod
    def is_allowed(self, request: Request) -> bool:
        """
        Abstract method to validate if the HTTP request is allowed based on the implemented
        authentication and authorization mechanism.
        This method should return True if the request is allowed, or False otherwise.
        """
        pass

    @abstractmethod
    def is_allowed_ws(self, websocket: WebSocket) -> bool:
        """
        Abstract method to validate if the WebSocket connection is allowed based on the
        implemented authentication and authorization mechanism.
        This method should return True if the connection is allowed, or False otherwise.
        """
        pass

then the AuthSettings model should look something like this:

class AuthSettings(BaseModel):
    _pyclass: Type[Auth] = None

    @classmethod
    def get_authorizator_from_config(cls, config: Dict):
        if cls._pyclass is None or not issubclass(cls._pyclass, Auth):
            raise ValueError(
                "Invalid configuration: _pyclass must be a subclass of Auth."
            )
        return cls._pyclass(**config)

I think this enforcement ensures consistency across different auth strategies, provides type safety on settings, reducing type errors and gives an easier way to devs to implement this.

@pieroit
Copy link
Member

pieroit commented Apr 30, 2024

Hi @giovannialbero1992 I think you are doing a great job, this is really flexible and easy to implement. I propose also to have a generic abstract class from which custom authorizators should inherit, since is_allowed and is_allowed_ws methods should be the same for every implementation:

class Auth(ABC):
    @abstractmethod
    def is_allowed(self, request: Request) -> bool:
        """
        Abstract method to validate if the HTTP request is allowed based on the implemented
        authentication and authorization mechanism.
        This method should return True if the request is allowed, or False otherwise.
        """
        pass

    @abstractmethod
    def is_allowed_ws(self, websocket: WebSocket) -> bool:
        """
        Abstract method to validate if the WebSocket connection is allowed based on the
        implemented authentication and authorization mechanism.
        This method should return True if the connection is allowed, or False otherwise.
        """
        pass

then the AuthSettings model should look something like this:

class AuthSettings(BaseModel):
    _pyclass: Type[Auth] = None

    @classmethod
    def get_authorizator_from_config(cls, config: Dict):
        if cls._pyclass is None or not issubclass(cls._pyclass, Auth):
            raise ValueError(
                "Invalid configuration: _pyclass must be a subclass of Auth."
            )
        return cls._pyclass(**config)

I think this enforcement ensures consistency across different auth strategies, provides type safety on settings, reducing type errors and gives an easier way to devs to implement this.

Agree on the parent class, also allows for more methods we may add later that may be overrideen or not from a subclass

P.S.: why ABS and not directly pydantic BaseModel as langchain does?
The latest covers having configs saved as dictionaries and calling _pyclass(**config), I don't how it works for ABC - maybe BaseModel is not needed

@lucagobbi
Copy link
Contributor

Hi @giovannialbero1992 I think you are doing a great job, this is really flexible and easy to implement. I propose also to have a generic abstract class from which custom authorizators should inherit, since is_allowed and is_allowed_ws methods should be the same for every implementation:

class Auth(ABC):
    @abstractmethod
    def is_allowed(self, request: Request) -> bool:
        """
        Abstract method to validate if the HTTP request is allowed based on the implemented
        authentication and authorization mechanism.
        This method should return True if the request is allowed, or False otherwise.
        """
        pass

    @abstractmethod
    def is_allowed_ws(self, websocket: WebSocket) -> bool:
        """
        Abstract method to validate if the WebSocket connection is allowed based on the
        implemented authentication and authorization mechanism.
        This method should return True if the connection is allowed, or False otherwise.
        """
        pass

then the AuthSettings model should look something like this:

class AuthSettings(BaseModel):
    _pyclass: Type[Auth] = None

    @classmethod
    def get_authorizator_from_config(cls, config: Dict):
        if cls._pyclass is None or not issubclass(cls._pyclass, Auth):
            raise ValueError(
                "Invalid configuration: _pyclass must be a subclass of Auth."
            )
        return cls._pyclass(**config)

I think this enforcement ensures consistency across different auth strategies, provides type safety on settings, reducing type errors and gives an easier way to devs to implement this.

Agree on the parent class, also allows for more methods we may add later that may be overrideen or not from a subclass

P.S.: why ABS and not directly pydantic BaseModel as langchain does? The latest covers having configs saved as dictionaries and calling _pyclass(**config), I don't how it works for ABC - maybe BaseModel is not needed

If you are referring to AuthSettings I'd still keep the pydantic model, since it needs data validation on the auth class you choose, but the auth class imho should be a simple interface (i.e. an abstract class in python) with focus on behaviour rather than data validatio. Having a pydantic model here seems quite an overhead to me. Isn't it?

@pieroit
Copy link
Member

pieroit commented Apr 30, 2024

If you are referring to AuthSettings I'd still keep the pydantic model, since it needs data validation on the auth class you choose, but the auth class imho should be a simple interface (i.e. an abstract class in python) with focus on behaviour rather than data validatio. Having a pydantic model here seems quite an overhead to me. Isn't it?

@lucagobbi fair enough!! Asking also to @Pingdred if you want to chip in

@Pingdred
Copy link
Member

Hi @giovannialbero1992 I think you are doing a great job, this is really flexible and easy to implement. I propose also to have a generic abstract class from which custom authorizators should inherit, since is_allowed and is_allowed_ws methods should be the same for every implementation:

class Auth(ABC):
    @abstractmethod
    def is_allowed(self, request: Request) -> bool:
        """
        Abstract method to validate if the HTTP request is allowed based on the implemented
        authentication and authorization mechanism.
        This method should return True if the request is allowed, or False otherwise.
        """
        pass

    @abstractmethod
    def is_allowed_ws(self, websocket: WebSocket) -> bool:
        """
        Abstract method to validate if the WebSocket connection is allowed based on the
        implemented authentication and authorization mechanism.
        This method should return True if the connection is allowed, or False otherwise.
        """
        pass

then the AuthSettings model should look something like this:

class AuthSettings(BaseModel):
    _pyclass: Type[Auth] = None

    @classmethod
    def get_authorizator_from_config(cls, config: Dict):
        if cls._pyclass is None or not issubclass(cls._pyclass, Auth):
            raise ValueError(
                "Invalid configuration: _pyclass must be a subclass of Auth."
            )
        return cls._pyclass(**config)

I think this enforcement ensures consistency across different auth strategies, provides type safety on settings, reducing type errors and gives an easier way to devs to implement this.

Agree on the parent class, also allows for more methods we may add later that may be overrideen or not from a subclass

P.S.: why ABS and not directly pydantic BaseModel as langchain does? The latest covers having configs saved as dictionaries and calling _pyclass(**config), I don't how it works for ABC - maybe BaseModel is not needed

If you are referring to AuthSettings I'd still keep the pydantic model, since it needs data validation on the auth class you choose, but the auth class imho should be a simple interface (i.e. an abstract class in python) with focus on behaviour rather than data validatio. Having a pydantic model here seems quite an overhead to me. Isn't it?

Totally agree

@pieroit pieroit mentioned this pull request May 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants