Skip to content

Commit 519b53d

Browse files
authored
Merge branch 'main' into fix-logout-baseurl
2 parents d4626ce + a15adf1 commit 519b53d

File tree

2 files changed

+102
-65
lines changed

2 files changed

+102
-65
lines changed

src/proxy/routes/index.ts

Lines changed: 68 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,31 @@ import { executeChain } from '../chain';
66
import { processUrlPath, validGitRequest, getAllProxiedHosts } from './helper';
77
import { ProxyOptions } from 'express-http-proxy';
88

9+
enum ActionType {
10+
// eslint-disable-next-line no-unused-vars
11+
ALLOWED = 'Allowed',
12+
// eslint-disable-next-line no-unused-vars
13+
ERROR = 'Error',
14+
// eslint-disable-next-line no-unused-vars
15+
BLOCKED = 'Blocked',
16+
}
17+
918
const logAction = (
1019
url: string,
1120
host: string | null | undefined,
1221
userAgent: string | null | undefined,
13-
errMsg: string | null | undefined,
14-
blockMsg?: string | null | undefined,
22+
type: ActionType,
23+
message?: string,
1524
) => {
16-
let msg = `Action processed: ${!(errMsg || blockMsg) ? 'Allowed' : 'Blocked'}
25+
let msg = `Action processed: ${type}
1726
Request URL: ${url}
1827
Host: ${host}
1928
User-Agent: ${userAgent}`;
20-
if (errMsg) {
21-
msg += `\n Error: ${errMsg}`;
22-
}
23-
if (blockMsg) {
24-
msg += `\n Blocked: ${blockMsg}`;
29+
30+
if (message && type !== ActionType.ALLOWED) {
31+
msg += `\n ${type}: ${message}`;
2532
}
33+
2634
console.log(msg);
2735
};
2836

@@ -34,76 +42,69 @@ const proxyFilter: ProxyOptions['filter'] = async (req, res) => {
3442
urlComponents.gitPath === undefined ||
3543
!validGitRequest(urlComponents.gitPath, req.headers)
3644
) {
37-
logAction(
38-
req.url,
39-
req.headers.host,
40-
req.headers['user-agent'],
41-
'Invalid request received',
42-
null,
43-
);
44-
// return status 200 to ensure that the error message is rendered by the git client
45-
res.status(200).send(handleMessage('Invalid request received'));
45+
const message = 'Invalid request received';
46+
logAction(req.url, req.headers.host, req.headers['user-agent'], ActionType.ERROR, message);
47+
res.status(200).send(handleMessage(message));
4648
return false;
4749
}
4850

4951
const action = await executeChain(req, res);
5052

5153
if (action.error || action.blocked) {
52-
res.set('content-type', 'application/x-git-receive-pack-result');
53-
res.set('expires', 'Fri, 01 Jan 1980 00:00:00 GMT');
54-
res.set('pragma', 'no-cache');
55-
res.set('cache-control', 'no-cache, max-age=0, must-revalidate');
56-
res.set('vary', 'Accept-Encoding');
57-
res.set('x-frame-options', 'DENY');
58-
res.set('connection', 'close');
59-
60-
const packetMessage = handleMessage(action.errorMessage ?? action.blockedMessage ?? '');
61-
62-
logAction(
63-
req.url,
64-
req.headers.host,
65-
req.headers['user-agent'],
66-
action.errorMessage,
67-
action.blockedMessage,
68-
);
69-
// return status 200 to ensure that the error message is rendered by the git client
70-
res.status(200).send(packetMessage);
54+
const message = action.errorMessage ?? action.blockedMessage ?? 'Unknown error';
55+
const type = action.error ? ActionType.ERROR : ActionType.BLOCKED;
56+
57+
logAction(req.url, req.headers.host, req.headers['user-agent'], type, message);
58+
sendErrorResponse(req, res, message);
7159
return false;
7260
}
7361

74-
logAction(
75-
req.url,
76-
req.headers.host,
77-
req.headers['user-agent'],
78-
action.errorMessage,
79-
action.blockedMessage,
80-
);
62+
logAction(req.url, req.headers.host, req.headers['user-agent'], ActionType.ALLOWED);
8163

8264
// this is the only case where we do not respond directly, instead we return true to proxy the request
8365
return true;
8466
} catch (e) {
85-
const packetMessage = handleMessage(`Error occurred in proxy filter function ${e}`);
86-
87-
logAction(
88-
req.url,
89-
req.headers.host,
90-
req.headers['user-agent'],
91-
'Error occurred in proxy filter function: ' + ((e as Error).message ?? e),
92-
null,
93-
);
67+
const message = `Error occurred in proxy filter function ${(e as Error).message ?? e}`;
9468

95-
// return status 200 to ensure that the error message is rendered by the git client
96-
res.status(200).send(packetMessage);
69+
logAction(req.url, req.headers.host, req.headers['user-agent'], ActionType.ERROR, message);
70+
sendErrorResponse(req, res, message);
9771
return false;
9872
}
9973
};
10074

75+
const sendErrorResponse = (req: Request, res: Response, message: string): void => {
76+
// GET requests to /info/refs (used to check refs for many git operations) must use Git protocol error packet format
77+
if (req.method === 'GET' && req.url.includes('/info/refs')) {
78+
res.set('content-type', 'application/x-git-upload-pack-advertisement');
79+
res.status(200).send(handleRefsErrorMessage(message));
80+
return;
81+
}
82+
83+
// Standard git receive-pack response
84+
res.set('content-type', 'application/x-git-receive-pack-result');
85+
res.set('expires', 'Fri, 01 Jan 1980 00:00:00 GMT');
86+
res.set('pragma', 'no-cache');
87+
res.set('cache-control', 'no-cache, max-age=0, must-revalidate');
88+
res.set('vary', 'Accept-Encoding');
89+
res.set('x-frame-options', 'DENY');
90+
res.set('connection', 'close');
91+
92+
res.status(200).send(handleMessage(message));
93+
};
94+
10195
const handleMessage = (message: string): string => {
10296
const body = `\t${message}`;
10397
const len = (6 + Buffer.byteLength(body)).toString(16).padStart(4, '0');
10498
return `${len}\x02${body}\n0000`;
10599
};
106100

101+
const handleRefsErrorMessage = (message: string): string => {
102+
// Git protocol for GET /info/refs error packets: PKT-LINE("ERR" SP explanation-text)
103+
const errorBody = `ERR ${message}`;
104+
const len = (4 + Buffer.byteLength(errorBody)).toString(16).padStart(4, '0');
105+
return `${len}${errorBody}\n0000`;
106+
};
107+
107108
const getRequestPathResolver: (prefix: string) => ProxyOptions['proxyReqPathResolver'] = (
108109
prefix,
109110
) => {
@@ -155,15 +156,10 @@ const teeAndValidate = async (req: Request, res: Response, next: NextFunction) =
155156
(req as any).body = buf;
156157
const verdict = await executeChain(req, res);
157158
if (verdict.error || verdict.blocked) {
158-
const msg = verdict.errorMessage ?? verdict.blockedMessage ?? '';
159+
const message = verdict.errorMessage ?? verdict.blockedMessage ?? 'Unknown error';
160+
const type = verdict.error ? ActionType.ERROR : ActionType.BLOCKED;
159161

160-
logAction(
161-
req.url,
162-
req.headers?.host,
163-
req.headers?.['user-agent'],
164-
verdict.errorMessage,
165-
verdict.blockedMessage,
166-
);
162+
logAction(req.url, req.headers?.host, req.headers?.['user-agent'], type, message);
167163

168164
res
169165
.set({
@@ -176,7 +172,7 @@ const teeAndValidate = async (req: Request, res: Response, next: NextFunction) =
176172
connection: 'close',
177173
})
178174
.status(200) // return status 200 to ensure that the error message is rendered by the git client
179-
.send(handleMessage(msg));
175+
.send(handleMessage(message));
180176
return;
181177
}
182178

@@ -261,4 +257,12 @@ const getRouter = async () => {
261257
return router;
262258
};
263259

264-
export { proxyFilter, getRouter, handleMessage, isPackPost, teeAndValidate, validGitRequest };
260+
export {
261+
proxyFilter,
262+
getRouter,
263+
handleMessage,
264+
handleRefsErrorMessage,
265+
isPackPost,
266+
teeAndValidate,
267+
validGitRequest,
268+
};

test/testProxyRoute.test.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { handleMessage, validGitRequest } = require('../src/proxy/routes');
1+
const { handleMessage, handleRefsErrorMessage, validGitRequest } = require('../src/proxy/routes');
22
const chai = require('chai');
33
const chaiHttp = require('chai-http');
44
chai.use(chaiHttp);
@@ -336,6 +336,39 @@ describe('proxyFilter function', async () => {
336336
const result = await proxyRoutes.proxyFilter(req, res);
337337
expect(result).to.be.true;
338338
});
339+
340+
it('should handle GET /info/refs with blocked action using Git protocol error format', async () => {
341+
const req = {
342+
url: '/proj/repo.git/info/refs?service=git-upload-pack',
343+
method: 'GET',
344+
headers: {
345+
host: 'localhost',
346+
'user-agent': 'git/2.34.1',
347+
},
348+
};
349+
const res = {
350+
set: sinon.spy(),
351+
status: sinon.stub().returnsThis(),
352+
send: sinon.spy(),
353+
};
354+
355+
const actionToReturn = {
356+
blocked: true,
357+
blockedMessage: 'Repository not in authorised list',
358+
};
359+
360+
executeChainStub.returns(actionToReturn);
361+
const result = await proxyRoutes.proxyFilter(req, res);
362+
363+
expect(result).to.be.false;
364+
365+
const expectedPacket = handleRefsErrorMessage('Repository not in authorised list');
366+
367+
expect(res.set.calledWith('content-type', 'application/x-git-upload-pack-advertisement')).to.be
368+
.true;
369+
expect(res.status.calledWith(200)).to.be.true;
370+
expect(res.send.calledWith(expectedPacket)).to.be.true;
371+
});
339372
});
340373

341374
describe('proxy express application', async () => {

0 commit comments

Comments
 (0)