Skip to content

Add XblTestAccount tool and silent test account authentication - #139

Open
Jon Martin (jonmartinms) wants to merge 6 commits into
mainfrom
user/jonmartin/xbltestaccount
Open

Add XblTestAccount tool and silent test account authentication#139
Jon Martin (jonmartinms) wants to merge 6 commits into
mainfrom
user/jonmartin/xbltestaccount

Conversation

@jonmartinms

Copy link
Copy Markdown
Contributor

Summary

Adds XblTestAccount, a new command line tool for signing in Xbox Live test accounts and managing their privileges and privacy settings, plus the auth library work needed to make sign in silent and repeatable.

Previously a test account sign in required interactive UI on every invocation, which makes it awkward to use from scripts or automated test passes. This branch caches the credential so subsequent runs need no UI.

Auth library changes

ToolAuthentication / AuthClient gain silent test account support:

  • SignInTestAccountAsync now tries, in order, a cached XSTS token, then a silent MSAL token, and only falls back to interactive sign in when both miss.
  • New public surface: LoadLastSignedInTestAccount, GetTestAccountSilentlyAsync, SignOutTestAccount, SaveLastSignedInTestAccount, plus forceInteractiveSignIn / forceRefresh overloads for callers that need to bypass the cache.
  • AuthTokenCache and MsalTestAuthContext updated to persist and retrieve the test account entry.

New tool

CommandLine/XblTestAccount with these verbs:

Verb Purpose
signin / signout / show Manage the cached test account
privilege Report, block or allow privileges
list-privileges List known privilege ids and names
privacy Report or change privacy settings
list-privacy-settings List known settings, aliases and values

It follows the conventions of the existing tools: same csproj/App.config/packages.config shape as XblDevAccount, Costura/Fody single file exe, StyleCop clean, errors on stderr, registered in signConfig.xml and postBuildScript.cmd so it is signed and included in ToolZip and Tools-VPack.

Dependencies

No new external dependencies. The tool references only Newtonsoft.Json 13.0.1, already used by Microsoft.Xbox.Service.DevTools, XblConfig, SessionHistoryViewer and the unit tests. No version bumps.

Tests

Four new cases in AuthTest.cs cover the silent path: SignInTestAccountSilentlyTest, SignInTestAccountFallsBackToInteractiveTest, SignInTestAccountReusesCachedXTokenTest and LoadLastSignedInTestAccountTest. All pass locally, along with the four pre-existing AuthTest cases.

Also verified the built exe end to end against a live sandbox: signin, show, list-privileges, list-privacy-settings and a privilege change all behave as expected.

Notes for reviewers

Two pre-existing issues surfaced while getting the test suite to run. Both predate this branch and are not addressed here, but are worth knowing about:

  1. The unit tests do not run out of the box. The test assembly is delay signed, but the RichardSzalay.MockHttp package binary is not strong named, so it fails to load at runtime. Separately, the library grants InternalsVisibleTo to DynamicProxyGenAssembly2 using the Microsoft shared key, while Castle DynamicProxy signs that generated assembly with its own key, so Moq cannot proxy the internal IAuthContext. I worked around both locally to validate the new tests.
  2. CI does not run the tests. DevTools.CI.yml builds, packs and publishes, but has no VSTest task, which is likely why the above went unnoticed.

Happy to open a follow up for either if useful.

Also note that building XblTestAccount.csproj on its own fails Fody weaving because Fody 3.x resolves weavers through $(SolutionDir). Build XboxLiveTools.sln instead, as CI does.

Reuse a cached XSTS token, then a silently refreshed MSA token, before
falling back to interactive sign in, so a test account can be used from a
script or an automated build with no UI. Persist the last signed in test
account to a lastTestUser cache file and add SignOutTestAccount to clear it.

Add forceRefresh overloads of GetTestTokenSilentlyAsync and a new
GetTestAccountSilentlyAsync, so callers can mint a new token to observe a
privilege or privacy change and to recover from the service rejecting a
token issued before such a change.

Make the token cache tolerate entries with no "enm" claim, which test
account XSTS tokens do not carry, and resolve MSAL accounts with
FirstOrDefault so a duplicate cache entry cannot make silent auth throw.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ba5c5086-7199-4fde-be11-88b6942afc7c
XblTestAccount signs in an Xbox Live test account and caches the credential,
then reads and changes the privileges and privacy settings of that account.
It is the command line equivalent of the Privacy and Privilege tabs in
XblTestAccountGui.

Verbs: signin, signout, show, privilege, list-privileges, privacy and
list-privacy-settings. The privilege action is a positional word (get, block
or allow) rather than an option, and both it and the privacy value are parsed
by name only, so a numeric argument cannot select a member by ordinal and
silently perform the wrong write.

