@codefresh-io/cf-git-providers 0.7.2 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/helpers/index.d.ts +3 -0
- package/lib/helpers/index.d.ts.map +1 -1
- package/lib/helpers/index.js +19 -1
- package/lib/helpers/index.js.map +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/providers/bitbucket-server.d.ts +2 -0
- package/lib/providers/bitbucket-server.d.ts.map +1 -1
- package/lib/providers/bitbucket-server.js +4 -7
- package/lib/providers/bitbucket-server.js.map +1 -1
- package/lib/providers/bitbucket.d.ts +2 -0
- package/lib/providers/bitbucket.d.ts.map +1 -1
- package/lib/providers/bitbucket.js +4 -7
- package/lib/providers/bitbucket.js.map +1 -1
- package/lib/providers/gerrit.d.ts +151 -0
- package/lib/providers/gerrit.d.ts.map +1 -0
- package/lib/providers/gerrit.js +729 -0
- package/lib/providers/gerrit.js.map +1 -0
- package/lib/providers/github.d.ts +1 -0
- package/lib/providers/github.d.ts.map +1 -1
- package/lib/providers/github.js +3 -0
- package/lib/providers/github.js.map +1 -1
- package/lib/providers/gitlab.d.ts +2 -0
- package/lib/providers/gitlab.d.ts.map +1 -1
- package/lib/providers/gitlab.js +4 -7
- package/lib/providers/gitlab.js.map +1 -1
- package/lib/providers/types.d.ts +3 -1
- package/lib/providers/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/* eslint-disable max-lines */
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
const https_1 = require("https");
|
|
6
|
+
const types_1 = require("./types");
|
|
7
|
+
const helpers_1 = require("../helpers");
|
|
8
|
+
const request_retry_1 = require("../helpers/request-retry");
|
|
9
|
+
const url_1 = require("url");
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
11
|
+
const wildcard = require('wildcard');
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
13
|
+
const CFError = require('cf-errors');
|
|
14
|
+
const logger = helpers_1.createNewLogger('codefresh:infra:git-providers:gerrit');
|
|
15
|
+
const GERRIT_CLOUD_HOST = 'https://gerrit.googlesource.com/';
|
|
16
|
+
const PROJECT_STATE = {
|
|
17
|
+
active: 'ACTIVE',
|
|
18
|
+
readOnly: 'READ_ONLY',
|
|
19
|
+
hidden: 'HIDDEN',
|
|
20
|
+
};
|
|
21
|
+
const PATH_PRIORITY = {
|
|
22
|
+
specific: 1,
|
|
23
|
+
wildcardDoubleStar: 1000,
|
|
24
|
+
wildcardStar: 100,
|
|
25
|
+
wildcardQuestion: 10,
|
|
26
|
+
};
|
|
27
|
+
const LIMIT_PER_PAGE = 500;
|
|
28
|
+
const ApiVersions = {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
30
|
+
V1: 'a/',
|
|
31
|
+
};
|
|
32
|
+
const RESPONSE_PREFIX = ')]}\'\n';
|
|
33
|
+
const defaultRepoPermission = {
|
|
34
|
+
read: false,
|
|
35
|
+
write: false,
|
|
36
|
+
};
|
|
37
|
+
const pluginsJsonFormat = { format: 'JSON' };
|
|
38
|
+
const scopesMap = {
|
|
39
|
+
read: 'repo_read',
|
|
40
|
+
write: 'repo_write',
|
|
41
|
+
create: 'repo_create',
|
|
42
|
+
admin: 'admin_repo_hook',
|
|
43
|
+
};
|
|
44
|
+
const ACCESS_ACTION = {
|
|
45
|
+
allow: 'ALLOW',
|
|
46
|
+
deny: 'DENY',
|
|
47
|
+
block: 'BLOCK',
|
|
48
|
+
unknown: 'UNKNOWN',
|
|
49
|
+
};
|
|
50
|
+
class Gerrit {
|
|
51
|
+
constructor(opts) {
|
|
52
|
+
this._getWildCardPriority = (ref) => {
|
|
53
|
+
let priority = 0;
|
|
54
|
+
let numNonWildcard = 0;
|
|
55
|
+
let numSingleWildcard = 0;
|
|
56
|
+
let numDoubleWildcard = 0;
|
|
57
|
+
ref.split('/').forEach((segment) => {
|
|
58
|
+
if (segment === '**') {
|
|
59
|
+
priority += PATH_PRIORITY.wildcardDoubleStar;
|
|
60
|
+
numDoubleWildcard++;
|
|
61
|
+
}
|
|
62
|
+
else if (segment === '*') {
|
|
63
|
+
priority += PATH_PRIORITY.wildcardStar;
|
|
64
|
+
numSingleWildcard++;
|
|
65
|
+
}
|
|
66
|
+
else if (segment === '?') {
|
|
67
|
+
priority += PATH_PRIORITY.wildcardQuestion;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
numNonWildcard++;
|
|
71
|
+
priority += PATH_PRIORITY.specific;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return [numNonWildcard, numSingleWildcard, numDoubleWildcard, priority];
|
|
75
|
+
};
|
|
76
|
+
this._getSortedPathByPriority = (refsScopesPermission) => {
|
|
77
|
+
return Object.keys(refsScopesPermission).sort((a, b) => {
|
|
78
|
+
const [aNonWildcard, aSingleWildcard, aDoubleWildcard, aPriority] = this._getWildCardPriority(a);
|
|
79
|
+
const [bNonWildcard, bSingleWildcard, bDoubleWildcard, bPriority] = this._getWildCardPriority(b);
|
|
80
|
+
return bNonWildcard - aNonWildcard || aSingleWildcard - bSingleWildcard || aDoubleWildcard - bDoubleWildcard || bPriority - aPriority;
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
this._getFinalBranchPermission = (refs, opts) => {
|
|
84
|
+
if ((!opts.childRefs || lodash_1.isEmpty(opts.childRefs)) && lodash_1.isEmpty(refs)) {
|
|
85
|
+
return {
|
|
86
|
+
read: true,
|
|
87
|
+
write: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
let permissions;
|
|
91
|
+
// if current repo (parent) doesnt have refs but his child has - get final refs permission from child
|
|
92
|
+
if (lodash_1.isEmpty(refs)) {
|
|
93
|
+
permissions = opts.childRefs ? this._getRefsScopesPermission(opts.childRefs) : {};
|
|
94
|
+
}
|
|
95
|
+
else { // both matching refs and childRefs exist
|
|
96
|
+
// calculate repo refs permission for user
|
|
97
|
+
const currentRefsPermissions = this._getRepoRefsPermission(refs, opts.user, opts.groups);
|
|
98
|
+
// merge permission of current repo permission and child
|
|
99
|
+
const permissionsRefs = this._getInheritRepoRefsPermission(currentRefsPermissions, opts.childRefs);
|
|
100
|
+
// get final refs permission
|
|
101
|
+
permissions = this._getRefsScopesPermission(permissionsRefs);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
105
|
+
read: permissions.read ? this._mapActionToBool(permissions.read) : true,
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
107
|
+
write: permissions.push ? this._mapActionToBool(permissions.push) : true,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
this._getRepoParentAccessInfo = (refs, parent, opts) => {
|
|
111
|
+
const parentOpts = { ...opts };
|
|
112
|
+
if (lodash_1.isEmpty(refs)) {
|
|
113
|
+
parentOpts.repo = parent.id;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const currentPermissions = this._getRepoRefsPermission(refs, opts.user, opts.groups);
|
|
117
|
+
const permissionsRefs = this._getInheritRepoRefsPermission(currentPermissions, opts.childRefs);
|
|
118
|
+
parentOpts.repo = parent.id;
|
|
119
|
+
parentOpts.childRefs = permissionsRefs;
|
|
120
|
+
}
|
|
121
|
+
return parentOpts;
|
|
122
|
+
};
|
|
123
|
+
const url = new url_1.URL(opts.apiURL || opts.apiUrl || GERRIT_CLOUD_HOST);
|
|
124
|
+
if (!opts.username) {
|
|
125
|
+
throw new Error(`Gerrit provider is using Basic authorization, please provide username`);
|
|
126
|
+
}
|
|
127
|
+
this.baseUrl = `${url.protocol}//${url.host}`;
|
|
128
|
+
if (!this.baseUrl.endsWith('/')) {
|
|
129
|
+
this.baseUrl = `${this.baseUrl}/`;
|
|
130
|
+
}
|
|
131
|
+
this.timeout = opts.timeout || 10000;
|
|
132
|
+
this.auth = {
|
|
133
|
+
username: opts.username,
|
|
134
|
+
password: opts.password,
|
|
135
|
+
refreshToken: opts.refreshToken,
|
|
136
|
+
};
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
138
|
+
this.authenticationHeader = { Authorization: `Basic ${Buffer.from(`${this.auth.username}:${this.auth.password}`).toString('base64')}` };
|
|
139
|
+
if (this.baseUrl.startsWith('https') && (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0' || opts.insecure)) {
|
|
140
|
+
logger.warn('using insecure mode');
|
|
141
|
+
this.agent = new https_1.Agent({ rejectUnauthorized: false });
|
|
142
|
+
}
|
|
143
|
+
this.retryConfig = opts.retryConfig;
|
|
144
|
+
}
|
|
145
|
+
async performAPICall(opts) {
|
|
146
|
+
const method = opts.method || 'GET';
|
|
147
|
+
const requestHeaders = {
|
|
148
|
+
...this.authenticationHeader,
|
|
149
|
+
...opts.headers,
|
|
150
|
+
};
|
|
151
|
+
const jsonFormat = opts.plugin && opts.json ? pluginsJsonFormat : {};
|
|
152
|
+
const qs = { ...opts.qs, ...jsonFormat };
|
|
153
|
+
const requestOptions = {
|
|
154
|
+
method,
|
|
155
|
+
url: `${this.baseUrl}${ApiVersions.V1}${opts.api}`,
|
|
156
|
+
qs,
|
|
157
|
+
headers: requestHeaders,
|
|
158
|
+
timeout: this.timeout,
|
|
159
|
+
body: opts.data,
|
|
160
|
+
resolveWithFullResponse: true,
|
|
161
|
+
agent: this.agent,
|
|
162
|
+
simple: false,
|
|
163
|
+
retryConfig: this.retryConfig,
|
|
164
|
+
json: true
|
|
165
|
+
};
|
|
166
|
+
logger.debug(`${method} ${requestOptions.url} qs: ${JSON.stringify(requestOptions.qs)}`);
|
|
167
|
+
return request_retry_1.RpRetry.rpRetry(requestOptions, logger)
|
|
168
|
+
.then(async (res) => {
|
|
169
|
+
res.body = this._parseBody(res.body);
|
|
170
|
+
return Promise.resolve(res);
|
|
171
|
+
})
|
|
172
|
+
.then((res) => {
|
|
173
|
+
const curLogger = res.statusCode >= 400 ? logger.error.bind(logger) : logger.debug.bind(logger);
|
|
174
|
+
curLogger(`${method} ${requestOptions.url} qs: ${JSON.stringify(requestOptions.qs)} status: ${res.statusCode}`);
|
|
175
|
+
res.body = this._parseBody(res.body);
|
|
176
|
+
return res;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async paginateForResult(opts) {
|
|
180
|
+
const limit = opts.limit || LIMIT_PER_PAGE;
|
|
181
|
+
// gerrit api is 0-based and our api is 1-based
|
|
182
|
+
const start = (opts.page - 1) * limit;
|
|
183
|
+
let resBatch = [];
|
|
184
|
+
const jsonFormat = opts.json ? pluginsJsonFormat : {};
|
|
185
|
+
opts.qs = {
|
|
186
|
+
...opts.qs,
|
|
187
|
+
start: start.toString(),
|
|
188
|
+
limit: limit.toString(),
|
|
189
|
+
...jsonFormat
|
|
190
|
+
};
|
|
191
|
+
const [err, res] = await helpers_1.to(this.performAPICall(opts));
|
|
192
|
+
this._handleError(`Failed with status code: ${res.statusCode}`, err, res);
|
|
193
|
+
resBatch = opts.parseBatch(res.body, opts.parseBatchOpts);
|
|
194
|
+
return resBatch;
|
|
195
|
+
}
|
|
196
|
+
getName() {
|
|
197
|
+
return 'gerrit';
|
|
198
|
+
}
|
|
199
|
+
async createRepository(opts) {
|
|
200
|
+
const data = {
|
|
201
|
+
create_empty_commit: true,
|
|
202
|
+
...(opts.owner && { owners: [opts.owner] })
|
|
203
|
+
};
|
|
204
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
205
|
+
api: `projects/${opts.repo}`,
|
|
206
|
+
method: 'PUT',
|
|
207
|
+
json: true,
|
|
208
|
+
data
|
|
209
|
+
}));
|
|
210
|
+
this._handleError(`Failed to create repository ${opts.repo}`, err, res);
|
|
211
|
+
return await this.toRepo(res.body);
|
|
212
|
+
}
|
|
213
|
+
async fetchRawFile(opts) {
|
|
214
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
215
|
+
api: `projects/${opts.repo}/branches/${opts.ref}/files/${helpers_1.cleanEncodedFilePath(opts.path)}/content`,
|
|
216
|
+
json: true,
|
|
217
|
+
}));
|
|
218
|
+
this._handleError(`Failed to retrieve file ${opts.path} on ${opts.repo}`, err, res);
|
|
219
|
+
const base64Content = res.body.replace(/\n/gi, '');
|
|
220
|
+
return Buffer.from(base64Content, 'base64').toString();
|
|
221
|
+
}
|
|
222
|
+
async getBranch(opts) {
|
|
223
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
224
|
+
api: `projects/${opts.repo}/branches/${opts.branch}`,
|
|
225
|
+
json: true,
|
|
226
|
+
}));
|
|
227
|
+
this._handleError(`Failed to get branch ${opts.branch} on ${opts.repo}`, err, res);
|
|
228
|
+
return await this.toBranch(res.body, opts.repo);
|
|
229
|
+
}
|
|
230
|
+
async getRepository(opts) {
|
|
231
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
232
|
+
api: `projects/${opts.repo}`,
|
|
233
|
+
json: true,
|
|
234
|
+
}));
|
|
235
|
+
this._handleError(`Failed to get repository ${opts.repo}`, err, res);
|
|
236
|
+
return await this.toRepo(res.body);
|
|
237
|
+
}
|
|
238
|
+
async listBranches(opts) {
|
|
239
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
240
|
+
api: `projects/${opts.repo}/branches`,
|
|
241
|
+
json: true,
|
|
242
|
+
}));
|
|
243
|
+
this._handleError(`Failed to list branch ${opts.repo}`, err, res);
|
|
244
|
+
return await Promise.all(lodash_1.map(res.body, async (branch) => await this.toBranch(branch, opts.repo)));
|
|
245
|
+
}
|
|
246
|
+
async listRepositoriesForOwner() {
|
|
247
|
+
throw new Error('Method listRepositoriesForOwner not implemented.');
|
|
248
|
+
}
|
|
249
|
+
async createRepositoryWebhook(opts) {
|
|
250
|
+
throw new Error('Method createRepositoryWebhook not implemented.');
|
|
251
|
+
}
|
|
252
|
+
async listWebhooks(opts) {
|
|
253
|
+
throw new Error('Method listWebhooks not implemented.');
|
|
254
|
+
}
|
|
255
|
+
async deleteRepositoryWebhook(opts) {
|
|
256
|
+
throw new Error('Method deleteRepositoryWebhook not implemented.');
|
|
257
|
+
}
|
|
258
|
+
async listRepositoriesWithAffiliation(opts) {
|
|
259
|
+
const repos = await this.paginateForResult({
|
|
260
|
+
api: `projects/`,
|
|
261
|
+
json: true,
|
|
262
|
+
parseBatch: this._parseBatch,
|
|
263
|
+
parseBatchOpts: {
|
|
264
|
+
keyName: 'name'
|
|
265
|
+
},
|
|
266
|
+
limit: opts.limit,
|
|
267
|
+
page: opts.page || 1
|
|
268
|
+
});
|
|
269
|
+
const filteredRepos = lodash_1.filter(repos, (repo) => repo.name !== 'All-Projects' && repo.name !== 'All-Users');
|
|
270
|
+
return await Promise.all(lodash_1.map(filteredRepos, async (repo) => await this.toRepo(repo)));
|
|
271
|
+
}
|
|
272
|
+
// orgs is groups, but there are no part of the repo
|
|
273
|
+
async listOrganizations(opts) {
|
|
274
|
+
const { limit = 25, page = 0 } = opts;
|
|
275
|
+
const start = page * limit;
|
|
276
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
277
|
+
api: `groups/`,
|
|
278
|
+
qs: {
|
|
279
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
280
|
+
S: String(start),
|
|
281
|
+
n: String(limit),
|
|
282
|
+
},
|
|
283
|
+
json: true,
|
|
284
|
+
}));
|
|
285
|
+
this._handleError(`Failed to list organization`, err, res);
|
|
286
|
+
return res.body ? Object.keys(res.body) : [];
|
|
287
|
+
}
|
|
288
|
+
// acorrding to https://gerrit-review.googlesource.com/Documentation/access-control.html
|
|
289
|
+
async getRepositoryPermissions(opts) {
|
|
290
|
+
if (!opts.user) {
|
|
291
|
+
throw new CFError({
|
|
292
|
+
message: `Failed to get ${opts.repo} permission: missing user`,
|
|
293
|
+
cause: new Error('user is required on gerrit repository permission request')
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
const defaultBranch = await this.getDefaultBranch(opts.repo, true).catch(error => {
|
|
297
|
+
logger.error(`Failed to check repository permission for user ${opts.user}: Failed to get default branch: ${error.message}`);
|
|
298
|
+
return defaultRepoPermission;
|
|
299
|
+
});
|
|
300
|
+
const userGroups = await this.getUserGroups(opts.user).catch(error => {
|
|
301
|
+
logger.error(`Failed to check repository permission for user ${opts.user}: Failed to get user groups: ${error.message}`);
|
|
302
|
+
return defaultRepoPermission;
|
|
303
|
+
});
|
|
304
|
+
const permissions = await this.getBranchAccess({ repo: opts.repo, user: opts.user, ref: defaultBranch, groups: userGroups });
|
|
305
|
+
return permissions;
|
|
306
|
+
}
|
|
307
|
+
async getBranchAccess(opts) {
|
|
308
|
+
var _a, _b, _c, _d;
|
|
309
|
+
const { repo, ref, user, groups, childRefs } = opts;
|
|
310
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
311
|
+
api: `access/`,
|
|
312
|
+
qs: {
|
|
313
|
+
project: repo
|
|
314
|
+
},
|
|
315
|
+
json: true,
|
|
316
|
+
}));
|
|
317
|
+
if (err || !res || (res === null || res === void 0 ? void 0 : res.statusCode) >= 400) {
|
|
318
|
+
logger.error(`Failed to check repository permission for user ${user}`);
|
|
319
|
+
return defaultRepoPermission;
|
|
320
|
+
}
|
|
321
|
+
const repositoryRefs = (_b = (_a = res === null || res === void 0 ? void 0 : res.body) === null || _a === void 0 ? void 0 : _a[repo]) === null || _b === void 0 ? void 0 : _b.local;
|
|
322
|
+
const parent = (_d = (_c = res === null || res === void 0 ? void 0 : res.body) === null || _c === void 0 ? void 0 : _c[repo]) === null || _d === void 0 ? void 0 : _d.inherits_from;
|
|
323
|
+
// no parent exist, calculating final permission res
|
|
324
|
+
const matchingRefs = this._getMatchingRefsForBranch(repositoryRefs, ref);
|
|
325
|
+
if (!parent) {
|
|
326
|
+
return this._getFinalBranchPermission(matchingRefs, { user, groups, childRefs });
|
|
327
|
+
}
|
|
328
|
+
const parentOpts = this._getRepoParentAccessInfo(matchingRefs, parent, opts);
|
|
329
|
+
return await this.getBranchAccess(parentOpts);
|
|
330
|
+
}
|
|
331
|
+
async listRepositoriesForOrganization() {
|
|
332
|
+
throw new Error('Method listRepositoriesForOrganization not implemented.');
|
|
333
|
+
}
|
|
334
|
+
async createCommitStatus(opts) {
|
|
335
|
+
throw new Error('Method createCommitStatus not implemented.');
|
|
336
|
+
}
|
|
337
|
+
async getUser(opts) {
|
|
338
|
+
const [err, res] = await helpers_1.to(this.getPlainUser(opts));
|
|
339
|
+
this._handleError(`Failed to get user`, err, res);
|
|
340
|
+
return await this.toUser(res.body);
|
|
341
|
+
}
|
|
342
|
+
async getUserByEmail(email) {
|
|
343
|
+
return await this.getUser({ username: email });
|
|
344
|
+
}
|
|
345
|
+
async getPullRequestFiles() {
|
|
346
|
+
throw new Error('Method getPullRequestFiles not implemented.');
|
|
347
|
+
}
|
|
348
|
+
async getPullRequest() {
|
|
349
|
+
throw new Error('Method getPullRequest not implemented.');
|
|
350
|
+
}
|
|
351
|
+
async assertApiScopes(opts) {
|
|
352
|
+
const res = await this.performAPICall({
|
|
353
|
+
api: `accounts/self/capabilities`,
|
|
354
|
+
json: true,
|
|
355
|
+
}).catch(error => {
|
|
356
|
+
logger.error(`failed assert api scopes with: ${JSON.stringify(error)}`);
|
|
357
|
+
});
|
|
358
|
+
if (!res) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const access = res.body;
|
|
362
|
+
const validationErrorMsg = 'ValidationError: check your token permissions';
|
|
363
|
+
if (opts.scopes.includes(scopesMap.read) && res.statusCode == 401) {
|
|
364
|
+
throw new CFError(`${validationErrorMsg} token or user is invalid`);
|
|
365
|
+
}
|
|
366
|
+
if (opts.scopes.includes(scopesMap.admin) && !(access.createProject && access.administrateServer) && opts.scopes.includes(scopesMap.create)) {
|
|
367
|
+
throw new CFError(`${validationErrorMsg} missing admin permission`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async validateToken() {
|
|
371
|
+
const res = await this.getPlainUser().catch(error => {
|
|
372
|
+
logger.error(`failed validating basic token permissions with ${JSON.stringify(error)}`);
|
|
373
|
+
});
|
|
374
|
+
if (!res) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (res.statusCode == 401) {
|
|
378
|
+
throw new CFError(`user token is invalid, failed to get current user with status code: ${res.statusCode}`);
|
|
379
|
+
}
|
|
380
|
+
if (res.statusCode >= 400) {
|
|
381
|
+
logger.error(`failed validating basic token permissions with status code: ${res.statusCode}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async toUser(user, email) {
|
|
385
|
+
const avatar = await this.getAvatar(user.username).catch((error) => {
|
|
386
|
+
logger.error(`failed to get avatar for user with ${JSON.stringify(error)}`);
|
|
387
|
+
});
|
|
388
|
+
return {
|
|
389
|
+
login: user.username,
|
|
390
|
+
email: email || user.email || '',
|
|
391
|
+
avatar_url: avatar,
|
|
392
|
+
web_url: `${this.baseUrl}dashboard/${user.username}`,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
async toBranch(rawBranch, repo) {
|
|
396
|
+
var _a, _b;
|
|
397
|
+
const branchName = this._extractBranchName(rawBranch.ref);
|
|
398
|
+
const lastCommit = await this.getCommit(repo, rawBranch.revision).catch(error => {
|
|
399
|
+
logger.error(`Failed to get commit info for ${repo} with ${JSON.stringify(error)}`);
|
|
400
|
+
});
|
|
401
|
+
const webUrl = (_a = rawBranch.web_links) === null || _a === void 0 ? void 0 : _a[0].url;
|
|
402
|
+
const url = webUrl ? this.baseUrl + helpers_1.cleanUrlPrefix(webUrl) : '';
|
|
403
|
+
return {
|
|
404
|
+
id: rawBranch.ref,
|
|
405
|
+
name: branchName || rawBranch.ref,
|
|
406
|
+
commit: {
|
|
407
|
+
sha: rawBranch.revision,
|
|
408
|
+
commiter_name: lastCommit ? (_b = lastCommit.committer) === null || _b === void 0 ? void 0 : _b.name : '',
|
|
409
|
+
message: lastCommit ? lastCommit.message : '',
|
|
410
|
+
url,
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
// currently we are not supporting ssh protocol on our platform - open for changes
|
|
415
|
+
async toRepo(rawRepo, sshProtocol) {
|
|
416
|
+
var _a, _b, _c, _d, _e;
|
|
417
|
+
let lastCommit;
|
|
418
|
+
const gitliesProjectInfo = sshProtocol ? await this.getProjectGitliesInfo(rawRepo).catch((error => {
|
|
419
|
+
logger.error(`Failed to get gitlies info for repoository ${rawRepo.name} with ${JSON.stringify(error)}`);
|
|
420
|
+
})) : {};
|
|
421
|
+
const defaultBranch = await this.getDefaultBranch(rawRepo.id).catch((error => {
|
|
422
|
+
logger.error(`Failed to get default branch for repository ${rawRepo.name} with ${JSON.stringify(error)}`);
|
|
423
|
+
}));
|
|
424
|
+
const webUrl = (_a = rawRepo.web_links) === null || _a === void 0 ? void 0 : _a[0].url;
|
|
425
|
+
const commits = defaultBranch ? await this.getCommits(rawRepo.id, defaultBranch, webUrl).catch((error => {
|
|
426
|
+
logger.error(`Failed to last commit for repository ${rawRepo.name} with ${JSON.stringify(error)}`);
|
|
427
|
+
})) : undefined;
|
|
428
|
+
if (commits && commits.length >= 0)
|
|
429
|
+
lastCommit = commits[0];
|
|
430
|
+
const avatar = lastCommit ? await this.getAvatar((_b = lastCommit.author) === null || _b === void 0 ? void 0 : _b.name).catch((error => {
|
|
431
|
+
logger.error(`Failed to get avatar for last committer on repository ${rawRepo.name} with ${JSON.stringify(error)}`);
|
|
432
|
+
})) : '';
|
|
433
|
+
const url = webUrl ? this.baseUrl + helpers_1.cleanUrlPrefix(webUrl) : '';
|
|
434
|
+
return {
|
|
435
|
+
id: rawRepo.id,
|
|
436
|
+
provider: 'gerrit',
|
|
437
|
+
name: rawRepo.name,
|
|
438
|
+
full_name: rawRepo.name,
|
|
439
|
+
private: rawRepo.state === PROJECT_STATE.hidden,
|
|
440
|
+
pushed_at: ((_c = lastCommit === null || lastCommit === void 0 ? void 0 : lastCommit.committer) === null || _c === void 0 ? void 0 : _c.time) ? new Date((_d = lastCommit === null || lastCommit === void 0 ? void 0 : lastCommit.committer) === null || _d === void 0 ? void 0 : _d.time) : new Date(0),
|
|
441
|
+
open_issues: 0,
|
|
442
|
+
clone_url: (gitliesProjectInfo === null || gitliesProjectInfo === void 0 ? void 0 : gitliesProjectInfo.clone_url) || this.baseUrl + rawRepo.name + '.git',
|
|
443
|
+
ssh_url: '',
|
|
444
|
+
owner: {
|
|
445
|
+
login: ((_e = lastCommit === null || lastCommit === void 0 ? void 0 : lastCommit.author) === null || _e === void 0 ? void 0 : _e.name) || '',
|
|
446
|
+
avatar_url: avatar,
|
|
447
|
+
creator: null,
|
|
448
|
+
},
|
|
449
|
+
org: rawRepo.parent,
|
|
450
|
+
default_branch: defaultBranch || '',
|
|
451
|
+
permissions: {
|
|
452
|
+
admin: true,
|
|
453
|
+
},
|
|
454
|
+
webUrl: url,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
// avatar plugin is supported from 3.7 version or need to be configured
|
|
458
|
+
async getAvatar(account) {
|
|
459
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
460
|
+
api: `accounts/${account}/avatar`,
|
|
461
|
+
json: true,
|
|
462
|
+
}));
|
|
463
|
+
if (err) {
|
|
464
|
+
throw new CFError({
|
|
465
|
+
message: `failed to get avatar for account ${account}`,
|
|
466
|
+
cause: err
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
// found
|
|
470
|
+
if (res.statusCode === 302 && res.body) {
|
|
471
|
+
return res.body.Location || '';
|
|
472
|
+
}
|
|
473
|
+
return '';
|
|
474
|
+
}
|
|
475
|
+
async getProjectGitliesInfo(rawRepo) {
|
|
476
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
477
|
+
api: `plugins/gitiles/${rawRepo.id}`,
|
|
478
|
+
json: true,
|
|
479
|
+
plugin: true,
|
|
480
|
+
}));
|
|
481
|
+
if (err || res.statusCode >= 400) {
|
|
482
|
+
throw new CFError({
|
|
483
|
+
message: `failed to get project ${rawRepo.name} gitlies info`,
|
|
484
|
+
cause: err
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (rawRepo.state === PROJECT_STATE.hidden) {
|
|
488
|
+
throw new CFError({
|
|
489
|
+
message: `project ${rawRepo.name} is hidden`
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (res.body.name != rawRepo.name) {
|
|
493
|
+
throw new CFError({
|
|
494
|
+
message: `failed to get project ${rawRepo.name} gitlies info: not found`
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
return res.body;
|
|
498
|
+
}
|
|
499
|
+
async getDefaultBranch(repoId, returnFullRef = false) {
|
|
500
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
501
|
+
api: `projects/${repoId}/HEAD`,
|
|
502
|
+
json: true,
|
|
503
|
+
}));
|
|
504
|
+
if (err) {
|
|
505
|
+
throw new CFError({
|
|
506
|
+
message: `Failed to get default branch for ${repoId}`,
|
|
507
|
+
cause: err
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return returnFullRef ? res.body : this._extractBranchName(res.body);
|
|
511
|
+
}
|
|
512
|
+
async getCommits(repoId, branch, pluginApiUrl) {
|
|
513
|
+
var _a;
|
|
514
|
+
const urlPrefix = pluginApiUrl ? helpers_1.cleanUrlPrefix(pluginApiUrl) : `plugins/gitiles/${repoId}`;
|
|
515
|
+
const api = `${urlPrefix}/+log/${branch}`;
|
|
516
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
517
|
+
api,
|
|
518
|
+
json: true,
|
|
519
|
+
plugin: true,
|
|
520
|
+
}));
|
|
521
|
+
if (err) {
|
|
522
|
+
throw new CFError({
|
|
523
|
+
message: `Failed to get commits for repo ${repoId} for branch ${branch}`,
|
|
524
|
+
casue: err
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return ((_a = res.body) === null || _a === void 0 ? void 0 : _a.log) || [];
|
|
528
|
+
}
|
|
529
|
+
async getCommit(repoId, sha) {
|
|
530
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
531
|
+
api: `projects/${repoId}/commits/${sha}`,
|
|
532
|
+
json: true,
|
|
533
|
+
}));
|
|
534
|
+
if (err) {
|
|
535
|
+
throw new CFError({
|
|
536
|
+
message: `Failed to get commit ${sha} for repo ${repoId}`,
|
|
537
|
+
cause: err
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
return res.body;
|
|
541
|
+
}
|
|
542
|
+
async getPlainUser(opts) {
|
|
543
|
+
const user = (opts === null || opts === void 0 ? void 0 : opts.username) ? opts.username : 'self';
|
|
544
|
+
return this.performAPICall({
|
|
545
|
+
api: `accounts/${user}`,
|
|
546
|
+
json: true,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
async getUserGroups(user) {
|
|
550
|
+
const [err, res] = await helpers_1.to(this.performAPICall({
|
|
551
|
+
api: `accounts/${user}/groups/`,
|
|
552
|
+
json: true,
|
|
553
|
+
}));
|
|
554
|
+
if (err || !res || (res === null || res === void 0 ? void 0 : res.statusCode) >= 400 || !res.body || !lodash_1.isArray(res.body)) {
|
|
555
|
+
throw new CFError({
|
|
556
|
+
message: `Failed to check repository permission for user ${user}: failed to get user groups`,
|
|
557
|
+
cause: err
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
return res.body.map((group) => decodeURIComponent(group.id));
|
|
561
|
+
}
|
|
562
|
+
toOwnerRepo(fullRepoName) {
|
|
563
|
+
return ['', encodeURIComponent(fullRepoName)];
|
|
564
|
+
}
|
|
565
|
+
getAuth() {
|
|
566
|
+
return {
|
|
567
|
+
headers: this.authenticationHeader,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
isTokenMutable() {
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
573
|
+
requiresRepoToCheckTokenScopes() {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
useAdminForUserPermission() {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
// Gerrit helpers
|
|
580
|
+
_parseBatch(body, opts) {
|
|
581
|
+
return lodash_1.map(body, (value, key) => {
|
|
582
|
+
const { ...rest } = value;
|
|
583
|
+
return { [opts.keyName]: key, ...rest };
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
_getGroupHigestAction(old, current) {
|
|
587
|
+
// in same section if has allow and block, allow will override the block
|
|
588
|
+
if ([old, current].includes(ACCESS_ACTION.allow)) {
|
|
589
|
+
return ACCESS_ACTION.allow;
|
|
590
|
+
}
|
|
591
|
+
if ([old, current].includes(ACCESS_ACTION.block)) {
|
|
592
|
+
return ACCESS_ACTION.block;
|
|
593
|
+
}
|
|
594
|
+
return current;
|
|
595
|
+
}
|
|
596
|
+
_getMatchingRefsForBranch(repositoryRefs, branch) {
|
|
597
|
+
return lodash_1.pickBy(repositoryRefs, (_, key) => {
|
|
598
|
+
return wildcard(key, branch);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
_getExclusiveResult(action, actionDefinedForUser, isExclusiveRule = false) {
|
|
602
|
+
// action is unknown and not defined for the user / groups, and rule is exclusice
|
|
603
|
+
if (action === ACCESS_ACTION.unknown && isExclusiveRule && !actionDefinedForUser) {
|
|
604
|
+
return ACCESS_ACTION.block;
|
|
605
|
+
}
|
|
606
|
+
return action;
|
|
607
|
+
}
|
|
608
|
+
_getExclusivesScopes(permissions) {
|
|
609
|
+
const exclusives = [];
|
|
610
|
+
for (const rule in permissions) {
|
|
611
|
+
if (permissions[rule].exclusice) {
|
|
612
|
+
exclusives.push(rule);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return exclusives;
|
|
616
|
+
}
|
|
617
|
+
_getRepoRefsPermission(refs, user, groups) {
|
|
618
|
+
const refsScopesPermission = {};
|
|
619
|
+
for (const ref in refs) {
|
|
620
|
+
const refPermissions = refs[ref].permissions;
|
|
621
|
+
const refScopesPermission = {};
|
|
622
|
+
// exclusive scope permission override all other group/user permission that are not permitted exclusivly
|
|
623
|
+
const exclusives = this._getExclusivesScopes(refPermissions);
|
|
624
|
+
for (const scope in refPermissions) {
|
|
625
|
+
const scopeDetails = refPermissions[scope];
|
|
626
|
+
const scopeRules = scopeDetails.rules;
|
|
627
|
+
let scopeAction = ACCESS_ACTION.unknown;
|
|
628
|
+
const groupsAndUser = [...groups, `user:${user}`];
|
|
629
|
+
let actionDefinedForUser = false;
|
|
630
|
+
for (const group of groupsAndUser) {
|
|
631
|
+
const groupScopeRule = scopeRules[group];
|
|
632
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
633
|
+
if (!groupScopeRule) {
|
|
634
|
+
// Current group doesnt have defined scopes rules
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
actionDefinedForUser = true;
|
|
638
|
+
scopeAction = this._getGroupHigestAction(scopeAction, groupScopeRule.action);
|
|
639
|
+
}
|
|
640
|
+
refScopesPermission[scope] = this._getExclusiveResult(scopeAction, actionDefinedForUser, exclusives.includes(scope));
|
|
641
|
+
}
|
|
642
|
+
refsScopesPermission[ref] = refScopesPermission;
|
|
643
|
+
}
|
|
644
|
+
return refsScopesPermission;
|
|
645
|
+
}
|
|
646
|
+
_getRefsScopesPermission(refsScopesPermission) {
|
|
647
|
+
const scopesPermission = {};
|
|
648
|
+
// refs oreder by less sepecific path to most sepecific path
|
|
649
|
+
const paths = this._getSortedPathByPriority(refsScopesPermission);
|
|
650
|
+
for (const path of paths) {
|
|
651
|
+
for (const [scope, permission] of Object.entries(refsScopesPermission[path])) {
|
|
652
|
+
// if permission is false or scopes doesnt exist already (from more specific path)
|
|
653
|
+
// cause less specific path can block permission for more specific path
|
|
654
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
655
|
+
if (scopesPermission[scope] === ACCESS_ACTION.unknown || !(scopesPermission.hasOwnProperty(scope))) {
|
|
656
|
+
scopesPermission[scope] = permission;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return scopesPermission;
|
|
661
|
+
}
|
|
662
|
+
_extractBranchName(branchRef) {
|
|
663
|
+
return lodash_1.last(lodash_1.split(branchRef, '/'));
|
|
664
|
+
}
|
|
665
|
+
_parseBody(body) {
|
|
666
|
+
if (lodash_1.isString(body) && body.startsWith(RESPONSE_PREFIX)) {
|
|
667
|
+
body = body.slice(RESPONSE_PREFIX.length);
|
|
668
|
+
}
|
|
669
|
+
return helpers_1.isJsonString(body) ? JSON.parse(body) : body;
|
|
670
|
+
}
|
|
671
|
+
_handleError(message, err, res) {
|
|
672
|
+
if (err || !res || (res === null || res === void 0 ? void 0 : res.statusCode) >= 400) {
|
|
673
|
+
throw new CFError({
|
|
674
|
+
message: message,
|
|
675
|
+
cause: err !== null && err !== void 0 ? err : new types_1.HttpError(res === null || res === void 0 ? void 0 : res.statusCode, res === null || res === void 0 ? void 0 : res.body)
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
_mergeRefs(childRef, parentRef) {
|
|
680
|
+
const newRef = {};
|
|
681
|
+
for (const scope in childRef) {
|
|
682
|
+
// if ref exists in the parent object, merge the two scopes else copy the child over
|
|
683
|
+
newRef[scope] = scope in parentRef ? this._mergeScope(childRef[scope], parentRef[scope]) : childRef[scope];
|
|
684
|
+
}
|
|
685
|
+
for (const scope in parentRef) {
|
|
686
|
+
// scope doesn't exist in the child object, copy the parent scope over
|
|
687
|
+
if (!(scope in childRef)) {
|
|
688
|
+
newRef[scope] = parentRef[scope];
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return newRef;
|
|
692
|
+
}
|
|
693
|
+
_getInheritRepoRefsPermission(parent, child) {
|
|
694
|
+
if (!child || lodash_1.isEmpty(child)) {
|
|
695
|
+
return parent;
|
|
696
|
+
}
|
|
697
|
+
const result = {};
|
|
698
|
+
for (const childKey in child) {
|
|
699
|
+
// if ref exists in the parent object, merge the two Refs else copy the child over
|
|
700
|
+
result[childKey] = childKey in parent ? this._mergeRefs(child[childKey], parent[childKey]) : child[childKey];
|
|
701
|
+
}
|
|
702
|
+
for (const parentKey in parent) {
|
|
703
|
+
// ref doesn't exist in the child object, copy the parent ref over
|
|
704
|
+
if (!(parentKey in child)) {
|
|
705
|
+
result[parentKey] = parent[parentKey];
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
_mergeScope(childScope, parentScope) {
|
|
711
|
+
// between child / parent BLOCK is always override any permission
|
|
712
|
+
if (childScope === ACCESS_ACTION.block || parentScope === ACCESS_ACTION.block) {
|
|
713
|
+
return ACCESS_ACTION.block;
|
|
714
|
+
}
|
|
715
|
+
// when child is DENY the parent overrides
|
|
716
|
+
if (childScope === ACCESS_ACTION.deny) {
|
|
717
|
+
return parentScope;
|
|
718
|
+
}
|
|
719
|
+
return childScope;
|
|
720
|
+
}
|
|
721
|
+
_mapActionToBool(action) {
|
|
722
|
+
if (action === ACCESS_ACTION.block || action === ACCESS_ACTION.deny) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
exports.default = Gerrit;
|
|
729
|
+
//# sourceMappingURL=gerrit.js.map
|