diff --git a/config.schema.json b/config.schema.json index 7a8ea1ad..0e56616d 100644 --- a/config.schema.json +++ b/config.schema.json @@ -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": { @@ -70,6 +76,15 @@ } }, "definitions": { + "proxy": { + "type": "object", + "properties": { + "path": { "type": "string"}, + "url": { "type": "string"}, + "enabled": { "type": "boolean" } + }, + "required": ["path", "url", "enabled"] + }, "authorisedRepo": { "type": "object", "properties": { diff --git a/packages/git-proxy-cli/test/testCli.test.js b/packages/git-proxy-cli/test/testCli.test.js index 0474bad8..ea569f37 100644 --- a/packages/git-proxy-cli/test/testCli.test.js +++ b/packages/git-proxy-cli/test/testCli.test.js @@ -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 *** @@ -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 () { @@ -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 () { @@ -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 () { @@ -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 () { @@ -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', @@ -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, @@ -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, @@ -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, diff --git a/packages/git-proxy-cli/test/testCliUtils.js b/packages/git-proxy-cli/test/testCliUtils.js index fb022320..76498b51 100644 --- a/packages/git-proxy-cli/test/testCliUtils.js +++ b/packages/git-proxy-cli/test/testCliUtils.js @@ -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'; @@ -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 diff --git a/proxy.config.json b/proxy.config.json index 02f390a8..f35b7ca0 100644 --- a/proxy.config.json +++ b/proxy.config.json @@ -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 + } + ], "cookieSecret": "cookie secret", "sessionMaxAgeHours": 12, "tempPassword": { diff --git a/src/config/index.js b/src/config/index.js index 1708fada..317d8748 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -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; @@ -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 = () => { @@ -141,7 +141,7 @@ const getCSRFProtection = () => { }; exports.getAPIs = getAPIs; -exports.getProxyUrl = getProxyUrl; +exports.getProxyConfigList = getProxyConfigList; exports.getAuthorisedList = getAuthorisedList; exports.getDatabase = getDatabase; exports.logConfiguration = logConfiguration; diff --git a/src/db/file/pushes.js b/src/db/file/pushes.js index cbc79244..d5401142 100644 --- a/src/db/file/pushes.js +++ b/src/db/file/pushes.js @@ -94,8 +94,8 @@ const cancel = async (id, logger) => { 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); if (isAllowed) { resolve(true); diff --git a/src/db/file/repo.js b/src/db/file/repo.js index e6f7dd89..787684eb 100644 --- a/src/db/file/repo.js +++ b/src/db/file/repo.js @@ -34,6 +34,22 @@ exports.getRepo = async (name) => { }); }; +exports.getRepoByUrl = async (url) => { + return new Promise((resolve, reject) => { + db.findOne({ url: url }, (err, doc) => { + if (err) { + reject(err); + } else { + if (!doc) { + resolve(null); + } else { + resolve(doc); + } + } + }); + }); +}; + exports.createRepo = async (repo) => { repo.users = { canPush: [], @@ -140,10 +156,9 @@ exports.deleteRepo = async (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); diff --git a/src/db/mongo/pushes.js b/src/db/mongo/pushes.js index 0927a6db..e91e5037 100644 --- a/src/db/mongo/pushes.js +++ b/src/db/mongo/pushes.js @@ -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); if (isAllowed) { resolve(true); diff --git a/src/db/mongo/repo.js b/src/db/mongo/repo.js index 1c604069..39fff698 100644 --- a/src/db/mongo/repo.js +++ b/src/db/mongo/repo.js @@ -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}}); +} + exports.createRepo = async (repo) => { console.log(`creating new repo ${JSON.stringify(repo)}`); @@ -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); diff --git a/src/model/Repo.js b/src/model/Repo.js new file mode 100644 index 00000000..89212e5f --- /dev/null +++ b/src/model/Repo.js @@ -0,0 +1,30 @@ +// Regex inspector - https://www.debuggex.com/ +const GIT_URL_REGEX = new RegExp("^(https)://(github\\.com|gitlab\\.com)/([a-zA-Z0-9\\-\\.]+)/([a-zA-Z0-9\\-]+)(\\.git)(/)?$"); + +/** 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]; + 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; \ No newline at end of file diff --git a/src/model/index.js b/src/model/index.js new file mode 100644 index 00000000..83545501 --- /dev/null +++ b/src/model/index.js @@ -0,0 +1 @@ +exports.Repo = require('./Repo').Repo; diff --git a/src/proxy/actions/Action.js b/src/proxy/actions/Action.js index 50b0e8fa..c9ceb065 100644 --- a/src/proxy/actions/Action.js +++ b/src/proxy/actions/Action.js @@ -1,5 +1,4 @@ /** Class representing a Push. */ -const config = require('../../config'); /** * Create a new action @@ -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; this.repo = repo; } @@ -97,7 +96,7 @@ class Action { } /** - *` + * */ setAllowPush() { this.allowPush = true; diff --git a/src/proxy/processors/pre-processor/parseAction.js b/src/proxy/processors/pre-processor/parseAction.js index b10f0372..16c15472 100644 --- a/src/proxy/processors/pre-processor/parseAction.js +++ b/src/proxy/processors/pre-processor/parseAction.js @@ -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); const paths = req.originalUrl.split('/'); let type = 'default'; @@ -18,16 +20,29 @@ const exec = async (req) => { ) { type = 'push'; } - return new actions.Action(id, type, req.method, timestamp, repoName); + return new actions.Action(id, type, req.method, timestamp, repo); }; -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'; @@ -35,3 +50,4 @@ const getRepoNameFromUrl = (url) => { exec.displayName = 'parseAction.exec'; exports.exec = exec; +exports.getRepoFromUrlPath = getRepoFromUrlPath; diff --git a/src/proxy/processors/push-action/checkRepoInAuthorisedList.js b/src/proxy/processors/push-action/checkRepoInAuthorisedList.js index e43a1d20..26664192 100644 --- a/src/proxy/processors/push-action/checkRepoInAuthorisedList.js +++ b/src/proxy/processors/push-action/checkRepoInAuthorisedList.js @@ -9,8 +9,8 @@ const exec = async (req, action, authorisedList = db.getRepos) => { console.log(list); const found = list.find((x) => { - const targetName = action.repo.replace('.git', '').toLowerCase(); - const allowedName = `${x.project}/${x.name}`.replace('.git', '').toLowerCase(); + const targetName = action.repo.url.toLowerCase(); + const allowedName = x.url.toLowerCase(); console.log(`${targetName} = ${allowedName}`); return targetName === allowedName; }); @@ -20,15 +20,15 @@ const exec = async (req, action, authorisedList = db.getRepos) => { if (!found) { console.log('not found'); step.error = true; - step.log(`repo ${action.repo} is not in the authorisedList, ending`); + step.log(`repo '${action.repo.url}' is not in the authorisedList, ending`); console.log('setting error'); - step.setError(`Rejecting repo ${action.repo} not in the authorisedList`); + step.setError(`Rejecting repo '${action.repo.url}' not in the authorisedList`); action.addStep(step); return action; } console.log('found'); - step.log(`repo ${action.repo} is in the authorisedList`); + step.log(`repo '${action.repo.url}' is in the authorisedList`); action.addStep(step); return action; }; diff --git a/src/proxy/processors/push-action/checkUserPushPermission.js b/src/proxy/processors/push-action/checkUserPushPermission.js index 3daff825..fd1fc341 100644 --- a/src/proxy/processors/push-action/checkUserPushPermission.js +++ b/src/proxy/processors/push-action/checkUserPushPermission.js @@ -5,7 +5,7 @@ const db = require('../../../db'); const exec = async (req, action) => { const step = new Step('checkUserPushPermission'); - const repoName = action.repo.split('/')[1].replace('.git', ''); + const repoUrl = action.repo.url; let isUserAllowed = false; let user = action.user; @@ -16,28 +16,28 @@ const exec = async (req, action) => { if (list.length == 1) { user = list[0].username; - isUserAllowed = await db.isUserPushAllowed(repoName, user); + isUserAllowed = await db.isUserPushAllowed(repoUrl, user); } - console.log(`User ${user} permission on Repo ${repoName} : ${isUserAllowed}`); + console.log(`User '${user}' permission on Repo '${repoUrl}' : '${isUserAllowed}'`); if (!isUserAllowed) { console.log('User not allowed to Push'); step.error = true; - step.log(`User ${user} is not allowed to push on repo ${action.repo}, ending`); + step.log(`User '${user}' is not allowed to push on repo '${action.repo.url}', ending`); console.log('setting error'); step.setError( - `Rejecting push as user ${action.user} ` + + `Rejecting push as user '${action.user}' ` + `is not allowed to push on repo ` + - `${action.repo}`, + `'${action.repo.url}'`, ); action.addStep(step); return action; } - step.log(`User ${user} is allowed to push on repo ${action.repo}`); + step.log(`User '${user}' is allowed to push on repo '${action.repo.url}'`); action.addStep(step); return action; }; diff --git a/src/proxy/routes/index.js b/src/proxy/routes/index.js index 50a5d53c..9df9f378 100644 --- a/src/proxy/routes/index.js +++ b/src/proxy/routes/index.js @@ -5,22 +5,28 @@ const chain = require('../chain'); const config = require('../../config'); /** - * For a given Git HTTP request destined for a GitHub repo, - * remove the GitHub specific components of the URL. - * @param {string} url URL path of the request - * @return {string} Modified path which removes the {owner}/{repo} parts + * Get git path from URL path. + * @param {string} urlPath URL path in GitLab or GitHub format. + * @return {string} The git path or undefined if given url path is invalid. */ -const stripGitHubFromGitPath = (url) => { - const parts = url.split('/'); - // url = '/{owner}/{repo}.git/{git-path}' - // url.split('/') = ['', '{owner}', '{repo}.git', '{git-path}'] - if (parts.length !== 4 && parts.length !== 5) { - console.error('unexpected url received: ', url); - return undefined; +const getGitPathFromUrlPath = (urlPath) => { + // urlPath = '/{namespace}/{repo}.git/{git-path}' + // where {namespace} can be a path structure with multiple path segments + // GitLab -> https://docs.gitlab.com/ee/user/namespace/index.html + // GitHub -> https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories + const gitPathSegments = ['']; + let repoSegmentFound = false; + for (const urlPathSegment of urlPath.split('/')) { + if (repoSegmentFound) { + gitPathSegments.push(urlPathSegment); + } + // eslint-disable-next-line no-useless-escape + if (urlPathSegment.match(/[a-zA-Z0-9\-]+\.git/)) { + repoSegmentFound = true; + } } - parts.splice(1, 2); // remove the {owner} and {repo} from the array - return parts.join('/'); -}; + return repoSegmentFound ? gitPathSegments.join('/') : undefined; +} /** * Check whether an HTTP request has the expected properties of a @@ -45,84 +51,97 @@ const validGitRequest = (url, headers) => { return false; }; -router.use( - '/', - proxy(config.getProxyUrl(), { - preserveHostHdr: false, - filter: async function (req, res) { - try { - console.log('request url: ', req.url); - console.log('host: ', req.headers.host); - console.log('user-agent: ', req.headers['user-agent']); - const gitPath = stripGitHubFromGitPath(req.url); - if (gitPath === undefined || !validGitRequest(gitPath, req.headers)) { - res.status(400).send('Invalid request received'); - return false; - } - if (req.body && req.body.length) { - req.rawBody = req.body.toString('utf8'); - } - - const action = await chain.exec(req, res); - console.log('action processed'); - - if (action.error || action.blocked) { - res.set('content-type', 'application/x-git-receive-pack-result'); - res.set('transfer-encoding', 'chunked'); - res.set('expires', 'Fri, 01 Jan 1980 00:00:00 GMT'); - res.set('pragma', 'no-cache'); - res.set('cache-control', 'no-cache, max-age=0, must-revalidate'); - res.set('vary', 'Accept-Encoding'); - res.set('x-frame-options', 'DENY'); - res.set('connection', 'close'); - - let message; - - if (action.error) { - message = action.errorMessage; - console.error(message); - } - if (action.blocked) { - message = action.blockedMessage; - } - - const packetMessage = handleMessage(message); - - console.log(req.headers); - - res.status(200).send(packetMessage); - - return false; - } - - return true; - } catch (e) { - console.error(e); - return false; +const proxyFilter = async function (req, res) { + try { + console.log('request url: ', req.url); + console.log('host: ', req.headers.host); + console.log('user-agent: ', req.headers['user-agent']); + const gitPath = getGitPathFromUrlPath(req.url); + if (gitPath === undefined || !validGitRequest(gitPath, req.headers)) { + res.status(400).send('Invalid request received'); + return false; + } + if (req.body && req.body.length) { + req.rawBody = req.body.toString('utf8'); + } + + const action = await chain.exec(req, res); + console.log('action processed'); + + if (action.error || action.blocked) { + res.set('content-type', 'application/x-git-receive-pack-result'); + res.set('transfer-encoding', 'chunked'); + res.set('expires', 'Fri, 01 Jan 1980 00:00:00 GMT'); + res.set('pragma', 'no-cache'); + res.set('cache-control', 'no-cache, max-age=0, must-revalidate'); + res.set('vary', 'Accept-Encoding'); + res.set('x-frame-options', 'DENY'); + res.set('connection', 'close'); + + let message; + + if (action.error) { + message = action.errorMessage; + console.error(message); } - }, - proxyReqPathResolver: (req) => { - const url = config.getProxyUrl() + req.originalUrl; - console.log('Sending request to ' + url); - return url; - }, - proxyReqOptDecorator: function (proxyReqOpts, srcReq) { - return proxyReqOpts; - }, - - proxyReqBodyDecorator: function (bodyContent, srcReq) { - if (srcReq.method === 'GET') { - return ''; + if (action.blocked) { + message = action.blockedMessage; } - return bodyContent; - }, - proxyErrorHandler: function (err, res, next) { - console.log(`ERROR=${err}`); - next(err); - }, - }), -); + const packetMessage = handleMessage(message); + + console.log(req.headers); + + res.status(200).send(packetMessage); + + return false; + } + + return true; + } catch (e) { + console.error(e); + return false; + } +}; + +const proxyReqOptDecorator = function (proxyReqOpts, srcReq) { + return proxyReqOpts; +}; + +const proxyReqBodyDecorator = function (bodyContent, srcReq) { + if (srcReq.method === 'GET') { + return ''; + } + return bodyContent; +}; + +const proxyErrorHandler = function (err, res, next) { + console.log(`ERROR=${err}`); + next(err); +}; + +for (const proxyConfig of config.getProxyConfigList()) { + if (proxyConfig.enabled) { + const proxyReqPathResolver = (req) => { + const url = req.originalUrl.replace(proxyConfig.path, proxyConfig.url); + console.log('Sending request to ' + url); + return url; + }; + const proxyOptions = { + preserveHostHdr: false, + filter: proxyFilter, + proxyReqPathResolver: proxyReqPathResolver, + proxyReqOptDecorator: proxyReqOptDecorator, + proxyReqBodyDecorator: proxyReqBodyDecorator, + proxyErrorHandler: proxyErrorHandler, + }; + router.use( + proxyConfig.path, + proxy(proxyConfig.url, proxyOptions) + ); + console.log(`Proxy route: ${proxyConfig.path} -> ${proxyConfig.url}`); + } +} const handleMessage = (message) => { const errorMessage = `\t${message}`; @@ -137,5 +156,5 @@ module.exports = { router, handleMessage, validGitRequest, - stripGitHubFromGitPath, + getGitPathFromUrlPath, }; diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.jsx b/src/ui/views/OpenPushRequests/components/PushesTable.jsx index a01f169e..8e6bc9d8 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.jsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.jsx @@ -58,7 +58,8 @@ export default function PushesTable(props) { {[...data].reverse().map((row) => { - const repoFullName = row.repo.replace('.git', ''); + const repoFullName = row.repo.name.replace('.git', ''); + const repoBaseUrl = row.repo.url.replace('.git', ''); const repoBranch = row.branch.replace('refs/heads/', ''); return ( @@ -69,13 +70,13 @@ export default function PushesTable(props) { .toString()} - + {repoFullName} @@ -84,7 +85,7 @@ export default function PushesTable(props) { diff --git a/src/ui/views/PushDetails/PushDetails.jsx b/src/ui/views/PushDetails/PushDetails.jsx index 6f02d717..25b340fa 100644 --- a/src/ui/views/PushDetails/PushDetails.jsx +++ b/src/ui/views/PushDetails/PushDetails.jsx @@ -93,7 +93,8 @@ export default function Dashboard() { }; } - const repoFullName = data.repo.replace('.git', ''); + const repoFullName = data.repo.name.replace('.git', ''); + const repoBaseUrl = data.repo.url.replace('.git', ''); const repoBranch = data.branch.replace('refs/heads/', ''); const generateIcon = (title) => { @@ -225,7 +226,7 @@ export default function Dashboard() {

Remote Head

@@ -237,7 +238,7 @@ export default function Dashboard() {

Commit SHA

@@ -248,7 +249,7 @@ export default function Dashboard() {

Repository

- + {repoFullName}

@@ -257,7 +258,7 @@ export default function Dashboard() {

Branch

diff --git a/src/ui/views/RepoList/Components/RepoOverview.jsx b/src/ui/views/RepoList/Components/RepoOverview.jsx index 772e8163..e22aed4b 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.jsx +++ b/src/ui/views/RepoList/Components/RepoOverview.jsx @@ -613,7 +613,7 @@ export default function Repositories(props) {

- {props.data.project}/{props.data.name} + {props.data.url} {github.parent && ( diff --git a/test/addRepoTest.test.js b/test/addRepoTest.test.js index 04983f63..b254e2a6 100644 --- a/test/addRepoTest.test.js +++ b/test/addRepoTest.test.js @@ -176,12 +176,12 @@ describe('add new repo', async () => { .send({ username: 'u2' }); res.should.have.status(200); - const isAllowed = await db.isUserPushAllowed('test-repo', 'u2'); + const isAllowed = await db.isUserPushAllowed('https://github.com/finos/test-repo.git', 'u2'); expect(isAllowed).to.be.true; }); it('Invalid user push permission on repo', async function () { - const isAllowed = await db.isUserPushAllowed('test-repo', 'test'); + const isAllowed = await db.isUserPushAllowed('https://github.com/finos/test-repo.git', 'test'); expect(isAllowed).to.be.false; }); diff --git a/test/testCheckRepoInAuthList.test.js b/test/testCheckRepoInAuthList.test.js index 19d161c1..a5a6207e 100644 --- a/test/testCheckRepoInAuthList.test.js +++ b/test/testCheckRepoInAuthList.test.js @@ -2,25 +2,27 @@ const chai = require('chai'); const actions = require('../src/proxy/actions/Action'); const processor = require('../src/proxy/processors/push-action/checkRepoInAuthorisedList'); const expect = chai.expect; +const { Repo } = require('../src/model'); const authList = () => { return [ { name: 'repo-is-ok', project: 'thisproject', + url: 'https://github.com/thisproject/repo-is-ok.git' }, ]; }; describe('Check a Repo is in the authorised list', async () => { it('Should set ok=true if repo in whitelist', async () => { - const action = new actions.Action('123', 'type', 'get', 1234, 'thisproject/repo-is-ok'); + const action = new actions.Action('123', 'type', 'get', 1234, new Repo('https://github.com/thisproject/repo-is-ok.git')); const result = await processor.exec(null, action, authList); expect(result.error).to.be.false; }); it('Should set ok=false if not in authorised', async () => { - const action = new actions.Action('123', 'type', 'get', 1234, 'thisproject/repo-is-not-ok'); + const action = new actions.Action('123', 'type', 'get', 1234, new Repo('https://github.com/thisproject/repo-is-not-ok.git')); const result = await processor.exec(null, action, authList); expect(result.error).to.be.true; }); diff --git a/test/testParseAction.js b/test/testParseAction.js new file mode 100644 index 00000000..5f7d9395 --- /dev/null +++ b/test/testParseAction.js @@ -0,0 +1,32 @@ +/* eslint-disable max-len */ +const chai = require('chai'); +const { Repo } = require('../src/model'); +const getRepoFromUrlPath = require('../src/proxy/processors/pre-processor/parseAction').getRepoFromUrlPath; + +chai.should(); + +const expect = chai.expect; + +describe('utility functions for pre-processors ', function () { + it('getRepoFromUrlPath should return NOT-FOUND for invalid repo urls', function () { + const invalidRepoUrls = [ + '', + '/info/refs?service=git-upload-pack', + '/finos/git-proxy-test.git/info/refs?service=git-receive-pack', + '/git-upload-pack', + ]; + for (const invalidRepoUrl of invalidRepoUrls) { + expect(getRepoFromUrlPath(invalidRepoUrl)).to.equal('NOT-FOUND'); + } + }); + + it('getRepoFromUrlPath should return an instance of Repo for invalid repo urls', function () { + const validRepoUrls = [ + '/gitlab.com/finos/git-proxy-test.git/info/refs?service=git-receive-pack', + '/github.com/finos/git-proxy-test.git/git-upload-pack', + ]; + for (const validRepoUrl of validRepoUrls) { + expect(getRepoFromUrlPath(validRepoUrl)).be.instanceOf(Repo); + } + }); +}); diff --git a/test/testRepoModel.js b/test/testRepoModel.js new file mode 100644 index 00000000..07c63271 --- /dev/null +++ b/test/testRepoModel.js @@ -0,0 +1,52 @@ +// Import the dependencies for testing +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const { Repo } = require('../src/model'); + +chai.use(chaiHttp); +chai.should(); +const expect = chai.expect; + +describe('model', async () => { + + before(async function () { + }); + + describe('model: Repo', async function () { + it('valid repo urls should pass', async function () { + const validRepoUrls = [ + "https://github.com/finos/proxy.git", + "https://github.com/finos/git-proxy.git", + "https://gitlab.com/finos/git-proxy.git", + "https://github.com/Citi/citi-ospo.git", + "https://github.com/RBC/finos-traderX.git/", + "https://gitlab.com/me-msagi.dev/git-proxy-test.git/" + ]; + + for (const url of validRepoUrls) { + expect(new Repo(url)).to.be.an("object"); + } + }); + + + it('invalid repo urls should throw error', async function () { + const invalidRepoUrls = [ + null, + "https://github.com/finos/proxy", + "http://github.com/finos/git-proxy.git", + "https://github..com/finos/git-proxy.git", + "https://github.com/finos/git-proxy..git", + "https://bitbucket.com/finos/git-proxy.git", + "https://github/Citi/citi-ospo.git", + "https://github.com/RBC/finos-traderX.git//" + ]; + + for (const url of invalidRepoUrls) { + expect(function(){new Repo(url);}).to.throw(); + } + }); + }); + + after(async function () { + }); +}); diff --git a/test/testRouteFilter.js b/test/testRouteFilter.js index 8a0262d1..e5295f76 100644 --- a/test/testRouteFilter.js +++ b/test/testRouteFilter.js @@ -1,24 +1,24 @@ /* eslint-disable max-len */ const chai = require('chai'); const validGitRequest = require('../src/proxy/routes').validGitRequest; -const stripGitHubFromGitPath = - require('../src/proxy/routes').stripGitHubFromGitPath; +const getGitPathFromUrlPath = + require('../src/proxy/routes').getGitPathFromUrlPath; chai.should(); const expect = chai.expect; describe('url filters for proxying ', function () { - it('stripGitHubFromGitPath should return the sanitized URL with owner & repo removed', function () { + it('getGitPathFromUrlPath should return the sanitized URL with owner & repo removed', function () { expect( - stripGitHubFromGitPath( + getGitPathFromUrlPath( '/octocat/hello-world.git/info/refs?service=git-upload-pack', ), ).eq('/info/refs?service=git-upload-pack'); }); - it('stripGitHubFromGitPath should return undefined if the url', function () { - expect(stripGitHubFromGitPath('/octocat/hello-world')).undefined; + it('getGitPathFromUrl should return undefined if the url', function () { + expect(getGitPathFromUrlPath('/octocat/hello-world')).undefined; }); it('validGitRequest should return true for safe requests on expected URLs', function () { diff --git a/website/docs/quickstart/intercept.mdx b/website/docs/quickstart/intercept.mdx index 7a55c039..73a0939f 100644 --- a/website/docs/quickstart/intercept.mdx +++ b/website/docs/quickstart/intercept.mdx @@ -58,7 +58,7 @@ By default the clone of your repository will communicate with GitHub. To change ```bash git remote -v -git remote set-url origin http://localhost:8000//git-proxy.git +git remote set-url origin http://localhost:8000/github.com//git-proxy.git git remote -v ```