@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.
@@ -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