eadwinCode / django-ninja-jwt

A JSON Web Token authentication plugin for the Django REST Framework.
https://eadwincode.github.io/django-ninja-jwt/
MIT License
144 stars 21 forks source link

Resolver gets called twice #67

Open geclick opened 6 months ago

geclick commented 6 months ago

I am adding a 'user' key to the token claims with the user data including its permissions. Following exactly this I get the tokens with no problem. But resolving the permissions raises a ValidationError, and I realized that the resolver gets called twice: -first time the obj is a User instance and context is None, so no problem for getting the permission list -the second time obj is not a User but a ModelAuthReadSchema instance and context is not None but its user is a AnonymousUser, and here comes the errors

this is my schema for User where AuthModel is just get_user_model()

class ModelAuthReadSchema(ModelSchema):
    permissions: List[str] | None

    class Meta:
        model = AuthModel
        fields = ['id', 'username', 'first_name', 'last_name', 'email']

    @staticmethod
    def resolve_permissions(obj, context):
        return get_permissions(obj)

get_permissions is a custom function for getting just business-related permissions

this is the error:

pydantic_core._pydantic_core.ValidationError: 3 validation errors for NinjaResponseSchema
response.refresh
  Field required [type=missing, input_value=<DjangoGetter: LoginInput...', username='cccccc')>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.6/v/missing
response.access
  Field required [type=missing, input_value=<DjangoGetter: LoginInput...', username='cccccc')>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.6/v/missing
response.user
  Field required [type=missing, input_value=<DjangoGetter: LoginInput...', username='cccccc')>, input_type=DjangoGetter]
    For further information visit https://errors.pydantic.dev/2.6/v/missing

this would be the desired output:

{
  "refresh": "xxxxxxxxxxx",
  "access": "yyyyyyyyyyy",
  "user": {
    "permissions": [
      "app_label1.add_model",
      "app_label2.delete_model"
    ],
    "id": 1,
    "username": "ccccccc",
    "first_name": "",
    "last_name": "",
    "email": "ccc@ccc.cc"
  }
}
eadwinCode commented 6 months ago

@geclick Sorry I didn't get notified of this issue on time. I am just seeing it now. I am currently looking into it at the moment

eadwinCode commented 6 months ago

@geclick the problem is the resolve_permission method. I don't use the resolver method provided in the Ninja model schema. The problem with that being called twice is likely a problem from pydantic. I know I faced that problem once but I can't remember where.

You can only solve this problem using pydantic model validator at mode='before'. Check the example below.

class ModelAuthReadSchema(ModelSchema):
    permissions: List[str] | None

    class Meta:
        model = AuthModel
        fields = ['id', 'username', 'first_name', 'last_name', 'email']

    @model_validator(mode="before")
    def validate_permission_list(cls, values: DjangoGetter) -> typing.Any:
        values = values._obj

        if isinstance(values, dict):
            # values will have ['id', 'username', 'first_name', 'last_name', 'email']
            user = None # add functionality to get the user
            permissions = get_permissions(user)
            values.update(permissions=permissions)
        return values
geclick commented 6 months ago

@geclick the problem is the resolve_permission method. I don't use the resolver method provided in the Ninja model schema. The problem with that being called twice is likely a problem from pydantic. I know I faced that problem once but I can't remember where.

You can only solve this problem using pydantic model validator at mode='before'. Check the example below.

class ModelAuthReadSchema(ModelSchema):
    permissions: List[str] | None

    class Meta:
        model = AuthModel
        fields = ['id', 'username', 'first_name', 'last_name', 'email']

    @model_validator(mode="before")
    def validate_permission_list(cls, values: DjangoGetter) -> typing.Any:
        values = values._obj

        if isinstance(values, dict):
            # values will have ['id', 'username', 'first_name', 'last_name', 'email']
            user = None # add functionality to get the user
            permissions = get_permissions(user)
            values.update(permissions=permissions)
        return values

tnks

eadwinCode commented 6 months ago

@geclick can you close this issue if the problem is resolved?