Repository: fedora-infra/bodhi

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?


Discussion:
> 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?

Yeah I think we could transform the '/' by '_' for example before creating the user in the database.


I for one think it is a very good bot username, because it reflects its kerberos principal :-)
(I do understand why it can be a problem)
After some thought, modifying the username in our db with arbitrary rules can be dangerous and can produce unexpected behavior.
For example, replacing all non alfanumeric characters with `_` we may end to identifying two different users with the same internal username.

Since we don't have any issue having those bad usernames in database, I'd let them unchanged. We only have to address how to produce safe URLs for those usernames.
By replacing characters on the fly, we get back to the former problem. So, two possible solutions are to replace username in URLs with user id or to add a new unique field to the User model like "url_id" and use that.
I would go for the second one, since it would not change current URLs for real users and also it would be user friendlier.

What do you think?
So, as discussed in my retired PR, we have to decide how should we manage situations like this:

- store a URL friendly username along with the real username, like in my PR.
- prevent bad usernames to be used with Bodhi

Overall, I would prefer n.2: I have retired my PR because, even if it worked and is backwards compatible with the current situation, it seems awful to me to have a real username and a URL friendly username in database. And it brings confusion. But that's the only solution I could think to prevent two similar bad usernames to have the same URL if they're translated on the fly.

The second solution will imply to prevent bad usernames to operate in Bodhi. As far as I know, since the username is supposed to be compatible with OpenID, it should never break URLs, even with Bodhi instances other than Fedora's. Currently the only problematic username in Bodhi is that `packagerbot/os-master01.phx2.fedoraproject.org` that is not even coming from FAS.

Resolving patch:
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)


Test patch:
diff --git a/bodhi/tests/server/functional/test_users.py b/bodhi/tests/server/functional/test_users.py
--- a/bodhi/tests/server/functional/test_users.py
+++ b/bodhi/tests/server/functional/test_users.py
@@ -39,6 +39,15 @@ def test_get_single_user(self):
         res = self.app.get('/users/bodhi')
         assert res.json_body['user']['name'] == 'bodhi'
 
+    def test_get_single_user_with_nonstandard_characters(self):
+        """Test that we don't receive a 404 page with bot usernames."""
+        user = User(name='bot/a.bad.name')
+        self.db.add(user)
+        self.db.flush()
+
+        res = self.app.get('/users/bot/a.bad.name')
+        assert res.json_body['user']['name'] == 'bot/a.bad.name'
+
     def test_get_hardcoded_avatar(self):
         res = self.app.get('/users/bodhi')
         assert res.json_body['user']['name'] == 'bodhi'