Skip to content

Commit

Permalink
GH-3397: stub
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta committed Nov 5, 2018
1 parent 3dd403d commit 104f356
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
"@theia/workspace": "^0.3.16",
"@types/diff": "^3.2.2",
"@types/fs-extra": "^4.0.2",
"@types/keytar": "^4.0.1",
"diff": "^3.4.0",
"dugite-extra": "0.1.7",
"find-git-repositories": "^0.1.0",
"fs-extra": "^4.0.2",
"keytar": "^4.3.0",
"moment": "^2.21.0",
"octicons": "^7.1.0",
"ts-md5": "^1.2.2"
Expand Down
147 changes: 147 additions & 0 deletions packages/git/src/electron-node/askpass/askpass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Based on: https://github.com/Microsoft/THEIA/blob/dd3e2d94f81139f9d18ba15a24c16c6061880b93/extensions/git/src/askpass.ts

import * as path from 'path';
import * as http from 'http';
import * as os from 'os';
import * as fs from 'fs';
import * as crypto from 'crypto';
import { Disposable } from '@theia/core/lib/common/disposable';
import { isWindows } from '@theia/core/lib/common/os';

export interface AskpassEnvironment {
readonly GIT_ASKPASS: string;
readonly ELECTRON_RUN_AS_NODE?: string;
readonly THEIA_GIT_ASKPASS_NODE?: string;
readonly THEIA_GIT_ASKPASS_MAIN?: string;
readonly THEIA_GIT_ASKPASS_HANDLE?: string;
}

export class Askpass implements Disposable {

private server: http.Server;
private ipcHandlePathPromise: Promise<string>;
private ipcHandlePath: string | undefined;
private enabled = true;

constructor() {
this.server = http.createServer((req, res) => this.onRequest(req, res));
this.ipcHandlePathPromise = this.setup().catch(err => {
console.error(err);
return '';
});
}

protected async setup(): Promise<string> {
const buffer = await this.randomBytes(20);
const nonce = buffer.toString('hex');
const ipcHandlePath = this.getIPCHandlePath(nonce);
this.ipcHandlePath = ipcHandlePath;

try {
this.server.listen(ipcHandlePath);
this.server.on('error', err => console.error(err));
} catch (err) {
console.error('Could not launch git askpass helper.');
this.enabled = false;
}

return ipcHandlePath;
}

protected getIPCHandlePath(nonce: string): string {
const fileName = `theia-git-askpass-${nonce}-sock`;
if (isWindows) {
return `\\\\.\\pipe\\${fileName}`;
}

if (process.env['XDG_RUNTIME_DIR']) {
return path.join(process.env['XDG_RUNTIME_DIR'] as string, fileName);
}

return path.join(os.tmpdir(), fileName);
}

protected onRequest(req: http.ServerRequest, res: http.ServerResponse): void {
const chunks: string[] = [];
req.setEncoding('utf8');
req.on('data', (d: string) => chunks.push(d));
req.on('end', () => {
const { request, host } = JSON.parse(chunks.join(''));

this.prompt(host, request).then(result => {
res.writeHead(200);
res.end(JSON.stringify(result));
}, () => {
res.writeHead(500);
res.end();
});
});
}

protected async prompt(host: string, request: string): Promise<string> {
const options: InputBoxOptions = {
password: /password/i.test(request),
placeHolder: request,
prompt: `Git: ${host}`,
ignoreFocusOut: true
};

return await window.showInputBox(options) || '';
}

protected async randomBytes(size: number): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
crypto.randomBytes(size, (error: Error, buffer: Buffer) => {
if (error) {
reject(error);
return;
}
resolve(buffer);
});
});
}

async getEnv(): Promise<AskpassEnvironment> {
if (!this.enabled) {
return {
GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh')
};
}

return {
ELECTRON_RUN_AS_NODE: '1',
GIT_ASKPASS: path.join(__dirname, 'askpass.sh'),
THEIA_GIT_ASKPASS_NODE: process.execPath,
THEIA_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'),
THEIA_GIT_ASKPASS_HANDLE: await this.ipcHandlePathPromise
};
}

dispose(): void {
this.server.close();
if (this.ipcHandlePath && !isWindows) {
fs.unlinkSync(this.ipcHandlePath);
}
}

}
87 changes: 87 additions & 0 deletions packages/git/src/electron-node/askpass/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// Based on: https://github.com/Microsoft/THEIA/blob/dd3e2d94f81139f9d18ba15a24c16c6061880b93/extensions/git/src/askpass-main.ts.

import * as http from 'http';
import * as fs from 'fs';

// tslint:disable-next-line:no-any
function fatal(err: any): void {
console.error('Missing or invalid credentials.');
console.error(err);
process.exit(1);
}

function main(argv: string[]): void {
if (argv.length !== 5) {
return fatal('Wrong number of arguments');
}

if (!process.env['THEIA_GIT_ASKPASS_HANDLE']) {
return fatal('Missing handle');
}

if (!process.env['THEIA_GIT_ASKPASS_PIPE']) {
return fatal('Missing pipe');
}

if (process.env['THEIA_GIT_COMMAND'] === 'fetch') {
return fatal('Skip fetch commands');
}

const output = process.env['THEIA_GIT_ASKPASS_PIPE'] as string;
const socketPath = process.env['THEIA_GIT_ASKPASS_HANDLE'] as string;
const request = argv[2];
const host = argv[4].substring(1, argv[4].length - 2);
const opts: http.RequestOptions = {
socketPath,
path: '/',
method: 'POST'
};

const req = http.request(opts, res => {
if (res.statusCode !== 200) {
return fatal(`Bad status code: ${res.statusCode}`);
}

const chunks: string[] = [];
res.setEncoding('utf8');
res.on('data', (d: string) => chunks.push(d));
res.on('end', () => {
const raw = chunks.join('');

try {
const result = JSON.parse(raw);
fs.writeFileSync(output, result + '\n');
} catch (err) {
return fatal('Error parsing response');
}

setTimeout(() => process.exit(0), 0);
});
});

req.on('error', () => fatal('Error in request'));
req.write(JSON.stringify({ request, host }));
req.end();
}

main(process.argv);
62 changes: 62 additions & 0 deletions packages/git/src/electron-node/askpass/token-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable } from 'inversify';
import { getPassword, setPassword, deletePassword } from 'keytar';

/**
* Shared access to the host system's keychain for Git:
* - on OS X the passwords are managed by the `Keychain`,
* - on Linux they are managed by the `Secret Service API`/`libsecret`, and
* - on Windows they are managed by `Credential Vault`.
*
* **Note**: There is a caveat on Linux. Since the underlying `keytar` library uses `libsecret` so you may need to install it before running npm install.
* Depending on your distribution, you will need to run the following command:
* - Debian/Ubuntu: `sudo apt-get install libsecret-1-dev`.
* - Red Hat-based: `sudo yum install libsecret-devel`.
* - Arch Linux: `sudo pacman -S libsecret`.
*/
@injectable()
export class GitTokenStore {

/**
* Adds the password for the service and account to the keychain.
*/
async set(serviceKey: string, account: string, password: string): Promise<void> {
return setPassword(serviceKey, account, password);
}

/**
* Resolves to the password for the service and account. `undefined` if the password cannot be retrieved.
*/
async get(serviceKey: string, account: string): Promise<string | undefined> {
const password = await getPassword(serviceKey, account);
// Do not let `null` to leak into the application code.
if (password === null) {
return undefined;
}
return password;
}

/**
* Deletes the stored password for the service and account.
* As a result, it resolves to the status of the operation; `true` if it was successful. Otherwise, `false`.
*/
async delete(key: string, login: string): Promise<boolean> {
return deletePassword(key, login);
}

}
24 changes: 24 additions & 0 deletions packages/git/src/electron-node/env/electron-git-env-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { ContainerModule } from 'inversify';
import { GitEnvProvider } from '../../node/env/git-env-provider';
import { ElectronGitEnvProvider } from './electron-git-env-provider';

export default new ContainerModule(bind => {
bind(ElectronGitEnvProvider).toSelf().inSingletonScope();
bind(GitEnvProvider).toService(ElectronGitEnvProvider);
});
30 changes: 30 additions & 0 deletions packages/git/src/electron-node/env/electron-git-env-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { injectable } from 'inversify';
import { DefaultGitEnvProvider } from '../../node/env/git-env-provider';

/**
* Git environment provider for Electron.
*
* TODO: describe this!!!
*/
@injectable()
export class ElectronGitEnvProvider extends DefaultGitEnvProvider {

// TODO ASKPASS

}
23 changes: 23 additions & 0 deletions packages/git/src/node/env/git-env-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/********************************************************************************
* Copyright (C) 2018 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { ContainerModule } from 'inversify';
import { GitEnvProvider, DefaultGitEnvProvider } from './git-env-provider';

export default new ContainerModule(bind => {
bind(DefaultGitEnvProvider).toSelf().inSingletonScope();
bind(GitEnvProvider).toService(DefaultGitEnvProvider);
});
Loading

0 comments on commit 104f356

Please sign in to comment.