• 
      

    Add formal support for service accounts.

    Review Request #15105 — Created June 7, 2026 and updated

    Information

    Review Board
    release-8.x

    Reviewers

    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 ServiceAccount object.
    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:

    1. 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.

    2. 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.

    3. 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_id in Profile.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_save signal 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 ServiceAccount is 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 this ServiceAccount, 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 ServiceAccount for 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
    Add formal support for service accounts.
    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 `ServiceAccount` object. 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: 1. 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. 2. 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. 3. 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_id` in `Profile.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_save` signal 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 `ServiceAccount` is 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 this `ServiceAccount`, 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.
    adaf6c43101922556356b290e1c5f5fa47d76fce
    Description From Last Updated

    local variable 'user' is assigned to but never used Column: 13 Error code: F841

    reviewbot reviewbot

    'datetime.timedelta' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    'django.utils.timezone' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    line too long (88 > 79 characters) Column: 80 Error code: E501

    reviewbot reviewbot

    redefinition of unused 'Iterator' from line 11 Column: 5 Error code: F811

    reviewbot reviewbot

    This is assigned None but never read. Were you intending on using this as a cache in get_api_token()? If you …

    david david

    Typo: thaat -> that

    david david

    If two services want the same username ("bot" for example), this will stomp over an existing one. We should check …

    david david

    Typo: Pleaase -> Please

    david david

    I don't see how profile could be falsy at this point.

    david david

    __init__ already defauts self.email to be mail_default_from, so the contents of this block are an unreachable duplicate of what we …

    david david

    last_name is a CharField(blank=True, null=False). Having None here works okay for sqlite but will fail on MySQL and Postgres.

    david david

    minimum subclass -> minimum init

    david david

    should mention subclass here (right now this has the same docstring as test_init_with_override_all)

    david david

    Should be ServiceAccountRegistry

    david david

    We can probably remove this since we're logging this error path in get_user() already.

    maubin maubin

    This should be ServiceAccountUserError now.

    maubin maubin

    This docs page isn't included in the change.

    maubin maubin
    Checks run (1 failed, 1 succeeded)
    flake8 failed.
    JSHint passed.

    flake8

    chipx86
    david
    1. 
        
    2. Show all issues

      This is assigned None but never read. Were you intending on using this as a cache in get_api_token()?

      If you keep it, it should be listed in # Instance variables too.

      1. Leftover code. Removing.

    3. Show all issues

      Typo: thaat -> that

    4. Show all issues

      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 either None or self, and skip if so.

      This situation should get a unit test two.

      1. I'm trying to figure out the resolution to this.

        Claiming is there for the situation where a username has been verified to be owned by that service already. So, if two service accounts claim the same username, there's a problem. We probably should not skip and try again. I'm thinking it should be a fatal error.

    5. Show all issues

      Typo: Pleaase -> Please

    6. Show all issues

      I don't see how profile could be falsy at this point.

      1. Leftover from older code.

    7. reviewboard/accounts/service_accounts.py (Diff revision 2)
       
       
       
       
      Show all issues

      __init__ already defauts self.email to be mail_default_from, so the contents of this block are an unreachable duplicate of what we already have done.

      1. Good catch. Leftover from older code.

    8. Show all issues

      last_name is a CharField(blank=True, null=False). Having None here works okay for sqlite but will fail on MySQL and Postgres.

    9. Show all issues

      minimum subclass -> minimum init

    10. Show all issues

      should mention subclass here (right now this has the same docstring as test_init_with_override_all)

    11. Show all issues

      Should be ServiceAccountRegistry

    12. 
        
    chipx86
    maubin
    1. Ok nice this makes sense to me and looks like a good approach.

    2. Show all issues

      We can probably remove this since we're logging this error path in get_user() already.

      1. Leftover from some debugging. Removed.

    3. reviewboard/extensions/hooks/users.py (Diff revision 3)
       
       
      Show all issues

      This docs page isn't included in the change.

      1. Haven't written it yet. It's more of a placeholder.

    4. 
        
    maubin
    1. Ship It!
    2. 
        
    chipx86
    Review request changed
    Change Summary:
    • Added an immediate failure if a claimed username is already claimed by another ServiceAccount.
    • Switched the error type to ServiceAccountUserError.
    • Removed leftover debug code.
    Commits:
    Summary ID
    Add formal support for service accounts.
    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 `ServiceAccount` object. 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: 1. 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. 2. 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. 3. 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_id` in `Profile.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_save` signal 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 `ServiceAccount` is 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 this `ServiceAccount`, 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.
    4fbe059acef989b77f8bb499ed5e01ccd89edc8e
    Add formal support for service accounts.
    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 `ServiceAccount` object. 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: 1. 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. 2. 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. 3. 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_id` in `Profile.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_save` signal 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 `ServiceAccount` is 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 this `ServiceAccount`, 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.
    adaf6c43101922556356b290e1c5f5fa47d76fce

    Checks run (2 succeeded)

    flake8 passed.
    JSHint passed.
    maubin
    1. 
        
    2. reviewboard/accounts/service_accounts.py (Diff revisions 3 - 4)
       
       
      Show all issues

      This should be ServiceAccountUserError now.

    3.