We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
DataError: No validator for user display name max length
https://sentry.io/organizations/python-software-foundation/issues/2847760761/?referrer=github_plugin

```
StringDataRightTruncation: value too long for type character varying(100)

  File "sqlalchemy/engine/base.py", line 1802, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 719, in do_execute
    cursor.execute(statement, parameters)

DataError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)
(psycopg2.errors.StringDataRightTruncation) value too long for type character varying(100)

[SQL: UPDATE users SET name=%(name)s WHERE users.id = %(users_id)s]
[parameters: {'name': 'James is a marketing executive who excels in the development and execution of integrated marketing campaigns to drive brand awareness and drive customer loyalty. Links - https://www.amenify.com/...
(54 additional frame(s) were not displayed)
...
  File "sqlalchemy/engine/base.py", line 1845, in _execute_context
    self._handle_dbapi_exception(
  File "sqlalchemy/engine/base.py", line 2026, in _handle_dbapi_exception
    util.raise_(
  File "sqlalchemy/util/compat.py", line 207, in raise_
    raise exception
  File "sqlalchemy/engine/base.py", line 1802, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 719, in do_execute
    cursor.execute(statement, parameters)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `warehouse/manage/forms.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 import json
14 
15 import wtforms
16 
17 import warehouse.utils.otp as otp
18 import warehouse.utils.webauthn as webauthn
19 
20 from warehouse import forms
21 from warehouse.accounts.forms import (
22     NewEmailMixin,
23     NewPasswordMixin,
24     PasswordMixin,
25     TOTPValueMixin,
26     WebAuthnCredentialMixin,
27 )
28 
29 
30 class RoleNameMixin:
31 
32     role_name = wtforms.SelectField(
33         "Select role",
34         choices=[("", "Select role"), ("Maintainer", "Maintainer"), ("Owner", "Owner")],
35         validators=[wtforms.validators.DataRequired(message="Select role")],
36     )
37 
38 
39 class UsernameMixin:
40 
41     username = wtforms.StringField(
42         validators=[wtforms.validators.DataRequired(message="Specify username")]
43     )
44 
45     def validate_username(self, field):
46         userid = self.user_service.find_userid(field.data)
47 
48         if userid is None:
49             raise wtforms.validators.ValidationError(
50                 "No user found with that username. Try again."
51             )
52 
53 
54 class CreateRoleForm(RoleNameMixin, UsernameMixin, forms.Form):
55     def __init__(self, *args, user_service, **kwargs):
56         super().__init__(*args, **kwargs)
57         self.user_service = user_service
58 
59 
60 class ChangeRoleForm(RoleNameMixin, forms.Form):
61     pass
62 
63 
64 class SaveAccountForm(forms.Form):
65 
66     __params__ = ["name", "public_email"]
67 
68     name = wtforms.StringField()
69     public_email = wtforms.SelectField(choices=[("", "Not displayed")])
70 
71     def __init__(self, *args, user_service, user_id, **kwargs):
72         super().__init__(*args, **kwargs)
73         self.user_service = user_service
74         self.user_id = user_id
75         user = user_service.get_user(user_id)
76         self.public_email.choices.extend(
77             [(e.email, e.email) for e in user.emails if e.verified]
78         )
79 
80     def validate_public_email(self, field):
81         if field.data:
82             user = self.user_service.get_user(self.user_id)
83             verified_emails = [e.email for e in user.emails if e.verified]
84             if field.data not in verified_emails:
85                 raise wtforms.validators.ValidationError(
86                     "%s is not a verified email for %s" % (field.data, user.username)
87                 )
88 
89 
90 class AddEmailForm(NewEmailMixin, forms.Form):
91 
92     __params__ = ["email"]
93 
94     def __init__(self, *args, user_service, user_id, **kwargs):
95         super().__init__(*args, **kwargs)
96         self.user_service = user_service
97         self.user_id = user_id
98 
99 
100 class ChangePasswordForm(PasswordMixin, NewPasswordMixin, forms.Form):
101 
102     __params__ = ["password", "new_password", "password_confirm"]
103 
104     def __init__(self, *args, user_service, **kwargs):
105         super().__init__(*args, **kwargs)
106         self.user_service = user_service
107 
108 
109 class ConfirmPasswordForm(UsernameMixin, PasswordMixin, forms.Form):
110 
111     __params__ = ["confirm_password"]
112 
113     def __init__(self, *args, user_service, **kwargs):
114         super().__init__(*args, **kwargs)
115         self.user_service = user_service
116 
117 
118 class DeleteTOTPForm(ConfirmPasswordForm):
119     # TODO: delete?
120     pass
121 
122 
123 class ProvisionTOTPForm(TOTPValueMixin, forms.Form):
124 
125     __params__ = ["totp_value"]
126 
127     def __init__(self, *args, totp_secret, **kwargs):
128         super().__init__(*args, **kwargs)
129         self.totp_secret = totp_secret
130 
131     def validate_totp_value(self, field):
132         totp_value = field.data.encode("utf8")
133         if not otp.verify_totp(self.totp_secret, totp_value):
134             raise wtforms.validators.ValidationError("Invalid TOTP code. Try again?")
135 
136 
137 class DeleteWebAuthnForm(forms.Form):
138     __params__ = ["confirm_device_name"]
139 
140     label = wtforms.StringField(
141         validators=[
142             wtforms.validators.DataRequired(message="Specify a device name"),
143             wtforms.validators.Length(
144                 max=64, message=("Label must be 64 characters or less")
145             ),
146         ]
147     )
148 
149     def __init__(self, *args, user_service, user_id, **kwargs):
150         super().__init__(*args, **kwargs)
151         self.user_service = user_service
152         self.user_id = user_id
153 
154     def validate_label(self, field):
155         label = field.data
156 
157         webauthn = self.user_service.get_webauthn_by_label(self.user_id, label)
158         if webauthn is None:
159             raise wtforms.validators.ValidationError("No WebAuthn key with given label")
160         self.webauthn = webauthn
161 
162 
163 class ProvisionWebAuthnForm(WebAuthnCredentialMixin, forms.Form):
164     __params__ = ["label", "credential"]
165 
166     label = wtforms.StringField(
167         validators=[
168             wtforms.validators.DataRequired(message="Specify a label"),
169             wtforms.validators.Length(
170                 max=64, message=("Label must be 64 characters or less")
171             ),
172         ]
173     )
174 
175     def __init__(
176         self, *args, user_service, user_id, challenge, rp_id, origin, **kwargs
177     ):
178         super().__init__(*args, **kwargs)
179         self.user_service = user_service
180         self.user_id = user_id
181         self.challenge = challenge
182         self.rp_id = rp_id
183         self.origin = origin
184 
185     def validate_credential(self, field):
186         try:
187             credential_dict = json.loads(field.data.encode("utf8"))
188         except json.JSONDecodeError:
189             raise wtforms.validators.ValidationError(
190                 "Invalid WebAuthn credential: Bad payload"
191             )
192 
193         try:
194             validated_credential = self.user_service.verify_webauthn_credential(
195                 credential_dict,
196                 challenge=self.challenge,
197                 rp_id=self.rp_id,
198                 origin=self.origin,
199             )
200         except webauthn.RegistrationRejectedError as e:
201             raise wtforms.validators.ValidationError(str(e))
202 
203         self.validated_credential = validated_credential
204 
205     def validate_label(self, field):
206         label = field.data
207 
208         if self.user_service.get_webauthn_by_label(self.user_id, label) is not None:
209             raise wtforms.validators.ValidationError(f"Label '{label}' already in use")
210 
211 
212 class CreateMacaroonForm(forms.Form):
213     __params__ = ["description", "token_scope"]
214 
215     def __init__(self, *args, user_id, macaroon_service, project_names, **kwargs):
216         super().__init__(*args, **kwargs)
217         self.user_id = user_id
218         self.macaroon_service = macaroon_service
219         self.project_names = project_names
220 
221     description = wtforms.StringField(
222         validators=[
223             wtforms.validators.DataRequired(message="Specify a token name"),
224             wtforms.validators.Length(
225                 max=100, message="Description must be 100 characters or less"
226             ),
227         ]
228     )
229 
230     token_scope = wtforms.StringField(
231         validators=[wtforms.validators.DataRequired(message="Specify the token scope")]
232     )
233 
234     def validate_description(self, field):
235         description = field.data
236 
237         if (
238             self.macaroon_service.get_macaroon_by_description(self.user_id, description)
239             is not None
240         ):
241             raise wtforms.validators.ValidationError("API token name already in use")
242 
243     def validate_token_scope(self, field):
244         scope = field.data
245 
246         try:
247             _, scope_kind = scope.split(":", 1)
248         except ValueError:
249             raise wtforms.ValidationError(f"Unknown token scope: {scope}")
250 
251         if scope_kind == "unspecified":
252             raise wtforms.ValidationError("Specify the token scope")
253 
254         if scope_kind == "user":
255             self.validated_scope = scope_kind
256             return
257 
258         try:
259             scope_kind, scope_value = scope_kind.split(":", 1)
260         except ValueError:
261             raise wtforms.ValidationError(f"Unknown token scope: {scope}")
262 
263         if scope_kind != "project":
264             raise wtforms.ValidationError(f"Unknown token scope: {scope}")
265         if scope_value not in self.project_names:
266             raise wtforms.ValidationError(
267                 f"Unknown or invalid project name: {scope_value}"
268             )
269 
270         self.validated_scope = {"projects": [scope_value]}
271 
272 
273 class DeleteMacaroonForm(UsernameMixin, PasswordMixin, forms.Form):
274     __params__ = ["confirm_password", "macaroon_id"]
275 
276     macaroon_id = wtforms.StringField(
277         validators=[wtforms.validators.DataRequired(message="Identifier required")]
278     )
279 
280     def __init__(self, *args, macaroon_service, user_service, **kwargs):
281         super().__init__(*args, **kwargs)
282         self.user_service = user_service
283         self.macaroon_service = macaroon_service
284 
285     def validate_macaroon_id(self, field):
286         macaroon_id = field.data
287         if self.macaroon_service.find_macaroon(macaroon_id) is None:
288             raise wtforms.validators.ValidationError("No such macaroon")
289
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/warehouse/manage/forms.py b/warehouse/manage/forms.py
--- a/warehouse/manage/forms.py
+++ b/warehouse/manage/forms.py
@@ -25,6 +25,7 @@
     TOTPValueMixin,
     WebAuthnCredentialMixin,
 )
