• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

screwdriver-cd / screwdriver / #3202

25 Jul 2025 04:52PM UTC coverage: 67.669% (-27.3%) from 94.935%
#3202

push

screwdriver

web-flow
feat(3363): Update the existing endpoint to get admin for a pipeline from the specified SCM context (#3370)

1284 of 2114 branches covered (60.74%)

Branch coverage included in aggregate %.

1 of 11 new or added lines in 1 file covered. (9.09%)

1235 existing lines in 49 files now uncovered.

3417 of 4833 relevant lines covered (70.7%)

50.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.48
/plugins/pipelines/index.js
1
'use strict';
2

3
const boom = require('@hapi/boom');
10✔
4
const createRoute = require('./create');
10✔
5
const updateRoute = require('./update');
10✔
6
const removeRoute = require('./remove');
10✔
7
const syncRoute = require('./sync');
10✔
8
const syncWebhooksRoute = require('./syncWebhooks');
10✔
9
const syncPRsRoute = require('./syncPRs');
10✔
10
const getRoute = require('./get');
10✔
11
const listRoute = require('./list');
10✔
12
const badgeRoute = require('./badge');
10✔
13
const jobBadgeRoute = require('./jobBadge');
10✔
14
const listJobsRoute = require('./listJobs');
10✔
15
const listStagesRoute = require('./listStages');
10✔
16
const listTriggersRoute = require('./listTriggers');
10✔
17
const listSecretsRoute = require('./listSecrets');
10✔
18
const listEventsRoute = require('./listEvents');
10✔
19
const listBuildsRoute = require('./listBuilds');
10✔
20
const startAllRoute = require('./startAll');
10✔
21
const createToken = require('./tokens/create');
10✔
22
const updateToken = require('./tokens/update');
10✔
23
const refreshToken = require('./tokens/refresh');
10✔
24
const listTokens = require('./tokens/list');
10✔
25
const removeToken = require('./tokens/remove');
10✔
26
const removeAllTokens = require('./tokens/removeAll');
10✔
27
const metricsRoute = require('./metrics');
10✔
28
const latestBuild = require('./latestBuild');
10✔
29
const latestCommitEvent = require('./latestCommitEvent');
10✔
30
const getAdmin = require('./admins/get');
10✔
31
const deleteCache = require('./caches/delete');
10✔
32
const openPrRoute = require('./openPr');
10✔
33
const createTemplateRoute = require('./templates/create');
10✔
34
const validateTemplateRoute = require('./templates/validate');
10✔
35
const listTemplatesRoute = require('./templates/list');
10✔
36
const listTemplateVersionsRoute = require('./templates/listVersions');
10✔
37
const listTagsRoute = require('./templates/listTags');
10✔
38
const getTemplateRoute = require('./templates/get');
10✔
39
const getTemplateByIdRoute = require('./templates/getTemplateById');
10✔
40
const createTagRoute = require('./templates/createTag');
10✔
41
const getVersionRoute = require('./templates/getVersion');
10✔
42
const removeTemplateRoute = require('./templates/remove');
10✔
43
const removeTemplateTagRoute = require('./templates/removeTag');
10✔
44
const removeTemplateVersionRoute = require('./templates/removeVersion');
10✔
45
const updateTrustedRoute = require('./templates/updateTrusted');
10✔
46
const updateBuildCluster = require('./updateBuildCluster');
10✔
47
const updateAdminsRoute = require('./updateAdmins');
10✔
48
const batchUpdateAdminsRoute = require('./batchUpdateAdmins');
10✔
49

50
/**
51
 * Pipeline API Plugin
52
 * @method register
53
 * @param  {Hapi}     server            Hapi Server
54
 */
55
const pipelinesPlugin = {
10✔
56
    name: 'pipelines',
57
    async register(server) {
58
        const statusColor = {
434✔
59
            unknown: 'lightgrey',
60
            disabled: 'lightgrey',
61
            created: 'lightgrey',
62
            success: 'green',
63
            queued: 'blue',
64
            blocked: 'blue',
65
            running: 'blue',
66
            collapsed: 'lightgrey',
67
            frozen: 'lightgrey',
68
            unstable: 'yellow',
69
            failure: 'red',
70
            aborted: 'red'
71
        };
72

73
        /**
74
         * Returns an encoded string of subject based on separator of the badge service
75
         * @method encodeBadgeSubject
76
         * @param  {String} badgeService           badge service url
77
         * @param  {String} subject                subject to put in the badge
78
         * @return {String} encodedSubject
79
         */
80
        server.expose('encodeBadgeSubject', ({ badgeService, subject }) => {
434✔
81
            const separator = badgeService.match(/}}(.){{/)[1];
×
82

83
            if (separator === '/') {
×
84
                return encodeURIComponent(subject);
×
85
            }
86

87
            // Reference: https://shields.io/
88
            if (separator === '-') {
×
89
                return subject.replace(/-/g, '--').replace(/_/g, '__');
×
90
            }
91

92
            return subject;
×
93
        });
94

95
        /**
96
         * Returns true if the scope does not include pipeline or includes pipeline
97
         * and its pipelineId matches the pipeline, otherwise returns false
98
         * @method isValidToken
99
         * @param  {String} id                     ID of pipeline
100
         * @param  {Object} credentials            Credential object from Hapi
101
         * @param  {String} credentials.pipelineId ID of pipeline which the token is allowed to access
102
         * @param  {String} credentials.scope      Scope whose token is allowed
103
         */
104
        server.expose(
434✔
105
            'isValidToken',
106
            (id, credentials) =>
107
                !credentials.scope.includes('pipeline') || parseInt(id, 10) === parseInt(credentials.pipelineId, 10)
61✔
108
        );
109

110
        /**
111
         * Throws error if a credential does not have access to a pipeline
112
         * If credential has access, returns pipeline
113
         * @method canAccessPipeline
114
         * @param {Object}  credentials              Credential object from Hapi
115
         * @param {String}  credentials.username     Username of the person logged in (or build ID)
116
         * @param {String}  credentials.scmContext   Scm of the person logged in (or build ID)
117
         * @param {Array}   credentials.scope        Scope of the credential (user, build, admin)
118
         * @param {String}  pipelineId               Target pipeline ID
119
         * @param {String}  permission               Required permission level
120
         * @param {String}  app                      Server app object
121
         * @return {Object} pipeline
122
         */
123
        server.expose('canAccessPipeline', (credentials, pipelineId, permission, app) => {
434✔
124
            const { username, scmContext, scope, scmUserId } = credentials;
60✔
125
            const { userFactory, pipelineFactory } = app;
60✔
126

127
            return pipelineFactory.get(pipelineId).then(pipeline => {
60✔
128
                if (!pipeline) {
60!
UNCOV
129
                    throw boom.notFound(`Pipeline ${pipelineId} does not exist`);
×
130
                }
131

132
                if (credentials.scope.includes('admin')) {
60✔
133
                    return pipeline;
2✔
134
                }
135

136
                if (!pipeline.scmRepo || !pipeline.scmRepo.private || (pipeline.settings && pipeline.settings.public)) {
58✔
137
                    return pipeline;
44✔
138
                }
139

140
                if (scope.includes('user')) {
14✔
141
                    return userFactory.get({ username, scmContext }).then(user => {
6✔
142
                        if (!user) {
6!
143
                            throw boom.notFound(`User ${username} does not exist`);
×
144
                        }
145

146
                        return user
6✔
147
                            .getPermissions(pipeline.scmUri)
148
                            .then(permissions => {
149
                                if (!permissions[permission]) {
6✔
150
                                    throw boom.forbidden(
4✔
151
                                        `User ${username} does not have ${permission} access for this pipeline`
152
                                    );
153
                                }
154

155
                                return pipeline;
2✔
156
                            })
157
                            .catch(() => {
158
                                const scmDisplayName = pipelineFactory.scm.getDisplayName({ scmContext });
4✔
159
                                const adminDetails = server.plugins.banners.screwdriverAdminDetails(
4✔
160
                                    username,
161
                                    scmDisplayName,
162
                                    scmUserId
163
                                );
164

165
                                if (adminDetails.isAdmin) {
4✔
166
                                    return pipeline;
2✔
167
                                }
168

169
                                throw boom.forbidden(
2✔
170
                                    `User ${username} does not have ${permission} access for this pipeline`
171
                                );
172
                            });
173
                    });
174
                }
175

176
                if (
8✔
177
                    (scope.includes('pipeline') || pipelineId !== credentials.configPipelineId) &&
18✔
178
                    pipelineId !== credentials.pipelineId
179
                ) {
180
                    throw boom.forbidden('Token does not have permission for this pipeline');
4✔
181
                }
182

183
                return pipeline;
4✔
184
            });
185
        });
186

187
        /**
188
         * Throws error if a credential does not have permission to remove pipeline template
189
         * If credential has access, resolves to true
190
         * @method canRemove
191
         * @param {Object}  credentials              Credential object from Hapi
192
         * @param {String}  credentials.username     Username of the person logged in (or build ID)
193
         * @param {String}  credentials.scmContext   Scm of the person logged in (or build ID)
194
         * @param {Array}   credentials.scope        Scope of the credential (user, build, admin)
195
         * @param {String}  [credentials.pipelineId] If credential is a build, this is the pipeline ID
196
         * @param {Object}  pipelineTemplate         Target pipeline template object
197
         * @param {String}  permission               Required permission level
198
         * @param {String}  app                      Server app object
199
         * @return {Promise}
200
         */
201
        server.expose('canRemove', async (credentials, pipelineTemplate, permission, app) => {
434✔
202
            const { username, scmContext, scope } = credentials;
15✔
203
            const { userFactory, pipelineFactory } = app;
15✔
204

205
            if (credentials.scope.includes('admin')) {
15✔
206
                return true;
1✔
207
            }
208

209
            const pipeline = await pipelineFactory.get(pipelineTemplate.pipelineId);
14✔
210

211
            if (!pipeline) {
14✔
212
                throw boom.notFound(`Pipeline ${pipelineTemplate.pipelineId} does not exist`);
2✔
213
            }
214

215
            if (scope.includes('user')) {
12✔
216
                const user = await userFactory.get({ username, scmContext });
6✔
217

218
                if (!user) {
6✔
219
                    throw boom.notFound(`User ${username} does not exist`);
2✔
220
                }
221

222
                const permissions = await user.getPermissions(pipeline.scmUri);
4✔
223

224
                if (!permissions[permission]) {
4✔
225
                    throw boom.forbidden(
2✔
226
                        `User ${username} does not have ${permission} access for this pipelineTemplate`
227
                    );
228
                }
229

230
                return true;
2✔
231
            }
232

233
            if (pipelineTemplate.pipelineId !== credentials.pipelineId || credentials.isPR) {
6✔
234
                throw boom.forbidden('Not allowed to remove this pipelineTemplate');
4✔
235
            }
236

237
            return true;
2✔
238
        });
239

240
        server.route([
434✔
241
            createRoute(),
242
            removeRoute(),
243
            updateRoute(),
244
            syncRoute(),
245
            syncWebhooksRoute(),
246
            syncPRsRoute(),
247
            getRoute(),
248
            listRoute(),
249
            badgeRoute({ statusColor }),
250
            jobBadgeRoute({ statusColor }),
251
            listJobsRoute(),
252
            listStagesRoute(),
253
            listTriggersRoute(),
254
            listSecretsRoute(),
255
            listEventsRoute(),
256
            listBuildsRoute(),
257
            startAllRoute(),
258
            updateToken(),
259
            refreshToken(),
260
            createToken(),
261
            listTokens(),
262
            removeToken(),
263
            removeAllTokens(),
264
            metricsRoute(),
265
            latestBuild(),
266
            latestCommitEvent(),
267
            getAdmin(),
268
            deleteCache(),
269
            openPrRoute(),
270
            createTemplateRoute(),
271
            validateTemplateRoute(),
272
            listTemplatesRoute(),
273
            listTemplateVersionsRoute(),
274
            listTagsRoute(),
275
            getVersionRoute(),
276
            getTemplateByIdRoute(),
277
            getTemplateRoute(),
278
            createTagRoute(),
279
            removeTemplateRoute(),
280
            removeTemplateTagRoute(),
281
            removeTemplateVersionRoute(),
282
            updateTrustedRoute(),
283
            updateBuildCluster(),
284
            updateAdminsRoute(),
285
            batchUpdateAdminsRoute()
286
        ]);
287
    }
288
};
289

290
module.exports = pipelinesPlugin;
10✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc