Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

Use keycloak token for oAuth requests #694

Merged
merged 19 commits into from
Apr 9, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { MiniBrowserOpenHandler } from '@theia/mini-browser/lib/browser/mini-bro
import { WebviewEnvironment } from '@theia/plugin-ext/lib/main/browser/webview/webview-environment';
import { CheWebviewEnvironment } from './che-webview-environment';
import { TaskStatusHandler } from './task-status-handler';
import { OauthUtils } from './oauth-utils';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(CheApiProvider).toSelf().inSingletonScope();
Expand Down Expand Up @@ -104,4 +105,5 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
rebind(MiniBrowserOpenHandler).to(CheMiniBrowserOpenHandler).inSingletonScope();

bind(TaskStatusHandler).toSelf().inSingletonScope();
bind(OauthUtils).toSelf().inSingletonScope();
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class CheGithubMainImpl implements CheGithubMain {
private readonly oAuthUtils: OauthUtils;

constructor(container: interfaces.Container) {
this.oAuthUtils = new OauthUtils(container);
this.oAuthUtils = container.get(OauthUtils);
}

async $uploadPublicSshKey(publicKey: string): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class CheOauthMainImpl implements CheOauthMain {
private readonly oAuthUtils: OauthUtils;

constructor(container: interfaces.Container) {
this.oAuthUtils = new OauthUtils(container);
this.oAuthUtils = container.get(OauthUtils);
}

$getProviders(): Promise<string[]> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class CheOpenshiftMainImpl implements CheOpenshiftMain {
private readonly oAuthUtils: OauthUtils;

constructor(container: interfaces.Container) {
this.oAuthUtils = new OauthUtils(container);
this.oAuthUtils = container.get(OauthUtils);
}
async $getToken(): Promise<string> {
const oAuthProvider = 'openshift';
Expand Down
79 changes: 43 additions & 36 deletions extensions/eclipse-che-theia-plugin-ext/src/browser/oauth-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,23 @@
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
import { interfaces } from 'inversify';
import { inject, injectable } from 'inversify';
import { EnvVariablesServer } from '@theia/core/lib/common/env-variables';
import { CheApiService } from '../common/che-protocol';
import { Emitter, Event } from '@theia/core/lib/common';

@injectable()
export class OauthUtils {
private readonly envVariableServer: EnvVariablesServer;

private apiUrl: string;
private machineToken: string | undefined;
private readonly cheApiService: CheApiService;

constructor(container: interfaces.Container) {
this.envVariableServer = container.get(EnvVariablesServer);
this.cheApiService = container.get(CheApiService);
private oAuthPopup: Window | undefined;
private userToken: string | undefined;
private readonly onDidReceiveToken: Event<void>;
constructor(@inject(EnvVariablesServer) private readonly envVariableServer: EnvVariablesServer,
@inject(CheApiService) private readonly cheApiService: CheApiService) {
const onDidReceiveTokenEmitter = new Emitter<void>();
this.onDidReceiveToken = onDidReceiveTokenEmitter.event;
this.envVariableServer.getValue('CHE_API').then(variable => {
if (variable && variable.value) {
this.apiUrl = variable.value;
Expand All @@ -30,56 +34,59 @@ export class OauthUtils {
this.machineToken = variable.value;
}
});
window.addEventListener('message', data => {
if (data.data.startsWith('token:') && this.oAuthPopup) {
this.oAuthPopup.close();
this.userToken = data.data.substring(6, data.data.length);
onDidReceiveTokenEmitter.fire(undefined);
} else if (data.data.startsWith('status:') && this.oAuthPopup) {
this.oAuthPopup.postMessage('token:' + (this.machineToken ? this.machineToken : ''), '*');
}
});
}

async getUserToken(): Promise<string | undefined> {
if (this.userToken) {
return this.userToken;
} else if (this.machineToken && this.machineToken.length > 0) {
const popup = window.open(`${this.apiUrl.substring(0, this.apiUrl.indexOf('/api'))}/_app/oauth.html`, 'popup');
if (popup) {
this.oAuthPopup = popup;
}
return new Promise(async resolve => {
this.onDidReceiveToken(() => resolve(this.userToken));
});
}
}

async getToken(oAuthProvider: string): Promise<string | undefined> {
return await this.cheApiService.getOAuthToken(oAuthProvider);
return await this.cheApiService.getOAuthToken(oAuthProvider, await this.getUserToken());
}

async getProviders(): Promise<string[]> {
return await this.cheApiService.getOAuthProviders();
return await this.cheApiService.getOAuthProviders(await this.getUserToken());
}

async isAuthenticated(provider: string): Promise<boolean> {
const token = await this.cheApiService.getOAuthToken(provider);
const token = await this.cheApiService.getOAuthToken(provider, await this.getUserToken());
return token !== null;
}

authenticate(oauthProvider: string, scope?: string[]): Promise<void> {
return new Promise(async (resolve, reject) => {
const redirectUrl = window.location.href;
let url = `${this.apiUrl}/oauth/authenticate?oauth_provider=${oauthProvider}&userId=${await this.cheApiService.getUserId()}`;
let url = `${this.apiUrl.substring(0, this.apiUrl.indexOf('/api'))}/_app/oauth.html?oauth_provider=${oauthProvider}`;
if (scope) {
for (const s of scope) {
url += `&scope=${s}`;
}
}
if (this.machineToken) {
url += `&token=${this.machineToken}`;
}
url += `&redirect_after_login=${redirectUrl}`;
const popupWindow = window.open(url, 'popup');
const popup_close_handler = async () => {
if (!popupWindow || popupWindow.closed) {
if (popupCloseHandlerIntervalId) {
window.clearInterval(popupCloseHandlerIntervalId);
}
reject(new Error('Authentication failed!'));
} else {
try {
if (redirectUrl === popupWindow.location.href) {
if (popupCloseHandlerIntervalId) {
window.clearInterval(popupCloseHandlerIntervalId);
}
popupWindow.close();
resolve();
}
} catch (error) {
}
}
};

const popupCloseHandlerIntervalId = window.setInterval(popup_close_handler, 80);
const popup = window.open(url, 'popup');
if (popup) {
this.oAuthPopup = popup;
}
this.onDidReceiveToken(() => resolve(undefined));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@ export interface CheApiService {

getFactoryById(factoryId: string): Promise<cheApi.factory.Factory>;

getUserId(): Promise<string>;
getUserId(token?: string): Promise<string>;
getUserPreferences(): Promise<Preferences>;
getUserPreferences(filter: string | undefined): Promise<Preferences>;
updateUserPreferences(update: Preferences): Promise<Preferences>;
Expand All @@ -477,8 +477,8 @@ export interface CheApiService {
getAllSshKey(service: string): Promise<cheApi.ssh.SshPair[]>;
submitTelemetryEvent(id: string, ownerId: string, ip: string, agent: string, resolution: string, properties: [string, string][]): Promise<void>;
submitTelemetryActivity(): Promise<void>;
getOAuthToken(oAuthProvider: string): Promise<string | undefined>;
getOAuthProviders(): Promise<string[]>;
getOAuthToken(oAuthProvider: string, token?: string): Promise<string | undefined>;
getOAuthProviders(token?: string): Promise<string[]>;
}

export const CHE_TASK_SERVICE_PATH = '/che-task-service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ export class CheApiServiceImpl implements CheApiService {
return process.env.CHE_API_INTERNAL;
}

async getUserId(): Promise<string> {
async getUserId(token?: string): Promise<string> {
const cheApiClient = await this.getCheApiClient();
const user = await cheApiClient.getCurrentUser();
const user = await cheApiClient.getCurrentUser(token);
return user.id;
}

Expand Down Expand Up @@ -296,19 +296,19 @@ export class CheApiServiceImpl implements CheApiService {
}
}

async getOAuthToken(oAuthProvider: string): Promise<string | undefined> {
async getOAuthToken(oAuthProvider: string, token?: string): Promise<string | undefined> {
const cheApiClient = await this.getCheApiClient();
try {
return await cheApiClient.getOAuthToken(oAuthProvider);
return await cheApiClient.getOAuthToken(oAuthProvider, token);
} catch (e) {
return undefined;
}
}

async getOAuthProviders(): Promise<string[]> {
async getOAuthProviders(token?: string): Promise<string[]> {
const cheApiClient = await this.getCheApiClient();
try {
return await cheApiClient.getOAuthProviders();
return await cheApiClient.getOAuthProviders(token);
} catch (e) {
return [];
}
Expand Down
7 changes: 0 additions & 7 deletions plugins/ssh-plugin/src/ssh-plugin-backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ export async function start() {
}
// If the remote repository is a GitHub repository, ask to upload a public SSH key.
if (out.indexOf('git@github.com') > -1) {
// Currently multi-user che-theia doesn't support GiHub oAuth.
if (isMultiUser()) {
showWarningMessage(keys.length === 0, 'GitHub');
return;
}
switch (command) {
case 'clone': {
if (await askToGenerateIfEmptyAndUploadKeyToGithub(keys, true)) {
Expand All @@ -83,8 +78,6 @@ export async function start() {
}
};

const isMultiUser = (): boolean => !!process.env['KEYCLOAK_SERVICE_HOST'];

const showWarningMessage = (showGenerate: boolean, gitProviderName?: string) =>
theia.window.showWarningMessage(`Permission denied, please ${showGenerate ? 'generate (F1 => ' + GENERATE.label + ') and ' : ''}
upload your public SSH key to ${gitProviderName ? gitProviderName : 'the Git provider'} and try again. To get the public key press F1 => ${VIEW.label}`);
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,9 @@
"@eclipse-che/api" latest

"@eclipse-che/workspace-client@latest":
version "0.0.1-1585324232"
resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1585324232.tgz#2d9c6d8b7b760173f3e194251196961150c3965d"
integrity sha512-65ZCRhdtG1oWYZdrCCVx9r5dk37ApTDY/xpBDjpCY6hfkMAnAUXb8PHqnm54SaqtGahojajobOBxPs2IZGKrDQ==
version "0.0.1-1585913592"
resolved "https://registry.yarnpkg.com/@eclipse-che/workspace-client/-/workspace-client-0.0.1-1585913592.tgz#509c0a12e96adfec3a8c619124c396ecdb755806"
integrity sha512-BxR1pydD3Qn6uS5Yp3TCWDEvgAmwWo254XC6KVJxdeofDeMNRpSx3KWuSrPWdC4jd5+urGKk/nG7vZyCST+85A==
dependencies:
"@eclipse-che/api" "^7.0.0-beta-4.0"
axios "0.19.0"
Expand Down