Only 185 and 254 can be blocked or allowed directly, so the tool refuses
other ids up front rather than surfacing the bare HTTP 400 the service
returns. Privileges the service derives from a privacy setting are reported
as such, and the error points at the setting that controls them.

The parental and privacy services require a user XSTS token, so requests are
issued directly rather than through XboxLiveHttpRequest, which attaches a
Partner Center eToken. Changing a privilege invalidates the token the call
was made with, so a 401 is retried once with a freshly minted token.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ba5c5086-7199-4fde-be11-88b6942afc7c
Document the XblTestAccount verbs in the README, and note that signing test
accounts in beforehand lets XblPlayerDataReset run unattended.

Cover test account sign in, the silent and cached paths, and sign out in
AuthTest. Clear both token caches during test setup: the caches are written
to disk and outlive the run, and SignOut only drops tokens whose "enm" claim
matches the signed in user name, which the mocked account never does, so a
token cached by one test would otherwise be served to the next and make the
results depend on the order the tests run in.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ba5c5086-7199-4fde-be11-88b6942afc7c
Register the new tool for release and match the style used by the
other command line tools.

- Add XblTestAccount.exe to signConfig.xml so it is Authenticode signed
- Copy XblTestAccount.exe into ToolZip and Tools-VPack in postBuildScript
- Write errors and warnings to stderr, matching XblConfig, GlobalStorage,
  XblConnectedStorage and XblPlayerDataReset
- Add the UTF-8 BOM carried by every other source file in the repo
- Drop an unused using and import Newtonsoft.Json rather than fully
  qualifying Formatting.None
- Remove the unused single argument SignInTestAccountAsync overload
- Mention test account support in the README capability list

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fb12dba7-caf1-4def-9e64-bf611128fbe2
Use the same Fody and Costura versions and configuration as the other command-line projects to prevent mixed weaver assemblies during parallel solution builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8048a542-e97b-44e1-8843-19912690da4b
}
}

[Verb("list-privacy-settings", HelpText = "List the known Xbox Live privacy settings, their aliases and their values.")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe change to "list-privacy" to match "list-privileges"?

/// </summary>
internal static class PrivacyNames
{
private static readonly Dictionary<string, string> DescriptionMapping = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only these 4 are listed? There are many more that show up when using the "privacy" command:

    AllowFollowers                     Everyone
    AllowFriendInvites                 Everyone
    AllowProfileViewing                Everyone
    AllowUserCreatedContentViewing     Everyone
    CollectVoiceData                   Blocked
    CollectVoiceGameChatData           Everyone
    CollectVoiceSearchData             Everyone
    CollectXboxVideoData               Everyone
    CommunicateDuringCrossNetworkPlay  Everyone
    CommunicateUsingTextAndVoice       Everyone
    CommunicateUsingVideo              PeopleOnMyList
    ShareActivityFeed                  Everyone
    ShareClubMembership                Everyone
    ShareContentToExternalNetworks     Everyone
    ShareFriendList                    Everyone
    ShareGameHistory                   Everyone
    ShareMusicHistory                  Everyone
    SharePresence                      Everyone
    ShareProfile                       PeopleOnMyList
    ShareRecordedGameSessions          Everyone
    ShareVideoHistory                  Blocked
    ShareXboxMusicActivity             Everyone

Perhaps this isn't a useful feature?

@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get this into something that isn't a separate file? It's the only enum broken out.

}
}

Console.WriteLine($"Restricted privileges for {testAccount.Gamertag} ({xuid}):");

@BrianPeekMSFT Brian Peek (BrianPeekMSFT) Aug 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things...

  • For privacy, things are broken out to a PrivacyClient.cs, but Privilege is just baked into Program.cs
  • In my usage, running show lists all numeric privileges for a test account. Running with privilege only shows restricted privileges and both are unknown. Running list-privileges gives me a full list of all privileges. This is confusing...
  • Both privacy and privilege allow setting/getting, but it's not at all obvious they have these features from the base usage of both...seems like they just list.
  • Instead of separate commands to list, how about just doing this in the show command and change privacy and privilege to require an action?

Overall, just seems very confusing and the outputs are not what I'd expect...happy to discuss further on changing the way this works. The features are good, but they are not easily discoverable or usable IMO.

@BrianPeekMSFT Brian Peek (BrianPeekMSFT) Aug 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe show does the list of privacy and privilege, with the number and mapping. Then privilege and privacy take a 2nd action of listall, get, set followed by the appropriate args for 2nd action?

Is get useful if we have the modified show?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants