Add formal support for service accounts.
Review Request #15105 — Created June 7, 2026 and updated
Review Bot and now the Doc Converter both create users designed for
automated communication with the Review Board API. Currently, both need
to create the users manually, and they both do this differently, with
different resulting states.These won't be our last such accounts, and we currently don't have a way
of distinguishing between these and normal users, which means they get
added to subscription licenses when they shouldn't.This change formalizes all of this with a new
ServiceAccountobject.
These can be subclassed or instantiated with information on the account
(a stable but unique service account registration ID, a name, e-mail
address, avatars, preferred username (if available), API token policy,
and more). They can then be registered in a central registry.Upon registration, a user account will be fetched or created for the
account and made available for later use. The logic for figuring out the
user account is probably the largest part of this change. It does the
following:
If the registration claims a user (which should only be done if
there's state saying it once created that user for this purpose), it
will be fetched and populated with state to identify it as a service
account user.If not claiming a user, it will fetch the preferred username and see
if it's marked with identifying state. If so, it returns it. Else, it
adds a number to the end and tries again.If a username is scanned and not found, it will be chosen as the
user.Any user that's picked for the service account will have a
service_account_idinProfile.extra_data, which will aid in finding
the user in the future. Extensions are advised to store the resulting
username and claim it for future use, to speed up the loading process.Due to the way that user creation works, it's now possible to listen for
post_savesignal for a user and check if it corresponds to a
ServiceAccount. This means we can avoid auto-adding new service
accounts to licenses.Once a
ServiceAccountis registered, both the user and an API token
can be accessed for use. This will use a client API token, identifying
the token as being owned by thisServiceAccount, and with a minimum
validity window of 5 hours (meaning if an existing token is found that
has less time than this, a new token will be created to help avoid
integration issues).If profile or API token state needs to be forcefully updated, the
extension can bump one of the two version fields (one for profile, one
for token information), which will force an update.A User Details Provider now exists to badge service accounts with
"Service Account", so that they can be easily distinguished whenever
they might be shown.
Tested registering a
ServiceAccountfor a test user and verified
the user account was claimed and the user badged.Tested registering without a user claim and verified it scanned and
created a suitable user.Unit tests pass.
| Summary | ID |
|---|---|
| 0cf5bb955484cac5c4945dc632169feefb23427c |
| Description | From | Last Updated |
|---|---|---|
|
local variable 'user' is assigned to but never used Column: 13 Error code: F841 |
|
|
|
'datetime.timedelta' imported but unused Column: 1 Error code: F401 |
|
|
|
'django.utils.timezone' imported but unused Column: 1 Error code: F401 |
|
|
|
line too long (88 > 79 characters) Column: 80 Error code: E501 |
|
|
|
redefinition of unused 'Iterator' from line 11 Column: 5 Error code: F811 |
|
|
|
This is assigned None but never read. Were you intending on using this as a cache in get_api_token()? If you … |
|
|
|
Typo: thaat -> that |
|
|
|
If two services want the same username ("bot" for example), this will stomp over an existing one. We should check … |
|
|
|
Typo: Pleaase -> Please |
|
|
|
I don't see how profile could be falsy at this point. |
|
|
|
__init__ already defauts self.email to be mail_default_from, so the contents of this block are an unreachable duplicate of what we … |
|
|
|
last_name is a CharField(blank=True, null=False). Having None here works okay for sqlite but will fail on MySQL and Postgres. |
|
|
|
minimum subclass -> minimum init |
|
|
|
should mention subclass here (right now this has the same docstring as test_init_with_override_all) |
|
|
|
Should be ServiceAccountRegistry |
|
- Change Summary:
-
Removed unused variables and imports.
- Commits:
-
Summary ID 819d46e96258ff2da4d1e5d3e9c6fadcb8987537 0cf5bb955484cac5c4945dc632169feefb23427c - Diff:
-
Revision 2 (+4392 -4)
Checks run (2 succeeded)
-
-
This is assigned
Nonebut never read. Were you intending on using this as a cache inget_api_token()?If you keep it, it should be listed in
# Instance variablestoo. -
-
If two services want the same username ("bot" for example), this will stomp over an existing one.
We should check if
claim_map.get(username)is not eitherNoneorself, and skip if so.This situation should get a unit test two.
-
-
-
__init__already defautsself.emailto bemail_default_from, so the contents of this block are an unreachable duplicate of what we already have done. -
last_nameis aCharField(blank=True, null=False). HavingNonehere works okay for sqlite but will fail on MySQL and Postgres. -
-
-