Skip to content

Commit

Permalink
fix: Fix github clone when several tokens in /.git-credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaliy-guliy committed Sep 28, 2023
1 parent 8ab4cf9 commit 650eaec
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 23 deletions.
26 changes: 18 additions & 8 deletions code/extensions/che-api/src/impl/github-service-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,50 @@ import { AxiosInstance } from 'axios';
import * as fs from 'fs-extra';
import * as path from 'path';

const GITHUB_GET_AUTHENTICATED_USER = 'https://api.github.com/user';

@injectable()
export class GithubServiceImpl implements GithubService {
private readonly token: string | undefined;

constructor(@inject(Symbol.for('AxiosInstance')) private readonly axiosInstance: AxiosInstance) {
const credentialsPath = path.resolve('/.git-credentials', 'credentials');
if (fs.existsSync(credentialsPath)) {
const token = fs.readFileSync(credentialsPath).toString();
this.token = token.substring(token.lastIndexOf(':') + 1, token.indexOf('@'));
const content = fs.readFileSync(credentialsPath).toString();

// Since this service is specific for github.com, we need to find only one corresponding token
const regex = /(http|https):\/\/oauth2:(\d|\S+)@github.com$/mg;
const found = content.match(regex);
if (found) {
// take the first token
const t = found[0];
this.token = t.substring(t.lastIndexOf(':') + 1, t.indexOf('@'));
}
}
}

private checkToken(): void {
private ensureTokenExists(): void {
if (!this.token) {
throw new Error('GitHub authentication token is not setup');
}
}

async getToken(): Promise<string> {
this.checkToken();
this.ensureTokenExists();
return this.token!;
}

async getUser(): Promise<GithubUser> {
this.checkToken();
const result = await this.axiosInstance.get<GithubUser>('https://api.github.com/user', {
this.ensureTokenExists();
const result = await this.axiosInstance.get<GithubUser>(GITHUB_GET_AUTHENTICATED_USER, {
headers: { Authorization: `Bearer ${this.token}` },
});
return result.data;
}

async getTokenScopes(token: string): Promise<string[]> {
this.checkToken();
const result = await this.axiosInstance.get<GithubUser>('https://api.github.com/user', {
this.ensureTokenExists();
const result = await this.axiosInstance.get<GithubUser>(GITHUB_GET_AUTHENTICATED_USER, {
headers: { Authorization: `Bearer ${token}` },
});
return result.headers['x-oauth-scopes'].split(', ');
Expand Down

This file was deleted.

52 changes: 38 additions & 14 deletions code/extensions/che-api/tests/github-service-impl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,59 +12,83 @@
import 'reflect-metadata';

import * as fs from 'fs-extra';
import * as path from 'path';

import { Container } from 'inversify';
import { GithubServiceImpl } from '../src/impl/github-service-impl';
import * as os from 'os';
import { GithubUser } from '../src/api/github-service';

const CREDENTIALS = 'https://oauth2:token@github.com';

const MULTIHOST_CREDENTIALS = `
https://oauth2:5761d0b375d627ca5a@gitlab.com
https://oauth2:gho_dbi6YDyjyNrkO0ag354@github.com
`;

describe('Test GithubServiceImpl', () => {
let container: Container;

let githubServiceImpl: GithubServiceImpl;

const axiosGetMock = jest.fn();
const axiosMock = {
get: axiosGetMock,
} as any;

let readFileSyncMock = jest.fn();
let existsSyncMock = jest.fn();

beforeEach(async () => {
jest.restoreAllMocks();
jest.resetAllMocks();

Object.assign(fs, {
readFileSync: readFileSyncMock,
existsSync: existsSyncMock
});

container = new Container();
container.bind(GithubServiceImpl).toSelf().inSingletonScope();
container.bind(Symbol.for('AxiosInstance')).toConstantValue(axiosMock);
});

jest.spyOn(os, 'homedir').mockReturnValue(path.resolve(__dirname, '_data'));
githubServiceImpl = container.get(GithubServiceImpl);
test('throw error when token is not setup', async () => {
const service = container.get(GithubServiceImpl);
await expect(service.getToken()).rejects.toThrow('GitHub authentication token is not setup');
});

test('get token', async () => {
const token = await githubServiceImpl.getToken();
existsSyncMock.mockReturnValue(true);
readFileSyncMock.mockReturnValue(CREDENTIALS);

const service = container.get(GithubServiceImpl);
const token = await service.getToken();

expect(token).toEqual('token');
});

test('get user', async () => {
existsSyncMock.mockReturnValue(true);
readFileSyncMock.mockReturnValue(CREDENTIALS);

const data: GithubUser = { id: 1, name: 'name', login: 'login', email: 'email' };
axiosGetMock.mockResolvedValue({ data });

const user = await githubServiceImpl.getUser();
const service = container.get(GithubServiceImpl);
const user = await service.getUser();

expect(axiosGetMock).toBeCalledWith('https://api.github.com/user', {
headers: { Authorization: 'Bearer token' },
});
expect(user).toEqual(data);
});

test('throw error when token is not setup', async () => {
container.rebind(GithubServiceImpl).toSelf().inSingletonScope();
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
test('should return token for github', async () => {
existsSyncMock.mockReturnValue(true);

githubServiceImpl = container.get(GithubServiceImpl);
readFileSyncMock.mockImplementation(path => {
return '/.git-credentials/credentials' === path ? MULTIHOST_CREDENTIALS : '';
});

await expect(githubServiceImpl.getToken()).rejects.toThrow('GitHub authentication token is not setup');
const service = container.get(GithubServiceImpl);
const token = await service.getToken();
expect(token).toEqual('gho_dbi6YDyjyNrkO0ag354');
});

});

0 comments on commit 650eaec

Please sign in to comment.