Ability to pass down WWW-Authenticate challenges down to Auth Providers (#261717)

* Initial plan

* Implement authentication challenges support for mandatory MFA

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Add documentation and integration test for authentication challenges

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Add validation script and finalize implementation

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Update authentication challenges API to use AuthenticationConstraint interface

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Get it compiling... who knows if it works

* New parseWWWAuthenticateHeader behavior

* works

* let's go with this for now

* Good shape

* bye

* final polish

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Tyler James Leonhardt
2025-08-14 18:10:05 -07:00
committed by GitHub
parent 36d72d9670
commit cf433b58e5
22 changed files with 703 additions and 120 deletions

View File

@@ -45,11 +45,17 @@ export class ScopeData {
*/
readonly tenantId: string | undefined;
constructor(readonly originalScopes: readonly string[] = [], authorizationServer?: Uri) {
/**
* The claims to include in the token request.
*/
readonly claims?: string;
constructor(readonly originalScopes: readonly string[] = [], claims?: string, authorizationServer?: Uri) {
const modifiedScopes = [...originalScopes];
modifiedScopes.sort();
this.allScopes = modifiedScopes;
this.scopeStr = modifiedScopes.join(' ');
this.claims = claims;
this.scopesToSend = this.getScopesToSend(modifiedScopes);
this.clientId = this.getClientId(this.allScopes);
this.tenant = this.getTenant(this.allScopes, authorizationServer);

View File

@@ -75,21 +75,31 @@ suite('ScopeData', () => {
assert.strictEqual(scopeData.tenantId, 'some_guid');
});
test('should not return claims', () => {
const scopeData = new ScopeData(['custom_scope']);
assert.strictEqual(scopeData.claims, undefined);
});
test('should return claims', () => {
const scopeData = new ScopeData(['custom_scope'], 'test');
assert.strictEqual(scopeData.claims, 'test');
});
test('should extract tenant from authorization server URL path', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com/tenant123/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope'], authorizationServer);
const scopeData = new ScopeData(['custom_scope'], undefined, authorizationServer);
assert.strictEqual(scopeData.tenant, 'tenant123');
});
test('should fallback to default tenant if authorization server URL has no path segments', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com');
const scopeData = new ScopeData(['custom_scope'], authorizationServer);
const scopeData = new ScopeData(['custom_scope'], undefined, authorizationServer);
assert.strictEqual(scopeData.tenant, 'organizations');
});
test('should prioritize authorization server URL over VSCODE_TENANT scope', () => {
const authorizationServer = Uri.parse('https://login.microsoftonline.com/url_tenant/oauth2/v2.0');
const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], authorizationServer);
const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], undefined, authorizationServer);
assert.strictEqual(scopeData.tenant, 'url_tenant');
});
});