• 
      

    Add GitHub App connectivity.

    Review Request #15144 — Created June 30, 2026 and discarded

    Information

    Review Board
    release-9.x

    Reviewers

    This change adds a GitHub App as an alternative to Personal Access
    Tokens. This involves a bunch of moving parts:

    1. The actual GitHub App backend, which includes a redirect view that
      will trigger the GitHub App Manifest flow, and callback views for
      when the app has been created and when the app has been installed
      into a user or organization. This ends up creating a hidden
      HostingServiceAccount which holds the app registration keys, and
      then each time the app gets installed (for a user or organization)
      will create a new HostingServiceAccount for that entry.

    2. Modifications to the GitHub client to use the app for authentication.
      This involves minting short-lived RS256 JWT tokens for the actual API
      requests to GitHub.

    3. Customized UI parts for the connected services list. This involves:

    • Special templates and flows for the "Connect a service" wizard which
      lets users choose how they want to connect, and triggers for the new
      app creation flow.

    • An override for the entry template which adds an extra controls
      panel that either encourages users to create the app (if they have
      existing connections using PATs only), or provides a link into
      GitHub's settings UI (allowing changes to URLs or transferring the
      app registration to another owner).

    This also currently includes a stub webhook handler (since GitHub Apps
    have a single webhook endpoint for all authorized repositories), as well
    as some commented-out bits for future enhancement.

    • Connected a GitHub App and installed it into multiple organizations.
    • Tested all aspects of the wizard flow, including creation of the app
      and reassignment of any PAT-based repositories after app install.
    • Verified that API access works correctly when authenticating using the
      app tokens.
    • Ran unit tests.
    Summary ID
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr

    Description From Last Updated

    Can we make a pydantic model for the GitHub app data (the github_app data stored on the installation account)?

    maubin maubin

    Ok so thinking about local sites again (this is on my mind from neglecting to really consider local sites when …

    maubin maubin

    To check, is there a situation where we'd want to establish multiple GitHub hidden app accounts? What's the story on …

    chipx86 chipx86

    Do we nicely handle things if there's a lot of repositories here? Like having a vertical scrollbar if there's a …

    maubin maubin

    continuation line under-indented for visual indent Column: 34 Error code: E128

    reviewbot reviewbot

    'reviewboard.site.models.LocalSite' imported but unused Column: 5 Error code: F401

    reviewbot reviewbot

    Can we rename to AppManifestResponse.

    maubin maubin

    Can we rename to InstallationAccountResponse.

    maubin maubin

    Can you add a Version Added to this.

    maubin maubin

    In this change the App views were put into reviewboard.hostingsvcs.github.views and I don't see any other changes where you split …

    maubin maubin

    This needs a Version Added.

    maubin maubin

    Should we pass local_site=request.get('local_site') here?

    maubin maubin

    I think there will always be a request.local_site attribute because of our LocalSiteMiddleware.

    maubin maubin

    Same note about request.local_site always existing.

    maubin maubin

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

    reviewbot reviewbot

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

    reviewbot reviewbot

    Missing a docstring.

    maubin maubin

    Could this be Literal['user', 'organization', '']?

    maubin maubin

    Same comment as above.

    maubin maubin

    Seems like this could be Literal['all', 'selected'].

    maubin maubin

    Can you add a docstring for this.

    maubin maubin

    This can be removed, we do this below in the if condition.

    maubin maubin

    We do these isinstance checks on the app data a lot more than we do is_installation_account() or is_app_record_account(). Might be …

    maubin maubin

    This can be an elif or add back in the blank line before this.

    maubin maubin

    We can remove local_site here because we do a local_site = app_account.local_site in the view instead of using this value …

    maubin maubin

    Same comment as above.

    maubin maubin

    I'm getting a bit mixed up with the namings. We have "app-record accounts" and "installation accounts". A hosting service account …

    maubin maubin

    Can we pull the URL out before this? Helps keep this readable.

    chipx86 chipx86

    We access self.client 4 times in this test. Can we pull it out? Same with other tests.

    chipx86 chipx86

    Can we move the encryption method to the description? We'll ultimately want to move away from encrypt_password() entirely and may …

    chipx86 chipx86

    Same here.

    chipx86 chipx86

    Should this be a TypeAlias?

    chipx86 chipx86

    These are missing a "Version Added".

    chipx86 chipx86

    Final[str]?

    chipx86 chipx86

    We have a standard symbol for this.

    chipx86 chipx86

    What happens if we need to change this data in the future? Will we hit validation problems? We probably should …

    chipx86 chipx86

    This is missing a description.

    chipx86 chipx86

    This is missing "Version Added".

    chipx86 chipx86

    All others have a "False if..." Can we add one for consistency?

    chipx86 chipx86

    This is missing a "Version Added".

    chipx86 chipx86

    This is missing a "Version Added".

    chipx86 chipx86

    This is missing a "Version Added".

    chipx86 chipx86

    This is missing a "Version Added".

    chipx86 chipx86

    Can we pull this out? It's easier to read function calls when they're not nested in f-strings. Also would help …

    chipx86 chipx86

    APIError sorts first.

    chipx86 chipx86

    This doesn't provide a lot of guidance as to what this means. Can we elaborate?

    chipx86 chipx86

    Can you move the )) to its own line, so the string is isolated?

    chipx86 chipx86

    Instead of cast, can we assert isinstance?

    chipx86 chipx86

    Is this something we want to encrypt?

    chipx86 chipx86

    These shouldn't be doc comments. Unless these should be on the class.

    chipx86 chipx86

    Should we log this?

    chipx86 chipx86

    Can you use the standard Tuple: form here?

    chipx86 chipx86

    Should this (and the one below) be Sequence?

    chipx86 chipx86

    We access self.account twice. Let's pull it out.

    chipx86 chipx86

    Can you use the standard Tuple: form?

    chipx86 chipx86

    We're mixing up locations for classmethods. These should be higher up. But I have a question about the design here. …

    chipx86 chipx86

    This is missing the full class path.

    chipx86 chipx86

    This is hard to read. Can you pull this out into a variable and then iterate over that?

    chipx86 chipx86

    This is missing a blank line. But this block would all be better as: if is_app_record(...): app_account = account else: …

    chipx86 chipx86

    This should probably be Mapping.

    chipx86 chipx86

    We should be escaping variables going into this.

    chipx86 chipx86

    These are missing Version Added, and should be Final.

    chipx86 chipx86

    Can you sort these alphabetically?

    chipx86 chipx86

    Can you document what this is protecting against (the TypeError and ValueError)? It's not clear.

    chipx86 chipx86

    Can you document this fallback? It's not clear why we'd need it or why this value would work.

    chipx86 chipx86

    Can you sort all these alphabetically?

    chipx86 chipx86

    From the above, this could end up being "Review Board (Review Board)".

    chipx86 chipx86

    Blank line before the comment.

    chipx86 chipx86

    Alphabetical here too.

    chipx86 chipx86

    Alphabetical here too.

    chipx86 chipx86

    And here.

    chipx86 chipx86

    And here.

    chipx86 chipx86

    Should we be doing this above if we return an error and this state is set?

    chipx86 chipx86

    Blank line before the comment.

    chipx86 chipx86

    Can you pull these out into separate statements? Upcoming work on stronger encryption's going to require more complex statements here.

    chipx86 chipx86

    Can you alphabetize this?

    chipx86 chipx86

    We should escape values going into the URL.

    chipx86 chipx86

    This can be one statement.

    chipx86 chipx86

    Or validation failed, I'm guessing?

    chipx86 chipx86

    We should escape the code going into this.

    chipx86 chipx86

    This should be in alphabetical order.

    chipx86 chipx86

    We should escape values going into the URL.

    chipx86 chipx86

    This can be one statement.

    chipx86 chipx86

    Same question about re: stale state if we hit an error above.

    chipx86 chipx86

    We should catch an error here.

    chipx86 chipx86

    Do we need the import to be inline here? If inline, it should be at the top of the function.

    chipx86 chipx86

    Can you pull this out into a variable so this is more readable/maintainable?

    chipx86 chipx86

    This needs to be the full class path.

    chipx86 chipx86

    This needs to be the full class path.

    chipx86 chipx86

    If this has to be an inline import, can we move it to the top of the function?

    chipx86 chipx86

    This needs to be the full class path.

    chipx86 chipx86

    No need for parens.

    chipx86 chipx86

    Can we move to the top of the function if needed here?

    chipx86 chipx86

    No need for parens.

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    Let's pull this out, because the newer cryptography stuff will involve some multi-line statements here eventually. Same below.

    chipx86 chipx86

    Let's pull out the encrypt_password calls, because the newer cryptography stuff will involve some multi-line statements here eventually and it …

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    We never type this. That should belong to the parent anyway, since we'd only retype in this case to aid …

    chipx86 chipx86

    Missing a Version Added.

    chipx86 chipx86

    This is missing docs.

    chipx86 chipx86

    Can we move the second selector to the next line?

    chipx86 chipx86

    The type should have parens for the | null = null.

    chipx86 chipx86

    Optional, but this can just use ?.

    chipx86 chipx86

    override is a keyword in TypeScript. Can we give this another name?

    chipx86 chipx86

    This is really dense. Can you break it up?

    chipx86 chipx86

    Missing indentation here.

    chipx86 chipx86

    Maybe "via a Personal Access Token"?

    chipx86 chipx86

    No blank line needed here.

    chipx86 chipx86

    Everywhere else, this is capitalized "Personal Access Tokens". Some pages also bold this and "GitHub App", and some don't. Not …

    chipx86 chipx86

    Can you switch this to use an Ink icon, and leave it out of the translation string?

    chipx86 chipx86

    'gettext.install' imported but unused Column: 1 Error code: F401

    reviewbot reviewbot

    'collections.abc.Sequence' imported but unused Column: 5 Error code: F401

    reviewbot reviewbot

    Use the function form of "use strict". Column: 1 Error code: W097

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 18 Error code: …

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 26 Error code: …

    reviewbot reviewbot

    Expected '{' and instead saw 'if'. Column: 48 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'd'. Column: 96 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'throw'. Column: 13 Error code: W116

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 25 Error code: …

    reviewbot reviewbot

    A constructor name should start with an uppercase letter. Column: 88 Error code: W055

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 29 Error code: …

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 37 Error code: …

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 21 Error code: …

    reviewbot reviewbot

    If a strict mode function is executed using function invocation, its 'this' value will be undefined. Column: 29 Error code: …

    reviewbot reviewbot

    Expected '{' and instead saw 'throw'. Column: 71 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'context'. Column: 34 Error code: W116

    reviewbot reviewbot

    'p' is already defined. Column: 18 Error code: W004

    reviewbot reviewbot

    Expected '{' and instead saw 'context'. Column: 41 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'throw'. Column: 59 Error code: W116

    reviewbot reviewbot

    Functions declared within loops referencing an outer scoped variable may lead to confusing semantics. (done, extraInitializers, accept) Column: 34 Error …

    reviewbot reviewbot

    Unorthodox function invocation. Column: 40 Error code: W067

    reviewbot reviewbot

    Expected '{' and instead saw 'continue'. Column: 36 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'throw'. Column: 64 Error code: W116

    reviewbot reviewbot

    Expected a conditional expression and instead saw an assignment. Column: 19 Error code: W084

    reviewbot reviewbot

    Expected '{' and instead saw 'descriptor'. Column: 41 Error code: W116

    reviewbot reviewbot

    Expected a conditional expression and instead saw an assignment. Column: 19 Error code: W084

    reviewbot reviewbot

    Expected '{' and instead saw 'descriptor'. Column: 41 Error code: W116

    reviewbot reviewbot

    Expected a conditional expression and instead saw an assignment. Column: 19 Error code: W084

    reviewbot reviewbot

    Expected '{' and instead saw 'initializers'. Column: 42 Error code: W116

    reviewbot reviewbot

    Expected a conditional expression and instead saw an assignment. Column: 20 Error code: W084

    reviewbot reviewbot

    Expected '{' and instead saw 'initializers'. Column: 35 Error code: W116

    reviewbot reviewbot

    Expected '{' and instead saw 'descriptor'. Column: 18 Error code: W116

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

    flake8

    david
    david
    david
    david
    david
    Review request changed
    Change Summary:

    Update for renamed and changed APIs

    Commits:
    Summary ID
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    david
    david
    david
    maubin
    1. 
        
    2. Show all issues

      Can we make a pydantic model for the GitHub app data (the github_app data stored on the installation account)?

      1. That's a great idea, but I'll need to do it in a separate change because I have a couple other github-related changes in my stack that will also need updating.

      2. Changed my mind, merging it into this (and other) changes.

    3. Show all issues

      Do we nicely handle things if there's a lot of repositories here? Like having a vertical scrollbar if there's a bunch of repositories listed.

      1. Yeah, the contents of this dialog are scrollable when necessary.

    4. reviewboard/hostingsvcs/github/api.py (Diff revision 9)
       
       
      Show all issues

      Can we rename to AppManifestResponse.

    5. reviewboard/hostingsvcs/github/api.py (Diff revision 9)
       
       
      Show all issues

      Can we rename to InstallationAccountResponse.

      1. This is actually nested data inside of the Installation (and in a later change, webhook payloads), so it's not a response directly. I can change Installation to InstallationResponse, though.

    6. reviewboard/hostingsvcs/github/client.py (Diff revision 9)
       
       
       
       
       
      Show all issues

      Can you add a Version Added to this.

    7. Show all issues

      In this change the App views were put into reviewboard.hostingsvcs.github.views and I don't see any other changes where you split them out into an app_views module.

    8. Show all issues

      This needs a Version Added.

    9. Show all issues

      Should we pass local_site=request.get('local_site') here?

    10. Show all issues

      I think there will always be a request.local_site attribute because of our LocalSiteMiddleware.

    11. reviewboard/hostingsvcs/github/views.py (Diff revision 9)
       
       
      Show all issues

      Same note about request.local_site always existing.

    12. 
        
    david
    Review request changed
    Commits:
    Summary ID
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr

    Checks run (1 failed, 1 succeeded)

    flake8 failed.
    JSHint passed.

    flake8

    david
    david
    david
    maubin
    1. 
        
    2. Show all issues

      Ok so thinking about local sites again (this is on my mind from neglecting to really consider local sites when implementing document review and then having to go in and add it later), is the idea for GitHub app-record accounts to be local-site dependent? This seems to be the current design because we pass local_site for both the app-record accounts and installation accounts (e.g. in make_connect_ui_context() we do HostingServiceAccount.objects.filter(service_name=..., local_site=request.local_site).

      But I know you mentioned possibly having a "GitHub App for all of RBCommons" in a reply to one of my other reviews. And we have staff_member_required decorators on the views, which means only server admins (not local site team admins) will be able to access the views.

      So even though RBCommons will require its own development effort, I think we need to make some design decisions at this stage since we are currently passing around local sites in this code.

      1. Hmm, I'll have to think about that a bit. Will follow up on that in a later change.

    3. Show all issues

      Missing a docstring.

    4. Show all issues

      Could this be Literal['user', 'organization', '']?

    5. Show all issues

      Same comment as above.

    6. Show all issues

      Seems like this could be Literal['all', 'selected'].

    7. reviewboard/hostingsvcs/github/accounts.py (Diff revision 13)
       
       
       
      Show all issues

      Can you add a docstring for this.

    8. Show all issues

      This can be removed, we do this below in the if condition.

    9. reviewboard/hostingsvcs/github/service.py (Diff revision 13)
       
       
       
       
       
       
      Show all issues

      We do these isinstance checks on the app data a lot more than we do is_installation_account() or is_app_record_account(). Might be worth making some is_installation_data() is_app_record_data() functions?

    10. Show all issues

      We can remove local_site here because we do a local_site = app_account.local_site in the view instead of using this value that was passed in.

      1. We do that in GitHubAppInstallCallbackView, but this is referring to GitHubAppInstallView.

    11. Show all issues

      Same comment as above.

    12. reviewboard/hostingsvcs/tests/github/test_app.py (Diff revision 13)
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      I'm getting a bit mixed up with the namings. We have "app-record accounts" and "installation accounts". A hosting service account can be either of those.

      Can we rename _create_app_record() to _create_app_record_account() to clarify that. And then rename _create_app_account() to _create_app_installation_account() to specify that its an installation account. Because "app account" could really mean either a record account or installation account, you even name the record account variable app_account.

    13. 
        
    david
    david
    maubin
    1. 
        
    2. reviewboard/hostingsvcs/github/service.py (Diff revisions 13 - 15)
       
       
      Show all issues

      This can be an elif or add back in the blank line before this.

    3. 
        
    chipx86
    1. This change is far too big to reason about. I had to stop part-way through views.py after 3 review sessions, and I'm not even half-way through

      I have a lot of higher-level questions and lots of code style issues. It's all blurring together and I can't fit this into my head. What I'd suggest is breaking this up into:

      1. The base shared account state stuff (and I really need to spend time just going over all that)
      2. Management for that state
      3. Flow
      4. Templates
      5. UI

      Something like that.

      A lot of the comments I have left are applicable across the change as well.

    2. Show all issues

      To check, is there a situation where we'd want to establish multiple GitHub hidden app accounts? What's the story on DB import/export there?

      1. At the moment the implementation assumes separate app accounts for each local site, but even that is a decision I'm thinking of revisiting. There should be no reason for multiple app accounts generally.

        I'm not sure what you're asking with regards to import/export?

      2. Mostly been trying to figure out the whole model.

        I can see multiple app accounts for GitHub vs. GitHub Enterprise @ domain1, vs. GitHub Enterprise @ domain2. If not for that, I'd imagine this could all live in siteconfig. (Potentially this kind of state could also live in a future secrets management store, but we won't have that today.)

        For import/export, the main thing I'm wondering is what things look like when exporting a repository connected to a GitHub app. Since we won't have a PAT encoded with it, we won't have any credentials that can be used for the repository again, and we certainly can't export the GitHub app secrets with it. It'd have to be attached to a new one, I'd imagine, but that wouldn't exist for an empty site.

        I don't know if there's a good answer for that today, but it's just something I thought about that we'll ultimately need an answer to.

      3. I'll spend some time thinking about this.

    3. Show all issues

      Can we pull the URL out before this? Helps keep this readable.

    4. Show all issues

      We access self.client 4 times in this test. Can we pull it out? Same with other tests.

    5. Show all issues

      Can we move the encryption method to the description? We'll ultimately want to move away from encrypt_password() entirely and may need to describe that here.

    6. Show all issues

      Same here.

    7. Show all issues

      Should this be a TypeAlias?

    8. reviewboard/hostingsvcs/github/accounts.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      These are missing a "Version Added".

    9. Show all issues

      Final[str]?

    10. reviewboard/hostingsvcs/github/accounts.py (Diff revision 15)
       
       
       
      Show all issues

      We have a standard symbol for this.

    11. Show all issues

      What happens if we need to change this data in the future? Will we hit validation problems?

      We probably should version the state as well.

      1. So I think the story here depends on how things might change:

        We already have most of the forward/backward compat solved:

        • The model config has extra='allow', so anything present in the JSON blob that's unrecognized for whatever reason will be preserved.
        • The most likely case will probably be "add a field", which can be added with default or default_factory without breaking anything.
        • We already key off of "role", so if we need to introduce a third role, that's already handled.

        Breaking cases would probably be:

        • Making an existing optional field required (let's just not do this).
        • Renaming a field (also don't do this).
        • Changing a field's semantics somehow.

        I think those are best handled the way we already do for things like we did for FileDiff.extra_data: detect the old shape and migrate either lazily or during rb-site upgrade.

        If we really need to introduce a version for some reason, we can do that later: the lack of a version key indicates version 1.

    12. reviewboard/hostingsvcs/github/accounts.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing a description.

    13. reviewboard/hostingsvcs/github/accounts.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing "Version Added".

    14. Show all issues

      All others have a "False if..." Can we add one for consistency?

    15. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing a "Version Added".

    16. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing a "Version Added".

    17. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing a "Version Added".

    18. reviewboard/hostingsvcs/github/app_auth.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is missing a "Version Added".

    19. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
      Show all issues

      Can we pull this out? It's easier to read function calls when they're not nested in f-strings.

      Also would help keep this header on one line.

    20. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      APIError sorts first.

    21. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
      Show all issues

      This doesn't provide a lot of guidance as to what this means. Can we elaborate?

      1. This indicates a situation where internal state on the installation account got corrupted. With the protection in /r/15194/ the only way this can really happen is if extra_data was edited, or if the database was manually changed in some way.

        I'm not sure what we could communicate to the admin that would actually mean anything to them beyond "you need to reconnect". The log message does have more detail.

      2. I meant more about the meaning of "reconnect the GitHub app". If I saw this from an application and was unfamiliar with this (say, I inherited a server), I wouldn't know if this was something I have to do on GitHub itself or something in Review Board, and where to go. So for guidance, I meant can we communicate "Please reconnect the GitHub app in Admin UI -> Connected Services" or something.

    22. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
      Show all issues

      Can you move the )) to its own line, so the string is isolated?

    23. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
      Show all issues

      Instead of cast, can we assert isinstance?

    24. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
      Show all issues

      Is this something we want to encrypt?

      1. This credential is pretty weak: it expires in less than an hour, and is scoped read-only. The cache already stores the plaintext of all the data that this token is able to access, as well as session data. If the cache is open to the world, that's already a serious misconfiguration.

      2. Definitely if a cache is open to the world it's a misconfiguration, but cache often crosses network boundaries and memcached is a plain text protocol. Thinking paranoia might be a good default in the current world of AI agents and security breakins.

    25. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
       
       
       
      Show all issues

      These shouldn't be doc comments. Unless these should be on the class.

    26. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
      Show all issues

      Should we log this?

    27. reviewboard/hostingsvcs/github/client.py (Diff revision 15)
       
       
       
       
      Show all issues

      Can you use the standard Tuple: form here?

    28. Show all issues

      Should this (and the one below) be Sequence?

    29. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
       
      Show all issues

      We access self.account twice. Let's pull it out.

    30. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
       
      Show all issues

      Can you use the standard Tuple: form?

      1. How does that work when the tuple is inside of a set?

      2. The form we've used before is along the lines of:

        set of tuple:
        A set of 2-tuples in the form of:
        
        Tuple:
            ...
        
    31. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
      Show all issues

      We're mixing up locations for classmethods. These should be higher up.

      But I have a question about the design here. Are these part of the hosting service interface now, these methods?

      I think rather than putting connect-related state here, we should do like we do with the forms, have a class that implements the interface and point to it. That keeps things manageable as hosting services grow.

      1. Some of the initial connected-services work has already gone in, so I'll need to handle that refactor in a follow-up change.

    32. Show all issues

      This is missing the full class path.

    33. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
       
      Show all issues

      This is hard to read. Can you pull this out into a variable and then iterate over that?

    34. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
      Show all issues

      This is missing a blank line. But this block would all be better as:

      if is_app_record(...):
          app_account = account
      else:
          ...
      
          connected_accounts.append(...)
      
    35. Show all issues

      This should probably be Mapping.

      1. That's a little awkward. For example, github's implementation of this calls super().make_connected_services_list_entry_context() and then mutates the result. If it's typed as mapping I'd have to either copy or cast.

        Most other places where we deal with template context we type as dict, since it's not something that really makes sense to be immutable.

      2. Ah, okay, I had missed that. Dropping it.

    36. reviewboard/hostingsvcs/github/service.py (Diff revision 15)
       
       
       
       
       
       
      Show all issues

      We should be escaping variables going into this.

    37. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
      Show all issues

      These are missing Version Added, and should be Final.

      1. I just want to state for the record that while I find slapping "Version Added" on things to be a mostly useless duplication of data that is better recorded in version control (except in specific circumstances like webapi or things that are directly part of our extension-oriented API), I find it extra useless for private implementation details.

      2. It's useful in version control only if you're dealing with version control and files haven't moved or lines haven't changed since they were introduced. I make use of version information in these all the time for public and private code, and we've done it this way for years.

    38. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Can you sort these alphabetically?

    39. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      Can you document what this is protecting against (the TypeError and ValueError)? It's not clear.

    40. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Can you document this fallback? It's not clear why we'd need it or why this value would work.

    41. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
       
       
       
      Show all issues

      Can you sort all these alphabetically?

    42. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      From the above, this could end up being "Review Board (Review Board)".

    43. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Blank line before the comment.

    44. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
      Show all issues

      Alphabetical here too.

    45. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Alphabetical here too.

    46. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      And here.

    47. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
      Show all issues

      And here.

    48. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      Should we be doing this above if we return an error and this state is set?

    49. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Blank line before the comment.

    50. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
      Show all issues

      Can you pull these out into separate statements? Upcoming work on stronger encryption's going to require more complex statements here.

    51. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      Can you alphabetize this?

    52. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      We should escape values going into the URL.

    53. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      This can be one statement.

    54. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
      Show all issues

      Or validation failed, I'm guessing?

    55. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      We should escape the code going into this.

    56. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      This should be in alphabetical order.

    57. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
      Show all issues

      We should escape values going into the URL.

    58. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      This can be one statement.

    59. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      Same question about re: stale state if we hit an error above.

    60. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      We should catch an error here.

    61. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      Do we need the import to be inline here?

      If inline, it should be at the top of the function.

      1. I really prefer to import just above use. What's the motivation behind hoisting?

      2. It's how we've always done this, it mirrors how it works in modules, and it's much easier to know when a local import is happening without reading through all the code in a function.

    62. 
        
    chipx86
    1. 
        
    2. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      Can you pull this out into a variable so this is more readable/maintainable?

    3. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      This needs to be the full class path.

    4. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      This needs to be the full class path.

    5. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      If this has to be an inline import, can we move it to the top of the function?

    6. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      This needs to be the full class path.

    7. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      No need for parens.

    8. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      Can we move to the top of the function if needed here?

    9. reviewboard/hostingsvcs/github/views.py (Diff revision 15)
       
       
      Show all issues

      No need for parens.

    10. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      Missing a Version Added.

    11. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
      Show all issues

      Missing a Version Added.

    12. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      Missing a Version Added.

    13. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
      Show all issues

      Let's pull this out, because the newer cryptography stuff will involve some multi-line statements here eventually.

      Same below.

    14. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
       
       
      Show all issues

      Let's pull out the encrypt_password calls, because the newer cryptography stuff will involve some multi-line statements here eventually and it won't be able to be inlined.

    15. reviewboard/hostingsvcs/tests/github/base.py (Diff revision 15)
       
       
       
       
       
       
       
       
       
       
       
       
      Show all issues

      Missing a Version Added.

    16. Show all issues

      Missing a Version Added.

    17. Show all issues

      Missing a Version Added.

    18. Show all issues

      We never type this. That should belong to the parent anyway, since we'd only retype in this case to aid subclasses to avoid typing as a literal.

    19. Show all issues

      Missing a Version Added.

    20. Show all issues

      This is missing docs.

      1. The actual contents of this are entirely service-dependent (e.g. #github-cs-entry-controls). I'm not sure what docs would say here other than <div class="rb-c-admin-cs-service-controls">

      2. We've been documenting components regardless of use for years now. It helps with understanding how it's put together, catching things like where we might be missing accessibility information or other important attributes, and better keeps the codebase understandable and maintainable later as we iterate.

    21. Show all issues

      Can we move the second selector to the next line?

    22. reviewboard/static/rb/js/admin/views/connectServiceWizardView.ts (Diff revision 15)
       
       
       
       
       
       
       
       
      Show all issues

      The type should have parens for the | null = null.

    23. Show all issues

      Optional, but this can just use ?.

      1. Can't use optional property access for assignment.

      2. You can in TypeScript.

      3. Nope:

        reviewboard/static/rb/js/admin/views/connectServiceWizardView.ts:437:9 - error TS2779: The left-hand side of an assignment expression may not be an optional property access.
        
        437         this.#cancelButton?.label = _`Cancel`;
                    ~~~~~~~~~~~~~~~~~~~~~~~~~
        
    24. Show all issues

      override is a keyword in TypeScript. Can we give this another name?

    25. reviewboard/static/rb/js/admin/views/connectServiceWizardView.ts (Diff revision 15)
       
       
       
       
       
       
       
       
       
      Show all issues

      This is really dense. Can you break it up?

    26. Show all issues

      Missing indentation here.

    27. Show all issues

      Maybe "via a Personal Access Token"?

    28. Show all issues

      No blank line needed here.

    29. Show all issues

      Everywhere else, this is capitalized "Personal Access Tokens".

      Some pages also bold this and "GitHub App", and some don't. Not sure which we'd want to standardize on.

      1. In some places I feel like the bold is helpful, but here I don't think it is. I'll make the capitalization match.

    30. Show all issues

      Can you switch this to use an Ink icon, and leave it out of the translation string?

    31. 
        
    david
    Review request changed
    Change Summary:

    New revision for interdiff purposes on refactors and fixes before I work on splitting up into multiple changes.

    Commits:
    Summary ID
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr
    Add GitHub App connectivity.
    This change adds a GitHub App as an alternative to Personal Access Tokens. This involves a bunch of moving parts: 1. The actual GitHub App backend, which includes a redirect view that will trigger the GitHub App Manifest flow, and callback views for when the app has been created and when the app has been installed into a user or organization. This ends up creating a hidden `HostingServiceAccount` which holds the app registration keys, and then each time the app gets installed (for a user or organization) will create a new `HostingServiceAccount` for that entry. 2. Modifications to the GitHub client to use the app for authentication. This involves minting short-lived RS256 JWT tokens for the actual API requests to GitHub. 3. Customized UI parts for the connected services list. This involves: - Special templates and flows for the "Connect a service" wizard which lets users choose how they want to connect, and triggers for the new app creation flow. - An override for the entry template which adds an extra controls panel that either encourages users to create the app (if they have existing connections using PATs only), or provides a link into GitHub's settings UI (allowing changes to URLs or transferring the app registration to another owner). This also currently includes a stub webhook handler (since GitHub Apps have a single webhook endpoint for all authorized repositories), as well as some commented-out bits for future enhancement. Testing Done: - Connected a GitHub App and installed it into multiple organizations. - Tested all aspects of the wizard flow, including creation of the app and reassignment of any PAT-based repositories after app install. - Verified that API access works correctly when authenticating using the app tokens. - Ran unit tests.
    qmxulzkloxrsnquyqwrxwlrrquzplqpr

    Checks run (2 failed)

    flake8 failed.
    JSHint failed.

    flake8

    JSHint

    david
    david
    1. Split up into the following changes:

      • /r/15221 Refactor connect UI into a helper object
      • /r/15222 Allow linking directly to a specific page for the connect wizard
      • /r/15223 GH app data model
      • /r/15224 GH app authentication
      • /r/15225 Customization capabilities for connect wizard
      • /r/15226 GH app webhook view
      • /r/15227 GH app connection flow and UI
    2. 
        
    david
    Review request changed
    Status:
    Discarded