Skip to content

Manage organization "People" #11342

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 65 commits into from
May 13, 2022

Conversation

sterbo and others added 30 commits May 5, 2022 14:17
Borrowing styles from list of projects for now.
Use the same gradient of grays as white-cube.svg.
- {.package-snippet__project-badge => .package-snippet__badge}
- {.package-snippet__released => .package-snippet__created}
Cards with similar styling but different icons:
- .organization-snippet
- .package-snippet
"Manage" link for each organization listed on /manage/organizations/
navigates to a "Collaborators" management page for the organization.
Discussed with @s-mm and renaming to differentiate "people" in
organizations from "collaborators" in projects.
I already added "Your organizations" to the sidebar for desktop but
forgot to add it for mobile.
Status badges:
- "Request Submitted" if still awaiting approve/decline
- "Inactive" if declined or inactive for other reasons

Role badges:
- "Manager" for organization manager
- "Owner" for organization owner
- "Billing Manager" for billing manager
This is my third attempt at stabilizing these tests but I finally see
the light of what I was doing wrong. I should not rely on probabilistic
tests at all even if the probability is "very very very low". @sterbo
actually suggested this earlier but it didn't fully click in my head
until now.

Co-authored-by: sterbo <[email protected]>
- Remove "Billing Manager" choice for non-companies
- Add "Billing Manager" role description from @s-mm
- Copy-paste `revoke_project_role_invitation(...)`.
divbzero and others added 15 commits May 5, 2022 14:39
- Copy-paste `TestVerifyProjectRole`.
- Implement approve/decline tests.
Notification emails for the organization owner:

- organization-member-invited
- organization-member-invite-canceled
- organization-member-invite-declined
- organization-member-added
- organization-member-removed
- organization-member-role-changed

Notification emails for the invited user:

- verify-organization-role
- canceled-as-invited-organization-member
- declined-as-invited-organization-member
- added-as-organization-member
- removed-as-organization-member
- role-changed-as-organization-member
@divbzero divbzero force-pushed the feature/manage-organization-people branch from b90fb95 to a332623 Compare May 5, 2022 21:57
Copy link
Contributor Author

@divbzero divbzero left a comment

Choose a reason for hiding this comment

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

@ewdurbin This is ready for code review but @sterbo and I wanted to confirm a few details about permissions with you, please see comments on those below. Thank you!

# acls = [
# (Allow, "group:admins", "manage:organization"),
# (Allow, "group:moderators", "manage:team"),
# ]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ewdurbin Do we want to grant PyPI admins or moderators sudo privileges to manage organizations or teams?

Copy link
Member

Choose a reason for hiding this comment

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

Yes. But as is we currently perform all admin actions via the admin app/interface.

Granting permissions this way would allow admins to use the user-facing interface to make these changes, I assume? That'd be a departure.... @di thoughts here? My preference is probably to maintain the current separation of concerns via the utility sets a la warehouse/utils/project.py

Copy link
Member

Choose a reason for hiding this comment

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

I've been thinking about doing this vs. maintaining a very similar but separate set of views. I think it's probably better to do the latter, because we can add additional context, and also easily do things like not send emails/notifications/events when admins perform actions

Copy link
Member

Choose a reason for hiding this comment

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

Agreed. In a quick review it appears that we do the ACL dance on Projects to set the level of access that PyPI Admins/Moderators have. So we should do similar here, but the use-case won't be a "sudo" mode, just setting the ACLs so we can act on them in warehouse/admin views.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sterbo added "admin" and "moderator" in a95f708.

elif role.role_name == OrganizationRoleType.Manager:
acls.append((Allow, f"user:{role.user.id}", ["manage:team"]))
else: # TODO Member: Do we need this? May be covered by Project acl?
acls.append((Allow, f"user:{role.user.id}", ["organization:member"]))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ewdurbin There are two things we want to confirm here:

  1. We think that project-related permissions will be governed on the project level with "manage:project" and we don’t need anything for that here. Would you agree?
  2. Will users need to be logged in and an organization member to view the organization’s projects and people? If yes, we think we’ll want to define a "view:organization" permission.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ewdurbin We’ve added "view:organization" with commit 5e453dc so that:

  • All organization members will have view access to organization settings
  • Non-owners can remove themselves from the organization

This feels like a sensible approach, but let us know your thoughts and we can adjust as needed.