+from warehouse.i18n import localize as _
 
 
 class RoleNameMixin:
@@ -65,7 +66,17 @@
 
     __params__ = ["name", "public_email"]
 
-    name = wtforms.StringField()
+    name = wtforms.StringField(
+        validators=[
+            wtforms.validators.Length(
+                max=100,
+                message=_(
+                    "The name is too long. "
+                    "Choose a name with 100 characters or less."
+                ),
+            )
+        ]
+    )
     public_email = wtforms.SelectField(choices=[("", "Not displayed")])
 
     def __init__(self, *args, user_service, user_id, **kwargs):

{"golden_diff": "diff --git a/warehouse/manage/forms.py b/warehouse/manage/forms.py\n--- a/warehouse/manage/forms.py\n+++ b/warehouse/manage/forms.py\n@@ -25,6 +25,7 @@\n     TOTPValueMixin,\n     WebAuthnCredentialMixin,\n )\n+from warehouse.i18n import localize as _\n \n \n class RoleNameMixin:\n@@ -65,7 +66,17 @@\n \n     __params__ = [\"name\", \"public_email\"]\n \n-    name = wtforms.StringField()\n+    name = wtforms.StringField(\n+        validators=[\n+            wtforms.validators.Length(\n+                max=100,\n+                message=_(\n+                    \"The name is too long. \"\n+                    \"Choose a name with 100 characters or less.\"\n+                ),\n+            )\n+        ]\n+    )\n     public_email = wtforms.SelectField(choices=[(\"\", \"Not displayed\")])\n \n     def __init__(self, *args, user_service, user_id, **kwargs):\n", "issue": "DataError: No validator for user display name max length\nhttps://sentry.io/organizations/python-software-foundation/issues/2847760761/?referrer=github_plugin\n\n```\nStringDataRightTruncation: value too long for type character varying(100)\n\n  File \"sqlalchemy/engine/base.py\", line 1802, in _execute_context\n    self.dialect.do_execute(\n  File \"sqlalchemy/engine/default.py\", line 719, in do_execute\n    cursor.execute(statement, parameters)\n\nDataError: (raised as a result of Query-invoked autoflush; consider using a session.no_autoflush block if this flush is occurring prematurely)\n(psycopg2.errors.StringDataRightTruncation) value too long for type character varying(100)\n\n[SQL: UPDATE users SET name=%(name)s WHERE users.id = %(users_id)s]\n[parameters: {'name': 'James is a marketing executive who excels in the development and execution of integrated marketing campaigns to drive brand awareness and drive customer loyalty. Links - https://www.amenify.com/...\n(54 additional frame(s) were not displayed)\n...\n  File \"sqlalchemy/engine/base.py\", line 1845, in _execute_context\n    self._handle_dbapi_exception(\n  File \"sqlalchemy/engine/base.py\", line 2026, in _handle_dbapi_exception\n    util.raise_(\n  File \"sqlalchemy/util/compat.py\", line 207, in raise_\n    raise exception\n  File \"sqlalchemy/engine/base.py\", line 1802, in _execute_context\n    self.dialect.do_execute(\n  File \"sqlalchemy/engine/default.py\", line 719, in do_execute\n    cursor.execute(statement, parameters)\n```\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport wtforms\n\nimport warehouse.utils.otp as otp\nimport warehouse.utils.webauthn as webauthn\n\nfrom warehouse import forms\nfrom warehouse.accounts.forms import (\n    NewEmailMixin,\n    NewPasswordMixin,\n    PasswordMixin,\n    TOTPValueMixin,\n    WebAuthnCredentialMixin,\n)\n\n\nclass RoleNameMixin:\n\n    role_name = wtforms.SelectField(\n        \"Select role\",\n        choices=[(\"\", \"Select role\"), (\"Maintainer\", \"Maintainer\"), (\"Owner\", \"Owner\")],\n        validators=[wtforms.validators.DataRequired(message=\"Select role\")],\n    )\n\n\nclass UsernameMixin:\n\n    username = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Specify username\")]\n    )\n\n    def validate_username(self, field):\n        userid = self.user_service.find_userid(field.data)\n\n        if userid is None:\n            raise wtforms.validators.ValidationError(\n                \"No user found with that username. Try again.\"\n            )\n\n\nclass CreateRoleForm(RoleNameMixin, UsernameMixin, forms.Form):\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass ChangeRoleForm(RoleNameMixin, forms.Form):\n    pass\n\n\nclass SaveAccountForm(forms.Form):\n\n    __params__ = [\"name\", \"public_email\"]\n\n    name = wtforms.StringField()\n    public_email = wtforms.SelectField(choices=[(\"\", \"Not displayed\")])\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n        user = user_service.get_user(user_id)\n        self.public_email.choices.extend(\n            [(e.email, e.email) for e in user.emails if e.verified]\n        )\n\n    def validate_public_email(self, field):\n        if field.data:\n            user = self.user_service.get_user(self.user_id)\n            verified_emails = [e.email for e in user.emails if e.verified]\n            if field.data not in verified_emails:\n                raise wtforms.validators.ValidationError(\n                    \"%s is not a verified email for %s\" % (field.data, user.username)\n                )\n\n\nclass AddEmailForm(NewEmailMixin, forms.Form):\n\n    __params__ = [\"email\"]\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n\n\nclass ChangePasswordForm(PasswordMixin, NewPasswordMixin, forms.Form):\n\n    __params__ = [\"password\", \"new_password\", \"password_confirm\"]\n\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass ConfirmPasswordForm(UsernameMixin, PasswordMixin, forms.Form):\n\n    __params__ = [\"confirm_password\"]\n\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass DeleteTOTPForm(ConfirmPasswordForm):\n    # TODO: delete?\n    pass\n\n\nclass ProvisionTOTPForm(TOTPValueMixin, forms.Form):\n\n    __params__ = [\"totp_value\"]\n\n    def __init__(self, *args, totp_secret, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.totp_secret = totp_secret\n\n    def validate_totp_value(self, field):\n        totp_value = field.data.encode(\"utf8\")\n        if not otp.verify_totp(self.totp_secret, totp_value):\n            raise wtforms.validators.ValidationError(\"Invalid TOTP code. Try again?\")\n\n\nclass DeleteWebAuthnForm(forms.Form):\n    __params__ = [\"confirm_device_name\"]\n\n    label = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a device name\"),\n            wtforms.validators.Length(\n                max=64, message=(\"Label must be 64 characters or less\")\n            ),\n        ]\n    )\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n\n    def validate_label(self, field):\n        label = field.data\n\n        webauthn = self.user_service.get_webauthn_by_label(self.user_id, label)\n        if webauthn is None:\n            raise wtforms.validators.ValidationError(\"No WebAuthn key with given label\")\n        self.webauthn = webauthn\n\n\nclass ProvisionWebAuthnForm(WebAuthnCredentialMixin, forms.Form):\n    __params__ = [\"label\", \"credential\"]\n\n    label = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a label\"),\n            wtforms.validators.Length(\n                max=64, message=(\"Label must be 64 characters or less\")\n            ),\n        ]\n    )\n\n    def __init__(\n        self, *args, user_service, user_id, challenge, rp_id, origin, **kwargs\n    ):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n        self.challenge = challenge\n        self.rp_id = rp_id\n        self.origin = origin\n\n    def validate_credential(self, field):\n        try:\n            credential_dict = json.loads(field.data.encode(\"utf8\"))\n        except json.JSONDecodeError:\n            raise wtforms.validators.ValidationError(\n                \"Invalid WebAuthn credential: Bad payload\"\n            )\n\n        try:\n            validated_credential = self.user_service.verify_webauthn_credential(\n                credential_dict,\n                challenge=self.challenge,\n                rp_id=self.rp_id,\n                origin=self.origin,\n            )\n        except webauthn.RegistrationRejectedError as e:\n            raise wtforms.validators.ValidationError(str(e))\n\n        self.validated_credential = validated_credential\n\n    def validate_label(self, field):\n        label = field.data\n\n        if self.user_service.get_webauthn_by_label(self.user_id, label) is not None:\n            raise wtforms.validators.ValidationError(f\"Label '{label}' already in use\")\n\n\nclass CreateMacaroonForm(forms.Form):\n    __params__ = [\"description\", \"token_scope\"]\n\n    def __init__(self, *args, user_id, macaroon_service, project_names, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_id = user_id\n        self.macaroon_service = macaroon_service\n        self.project_names = project_names\n\n    description = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a token name\"),\n            wtforms.validators.Length(\n                max=100, message=\"Description must be 100 characters or less\"\n            ),\n        ]\n    )\n\n    token_scope = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Specify the token scope\")]\n    )\n\n    def validate_description(self, field):\n        description = field.data\n\n        if (\n            self.macaroon_service.get_macaroon_by_description(self.user_id, description)\n            is not None\n        ):\n            raise wtforms.validators.ValidationError(\"API token name already in use\")\n\n    def validate_token_scope(self, field):\n        scope = field.data\n\n        try:\n            _, scope_kind = scope.split(\":\", 1)\n        except ValueError:\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n\n        if scope_kind == \"unspecified\":\n            raise wtforms.ValidationError(\"Specify the token scope\")\n\n        if scope_kind == \"user\":\n            self.validated_scope = scope_kind\n            return\n\n        try:\n            scope_kind, scope_value = scope_kind.split(\":\", 1)\n        except ValueError:\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n\n        if scope_kind != \"project\":\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n        if scope_value not in self.project_names:\n            raise wtforms.ValidationError(\n                f\"Unknown or invalid project name: {scope_value}\"\n            )\n\n        self.validated_scope = {\"projects\": [scope_value]}\n\n\nclass DeleteMacaroonForm(UsernameMixin, PasswordMixin, forms.Form):\n    __params__ = [\"confirm_password\", \"macaroon_id\"]\n\n    macaroon_id = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Identifier required\")]\n    )\n\n    def __init__(self, *args, macaroon_service, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.macaroon_service = macaroon_service\n\n    def validate_macaroon_id(self, field):\n        macaroon_id = field.data\n        if self.macaroon_service.find_macaroon(macaroon_id) is None:\n            raise wtforms.validators.ValidationError(\"No such macaroon\")\n", "path": "warehouse/manage/forms.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport wtforms\n\nimport warehouse.utils.otp as otp\nimport warehouse.utils.webauthn as webauthn\n\nfrom warehouse import forms\nfrom warehouse.accounts.forms import (\n    NewEmailMixin,\n    NewPasswordMixin,\n    PasswordMixin,\n    TOTPValueMixin,\n    WebAuthnCredentialMixin,\n)\nfrom warehouse.i18n import localize as _\n\n\nclass RoleNameMixin:\n\n    role_name = wtforms.SelectField(\n        \"Select role\",\n        choices=[(\"\", \"Select role\"), (\"Maintainer\", \"Maintainer\"), (\"Owner\", \"Owner\")],\n        validators=[wtforms.validators.DataRequired(message=\"Select role\")],\n    )\n\n\nclass UsernameMixin:\n\n    username = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Specify username\")]\n    )\n\n    def validate_username(self, field):\n        userid = self.user_service.find_userid(field.data)\n\n        if userid is None:\n            raise wtforms.validators.ValidationError(\n                \"No user found with that username. Try again.\"\n            )\n\n\nclass CreateRoleForm(RoleNameMixin, UsernameMixin, forms.Form):\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass ChangeRoleForm(RoleNameMixin, forms.Form):\n    pass\n\n\nclass SaveAccountForm(forms.Form):\n\n    __params__ = [\"name\", \"public_email\"]\n\n    name = wtforms.StringField(\n        validators=[\n            wtforms.validators.Length(\n                max=100,\n                message=_(\n                    \"The name is too long. \"\n                    \"Choose a name with 100 characters or less.\"\n                ),\n            )\n        ]\n    )\n    public_email = wtforms.SelectField(choices=[(\"\", \"Not displayed\")])\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n        user = user_service.get_user(user_id)\n        self.public_email.choices.extend(\n            [(e.email, e.email) for e in user.emails if e.verified]\n        )\n\n    def validate_public_email(self, field):\n        if field.data:\n            user = self.user_service.get_user(self.user_id)\n            verified_emails = [e.email for e in user.emails if e.verified]\n            if field.data not in verified_emails:\n                raise wtforms.validators.ValidationError(\n                    \"%s is not a verified email for %s\" % (field.data, user.username)\n                )\n\n\nclass AddEmailForm(NewEmailMixin, forms.Form):\n\n    __params__ = [\"email\"]\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n\n\nclass ChangePasswordForm(PasswordMixin, NewPasswordMixin, forms.Form):\n\n    __params__ = [\"password\", \"new_password\", \"password_confirm\"]\n\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass ConfirmPasswordForm(UsernameMixin, PasswordMixin, forms.Form):\n\n    __params__ = [\"confirm_password\"]\n\n    def __init__(self, *args, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n\n\nclass DeleteTOTPForm(ConfirmPasswordForm):\n    # TODO: delete?\n    pass\n\n\nclass ProvisionTOTPForm(TOTPValueMixin, forms.Form):\n\n    __params__ = [\"totp_value\"]\n\n    def __init__(self, *args, totp_secret, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.totp_secret = totp_secret\n\n    def validate_totp_value(self, field):\n        totp_value = field.data.encode(\"utf8\")\n        if not otp.verify_totp(self.totp_secret, totp_value):\n            raise wtforms.validators.ValidationError(\"Invalid TOTP code. Try again?\")\n\n\nclass DeleteWebAuthnForm(forms.Form):\n    __params__ = [\"confirm_device_name\"]\n\n    label = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a device name\"),\n            wtforms.validators.Length(\n                max=64, message=(\"Label must be 64 characters or less\")\n            ),\n        ]\n    )\n\n    def __init__(self, *args, user_service, user_id, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n\n    def validate_label(self, field):\n        label = field.data\n\n        webauthn = self.user_service.get_webauthn_by_label(self.user_id, label)\n        if webauthn is None:\n            raise wtforms.validators.ValidationError(\"No WebAuthn key with given label\")\n        self.webauthn = webauthn\n\n\nclass ProvisionWebAuthnForm(WebAuthnCredentialMixin, forms.Form):\n    __params__ = [\"label\", \"credential\"]\n\n    label = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a label\"),\n            wtforms.validators.Length(\n                max=64, message=(\"Label must be 64 characters or less\")\n            ),\n        ]\n    )\n\n    def __init__(\n        self, *args, user_service, user_id, challenge, rp_id, origin, **kwargs\n    ):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.user_id = user_id\n        self.challenge = challenge\n        self.rp_id = rp_id\n        self.origin = origin\n\n    def validate_credential(self, field):\n        try:\n            credential_dict = json.loads(field.data.encode(\"utf8\"))\n        except json.JSONDecodeError:\n            raise wtforms.validators.ValidationError(\n                \"Invalid WebAuthn credential: Bad payload\"\n            )\n\n        try:\n            validated_credential = self.user_service.verify_webauthn_credential(\n                credential_dict,\n                challenge=self.challenge,\n                rp_id=self.rp_id,\n                origin=self.origin,\n            )\n        except webauthn.RegistrationRejectedError as e:\n            raise wtforms.validators.ValidationError(str(e))\n\n        self.validated_credential = validated_credential\n\n    def validate_label(self, field):\n        label = field.data\n\n        if self.user_service.get_webauthn_by_label(self.user_id, label) is not None:\n            raise wtforms.validators.ValidationError(f\"Label '{label}' already in use\")\n\n\nclass CreateMacaroonForm(forms.Form):\n    __params__ = [\"description\", \"token_scope\"]\n\n    def __init__(self, *args, user_id, macaroon_service, project_names, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_id = user_id\n        self.macaroon_service = macaroon_service\n        self.project_names = project_names\n\n    description = wtforms.StringField(\n        validators=[\n            wtforms.validators.DataRequired(message=\"Specify a token name\"),\n            wtforms.validators.Length(\n                max=100, message=\"Description must be 100 characters or less\"\n            ),\n        ]\n    )\n\n    token_scope = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Specify the token scope\")]\n    )\n\n    def validate_description(self, field):\n        description = field.data\n\n        if (\n            self.macaroon_service.get_macaroon_by_description(self.user_id, description)\n            is not None\n        ):\n            raise wtforms.validators.ValidationError(\"API token name already in use\")\n\n    def validate_token_scope(self, field):\n        scope = field.data\n\n        try:\n            _, scope_kind = scope.split(\":\", 1)\n        except ValueError:\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n\n        if scope_kind == \"unspecified\":\n            raise wtforms.ValidationError(\"Specify the token scope\")\n\n        if scope_kind == \"user\":\n            self.validated_scope = scope_kind\n            return\n\n        try:\n            scope_kind, scope_value = scope_kind.split(\":\", 1)\n        except ValueError:\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n\n        if scope_kind != \"project\":\n            raise wtforms.ValidationError(f\"Unknown token scope: {scope}\")\n        if scope_value not in self.project_names:\n            raise wtforms.ValidationError(\n                f\"Unknown or invalid project name: {scope_value}\"\n            )\n\n        self.validated_scope = {\"projects\": [scope_value]}\n\n\nclass DeleteMacaroonForm(UsernameMixin, PasswordMixin, forms.Form):\n    __params__ = [\"confirm_password\", \"macaroon_id\"]\n\n    macaroon_id = wtforms.StringField(\n        validators=[wtforms.validators.DataRequired(message=\"Identifier required\")]\n    )\n\n    def __init__(self, *args, macaroon_service, user_service, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.user_service = user_service\n        self.macaroon_service = macaroon_service\n\n    def validate_macaroon_id(self, field):\n        macaroon_id = field.data\n        if self.macaroon_service.find_macaroon(macaroon_id) is None:\n            raise wtforms.validators.ValidationError(\"No such macaroon\")\n", "path": "warehouse/manage/forms.py"}]}