fix(auth_identity): accept @ in down-level account names - #719
fix(auth_identity): accept @ in down-level account names#719GlassOnTin wants to merge 3 commits into
@ in down-level account names#719Conversation
A Microsoft account's account name is the entire e-mail address, and Windows documents `MicrosoftAccount\user@example.com` as the way to qualify one. `new_down_level_logon_name` rejected any account name containing `@` as MixedFormat, which left Microsoft accounts with no representable form at all: the qualified string and the split username+domain form both errored, and the bare e-mail parses as a UPN whose account_name() is truncated at the `@` (Devolutions#718). Once the down-level format is established — by the `\` in the string or by an explicit domain argument — the account name is opaque, so the `@` rejection guarded nothing. Windows accepts such logon names; FreeRDP likewise splits only on the backslash. The check now rejects only `\`. Strictly widens the accepted set: every previously-valid input parses exactly as before, including the bare `user@example.com` UPN reading, which stays untouched because it is indistinguishable from an AD UPN and guessing "Microsoft account" would break AD logons. One inherent asymmetry, now documented on `parse`: a value containing both separators is a down-level logon name (the `\` wins), and a *domain-less* down-level name built directly with an `@` in the account re-parses as a UPN — the string form is ambiguous, which is what the typed constructors are for. Closes Devolutions#718
Pavlo Myroniuk (TheBestTvarynka)
left a comment
There was a problem hiding this comment.
LGTM.
I also did some small dev testing using FreeRDP and MsRdpEx. Seems like everything works just fine.
There was a problem hiding this comment.
🟡 Not ready to approve
Domain-less names containing @ can change from down-level format to UPN during buffer round-trips.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Allows qualified Microsoft account email addresses to remain intact as down-level logon names.
Changes:
- Permits
@in down-level account names. - Documents separator precedence.
- Adds Microsoft-account and property-test coverage.
File summaries
| File | Description |
|---|---|
src/auth_identity.rs |
Updates validation, documentation, and username tests. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
Thank you! Can you double check the Copilot comment please? |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
I’ve reviewing myself a little bit more in depth -- thanks for the very thorough write-up, the description made this much easier to review. Pavlo and I went through this together, and neither of us is particularly familiar with the What gives us pause is less the patch than the shape it leaves behind:
Same identity, and the answer to "what is the account name?" changes depending on whether the qualification is present. You document the precedence rule clearly and the reasoning behind it is sound, so this isn't an objection to the behavior as such, but it's that the type now seems to be modelling the domain slightly wrong. It has no good answer for "can down-level and UPN mix, and if so what do the accessors mean?", and the patch answers that locally rather than at the level of the model. Which brings me to the question we can't settle on our own: is To be clear about where we land: the change is strictly widening and every previously-valid input parses as before, so we're happy to merge as-is if unblocking the downstream case matters now, and treat the API rework as a follow-up. I mainly wanted to raise the modelling concern before it gets baked in rather than after. On your offer at the end (rejecting |
Regression test for the round-trip hazard fixed in 2e05ed1: with no NetBIOS domain there is no `\\` in the serialized string, so an `@`-bearing account name came back from an AuthIdentityBuffers round trip as a UPN. Verified the test earns its place — it fails if the `netbios_domain_name .is_empty() && account_name.contains('@')` clause is removed. 250/250 pass.
|
Double-checked the Copilot comment: it's right, and I reproduced it independently before looking at the suggested fix — adding On the modelling questionYou asked whether The SAM-Account-Name schema reference gives the disallowed set verbatim:
That gives the asymmetry a principled basis rather than a stylistic one:
So the precedence rule in this PR isn't a local patch to keep an odd input working; it's what the underlying alphabets require. Under that reading the Where that leaves your two accessors:
Which is to say the awkwardness you're pointing at is real but isn't in If you'd rather rework For the downstream context, since you asked about urgency: this unblocks Microsoft-account logons over NLA in Haven, an Android RDP client — currently those users have to turn NLA off at both ends. Not an emergency, and I'd rather it land in a shape you're happy to maintain. |
Fixes #718.
What
Username::new_down_level_logon_namerejected any account name containing@asMixedFormat. That left a Microsoft account — where the entire e-mail address is the account name — with no representable form:MicrosoftAccount\me@example.com(the qualification Microsoft documents for users) andUsername::new("me@example.com", Some("MicrosoftAccount"))both errored, and the bare e-mail parses as a UPN whoseaccount_name()is truncated tome.The check now rejects only
\. Once the down-level format is established — by the backslash in the string or by an explicit domain argument — the account name is opaque. Windows itself accepts such logon names, and FreeRDP likewise splits only on the backslash.What deliberately does not change
Username::parse("me@example.com")still parses as a UPN withaccount_name() == "me". The bare e-mail is indistinguishable from an AD UPN, and guessing "Microsoft account" on sight would break AD logons. Under NLA a Microsoft account therefore needs the qualified form — but now it has one.Semantics note, now documented on
parseA value containing both separators is a down-level logon name: the
\takes precedence and everything after it is the account name, matching how Windows reads the qualification. The inverse asymmetry also exists and is inherent to the string form: a domain-less down-level name built directly vianew_down_level_logon_name("me@example.com", "")stringifies to somethingparsereads back as a UPN. The typed constructors are the unambiguous path; happy to instead reject@in the empty-domain case if you'd rather preserve universal round-tripping.Tests
microsoft_account_forms_are_representablepins the three forms from Microsoft account usernames are unrepresentable: account name truncated at '@', qualified forms rejected #718: qualified string ✓, split username+domain ✓ (equal to the qualified form), bare e-mail unchanged as UPN.down_level_logon_name_round_tripproptest alphabet now includes@in account names.username_format_conversionproptest guard is narrowed to the one genuinely non-reconstructible combination (no domain +@in the account).Downstream context
Hit via IronRDP → CredSSP → NTLM: a Haven for Android user RDPing to a consumer Windows 11 box (Microsoft-account login) gets
user = "me",domain = "example.com"on the wire and lands on the lock screen; the qualified spelling they were told to use is rejected before it reaches the wire (GlassHaven/Haven#461). With this change the qualified forms reach NTLM asuser = "me@example.com",domain = "MicrosoftAccount", which is what mstsc sends.