Skip to content
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

fix: add gitlab support #545

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
17 changes: 16 additions & 1 deletion config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
"description": "Configuration for customizing git-proxy",
"type": "object",
"properties": {
"proxyUrl": { "type": "string" },
"proxyConfig": {
"description": "Proxy configuration",
"type": "array",
"items": {
"$ref": "#/definitions/proxy"
}
},
"cookieSecret": { "type": "string" },
"sessionMaxAgeHours": { "type": "number" },
"api": {
Expand Down Expand Up @@ -70,6 +76,15 @@
}
},
"definitions": {
"proxy": {
"type": "object",
"properties": {
"path": { "type": "string"},
"url": { "type": "string"},
Copy link
Member

Choose a reason for hiding this comment

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

Do we have type url?

"enabled": { "type": "boolean" }
},
"required": ["path", "url", "enabled"]
},
"authorisedRepo": {
"type": "object",
"properties": {
Expand Down
17 changes: 8 additions & 9 deletions packages/git-proxy-cli/test/testCli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const TEST_REPO_CONFIG = {
name: 'git-proxy-test',
url: 'https://github.com/finos/git-proxy-test.git'
}
const TEST_REPO = 'finos/git-proxy-test.git';

describe('test git-proxy-cli', function () {
// *** help ***
Expand Down Expand Up @@ -291,7 +290,7 @@ describe('test git-proxy-cli', function () {

before(async function() {
await helper.addRepoToDb(TEST_REPO_CONFIG);
await helper.addGitPushToDb(pushId, TEST_REPO);
await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url);
})

it('attempt to authorise should fail when server is down', async function () {
Expand Down Expand Up @@ -391,7 +390,7 @@ describe('test git-proxy-cli', function () {

before(async function() {
await helper.addRepoToDb(TEST_REPO_CONFIG);
await helper.addGitPushToDb(pushId, TEST_REPO);
await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url);
})

it('attempt to cancel should fail when server is down', async function () {
Expand Down Expand Up @@ -552,7 +551,7 @@ describe('test git-proxy-cli', function () {

before(async function() {
await helper.addRepoToDb(TEST_REPO_CONFIG);
await helper.addGitPushToDb(pushId, TEST_REPO);
await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url);
})

it('attempt to reject should fail when server is down', async function () {
Expand Down Expand Up @@ -648,7 +647,7 @@ describe('test git-proxy-cli', function () {

before(async function () {
await helper.addRepoToDb(TEST_REPO_CONFIG);
await helper.addGitPushToDb(pushId, TEST_REPO);
await helper.addGitPushToDb(pushId, TEST_REPO_CONFIG.url);
});

it('attempt to ls should list existing push', async function () {
Expand All @@ -662,7 +661,7 @@ describe('test git-proxy-cli', function () {
const expectedExitCode = 0;
const expectedMessages = [
pushId,
TEST_REPO,
TEST_REPO_CONFIG.url,
'authorised: false',
'blocked: true',
'canceled: false',
Expand Down Expand Up @@ -800,7 +799,7 @@ describe('test git-proxy-cli', function () {

cli = `npx -- @finos/git-proxy-cli ls --authorised true --canceled false --rejected false`;
expectedExitCode = 0;
expectedMessages = [pushId, TEST_REPO];
expectedMessages = [pushId, TEST_REPO_CONFIG.url];
expectedErrorMessages = null;
await helper.runCli(
cli,
Expand Down Expand Up @@ -844,7 +843,7 @@ describe('test git-proxy-cli', function () {

cli = `npx -- @finos/git-proxy-cli ls --authorised false --canceled false --rejected true`;
expectedExitCode = 0;
expectedMessages = [pushId, TEST_REPO];
expectedMessages = [pushId, TEST_REPO_CONFIG.url];
expectedErrorMessages = null;
await helper.runCli(
cli,
Expand Down Expand Up @@ -888,7 +887,7 @@ describe('test git-proxy-cli', function () {

cli = `npx -- @finos/git-proxy-cli ls --authorised false --canceled true --rejected false`;
expectedExitCode = 0;
expectedMessages = [pushId, TEST_REPO];
expectedMessages = [pushId, TEST_REPO_CONFIG.url];
expectedErrorMessages = null;
await helper.runCli(
cli,
Expand Down
7 changes: 4 additions & 3 deletions packages/git-proxy-cli/test/testCliUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const actions = require('../../../src/proxy/actions/Action');
const steps = require('../../../src/proxy/actions/Step');
const processor = require('../../../src/proxy/processors/push-action/audit');
const db = require('../../../src/db');
const { Repo } = require('../../../src/model');

// cookie file name
const GIT_PROXY_COOKIE_FILE = 'git-proxy-cookie';
Expand Down Expand Up @@ -167,16 +168,16 @@ async function addRepoToDb(newRepo, debug = false) {
/**
* Add a new git push record to the database.
* @param {string} id The ID of the git push.
* @param {string} repo The repository of the git push.
* @param {string} repoUrl The repository url of the git push.
* @param {boolean} debug Flag to enable logging for debugging.
*/
async function addGitPushToDb(id, repo, debug = false) {
async function addGitPushToDb(id, repoUrl, debug = false) {
const action = new actions.Action(
id,
'push', // type
'get', // method
Date.now(), // timestamp
repo,
new Repo(repoUrl),
);
const step = new steps.Step(
'authBlock', // stepName
Expand Down
13 changes: 12 additions & 1 deletion proxy.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
{
"proxyUrl": "https://github.com",
"proxyConfig": [
{
"path": "/github.com",
"url": "https://github.com",
"enabled": true
},
{
"path": "/gitlab.com",
"url": "https://gitlab.com",
"enabled": true
Comment on lines +4 to +11
Copy link
Member

Choose a reason for hiding this comment

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

I like your proposed configuration. Would it not be better to recommend /github and /gitlab? I realise that this is configurable so doesn't really matter and gives control and choice to the developer.

}
],
"cookieSecret": "cookie secret",
"sessionMaxAgeHours": 12,
"tempPassword": {
Expand Down
16 changes: 8 additions & 8 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ let _authorisedList = defaultSettings.authorisedList;
let _database = defaultSettings.sink;
let _authentication = defaultSettings.authentication;
let _tempPassword = defaultSettings.tempPassword;
let _proxyUrl = defaultSettings.proxyUrl;
let _proxyConfig = defaultSettings.proxyConfig;
let _api = defaultSettings.api;
let _cookieSecret = defaultSettings.cookieSecret;
let _sessionMaxAgeHours = defaultSettings.sessionMaxAgeHours;
Expand All @@ -22,14 +22,14 @@ const _urlShortener = defaultSettings.urlShortener;
const _contactEmail = defaultSettings.contactEmail;
const _csrfProtection = defaultSettings.csrfProtection;

// Get configured proxy URL
const getProxyUrl = () => {
if (_userSettings !== null && _userSettings.proxyUrl) {
_proxyUrl = _userSettings.proxyUrl;
// Gets a list of configured proxies
const getProxyConfigList = () => {
if (_userSettings !== null && _userSettings.proxyConfig) {
_proxyConfig = _userSettings.proxyConfig;
}

return _proxyUrl;
};
return _proxyConfig;
}

// Gets a list of authorised repositories
const getAuthorisedList = () => {
Expand Down Expand Up @@ -141,7 +141,7 @@ const getCSRFProtection = () => {
};

exports.getAPIs = getAPIs;
exports.getProxyUrl = getProxyUrl;
exports.getProxyConfigList = getProxyConfigList;
exports.getAuthorisedList = getAuthorisedList;
exports.getDatabase = getDatabase;
exports.logConfiguration = logConfiguration;
Expand Down
4 changes: 2 additions & 2 deletions src/db/file/pushes.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@
const canUserCancelPush = async (id, user) => {
return new Promise(async (resolve) => {
const pushDetail = await getPush(id);
const repoName = pushDetail.repoName.replace('.git', '');
const isAllowed = await repo.isUserPushAllowed(repoName, user);
const repoUrl = pushDetail.repo.url;
Copy link
Member

Choose a reason for hiding this comment

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

What about pre-existing repositories using the repoName property in the database? We need to think about how the update propagates to the previous data model and its impact on already stored properties in DB.

const isAllowed = await repo.isUserPushAllowed(repoUrl, user);

Check warning on line 98 in src/db/file/pushes.js

View check run for this annotation

Codecov / codecov/patch

src/db/file/pushes.js#L97-L98

Added lines #L97 - L98 were not covered by tests

if (isAllowed) {
resolve(true);
Expand Down
21 changes: 18 additions & 3 deletions src/db/file/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@
});
};

exports.getRepoByUrl = async (url) => {
Copy link
Member

Choose a reason for hiding this comment

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

Keep me honest, but can we not enforce uniqueness with organization, name and provider, i.e. zsh-org, zsh and gitlab.

Can we take advantage of the fact that both GitHub and GitLab follow the same naming convention for repositories and enforce uniqueness this way?

GitHub: finos, git-proxy, github
GitLab: zsh-org, zsh, gitlab

return new Promise((resolve, reject) => {
db.findOne({ url: url }, (err, doc) => {
if (err) {
reject(err);

Check warning on line 41 in src/db/file/repo.js

View check run for this annotation

Codecov / codecov/patch

src/db/file/repo.js#L41

Added line #L41 was not covered by tests
} else {
if (!doc) {
resolve(null);

Check warning on line 44 in src/db/file/repo.js

View check run for this annotation

Codecov / codecov/patch

src/db/file/repo.js#L44

Added line #L44 was not covered by tests
} else {
resolve(doc);
}
}
});
});
};

exports.createRepo = async (repo) => {
repo.users = {
canPush: [],
Expand Down Expand Up @@ -140,10 +156,9 @@
});
};

exports.isUserPushAllowed = async (name, user) => {
name = name.toLowerCase();
exports.isUserPushAllowed = async (url, user) => {
return new Promise(async (resolve, reject) => {
const repo = await exports.getRepo(name);
const repo = await exports.getRepoByUrl(url);
Copy link
Member

Choose a reason for hiding this comment

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

Again, same point here - this is fine moving forward for updated data properties but what about previous stored data? Do we need a transformer?

console.log(repo.users.canPush);
console.log(repo.users.canAuthorise);

Expand Down
4 changes: 2 additions & 2 deletions src/db/mongo/pushes.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ const canUserApproveRejectPush = async (id, user) => {
const canUserCancelPush = async (id, user) => {
return new Promise(async (resolve) => {
const pushDetail = await getPush(id);
const repoName = pushDetail.repoName.replace('.git', '');
const isAllowed = await repo.isUserPushAllowed(repoName, user);
const repoUrl = pushDetail.repo.url;
const isAllowed = await repo.isUserPushAllowed(repoUrl, user);
Comment on lines +105 to +106
Copy link
Member

Choose a reason for hiding this comment

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

Same point as the above comments here.


if (isAllowed) {
resolve(true);
Expand Down
10 changes: 7 additions & 3 deletions src/db/mongo/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ exports.getRepo = async (name) => {
return collection.findOne({ name: { $eq: name } });
};

exports.getRepoByUrl = async (url) => {
const collection = await connect(cnName);
return collection.findOne({ url: { $eq: url}});
Copy link
Member

Choose a reason for hiding this comment

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

return collection.findOne({ org: ..., repo: ..., platform: ... })

Thoughts? We should probably step back a little and define a data model for repository together that will serve us well moving forward and isn't too opinionated. I am not sure about URL as only as this is quite un-atomic in its nature and includes content that we don't need in DB, i.e. https, :// - I feel like it can be broken down but happy to hear otherwise of course.

}

exports.createRepo = async (repo) => {
console.log(`creating new repo ${JSON.stringify(repo)}`);

Expand Down Expand Up @@ -67,10 +72,9 @@ exports.deleteRepo = async (name) => {
await collection.deleteMany({ name: name });
};

exports.isUserPushAllowed = async (name, user) => {
name = name.toLowerCase();
exports.isUserPushAllowed = async (url, user) => {
return new Promise(async (resolve, reject) => {
const repo = await exports.getRepo(name);
const repo = await exports.getRepoByUrl(url);
console.log(repo.users.canPush);
console.log(repo.users.canAuthorise);

Expand Down
31 changes: 31 additions & 0 deletions src/model/Repo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Regex inspector - https://www.debuggex.com/
// eslint-disable-next-line no-useless-escape
const GIT_URL_REGEX = new RegExp("^(https)://(github\.com|gitlab\.com)/([a-zA-Z0-9\-\.]+)/([a-zA-Z0-9\-]+)(\.git)(/)?$");
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed

/** Class representing a Repo. */
class Repo {
url;
protocol;
host;
project;
name;

/**
*
* @param {string} url The url for the repo.
*/
constructor(url) {
const parsedUrl = url?.match(GIT_URL_REGEX);
if (parsedUrl) {
this.url = url;
this.protocol = parsedUrl[1];
Copy link
Member

Choose a reason for hiding this comment

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

Why is this required?

this.host = parsedUrl[2];
this.project = parsedUrl[3];
this.name = parsedUrl[4] + parsedUrl[5]; // repo name + .git
return;
}
throw new Error(`Invalid repo url: "${url}"`);
}
}

exports.Repo = Repo;
1 change: 1 addition & 0 deletions src/model/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exports.Repo = require('./Repo').Repo;
9 changes: 4 additions & 5 deletions src/proxy/actions/Action.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/** Class representing a Push. */
const config = require('../../config');

/**
* Create a new action
Expand Down Expand Up @@ -35,9 +34,9 @@ class Action {
this.type = type;
this.method = method;
this.timestamp = timestamp;
this.project = repo.split('/')[0];
this.repoName = repo.split('/')[1];
this.url = `${config.getProxyUrl()}/${repo}`;
this.project = repo.project;
this.repoName = repo.name;
this.url = repo.url;
Comment on lines +37 to +39
Copy link
Member

Choose a reason for hiding this comment

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

Nice 👍

this.repo = repo;
}

Expand Down Expand Up @@ -97,7 +96,7 @@ class Action {
}

/**
*`
*
*/
setAllowPush() {
this.allowPush = true;
Expand Down
34 changes: 25 additions & 9 deletions src/proxy/processors/pre-processor/parseAction.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const actions = require('../../actions');
const config = require('../../../config');
const { Repo } = require('../../../model');

const exec = async (req) => {
const id = Date.now();
const timestamp = id;
const repoName = getRepoNameFromUrl(req.originalUrl);
const repo = getRepoFromUrlPath(req.originalUrl);

Check warning on line 8 in src/proxy/processors/pre-processor/parseAction.js

View check run for this annotation

Codecov / codecov/patch

src/proxy/processors/pre-processor/parseAction.js#L8

Added line #L8 was not covered by tests
const paths = req.originalUrl.split('/');

let type = 'default';
Expand All @@ -18,20 +20,34 @@
) {
type = 'push';
}
return new actions.Action(id, type, req.method, timestamp, repoName);
return new actions.Action(id, type, req.method, timestamp, repo);

Check warning on line 23 in src/proxy/processors/pre-processor/parseAction.js

View check run for this annotation

Codecov / codecov/patch

src/proxy/processors/pre-processor/parseAction.js#L23

Added line #L23 was not covered by tests
};

const getRepoNameFromUrl = (url) => {
const parts = url.split('/');
for (let i = 0, len = parts.length; i < len; i++) {
const part = parts[i];
if (part.endsWith('.git')) {
const repo = `${parts[i - 1]}/${part}`;
return repo.trim();
// Get repo from URL path
const getRepoFromUrlPath = (urlPath) => {
const urlPathSegments = urlPath.split('/');
const proxyPath = [ urlPathSegments[0], urlPathSegments[1]].join('/');
for (const proxyConfig of config.getProxyConfigList()) {
if (proxyConfig.path == proxyPath) {
// replace '/proxyPath' -> 'proxyUrl' from proxy config
urlPathSegments.shift(); // remove first '/' item
urlPathSegments[0] = proxyConfig.url; // replace proxy path with proxy url
// build repo url without git path
const repoUrlSegments = [];
for (const urlPathSegment of urlPathSegments) {
repoUrlSegments.push(urlPathSegment);
// eslint-disable-next-line no-useless-escape
if (urlPathSegment.match(/[a-zA-Z0-9\-]+\.git/)) {
break;
}
}
const repoUrl = repoUrlSegments.join('/');
return new Repo(repoUrl);
}
}
return 'NOT-FOUND';
};

exec.displayName = 'parseAction.exec';
exports.exec = exec;
exports.getRepoFromUrlPath = getRepoFromUrlPath;
Loading
Loading