Skip to content

Commit 4c74a3a

Browse files
authored
Merge pull request #1094 from 06kellyjac/trim_dot_git_correctly
fix: only trim trailing .git not any match
2 parents 15c68a3 + 9a4032f commit 4c74a3a

File tree

10 files changed

+178
-20
lines changed

10 files changed

+178
-20
lines changed

src/db/file/pushes.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import fs from 'fs';
22
import _ from 'lodash';
33
import Datastore from '@seald-io/nedb';
44
import { Action } from '../../proxy/actions/Action';
5-
import { toClass } from '../helper';
5+
import { toClass, trimTrailingDotGit } from '../helper';
66
import * as repo from './repo';
77
import { PushQuery } from '../types';
88

@@ -138,7 +138,7 @@ export const canUserCancelPush = async (id: string, user: string) => {
138138
return;
139139
}
140140

141-
const repoName = pushDetail.repoName.replace('.git', '');
141+
const repoName = trimTrailingDotGit(pushDetail.repoName);
142142
const isAllowed = await repo.isUserPushAllowed(repoName, user);
143143

144144
if (isAllowed) {
@@ -156,7 +156,8 @@ export const canUserApproveRejectPush = async (id: string, user: string) => {
156156
resolve(false);
157157
return;
158158
}
159-
const repoName = action.repoName.replace('.git', '');
159+
160+
const repoName = trimTrailingDotGit(action.repoName);
160161
const isAllowed = await repo.canUserApproveRejectPushRepo(repoName, user);
161162

162163
resolve(isAllowed);

src/db/helper.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,21 @@ export const toClass = function (obj: any, proto: any) {
33
obj.__proto__ = proto;
44
return obj;
55
};
6+
7+
export const trimTrailingDotGit = (str: string): string => {
8+
const target = '.git';
9+
if (str.endsWith(target)) {
10+
// extract string from 0 to the end minus the length of target
11+
return str.slice(0, -target.length);
12+
}
13+
return str;
14+
};
15+
16+
export const trimPrefixRefsHeads = (str: string): string => {
17+
const target = 'refs/heads/';
18+
if (str.startsWith(target)) {
19+
// extract string from the end of the target to the end of str
20+
return str.slice(target.length);
21+
}
22+
return str;
23+
};

src/db/mongo/pushes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { connect, findDocuments, findOneDocument } from './helper';
22
import { Action } from '../../proxy/actions';
3-
import { toClass } from '../helper';
3+
import { toClass, trimTrailingDotGit } from '../helper';
44
import * as repo from './repo';
55
import { Push, PushQuery } from '../types';
66

@@ -108,7 +108,7 @@ export const canUserApproveRejectPush = async (id: string, user: string) => {
108108
return;
109109
}
110110

111-
const repoName = action.repoName.replace('.git', '');
111+
const repoName = trimTrailingDotGit(action.repoName);
112112
const isAllowed = await repo.canUserApproveRejectPushRepo(repoName, user);
113113

114114
resolve(isAllowed);
@@ -123,7 +123,7 @@ export const canUserCancelPush = async (id: string, user: string) => {
123123
return;
124124
}
125125

126-
const repoName = pushDetail.repoName.replace('.git', '');
126+
const repoName = trimTrailingDotGit(pushDetail.repoName);
127127
const isAllowed = await repo.isUserPushAllowed(repoName, user);
128128

129129
if (isAllowed) {

src/proxy/processors/push-action/checkRepoInAuthorisedList.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Action, Step } from '../../actions';
22
import { getRepos } from '../../../db';
33
import { Repo } from '../../../db/types';
4+
import { trimTrailingDotGit } from '../../../db/helper';
45

56
// Execute if the repo is approved
67
const exec = async (
@@ -14,8 +15,8 @@ const exec = async (
1415
console.log(list);
1516

1617
const found = list.find((x: Repo) => {
17-
const targetName = action.repo.replace('.git', '').toLowerCase();
18-
const allowedName = `${x.project}/${x.name}`.replace('.git', '').toLowerCase();
18+
const targetName = trimTrailingDotGit(action.repo.toLowerCase());
19+
const allowedName = trimTrailingDotGit(`${x.project}/${x.name}`.toLowerCase());
1920
console.log(`${targetName} = ${allowedName}`);
2021
return targetName === allowedName;
2122
});

src/proxy/processors/push-action/checkUserPushPermission.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import { Action, Step } from '../../actions';
22
import { getUsers, isUserPushAllowed } from '../../../db';
3+
import { trimTrailingDotGit } from '../../../db/helper';
34

45
// Execute if the repo is approved
56
const exec = async (req: any, action: Action): Promise<Action> => {
67
const step = new Step('checkUserPushPermission');
78

8-
const repoName = action.repo.split('/')[1].replace('.git', '');
9+
const repoSplit = trimTrailingDotGit(action.repo.toLowerCase()).split('/');
10+
// we expect there to be exactly one / separating org/repoName
11+
if (repoSplit.length != 2) {
12+
step.setError('Server-side issue extracting repoName');
13+
action.addStep(step);
14+
return action;
15+
}
16+
// pull the 2nd value of the split for repoName
17+
const repoName = repoSplit[1];
918
let isUserAllowed = false;
1019
let user = action.user;
1120

src/ui/views/OpenPushRequests/components/PushesTable.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { KeyboardArrowRight } from '@material-ui/icons';
1616
import Search from '../../../components/Search/Search';
1717
import Pagination from '../../../components/Pagination/Pagination';
1818
import { PushData } from '../../../../types/models';
19+
import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../../db/helper';
1920

2021
interface PushesTableProps {
2122
[key: string]: any;
@@ -100,8 +101,8 @@ const PushesTable: React.FC<PushesTableProps> = (props) => {
100101
</TableHead>
101102
<TableBody>
102103
{[...currentItems].reverse().map((row) => {
103-
const repoFullName = row.repo.replace('.git', '');
104-
const repoBranch = row.branch.replace('refs/heads/', '');
104+
const repoFullName = trimTrailingDotGit(row.repo);
105+
const repoBranch = trimPrefixRefsHeads(row.branch);
105106
const commitTimestamp =
106107
row.commitData[0]?.commitTs || row.commitData[0]?.commitTimestamp;
107108

src/ui/views/PushDetails/PushDetails.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { CheckCircle, Visibility, Cancel, Block } from '@material-ui/icons';
2323
import Snackbar from '@material-ui/core/Snackbar';
2424
import Tooltip from '@material-ui/core/Tooltip';
2525
import { PushData } from '../../../types/models';
26+
import { trimPrefixRefsHeads, trimTrailingDotGit } from '../../../db/helper';
2627

2728
const Dashboard: React.FC = () => {
2829
const { id } = useParams<{ id: string }>();
@@ -105,8 +106,8 @@ const Dashboard: React.FC = () => {
105106
};
106107
}
107108

108-
const repoFullName = data.repo.replace('.git', '');
109-
const repoBranch = data.branch.replace('refs/heads/', '');
109+
const repoFullName = trimTrailingDotGit(data.repo);
110+
const repoBranch = trimPrefixRefsHeads(data.branch);
110111

111112
const generateIcon = (title: string) => {
112113
switch (title) {

src/ui/views/RepoDetails/RepoDetails.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { useNavigate, useParams } from 'react-router-dom';
2020
import { UserContext } from '../../../context';
2121
import CodeActionButton from '../../components/CustomButtons/CodeActionButton';
2222
import { Box } from '@material-ui/core';
23+
import { trimTrailingDotGit } from '../../../db/helper';
2324

2425
interface RepoData {
2526
project: string;
@@ -148,7 +149,7 @@ const RepoDetails: React.FC = () => {
148149
<FormLabel component='legend'>URL</FormLabel>
149150
<h4>
150151
<a href={data.url} target='_blank' rel='noopener noreferrer'>
151-
{data.url.replace('.git', '')}
152+
{trimTrailingDotGit(data.url)}
152153
</a>
153154
</h4>
154155
</GridItem>

test/db-helper.test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const { expect } = require('chai');
2+
const { trimPrefixRefsHeads, trimTrailingDotGit } = require('../src/db/helper');
3+
4+
describe('db helpers', () => {
5+
describe('trimPrefixRefsHeads', () => {
6+
it('removes `refs/heads/`', () => {
7+
const res = trimPrefixRefsHeads('refs/heads/test');
8+
expect(res).to.equal('test');
9+
});
10+
11+
it('removes only one `refs/heads/`', () => {
12+
const res = trimPrefixRefsHeads('refs/heads/refs/heads/');
13+
expect(res).to.equal('refs/heads/');
14+
});
15+
16+
it('removes only the first `refs/heads/`', () => {
17+
const res = trimPrefixRefsHeads('refs/heads/middle/refs/heads/end/refs/heads/');
18+
expect(res).to.equal('middle/refs/heads/end/refs/heads/');
19+
});
20+
21+
it('handles empty string', () => {
22+
const res = trimPrefixRefsHeads('');
23+
expect(res).to.equal('');
24+
});
25+
26+
it("doesn't remove `refs/heads`", () => {
27+
const res = trimPrefixRefsHeads('refs/headstest');
28+
expect(res).to.equal('refs/headstest');
29+
});
30+
31+
it("doesn't remove `/refs/heads/`", () => {
32+
const res = trimPrefixRefsHeads('/refs/heads/test');
33+
expect(res).to.equal('/refs/heads/test');
34+
});
35+
});
36+
37+
describe('trimTrailingDotGit', () => {
38+
it('removes `.git`', () => {
39+
const res = trimTrailingDotGit('test.git');
40+
expect(res).to.equal('test');
41+
});
42+
43+
it('removes only one `.git`', () => {
44+
const res = trimTrailingDotGit('.git.git');
45+
expect(res).to.equal('.git');
46+
});
47+
48+
it('removes only the last `.git`', () => {
49+
const res = trimTrailingDotGit('.git-middle.git-end.git');
50+
expect(res).to.equal('.git-middle.git-end');
51+
});
52+
53+
it('handles empty string', () => {
54+
const res = trimTrailingDotGit('');
55+
expect(res).to.equal('');
56+
});
57+
58+
it("doesn't remove just `git`", () => {
59+
const res = trimTrailingDotGit('testgit');
60+
expect(res).to.equal('testgit');
61+
});
62+
});
63+
});

test/testDb.test.js

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// This test needs to run first
22
const chai = require('chai');
33
const db = require('../src/db');
4+
const { trimTrailingDotGit } = require('../src/db/helper');
45

56
const { expect } = chai;
67

@@ -46,6 +47,21 @@ const TEST_PUSH = {
4647
attestation: null,
4748
};
4849

50+
const TEST_REPO_DOT_GIT = {
51+
project: 'finos',
52+
name: 'db.git-test-repo',
53+
url: 'https://github.com/finos/db.git-test-repo.git',
54+
};
55+
56+
// the same as TEST_PUSH but with .git somewhere valid within the name
57+
// to ensure a global replace isn't done when trimming, just to the end
58+
const TEST_PUSH_DOT_GIT = {
59+
...TEST_PUSH,
60+
repoName: 'db.git-test-repo.git',
61+
url: 'https://github.com/finos/db.git-test-repo.git',
62+
repo: 'finos/db.git-test-repo.git',
63+
};
64+
4965
/**
5066
* Clean up response data from the DB by removing an extraneous properties,
5167
* allowing comparison with expect.
@@ -563,7 +579,7 @@ describe('Database clients', async () => {
563579

564580
it('should be able to check if a user can cancel push', async function () {
565581
let threwError = false;
566-
const repoName = TEST_PUSH.repoName.replace('.git', '');
582+
const repoName = trimTrailingDotGit(TEST_PUSH.repoName);
567583
try {
568584
// push does not exist yet, should return false
569585
let allowed = await db.canUserCancelPush(TEST_PUSH.id, TEST_USER.username);
@@ -588,34 +604,81 @@ describe('Database clients', async () => {
588604
});
589605

590606
it('should be able to check if a user can approve/reject push', async function () {
591-
let threwError = false;
592-
const repoName = TEST_PUSH.repoName.replace('.git', '');
607+
let allowed = undefined;
608+
const repoName = trimTrailingDotGit(TEST_PUSH.repoName);
609+
593610
try {
594611
// push does not exist yet, should return false
595-
let allowed = await db.canUserApproveRejectPush(TEST_PUSH.id, TEST_USER.username);
612+
allowed = await db.canUserApproveRejectPush(TEST_PUSH.id, TEST_USER.username);
596613
expect(allowed).to.be.false;
614+
} catch (e) {
615+
expect.fail(e);
616+
}
597617

618+
try {
598619
// create the push - user should already exist and not authorised to push
599620
await db.writeAudit(TEST_PUSH);
600621
allowed = await db.canUserApproveRejectPush(TEST_PUSH.id, TEST_USER.username);
601622
expect(allowed).to.be.false;
623+
} catch (e) {
624+
expect.fail(e);
625+
}
602626

627+
try {
603628
// authorise user and recheck
604629
await db.addUserCanAuthorise(repoName, TEST_USER.username);
605630
allowed = await db.canUserApproveRejectPush(TEST_PUSH.id, TEST_USER.username);
606631
expect(allowed).to.be.true;
607632
} catch (e) {
608-
threwError = true;
633+
expect.fail(e);
609634
}
610-
expect(threwError).to.be.false;
635+
611636
// clean up
612637
await db.deletePush(TEST_PUSH.id);
613638
await db.removeUserCanAuthorise(repoName, TEST_USER.username);
614639
});
615640

641+
it('should be able to check if a user can approve/reject push including .git within the repo name', async function () {
642+
let allowed = undefined;
643+
const repoName = trimTrailingDotGit(TEST_PUSH_DOT_GIT.repoName);
644+
645+
await db.createRepo(TEST_REPO_DOT_GIT);
646+
try {
647+
// push does not exist yet, should return false
648+
allowed = await db.canUserApproveRejectPush(TEST_PUSH_DOT_GIT.id, TEST_USER.username);
649+
expect(allowed).to.be.false;
650+
} catch (e) {
651+
expect.fail(e);
652+
}
653+
654+
try {
655+
// create the push - user should already exist and not authorised to push
656+
await db.writeAudit(TEST_PUSH_DOT_GIT);
657+
allowed = await db.canUserApproveRejectPush(TEST_PUSH_DOT_GIT.id, TEST_USER.username);
658+
expect(allowed).to.be.false;
659+
} catch (e) {
660+
expect.fail(e);
661+
}
662+
663+
try {
664+
// authorise user and recheck
665+
await db.addUserCanAuthorise(repoName, TEST_USER.username);
666+
allowed = await db.canUserApproveRejectPush(TEST_PUSH_DOT_GIT.id, TEST_USER.username);
667+
expect(allowed).to.be.true;
668+
} catch (e) {
669+
expect.fail(e);
670+
}
671+
672+
// clean up
673+
await db.deletePush(TEST_PUSH_DOT_GIT.id);
674+
await db.removeUserCanAuthorise(repoName, TEST_USER.username);
675+
});
676+
616677
after(async function () {
617678
await db.deleteRepo(TEST_REPO.name);
679+
await db.deleteRepo(TEST_REPO_DOT_GIT.name);
618680
await db.deleteUser(TEST_USER.username);
619681
await db.deletePush(TEST_PUSH.id);
682+
await db.deletePush(TEST_PUSH_DOT_GIT.id);
620683
});
621684
});

0 commit comments

Comments
 (0)