We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bad characters in username
There's a bot with a bad username of `packagerbot/os-master01.phx2.fedoraproject.org` that makes CI tests failing.
https://bodhi.fedoraproject.org/users/packagerbot/os-master01.phx2.fedoraproject.org

I'm pushing a PR to safe check CI tests, but do we want to make Bodhi safe to bad usernames like this? Since usernames are from outside world, should we modify them in a safe way before storing in the database?

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bodhi/server/services/user.py`
Content:
```
1 # Copyright 2014-2019 Red Hat, Inc. and others
2 #
3 # This file is part of Bodhi.
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 """Defines API services that pertain to users."""
19 import math
20 
21 from cornice import Service
22 from cornice.validators import colander_querystring_validator
23 from pyramid.exceptions import HTTPNotFound
24 from sqlalchemy import func, distinct
25 from sqlalchemy.sql import or_
26 
27 from bodhi.server.models import Group, Update, User
28 from bodhi.server.validators import (validate_updates, validate_groups)
29 import bodhi.server.schemas
30 import bodhi.server.security
31 import bodhi.server.services.errors
32 import bodhi.server.services.updates
33 
34 
35 user = Service(name='user', path='/users/{name}',
36                description='Bodhi users',
37                # These we leave wide-open since these are only GETs
38                cors_origins=bodhi.server.security.cors_origins_ro)
39 
40 users = Service(name='users', path='/users/',
41                 description='Bodhi users',
42                 # These we leave wide-open since these are only GETs
43                 cors_origins=bodhi.server.security.cors_origins_ro)
44 
45 users_rss = Service(name='users_rss', path='/rss/users/', description='Bodhi users RSS feed',
46                     cors_origins=bodhi.server.security.cors_origins_ro)
47 
48 
49 @user.get(accept=("application/json", "text/json"), renderer="json",
50           error_handler=bodhi.server.services.errors.json_handler)
51 @user.get(accept=("application/javascript"), renderer="jsonp",
52           error_handler=bodhi.server.services.errors.json_handler)
53 @user.get(accept="text/html", renderer="user.html",
54           error_handler=bodhi.server.services.errors.html_handler)
55 def get_user(request):
56     """
57     Return a user given by username.
58 
59     Args:
60         request (pyramid.request): The current request.
61     Returns:
62         dict: A dictionary with two keys. "user" maps to a dictionary representation of the User
63             object. "urls" maps to various URLs that describe various other objects related to the
64             user.
65     """
66     id = request.matchdict.get('name')
67     user = User.get(id)
68 
69     if not user:
70         request.errors.add('body', 'name', 'No such user')
71         request.errors.status = HTTPNotFound.code
72         return
73 
74     user = user.__json__(request)
75 
76     # Throw some extra information in there
77     rurl = request.route_url  # Just shorthand
78     urls = {
79         'comments_by': rurl('comments') + '?user=%s' % id,
80         'comments_on': rurl('comments') + '?update_owner=%s' % id,
81         'recent_updates': rurl('updates') + '?user=%s' % id,
82         'recent_overrides': rurl('overrides') + '?user=%s' % id,
83         'comments_by_rss': rurl('comments_rss') + '?user=%s' % id,
84         'comments_on_rss': rurl('comments_rss') + '?update_owner=%s' % id,
85         'recent_updates_rss': rurl('updates_rss') + '?user=%s' % id,
86         'recent_overrides_rss': rurl('overrides_rss') + '?user=%s' % id,
87     }
88 
89     return dict(user=user, urls=urls)
90 
91 
92 validators = (
93     colander_querystring_validator,
94     validate_groups,
95     validate_updates,
96 )
97 
98 
99 @users.get(schema=bodhi.server.schemas.ListUserSchema,
100            accept=("application/json", "text/json"), renderer="json",
101            error_handler=bodhi.server.services.errors.json_handler,
102            validators=validators)
103 @users.get(schema=bodhi.server.schemas.ListUserSchema,
104            accept=("application/javascript"), renderer="jsonp",
105            error_handler=bodhi.server.services.errors.jsonp_handler,
106            validators=validators)
107 @users.get(schema=bodhi.server.schemas.ListUserSchema, renderer="rss",
108            accept=('application/atom+xml',),
109            error_handler=bodhi.server.services.errors.html_handler,
110            validators=validators)
111 @users_rss.get(schema=bodhi.server.schemas.ListUserSchema, renderer="rss",
112                error_handler=bodhi.server.services.errors.html_handler,
113                validators=validators)
114 def query_users(request):
115     """
116     Search for users by various criteria.
117 
118     Args:
119         request (pyramid.request): The current web request.
120     Returns:
121         dict: A dictionary with the follow key mappings:
122             users: A list of users matching the search criteria.
123             page: The current page of results.
124             pages: The total number of pages available.
125             rows_per_page: The number of users on the page.
126             total: The total number of users matching the search criteria.
127     """
128     db = request.db
129     data = request.validated
130     query = db.query(User)
131 
132     like = data.get('like')
133     if like is not None:
134         query = query.filter(or_(*[
135             User.name.like('%%%s%%' % like)
136         ]))
137 
138     search = data.get('search')
139     if search is not None:
140         query = query.filter(User.name.ilike('%%%s%%' % search))
141 
142     name = data.get('name')
143     if name is not None:
144         query = query.filter(User.name.like(name))
145 
146     groups = data.get('groups')
147     if groups is not None:
148         query = query.join(User.groups)
149         query = query.filter(or_(*[Group.id == grp.id for grp in groups]))
150 
151     updates = data.get('updates')
152     if updates is not None:
153         query = query.join(User.updates)
154         args = [Update.alias == update.alias for update in updates]
155         query = query.filter(or_(*args))
156 
157     # We can't use ``query.count()`` here because it is naive with respect to
158     # all the joins that we're doing above.
159     count_query = query.with_labels().statement\
160         .with_only_columns([func.count(distinct(User.id))])\
161         .order_by(None)
162     total = request.db.execute(count_query).scalar()
163 
164     page = data.get('page')
165     rows_per_page = data.get('rows_per_page')
166     pages = int(math.ceil(total / float(rows_per_page)))
167     query = query.offset(rows_per_page * (page - 1)).limit(rows_per_page)
168 
169     return dict(
170         users=query.all(),
171         page=page,
172         pages=pages,
173         rows_per_page=rows_per_page,
174         total=total,
175     )
176
```
--- 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/bodhi/server/services/user.py b/bodhi/server/services/user.py
--- a/bodhi/server/services/user.py
+++ b/bodhi/server/services/user.py
@@ -32,7 +32,7 @@
 import bodhi.server.services.updates
 
 
-user = Service(name='user', path='/users/{name}',
+user = Service(name='user', path=r'/users/{name:\S+}',
                description='Bodhi users',
                # These we leave wide-open since these are only GETs
                cors_origins=bodhi.server.security.cors_origins_ro)

{"golden_diff": "diff --git a/bodhi/server/services/user.py b/bodhi/server/services/user.py\n--- a/bodhi/server/services/user.py\n+++ b/bodhi/server/services/user.py\n@@ -32,7 +32,7 @@\n import bodhi.server.services.updates\n \n \n-user = Service(name='user', path='/users/{name}',\n+user = Service(name='user', path=r'/users/{name:\\S+}',\n                description='Bodhi users',\n                # These we leave wide-open since these are only GETs\n                cors_origins=bodhi.server.security.cors_origins_ro)\n", "issue": "Bad characters in username\nThere's a bot with a bad username of `packagerbot/os-master01.phx2.fedoraproject.org` that makes CI tests failing.\r\nhttps://bodhi.fedoraproject.org/users/packagerbot/os-master01.phx2.fedoraproject.org\r\n\r\nI'm pushing a PR to safe check CI tests, but do we want to make Bodhi safe to bad usernames like this? Since usernames are from outside world, should we modify them in a safe way before storing in the database?\n", "before_files": [{"content": "# Copyright 2014-2019 Red Hat, Inc. and others\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n\"\"\"Defines API services that pertain to users.\"\"\"\nimport math\n\nfrom cornice import Service\nfrom cornice.validators import colander_querystring_validator\nfrom pyramid.exceptions import HTTPNotFound\nfrom sqlalchemy import func, distinct\nfrom sqlalchemy.sql import or_\n\nfrom bodhi.server.models import Group, Update, User\nfrom bodhi.server.validators import (validate_updates, validate_groups)\nimport bodhi.server.schemas\nimport bodhi.server.security\nimport bodhi.server.services.errors\nimport bodhi.server.services.updates\n\n\nuser = Service(name='user', path='/users/{name}',\n               description='Bodhi users',\n               # These we leave wide-open since these are only GETs\n               cors_origins=bodhi.server.security.cors_origins_ro)\n\nusers = Service(name='users', path='/users/',\n                description='Bodhi users',\n                # These we leave wide-open since these are only GETs\n                cors_origins=bodhi.server.security.cors_origins_ro)\n\nusers_rss = Service(name='users_rss', path='/rss/users/', description='Bodhi users RSS feed',\n                    cors_origins=bodhi.server.security.cors_origins_ro)\n\n\n@user.get(accept=(\"application/json\", \"text/json\"), renderer=\"json\",\n          error_handler=bodhi.server.services.errors.json_handler)\n@user.get(accept=(\"application/javascript\"), renderer=\"jsonp\",\n          error_handler=bodhi.server.services.errors.json_handler)\n@user.get(accept=\"text/html\", renderer=\"user.html\",\n          error_handler=bodhi.server.services.errors.html_handler)\ndef get_user(request):\n    \"\"\"\n    Return a user given by username.\n\n    Args:\n        request (pyramid.request): The current request.\n    Returns:\n        dict: A dictionary with two keys. \"user\" maps to a dictionary representation of the User\n            object. \"urls\" maps to various URLs that describe various other objects related to the\n            user.\n    \"\"\"\n    id = request.matchdict.get('name')\n    user = User.get(id)\n\n    if not user:\n        request.errors.add('body', 'name', 'No such user')\n        request.errors.status = HTTPNotFound.code\n        return\n\n    user = user.__json__(request)\n\n    # Throw some extra information in there\n    rurl = request.route_url  # Just shorthand\n    urls = {\n        'comments_by': rurl('comments') + '?user=%s' % id,\n        'comments_on': rurl('comments') + '?update_owner=%s' % id,\n        'recent_updates': rurl('updates') + '?user=%s' % id,\n        'recent_overrides': rurl('overrides') + '?user=%s' % id,\n        'comments_by_rss': rurl('comments_rss') + '?user=%s' % id,\n        'comments_on_rss': rurl('comments_rss') + '?update_owner=%s' % id,\n        'recent_updates_rss': rurl('updates_rss') + '?user=%s' % id,\n        'recent_overrides_rss': rurl('overrides_rss') + '?user=%s' % id,\n    }\n\n    return dict(user=user, urls=urls)\n\n\nvalidators = (\n    colander_querystring_validator,\n    validate_groups,\n    validate_updates,\n)\n\n\n@users.get(schema=bodhi.server.schemas.ListUserSchema,\n           accept=(\"application/json\", \"text/json\"), renderer=\"json\",\n           error_handler=bodhi.server.services.errors.json_handler,\n           validators=validators)\n@users.get(schema=bodhi.server.schemas.ListUserSchema,\n           accept=(\"application/javascript\"), renderer=\"jsonp\",\n           error_handler=bodhi.server.services.errors.jsonp_handler,\n           validators=validators)\n@users.get(schema=bodhi.server.schemas.ListUserSchema, renderer=\"rss\",\n           accept=('application/atom+xml',),\n           error_handler=bodhi.server.services.errors.html_handler,\n           validators=validators)\n@users_rss.get(schema=bodhi.server.schemas.ListUserSchema, renderer=\"rss\",\n               error_handler=bodhi.server.services.errors.html_handler,\n               validators=validators)\ndef query_users(request):\n    \"\"\"\n    Search for users by various criteria.\n\n    Args:\n        request (pyramid.request): The current web request.\n    Returns:\n        dict: A dictionary with the follow key mappings:\n            users: A list of users matching the search criteria.\n            page: The current page of results.\n            pages: The total number of pages available.\n            rows_per_page: The number of users on the page.\n            total: The total number of users matching the search criteria.\n    \"\"\"\n    db = request.db\n    data = request.validated\n    query = db.query(User)\n\n    like = data.get('like')\n    if like is not None:\n        query = query.filter(or_(*[\n            User.name.like('%%%s%%' % like)\n        ]))\n\n    search = data.get('search')\n    if search is not None:\n        query = query.filter(User.name.ilike('%%%s%%' % search))\n\n    name = data.get('name')\n    if name is not None:\n        query = query.filter(User.name.like(name))\n\n    groups = data.get('groups')\n    if groups is not None:\n        query = query.join(User.groups)\n        query = query.filter(or_(*[Group.id == grp.id for grp in groups]))\n\n    updates = data.get('updates')\n    if updates is not None:\n        query = query.join(User.updates)\n        args = [Update.alias == update.alias for update in updates]\n        query = query.filter(or_(*args))\n\n    # We can't use ``query.count()`` here because it is naive with respect to\n    # all the joins that we're doing above.\n    count_query = query.with_labels().statement\\\n        .with_only_columns([func.count(distinct(User.id))])\\\n        .order_by(None)\n    total = request.db.execute(count_query).scalar()\n\n    page = data.get('page')\n    rows_per_page = data.get('rows_per_page')\n    pages = int(math.ceil(total / float(rows_per_page)))\n    query = query.offset(rows_per_page * (page - 1)).limit(rows_per_page)\n\n    return dict(\n        users=query.all(),\n        page=page,\n        pages=pages,\n        rows_per_page=rows_per_page,\n        total=total,\n    )\n", "path": "bodhi/server/services/user.py"}], "after_files": [{"content": "# Copyright 2014-2019 Red Hat, Inc. and others\n#\n# This file is part of Bodhi.\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License\n# as published by the Free Software Foundation; either version 2\n# of the License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n\"\"\"Defines API services that pertain to users.\"\"\"\nimport math\n\nfrom cornice import Service\nfrom cornice.validators import colander_querystring_validator\nfrom pyramid.exceptions import HTTPNotFound\nfrom sqlalchemy import func, distinct\nfrom sqlalchemy.sql import or_\n\nfrom bodhi.server.models import Group, Update, User\nfrom bodhi.server.validators import (validate_updates, validate_groups)\nimport bodhi.server.schemas\nimport bodhi.server.security\nimport bodhi.server.services.errors\nimport bodhi.server.services.updates\n\n\nuser = Service(name='user', path=r'/users/{name:\\S+}',\n               description='Bodhi users',\n               # These we leave wide-open since these are only GETs\n               cors_origins=bodhi.server.security.cors_origins_ro)\n\nusers = Service(name='users', path='/users/',\n                description='Bodhi users',\n                # These we leave wide-open since these are only GETs\n                cors_origins=bodhi.server.security.cors_origins_ro)\n\nusers_rss = Service(name='users_rss', path='/rss/users/', description='Bodhi users RSS feed',\n                    cors_origins=bodhi.server.security.cors_origins_ro)\n\n\n@user.get(accept=(\"application/json\", \"text/json\"), renderer=\"json\",\n          error_handler=bodhi.server.services.errors.json_handler)\n@user.get(accept=(\"application/javascript\"), renderer=\"jsonp\",\n          error_handler=bodhi.server.services.errors.json_handler)\n@user.get(accept=\"text/html\", renderer=\"user.html\",\n          error_handler=bodhi.server.services.errors.html_handler)\ndef get_user(request):\n    \"\"\"\n    Return a user given by username.\n\n    Args:\n        request (pyramid.request): The current request.\n    Returns:\n        dict: A dictionary with two keys. \"user\" maps to a dictionary representation of the User\n            object. \"urls\" maps to various URLs that describe various other objects related to the\n            user.\n    \"\"\"\n    id = request.matchdict.get('name')\n    user = User.get(id)\n\n    if not user:\n        request.errors.add('body', 'name', 'No such user')\n        request.errors.status = HTTPNotFound.code\n        return\n\n    user = user.__json__(request)\n\n    # Throw some extra information in there\n    rurl = request.route_url  # Just shorthand\n    urls = {\n        'comments_by': rurl('comments') + '?user=%s' % id,\n        'comments_on': rurl('comments') + '?update_owner=%s' % id,\n        'recent_updates': rurl('updates') + '?user=%s' % id,\n        'recent_overrides': rurl('overrides') + '?user=%s' % id,\n        'comments_by_rss': rurl('comments_rss') + '?user=%s' % id,\n        'comments_on_rss': rurl('comments_rss') + '?update_owner=%s' % id,\n        'recent_updates_rss': rurl('updates_rss') + '?user=%s' % id,\n        'recent_overrides_rss': rurl('overrides_rss') + '?user=%s' % id,\n    }\n\n    return dict(user=user, urls=urls)\n\n\nvalidators = (\n    colander_querystring_validator,\n    validate_groups,\n    validate_updates,\n)\n\n\n@users.get(schema=bodhi.server.schemas.ListUserSchema,\n           accept=(\"application/json\", \"text/json\"), renderer=\"json\",\n           error_handler=bodhi.server.services.errors.json_handler,\n           validators=validators)\n@users.get(schema=bodhi.server.schemas.ListUserSchema,\n           accept=(\"application/javascript\"), renderer=\"jsonp\",\n           error_handler=bodhi.server.services.errors.jsonp_handler,\n           validators=validators)\n@users.get(schema=bodhi.server.schemas.ListUserSchema, renderer=\"rss\",\n           accept=('application/atom+xml',),\n           error_handler=bodhi.server.services.errors.html_handler,\n           validators=validators)\n@users_rss.get(schema=bodhi.server.schemas.ListUserSchema, renderer=\"rss\",\n               error_handler=bodhi.server.services.errors.html_handler,\n               validators=validators)\ndef query_users(request):\n    \"\"\"\n    Search for users by various criteria.\n\n    Args:\n        request (pyramid.request): The current web request.\n    Returns:\n        dict: A dictionary with the follow key mappings:\n            users: A list of users matching the search criteria.\n            page: The current page of results.\n            pages: The total number of pages available.\n            rows_per_page: The number of users on the page.\n            total: The total number of users matching the search criteria.\n    \"\"\"\n    db = request.db\n    data = request.validated\n    query = db.query(User)\n\n    like = data.get('like')\n    if like is not None:\n        query = query.filter(or_(*[\n            User.name.like('%%%s%%' % like)\n        ]))\n\n    search = data.get('search')\n    if search is not None:\n        query = query.filter(User.name.ilike('%%%s%%' % search))\n\n    name = data.get('name')\n    if name is not None:\n        query = query.filter(User.name.like(name))\n\n    groups = data.get('groups')\n    if groups is not None:\n        query = query.join(User.groups)\n        query = query.filter(or_(*[Group.id == grp.id for grp in groups]))\n\n    updates = data.get('updates')\n    if updates is not None:\n        query = query.join(User.updates)\n        args = [Update.alias == update.alias for update in updates]\n        query = query.filter(or_(*args))\n\n    # We can't use ``query.count()`` here because it is naive with respect to\n    # all the joins that we're doing above.\n    count_query = query.with_labels().statement\\\n        .with_only_columns([func.count(distinct(User.id))])\\\n        .order_by(None)\n    total = request.db.execute(count_query).scalar()\n\n    page = data.get('page')\n    rows_per_page = data.get('rows_per_page')\n    pages = int(math.ceil(total / float(rows_per_page)))\n    query = query.offset(rows_per_page * (page - 1)).limit(rows_per_page)\n\n    return dict(\n        users=query.all(),\n        page=page,\n        pages=pages,\n        rows_per_page=rows_per_page,\n        total=total,\n    )\n", "path": "bodhi/server/services/user.py"}]}