Skip to content

Commit 8e43e7f

Browse files
Copilotfgreinacher
andcommitted
fix: handle Group Access Token permissions correctly when project/group access are null
Co-authored-by: fgreinacher <[email protected]>
1 parent fb88412 commit 8e43e7f

File tree

2 files changed

+178
-13
lines changed

2 files changed

+178
-13
lines changed

lib/verify.js

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,70 @@ export default async (pluginConfig, context) => {
6060
logger.log("Verify GitLab authentication (%s)", gitlabApiUrl);
6161

6262
try {
63-
({
64-
permissions: { project_access: projectAccess, group_access: groupAccess },
65-
} = await got
63+
const projectData = await got
6664
.get(projectApiUrl, {
6765
headers: { "PRIVATE-TOKEN": gitlabToken },
6866
...proxy,
6967
})
70-
.json());
71-
if (
72-
context.options.dryRun &&
73-
!((projectAccess && projectAccess.access_level >= 10) || (groupAccess && groupAccess.access_level >= 10))
74-
) {
75-
errors.push(getError("EGLNOPULLPERMISSION", { projectPath }));
76-
} else if (
77-
!((projectAccess && projectAccess.access_level >= 30) || (groupAccess && groupAccess.access_level >= 30))
78-
) {
79-
errors.push(getError("EGLNOPUSHPERMISSION", { projectPath }));
68+
.json();
69+
70+
({
71+
permissions: { project_access: projectAccess, group_access: groupAccess },
72+
} = projectData);
73+
74+
// Check if we have direct project or group access
75+
const hasDirectPushAccess =
76+
(projectAccess && projectAccess.access_level >= 30) || (groupAccess && groupAccess.access_level >= 30);
77+
const hasDirectPullAccess =
78+
(projectAccess && projectAccess.access_level >= 10) || (groupAccess && groupAccess.access_level >= 10);
79+
80+
// Original logic for direct access
81+
if (context.options.dryRun && !hasDirectPullAccess) {
82+
// If we have explicit low permissions, fail immediately
83+
if ((projectAccess && projectAccess.access_level < 10) || (groupAccess && groupAccess.access_level < 10)) {
84+
errors.push(getError("EGLNOPULLPERMISSION", { projectPath }));
85+
} else if (!projectAccess && !groupAccess) {
86+
// If both are null, try alternative permission verification for pull access
87+
try {
88+
await got
89+
.get(`${projectApiUrl}/repository/branches`, {
90+
headers: { "PRIVATE-TOKEN": gitlabToken },
91+
...proxy,
92+
})
93+
.json();
94+
debug("Verified pull permissions through branches endpoint");
95+
} catch (pullError) {
96+
if (pullError.response && pullError.response.statusCode === 403) {
97+
errors.push(getError("EGLNOPULLPERMISSION", { projectPath }));
98+
} else {
99+
// For other errors, assume permission granted to avoid false negatives
100+
debug("Pull permission check failed with non-403 error, assuming access granted");
101+
}
102+
}
103+
}
104+
} else if (!hasDirectPushAccess) {
105+
// If we have explicit low permissions, fail immediately
106+
if ((projectAccess && projectAccess.access_level < 30) || (groupAccess && groupAccess.access_level < 30)) {
107+
errors.push(getError("EGLNOPUSHPERMISSION", { projectPath }));
108+
} else if (!projectAccess && !groupAccess) {
109+
// If both are null, try alternative permission verification
110+
try {
111+
await got
112+
.get(`${projectApiUrl}/variables`, {
113+
headers: { "PRIVATE-TOKEN": gitlabToken },
114+
...proxy,
115+
})
116+
.json();
117+
debug("Verified push permissions through variables endpoint");
118+
} catch (permissionError) {
119+
if (permissionError.response && permissionError.response.statusCode === 403) {
120+
errors.push(getError("EGLNOPUSHPERMISSION", { projectPath }));
121+
} else {
122+
// For other errors, assume permission granted to avoid false negatives
123+
debug("Push permission check failed with non-403 error, assuming access granted");
124+
}
125+
}
126+
}
80127
}
81128
} catch (error) {
82129
if (error.response && error.response.statusCode === 401) {

test/verify.test.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,3 +988,121 @@ test.serial(
988988
t.true(gitlab.isDone());
989989
}
990990
);
991+
992+
test.serial(
993+
"Throw SemanticReleaseError for group access token with null permissions (shared_with_groups scenario)",
994+
async (t) => {
995+
const owner = "test_user";
996+
const repo = "test_repo";
997+
const env = { GL_TOKEN: "group_access_token" };
998+
const gitlab = authenticate(env)
999+
.get(`/projects/${owner}%2F${repo}`)
1000+
.reply(200, {
1001+
permissions: {
1002+
project_access: null,
1003+
group_access: null,
1004+
},
1005+
shared_with_groups: [
1006+
{
1007+
group_id: 123,
1008+
group_name: "test_group",
1009+
group_full_path: "test_group",
1010+
group_access_level: 40,
1011+
expires_at: null,
1012+
},
1013+
],
1014+
})
1015+
.get(`/projects/${owner}%2F${repo}/variables`)
1016+
.reply(403);
1017+
1018+
const {
1019+
errors: [error, ...errors],
1020+
} = await t.throwsAsync(
1021+
verify(
1022+
{},
1023+
{ env, options: { repositoryUrl: `https://gitlab.com:${owner}/${repo}.git` }, logger: t.context.logger }
1024+
)
1025+
);
1026+
1027+
t.is(errors.length, 0);
1028+
t.is(error.name, "SemanticReleaseError");
1029+
t.is(error.code, "EGLNOPUSHPERMISSION");
1030+
t.true(gitlab.isDone());
1031+
}
1032+
);
1033+
1034+
test.serial(
1035+
"Verify token and repository access with null permissions but successful permission test (group access token)",
1036+
async (t) => {
1037+
const owner = "test_user";
1038+
const repo = "test_repo";
1039+
const env = { GL_TOKEN: "group_access_token" };
1040+
const gitlab = authenticate(env)
1041+
.get(`/projects/${owner}%2F${repo}`)
1042+
.reply(200, {
1043+
permissions: {
1044+
project_access: null,
1045+
group_access: null,
1046+
},
1047+
shared_with_groups: [
1048+
{
1049+
group_id: 123,
1050+
group_name: "test_group",
1051+
group_full_path: "test_group",
1052+
group_access_level: 40,
1053+
expires_at: null,
1054+
},
1055+
],
1056+
})
1057+
.get(`/projects/${owner}%2F${repo}/variables`)
1058+
.reply(200, []);
1059+
1060+
await t.notThrowsAsync(
1061+
verify(
1062+
{},
1063+
{ env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger }
1064+
)
1065+
);
1066+
t.true(gitlab.isDone());
1067+
}
1068+
);
1069+
1070+
test.serial(
1071+
"Throw SemanticReleaseError for insufficient group access level even with shared_with_groups",
1072+
async (t) => {
1073+
const owner = "test_user";
1074+
const repo = "test_repo";
1075+
const env = { GL_TOKEN: "group_access_token" };
1076+
const gitlab = authenticate(env)
1077+
.get(`/projects/${owner}%2F${repo}`)
1078+
.reply(200, {
1079+
permissions: {
1080+
project_access: null,
1081+
group_access: { access_level: 20 }, // Reporter level, insufficient for push
1082+
},
1083+
shared_with_groups: [
1084+
{
1085+
group_id: 123,
1086+
group_name: "test_group",
1087+
group_full_path: "test_group",
1088+
group_access_level: 40,
1089+
expires_at: null,
1090+
},
1091+
],
1092+
});
1093+
1094+
const {
1095+
errors: [error, ...errors],
1096+
} = await t.throwsAsync(
1097+
verify(
1098+
{},
1099+
{ env, options: { repositoryUrl: `https://gitlab.com/${owner}/${repo}.git` }, logger: t.context.logger }
1100+
)
1101+
);
1102+
1103+
t.is(errors.length, 0);
1104+
t.is(error.name, "SemanticReleaseError");
1105+
t.is(error.code, "EGLNOPUSHPERMISSION");
1106+
t.true(gitlab.isDone());
1107+
}
1108+
);

0 commit comments

Comments
 (0)