(In the future, probably when package namespacing is implemented, it could make sense to add a public view of each organization’s packages at https://pypi.org/~pypa/ or https://pypi.org/@pypa/.)

@divbzero divbzero requested a review from ewdurbin May 5, 2022 22:08
- Allow read access to organization for all members
- Allow non-owners to remove self from organization
@divbzero divbzero force-pushed the feature/manage-organization-people branch from ba4bfcf to 5e453dc Compare May 6, 2022 18:58
Copy link
Member

@ewdurbin ewdurbin left a comment

Choose a reason for hiding this comment

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

No blockers. Could another of @pypa/warehouse-committers review? One place I didn't have a lot of time to look over was the email templates, as I hope these will be re-considered during user testing.

submitter_user = user_service.get_user(data.get("submitter_id"))
organization.record_event(
tag="organization:organization_role:declined",
ip_address=request.remote_addr,
Copy link
Member

Choose a reason for hiding this comment

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

In this instance we may want to either anonymize the ip_address on the event for the OrganizationEvent by hard coding to 0.0.0.0/127.0.0.1 or somehow be sure we don't leak it on display to Organization admins.

This is a potential vector for abuse/leak. I can just invite a bunch of people to an org and get their IPs if they decline (or accept I suppose!)

Copy link
Member

Choose a reason for hiding this comment

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

@di @dstufft thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We would probably want to anonymize/redact IP addresses on project collaboration invitations as well.

A pattern we use elsewhere is adding "redact_ip": True to "account:email:sent" events:
https://github.com/pypa/warehouse/blob/64fc7f1e4922017b476643ba7eda0859d69ae137/warehouse/admin/views/emails.py#L102-L111

And then using that flag to mask the IP address when displaying the event to users:
https://github.com/pypa/warehouse/blob/64fc7f1e4922017b476643ba7eda0859d69ae137/warehouse/templates/manage/account.html#L669-L672

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A somewhat related issue regarding project deletion/re-use:

# acls = [
# (Allow, "group:admins", "manage:organization"),
# (Allow, "group:moderators", "manage:team"),
# ]
Copy link
Member

Choose a reason for hiding this comment

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

Agreed. In a quick review it appears that we do the ACL dance on Projects to set the level of access that PyPI Admins/Moderators have. So we should do similar here, but the use-case won't be a "sudo" mode, just setting the ACLs so we can act on them in warehouse/admin views.

@@ -1028,6 +1039,20 @@ def user_organizations(request):
}


def organization_owners(request, organization):
Copy link
Member

Choose a reason for hiding this comment

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

For some reason I want this to exist on the Organization service... But we do a similar thing for Project roles. I can't recall if there's a reason for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we want to do this, it could be a bigger refactor where we move the helper functions for Project roles to warehouse.packaging.services.

@@ -14,4 +14,4 @@

{% extends "email/_base/subject.txt" %}

{% block subject %}{{ initiator_username }} has invited you to join the {{ project_name }} project{% endblock %}
{% block subject %}You have joined the {{ project_name }} project{% endblock %}
Copy link
Member

Choose a reason for hiding this comment

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

Good catch!

@@ -19,7 +19,7 @@

{% block main %}
{% if project_invites %}
<h1 class="page-title">{% trans project_count=project_invites|length %}Pending invitations ({{ project_count }}){% endtrans %}</h1>
<h1 class="page-title">{% trans invitation_count=project_invites|length %}Pending invitations ({{ invitation_count }}){% endtrans %}</h1>
Copy link
Member

Choose a reason for hiding this comment

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

another good catch 🐟

Update error text based on @ewdurbin's suggestion in pypi#11342.
Copy link
Contributor

@brainwane brainwane left a comment

Choose a reason for hiding this comment

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

Minor wording stuff.

token = request.params.get("token")
data = token_service.loads(token)
except TokenExpired:
return _error(request._("Expired token: request a new organization invite"))
Copy link
Contributor

Choose a reason for hiding this comment

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

Double-checking: should this (and the "invalid token" message) say "invite" or "invitation"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated error text in 2480614. We seem to use ‘invitation’ everywhere else and it is unambiguously a noun.

msgid "Email address ${email_address} verified. ${confirm_message}."
msgstr ""

#: warehouse/accounts/views.py:839
#: warehouse/accounts/views.py:846
msgid "Expired token: request a new organization invite"
Copy link
Contributor

Choose a reason for hiding this comment

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

(Same question - whether "invite" or "invitation" is right here, and in the Invalid token message below as well.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(Updated error text in 2480614.)

#: warehouse/templates/manage/organizations.html:22
#: warehouse/templates/manage/projects.html:22
#, python-format
msgid "Pending invitations (%(invitation_count)s)"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this will read better as

"(%(invitation_count)s) pending invitations"

or as

"Pending invitations: (%(invitation_count)s)" [note the colon]

@@ -18,8 +18,35 @@
{% block title %}{% trans %}Your organizations{% endtrans %}{% endblock %}

{% block main %}
<h1 class="page-title">{% trans organization_count=organizations|length %}Your organizations ({{ organization_count }}){% endtrans %}</h1>
{% if organization_invites %}
<h1 class="page-title">{% trans invitation_count=organization_invites|length %}Pending invitations ({{ invitation_count }}){% endtrans %}</h1>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<h1 class="page-title">{% trans invitation_count=organization_invites|length %}Pending invitations ({{ invitation_count }}){% endtrans %}</h1>
<h1 class="page-title">{% trans invitation_count=organization_invites|length %}Pending invitations: ({{ invitation_count }}){% endtrans %}</h1>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could affect other heading text as well such as Your projects (5) or Your organizations (2) which appear in the side pane and the main pane on different pages.

@ewdurbin ewdurbin merged commit addad4d into pypi:main May 13, 2022
domdfcoding pushed a commit to domdfcoding/warehouse that referenced this pull request Jun 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants