Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 894f30f

Browse files
committed
Github team access: Add member into organisation before add to team.
1 parent 9897bd5 commit 894f30f

File tree

7 files changed

+195
-4
lines changed

7 files changed

+195
-4
lines changed

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"heroku-postbuild": "gulp build",
2424
"create-tables": "CREATE_DB=true node scripts/create-update-tables.js",
2525
"migrate-user-mapping": "node scripts/migrate-user-mapping.js",
26+
"add-organisation": "node scripts/add-organisation.js",
2627
"log-repository-collisions": "node scripts/log-repository-collisions.js"
2728
},
2829
"dependencies": {

Diff for: scripts/add-organisation.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const dbHelper = require('../src/common/db-helper');
2+
const helper = require('../src/common/helper');
3+
const Organisation = require('../src/models').Organisation;
4+
5+
const args = process.argv;
6+
if (args.length < 5) {
7+
console.log('Please provide data. Example: npm run add-organisation MyOrganisation ownername PAT-Token');
8+
return;
9+
}
10+
const organisationName = args[2];
11+
const owner = args[3];
12+
const pat = args[4];
13+
14+
(async () => {
15+
const dbOrganisation = await dbHelper.queryOneOrganisation(Organisation, organisationName);
16+
if (dbOrganisation) {
17+
console.log(`Updating Organisation = ${organisationName} Owner = ${owner} PAT = ${pat}.`);
18+
await dbHelper.update(Organisation, dbOrganisation.id, {
19+
name: organisationName,
20+
owner,
21+
personalAccessToken: pat
22+
});
23+
}
24+
else {
25+
console.log(`Adding Organisation = ${organisationName} Owner = ${owner} PAT = ${pat}.`);
26+
await dbHelper.create(Organisation, {
27+
id: helper.generateIdentifier(),
28+
name: organisationName,
29+
owner,
30+
personalAccessToken: pat
31+
});
32+
}
33+
})();

Diff for: scripts/migrate-user-mapping.js

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const dbHelper = require('../src/common/db-helper');
44
const GithubUserMapping = require('../src/models').GithubUserMapping;
55
const GitlabUserMapping = require('../src/models').GitlabUserMapping;
66

7-
console.log(process.env.IS_LOCAL);
87
if (process.env.IS_LOCAL) {
98
AWS.config.update({
109
endpoint: 'http://localhost:8000'

Diff for: src/common/db-helper.js

+20
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,25 @@ async function removeUser(Model, username, type) {
367367
});
368368
}
369369

370+
/**
371+
* Get single data by query parameters
372+
* @param {Object} model The dynamoose model to query
373+
* @param {String} organisation The organisation name
374+
* @returns {Promise<void>}
375+
*/
376+
async function queryOneOrganisation(model, organisation) {
377+
return await new Promise((resolve, reject) => {
378+
model.queryOne('name').eq(organisation)
379+
.all()
380+
.exec((err, result) => {
381+
if (err) {
382+
logger.debug(`queryOneOrganisation. Error. ${err}`);
383+
return reject(err);
384+
}
385+
return resolve(result);
386+
});
387+
});
388+
}
370389

371390
module.exports = {
372391
getById,
@@ -379,6 +398,7 @@ module.exports = {
379398
queryOneActiveCopilotPayment,
380399
queryOneActiveProject,
381400
queryOneActiveProjectWithFilter,
401+
queryOneOrganisation,
382402
queryOneIssue,
383403
queryOneUserByType,
384404
queryOneUserByTypeAndRole,

Diff for: src/controllers/GithubController.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ async function addUserToTeam(req, res) {
127127
config.GITHUB_CLIENT_ID
128128
}&redirect_uri=${
129129
encodeURIComponent(callbackUri)
130+
}&scope=${
131+
encodeURIComponent('admin:org')
130132
}&state=${identifier}`);
131133
}
132134

@@ -156,15 +158,25 @@ async function addUserToTeamCallback(req, res) {
156158
throw new errors.UnauthorizedError('Github authorization failed.', result.body.error_description);
157159
}
158160
const token = result.body.access_token;
161+
162+
// get team details
163+
const teamDetails = await GithubService.getTeamDetails(team.ownerToken, team.teamId);
164+
const organisation = teamDetails.organization.login;
165+
166+
// Add member to organisation
167+
const addOrganisationResult = await GithubService.addOrganisationMember(organisation, token);
168+
console.log(`Add organisation member, state = ${addOrganisationResult.state}`); /* eslint-disable-line no-console */
169+
if (addOrganisationResult.state === 'pending') {
170+
const acceptInvitation = await GithubService.acceptOrganisationInvitation(organisation, token);
171+
console.log(`Accept organisation invitation by member, state = ${acceptInvitation.state}`); /* eslint-disable-line no-console */
172+
}
173+
159174
// add user to team
160175
console.log(`adding ${token} to ${team.teamId} with ${team.ownerToken}`); /* eslint-disable-line no-console */
161176
const githubUser = await GithubService.addTeamMember(team.teamId, team.ownerToken, token, team.accessLevel);
162177
// associate github username with TC username
163178
const mapping = await dbHelper.queryOneUserMappingByTCUsername(GithubUserMapping, req.session.tcUsername);
164179

165-
// get team details
166-
const teamDetails = await GithubService.getTeamDetails(team.ownerToken, team.teamId);
167-
168180
if (mapping) {
169181
await dbHelper.update(GithubUserMapping, mapping.id, {
170182
githubUsername: githubUser.username,

Diff for: src/models/Organisation.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* This defines organisation model.
3+
*/
4+
'use strict';
5+
6+
const dynamoose = require('dynamoose');
7+
8+
const Schema = dynamoose.Schema;
9+
10+
const schema = new Schema({
11+
id: {
12+
type: String,
13+
required: true,
14+
hashKey: true
15+
},
16+
name: {
17+
type: String,
18+
required: true,
19+
index: {
20+
global: true,
21+
project: true,
22+
rangKey: 'id',
23+
name: 'NameIndex'
24+
}
25+
},
26+
owner: {
27+
type: String,
28+
required: true
29+
},
30+
personalAccessToken: {
31+
type: String,
32+
required: true
33+
}
34+
});
35+
36+
module.exports = schema;

Diff for: src/services/GithubService.js

+90
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ const dbHelper = require('../common/db-helper');
1919
const User = require('../models').User;
2020
const GithubUserMapping = require('../models').GithubUserMapping;
2121
const OwnerUserTeam = require('../models').OwnerUserTeam;
22+
const Organisation = require('../models').Organisation;
2223
const errors = require('../common/errors');
24+
const superagent = require('superagent');
25+
const superagentPromise = require('superagent-promise');
26+
27+
const request = superagentPromise(superagent, Promise);
2328

2429
/**
2530
* Ensure the owner user is in database.
@@ -229,6 +234,89 @@ addTeamMember.schema = Joi.object().keys({
229234
accessLevel: Joi.string().required(),
230235
});
231236

237+
/**
238+
* Add organisation member.
239+
* @param {String} organisation the organisation name
240+
* @param {String} normalUserToken the normal user token
241+
* @returns {Promise} the promise result
242+
*/
243+
async function addOrganisationMember(organisation, normalUserToken) {
244+
let state;
245+
try {
246+
const dbOrganisation = await dbHelper.queryOneOrganisation(Organisation, organisation);
247+
if (!dbOrganisation) {
248+
console.log(`Personal access token not found for organisation ${organisation}.`); /* eslint-disable-line no-console */
249+
return {};
250+
}
251+
const githubNormalUser = new GitHub({
252+
token: normalUserToken,
253+
});
254+
const normalUser = await githubNormalUser.getUser().getProfile();
255+
const username = normalUser.data.login;
256+
const base64PAT = Buffer.from(`${dbOrganisation.owner}:${dbOrganisation.personalAccessToken}`).toString('base64');
257+
const result = await request
258+
.put(`https://api.github.com/orgs/${organisation}/memberships/${username}`)
259+
.send({role: 'member'})
260+
.set('User-Agent', 'superagent')
261+
.set('Accept', 'application/vnd.github.v3+json')
262+
.set('Authorization', `Basic ${base64PAT}`)
263+
.end();
264+
state = _.get(result, 'body.state');
265+
} catch (err) {
266+
// if error is already exists discard
267+
if (_.chain(err).get('body.errors').countBy({
268+
code: 'already_exists',
269+
}).get('true')
270+
.isUndefined()
271+
.value()) {
272+
throw helper.convertGitHubError(err, 'Failed to add organisation member');
273+
}
274+
}
275+
// return its state
276+
return {state};
277+
}
278+
279+
addOrganisationMember.schema = Joi.object().keys({
280+
organisation: Joi.string().required(),
281+
normalUserToken: Joi.string().required()
282+
});
283+
284+
/**
285+
* Accept organisation invitation by member.
286+
* @param {String} organisation the organisation name
287+
* @param {String} normalUserToken the normal user token
288+
* @returns {Promise} the promise result
289+
*/
290+
async function acceptOrganisationInvitation(organisation, normalUserToken) {
291+
let state;
292+
try {
293+
const result = await request
294+
.patch(`https://api.github.com/user/memberships/orgs/${organisation}`)
295+
.send({state: 'active'})
296+
.set('User-Agent', 'superagent')
297+
.set('Accept', 'application/vnd.github.v3+json')
298+
.set('Authorization', `token ${normalUserToken}`)
299+
.end();
300+
state = _.get(result, 'body.state');
301+
} catch (err) {
302+
// if error is already exists discard
303+
if (_.chain(err).get('body.errors').countBy({
304+
code: 'already_exists',
305+
}).get('true')
306+
.isUndefined()
307+
.value()) {
308+
throw helper.convertGitHubError(err, 'Failed to accept organisation invitation');
309+
}
310+
}
311+
// return its state
312+
return {state};
313+
}
314+
315+
acceptOrganisationInvitation.schema = Joi.object().keys({
316+
organisation: Joi.string().required(),
317+
normalUserToken: Joi.string().required()
318+
});
319+
232320
/**
233321
* Gets the user id by username
234322
* @param {string} username the username
@@ -320,6 +408,8 @@ module.exports = {
320408
getUserIdByUsername,
321409
getTeamDetails,
322410
deleteUserFromGithubTeam,
411+
addOrganisationMember,
412+
acceptOrganisationInvitation
323413
};
324414

325415
helper.buildService(module.exports);

0 commit comments

Comments
 (0)