Skip to content

ref(users): move user & user_identity_config & auth serializers to new abode #76896

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

Merged
merged 25 commits into from
Sep 10, 2024

Conversation

Christinarlong
Copy link
Contributor

@Christinarlong Christinarlong commented Sep 3, 2024

Moves

  • User
  • User_Identity_Config
  • Authenticator
    and their tests to the user's directory :0) Fixes any additional typing along the journey (mainly serializer response dicts) ~

split of (#76822)
ref(issue # here)

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Sep 3, 2024
Copy link

codecov bot commented Sep 3, 2024

Codecov Report

Attention: Patch coverage is 98.93617% with 3 lines in your changes missing coverage. Please review.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/sentry/users/api/serializers/user.py 98.17% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #76896       +/-   ##
===========================================
+ Coverage   64.76%   78.25%   +13.49%     
===========================================
  Files        6894     6900        +6     
  Lines      306469   306875      +406     
  Branches    50250    50268       +18     
===========================================
+ Hits       198493   240154    +41661     
+ Misses     101387    60292    -41095     
+ Partials     6589     6429      -160     

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced file with import shim for any getsentry refs

from sentry.api.serializers import Serializer, register
from sentry.auth.authenticators.base import AuthenticatorInterface
from sentry.auth.authenticators.recovery_code import RecoveryCodeInterface
from sentry.auth.authenticators.sms import SmsInterface
from sentry.auth.authenticators.totp import TotpInterface
from sentry.auth.authenticators.u2f import U2fInterface

if TYPE_CHECKING:
from django.utils.functional import _StrOrPromise
Copy link
Contributor Author

Choose a reason for hiding this comment

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

From: typeddjango/django-stubs#1804
is it ok if we potentially return/serialize a promise?

Copy link
Member

Choose a reason for hiding this comment

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

is it ok if we potentially return/serialize a promise?

If we can we should avoid importing symbols with _ prefixes. We could str() cast these translation related containers to simplify the typed dict definitions.

Copy link
Member

Choose a reason for hiding this comment

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

this is actually fine -- _StrOrPromise is ~kinda public and typing-only



def manytoone_to_dict(
queryset: QuerySet[T], key: str, filter_func: Callable[[Any], bool] | None = None
Copy link
Contributor Author

@Christinarlong Christinarlong Sep 4, 2024

Choose a reason for hiding this comment

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

typed and made this generic-ish

dateJoined: datetime
lastLogin: datetime | None
has2fa: bool
lastActive: datetime | None
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The model says that lastLogin and lastActive are nullable fields but the openAPI spec says these are required fields, not sure if there's a source of truth/who to follow for this response

Copy link
Member

Choose a reason for hiding this comment

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

We could have casting/fallback in the serializer that removes the None values, or the api schema could be wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I definitely interpreted the OpenApi failing job wrong, as I didn't realize the OpenApi spec is generated from the model serializer which I guess didn't exist until now (?????)

Copy link
Member

Choose a reason for hiding this comment

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

That job fails anytime there is a delta which isn't great. I took a look at the output and because we've added more accurate types schema generation has better metadata to use and the schema has changed. The properties were always nullable, but our schema didn't reflect that because of limited information.

]

return d

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I got rid of the DetailedUserSerializerResponse here as it wasn't really adding anything new

@Christinarlong Christinarlong requested review from GabeVillalobos and a team September 4, 2024 21:48
@Christinarlong Christinarlong changed the title ref(users): move user & user_identity_config serializers to new abode ref(users): move user & user_identity_config & auth serializers to new abode Sep 4, 2024
from sentry.models.notificationaction import (
ActionService,
ActionTarget,
ActionTrigger,
NotificationAction,
NotificationActionProject,
)
from sentry.users.api.serializers.user import manytoone_to_dict
Copy link
Member

Choose a reason for hiding this comment

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

Not entirely related to these changes, but this module seems like a strange place for a very generic query utility. We can move it separately later.

@@ -0,0 +1,3 @@
from .authenticator import * # noqa: F401,F403
from .user import * # noqa: F401,F403
from .user_identity_config import * # noqa: F401,F403
Copy link
Member

Choose a reason for hiding this comment

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

Do we need this barrel file? It seems like we could avoid it right now.

from sentry.api.serializers import Serializer, register
from sentry.auth.authenticators.base import AuthenticatorInterface
from sentry.auth.authenticators.recovery_code import RecoveryCodeInterface
from sentry.auth.authenticators.sms import SmsInterface
from sentry.auth.authenticators.totp import TotpInterface
from sentry.auth.authenticators.u2f import U2fInterface

if TYPE_CHECKING:
from django.utils.functional import _StrOrPromise
Copy link
Member

Choose a reason for hiding this comment

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

is it ok if we potentially return/serialize a promise?

If we can we should avoid importing symbols with _ prefixes. We could str() cast these translation related containers to simplify the typed dict definitions.

dateJoined: datetime
lastLogin: datetime | None
has2fa: bool
lastActive: datetime | None
Copy link
Member

Choose a reason for hiding this comment

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

We could have casting/fallback in the serializer that removes the None values, or the api schema could be wrong.

Comment on lines +97 to +100
def get_interface_serializer(interface: AuthenticatorInterface) -> AuthenticatorInterfaceSerializer:
if isinstance(interface, SmsInterface):
return SmsInterfaceSerializer()
return AuthenticatorInterfaceSerializer()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the endpoints that are given an ApplicationInterface, interface, to serialize, this 'getter' is used to make sure we're using the right serializer. It's a weird scenario because registering the serializer in line 76 and 94 should solve this for us, but because we also have that bug of the file not being imported and therefore the serializers not being registered, this redundant function is here.....

Maybe other alternatives could be having barrel file for just the authenticator ?

Copy link
Member

Choose a reason for hiding this comment

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

I don't hate the factory function. In the longer term, I think we should consider removing the @register() calls as they contribute to import tangle and import side-effects create hidden dependencies between modules.

It's a weird scenario because registering the serializer in line 76 and 94 should solve this for us, but because we also have that bug of the file not being imported and therefore the serializers not being registered, this redundant function is here.....

If the endpoint imports the sentry.users.api.serializers.authenticators module I would expect that the register() calls would behave correctly. Is that not the case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah that's correct, but if we import the authenticator serializer and don't do anything with it, we'll get shaboinked by mypy & linter. OTOH we can't just pass in AuthenticatorInterface for everything because of SmsInterface :/

@Christinarlong Christinarlong marked this pull request as ready for review September 6, 2024 19:32
@Christinarlong Christinarlong requested review from a team as code owners September 6, 2024 19:32
Copy link
Member

@markstory markstory left a comment

Choose a reason for hiding this comment

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

Looks good to me.

Comment on lines 14 to 17
class EnrolledAuthenticatorInterfaceSerializerResponse(TypedDict, total=False):
authId: str
createdAt: datetime
lastUsedAt: datetime | None
Copy link
Member

Choose a reason for hiding this comment

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

Another way to solve this in future changes would be to use typing.NotRequired to mark these keys as optional.

Copy link
Member

Choose a reason for hiding this comment

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

actually that's a good point -- is this actually optional and nullable? (rarely something is)

Copy link
Contributor Author

@Christinarlong Christinarlong Sep 9, 2024

Choose a reason for hiding this comment

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

@asottile-sentry like are the "enrolled" fields optional & nullable ? I believe so, because they are some test cases like tests/sentry/users/api/endpoints/test_user_authenticator_enroll.py where the enrollment in an authenticator interface failed, so the returned user doesn't have any of these fields. Or were you talking about a different field (?) ...

Copy link
Member

Choose a reason for hiding this comment

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

as written now there's these possibilities:

{}
{"lastUsedAt": some_datetime}
{"lastUsedAt": None}

which is usually a bug

Copy link
Contributor Author

@Christinarlong Christinarlong Sep 9, 2024

Choose a reason for hiding this comment

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

yeah I dunno, from what I'm seeing from our tests
{} -> is a real scenario [it seems like this can happen when enrollment fails], so Optional seems to be valid
{"lastUsedAt": some_datetime} -> also is a real scenario when we mark an authenticator as used.
{"lastUsedAt": None} -> also is real when a user is newly enrolled to an authenticator

It looks like the three scenarios all happen so probably not a bug, albeit also somewhat confusing to deal with

Comment on lines +97 to +100
def get_interface_serializer(interface: AuthenticatorInterface) -> AuthenticatorInterfaceSerializer:
if isinstance(interface, SmsInterface):
return SmsInterfaceSerializer()
return AuthenticatorInterfaceSerializer()
Copy link
Member

Choose a reason for hiding this comment

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

I don't hate the factory function. In the longer term, I think we should consider removing the @register() calls as they contribute to import tangle and import side-effects create hidden dependencies between modules.

It's a weird scenario because registering the serializer in line 76 and 94 should solve this for us, but because we also have that bug of the file not being imported and therefore the serializers not being registered, this redundant function is here.....

If the endpoint imports the sentry.users.api.serializers.authenticators module I would expect that the register() calls would behave correctly. Is that not the case?

# OrganizationMemberResponse).
identities: list[_Identity]
avatar: SerializedAvatarFields
authenticators: list[Any] # TODO: find out what type this is
Copy link
Member

Choose a reason for hiding this comment

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

Interesting that we have a special serialization format for authenticators here that is different from the representation that authenticator endpoints use. 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeahh..... the user_authenticator_enroll.py endpoint also has it's own serializer /format for each of the interfaces that's different. AuthenticatorInterface serialization in general is like p. inconsistent ;-;

T = TypeVar("T", bound=Model)


def manytoone_to_dict(
Copy link
Member

Choose a reason for hiding this comment

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

👏

@Christinarlong Christinarlong merged commit 1c2e8a6 into master Sep 10, 2024
49 of 50 checks passed
@Christinarlong Christinarlong deleted the christinarlong/move-user-serializers-user branch September 10, 2024 17:07
@github-actions github-actions bot locked and limited conversation to collaborators Sep 26, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants