Skip to content

Make the --github-auth flag work for extensions #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion src/vs/code/browser/workbench/workbench.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { parseLogLevel } from 'vs/platform/log/common/log';
import product from 'vs/platform/product/common/product';
import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/windows/common/windows';
import { create, ICredentialsProvider, IHomeIndicator, IProductQualityChangeHandler, ISettingsSyncOptions, IURLCallbackProvider, IWelcomeBanner, IWindowIndicator, IWorkbenchConstructionOptions, IWorkspace, IWorkspaceProvider } from 'vs/workbench/workbench.web.api';
import { equals as arrayEquals } from 'vs/base/common/arrays';

function doCreateUri(path: string, queryValues: Map<string, string>): URI {
let query: string | undefined = undefined;
Expand Down Expand Up @@ -49,6 +50,14 @@ interface ICredential {
password: string;
}

/** @author coder */
interface IToken {
accessToken: string
account?: { label: string }
id: string
scopes: string[]
}

class LocalStorageCredentialsProvider implements ICredentialsProvider {

static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider';
Expand Down Expand Up @@ -76,6 +85,61 @@ class LocalStorageCredentialsProvider implements ICredentialsProvider {
scopes,
accessToken: authSessionInfo!.accessToken
}))));

/**
* Add tokens for extensions to use. This works for extensions like the
* pull requests one or GitLens.
* @author coder
*/
const extensionId = `vscode.${authSessionInfo.providerId}-authentication`;
const service = `${product.urlProtocol}${extensionId}`;
const account = `${authSessionInfo.providerId}.auth`;
// Oddly the scopes need to match exactly so we cannot just have one token
// with all the scopes, instead we have to duplicate the token for each
// expected set of scopes.
const tokens: IToken[] = authSessionInfo.scopes.map((scopes) => ({
id: authSessionInfo!.id,
scopes: scopes.sort(), // Sort for comparing later.
accessToken: authSessionInfo!.accessToken,
}));
this.getPassword(service, account).then((raw) => {
let existing: {
content: IToken[]
} | undefined;

if (raw) {
try {
const json = JSON.parse(raw);
json.content = JSON.parse(json.content);
existing = json;
} catch (error) {
console.log(error);
}
}

// Keep tokens for account and scope combinations we do not have in case
// there is an extension that uses scopes we have not accounted for (in
// these cases the user will need to manually authenticate the extension
// through the UI) or the user has tokens for other accounts.
if (existing?.content) {
existing.content = existing.content.filter((existingToken) => {
const scopes = existingToken.scopes.sort();
return !(tokens.find((token) => {
return arrayEquals(scopes, token.scopes)
&& token.account?.label === existingToken.account?.label;
}))
})
}

return this.setPassword(service, account, JSON.stringify({
extensionId,
...(existing || {}),
content: JSON.stringify([
...tokens,
...(existing?.content || []),
])
}));
})
}
}

Expand Down Expand Up @@ -446,7 +510,7 @@ class WindowIndicator implements IWindowIndicator {
/**
* If the value begins with a slash assume it is a file path and convert it to
* use the vscode-remote scheme.
*
*
Comment on lines -449 to +513
Copy link

Choose a reason for hiding this comment

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

Was this intentional?

Copy link
Member Author

@code-asher code-asher Mar 1, 2022

Choose a reason for hiding this comment

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

Ah it kind of was intentional in that I did see my editor had done this but for some reason it did not occur to me to skip committing this. Gonna remove to not needlessly increase the patch size.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually looks like it is our own comment so I will go ahead and leave it.

* We also add the remote authority in toRemote. It needs to be accurate
* otherwise other URIs won't match it, leading to issues such as this one:
* https://github.com/coder/code-server/issues/4630
Expand Down
7 changes: 6 additions & 1 deletion src/vs/server/webClientServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,12 @@ export class WebClientServer {
id: generateUuid(),
providerId: 'github',
accessToken: this._environmentService.args['github-auth'],
scopes: [['user:email'], ['repo']]
/**
* Add a set of scopes for the pull request extension which also wants
* read:user.
* @author coder
*/
scopes: [['read:user', 'user:email', 'repo'], ['user:email'], ['repo']]
Copy link

Choose a reason for hiding this comment

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

❓ How come they have to be duplicated like this? I would think it would be enough to do:

Suggested change
scopes: [['read:user', 'user:email', 'repo'], ['user:email'], ['repo']]
scopes: [['read:user'], ['user:email'], ['repo']]

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, I thought so too but the scopes have to match exactly. So if an extension wants ['repo'] but the token offers both ['repo', 'read:user'] then it will not match.

You can see here they do a strict equals on the scopes array:

const finalSessions = scopes
? sessions.filter(session => arrayEquals([...session.scopes].sort(), scopes.sort()))
: sessions;

It does feel like a bug so maybe we should patch this instead but idk.

Copy link

Choose a reason for hiding this comment

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

AH...that make sense. Thanks for explaining. When we get to patches, I will see if it's a bug and we can raise upstream.

} : undefined;

const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource);
Expand Down