Repository: pypi/warehouse

Issue:
No difference between internal and external collaborator in organization account
When a user is added as an internal collaborator and this user is not a member of the organization, the user is sent an invite to join the project. This not different from adding an external collaborator.

To recreate the steps-

- Click on Your organizations
- Click on Manage for a specific organization
- Click on Projects
- Click on Manage for a specific project
- Click on Collaborators
- Select a team within the organization or enter a username
- Assign a role for the collaborator
- Click on Add

An error message should inform that the user is not a member of the organization. Internal collaborators should be restricted to members of the organization. 


Discussion:

Resolving patch:
diff --git a/warehouse/manage/forms.py b/warehouse/manage/forms.py
--- a/warehouse/manage/forms.py
+++ b/warehouse/manage/forms.py
@@ -79,7 +79,6 @@ def __init__(self, *args, user_service, **kwargs):
 class CreateInternalRoleForm(
     RoleNameMixin,
     TeamProjectRoleNameMixin,
-    UsernameMixin,
     forms.Form,
 ):
     is_team = wtforms.RadioField(
@@ -97,9 +96,17 @@ class CreateInternalRoleForm(
         validators=[wtforms.validators.InputRequired()],
     )
 
-    def __init__(self, *args, team_choices, user_service, **kwargs):
+    username = wtforms.SelectField(
+        "Select user",
+        choices=[("", "Select user")],
+        default="",  # Set default to avoid error when there are no user choices.
+        validators=[wtforms.validators.InputRequired()],
+    )
+
+    def __init__(self, *args, team_choices, user_choices, user_service, **kwargs):
         super().__init__(*args, **kwargs)
         self.team_name.choices += [(name, name) for name in sorted(team_choices)]
+        self.username.choices += [(name, name) for name in sorted(user_choices)]
         self.user_service = user_service
 
         # Do not check for required fields in browser.
@@ -116,10 +123,6 @@ def __init__(self, *args, team_choices, user_service, **kwargs):
             self.team_name.validators = []
             self.team_project_role_name.validators = []
 
-    def validate_username(self, field):
-        if not self.is_team.data:
-            super().validate_username(field)
-
 
 class ChangeRoleForm(RoleNameMixin, forms.Form):
     pass
diff --git a/warehouse/manage/views.py b/warehouse/manage/views.py
--- a/warehouse/manage/views.py
+++ b/warehouse/manage/views.py
@@ -4086,16 +4086,19 @@ def manage_project_roles(project, request, _form_class=CreateRoleForm):
             .filter(TeamProjectRole.project == project)
             .all()
         )
-        internal_role_form = CreateInternalRoleForm(
-            request.POST,
-            team_choices=sorted(team.name for team in project.organization.teams),
-            user_service=user_service,
-        )
         internal_users = set(
             organization_owners(request, project.organization)
             + organization_managers(request, project.organization)
             + organization_members(request, project.organization)
         )
+        internal_role_form = CreateInternalRoleForm(
+            request.POST,
+            team_choices=sorted(team.name for team in project.organization.teams),
+            user_choices=sorted(
+                user.username for user in internal_users if user not in project.users
+            ),
+            user_service=user_service,
+        )
     else:
         team_project_roles = set()
         internal_role_form = None


Test patch: