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

Commit c9d9dad

Browse files
committed
Azure cleanup. Add user to team.
1 parent d7b5e6a commit c9d9dad

File tree

11 files changed

+161
-157
lines changed

11 files changed

+161
-157
lines changed

src/common/helper.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,26 @@ function convertGitLabError(err, message) {
166166
return apiError;
167167
}
168168

169+
/**
170+
* Convert azure api error.
171+
* @param {Error} err the azure api error
172+
* @param {String} message the error message
173+
* @returns {Error} converted error
174+
*/
175+
function convertAzureError(err, message) {
176+
let resMsg = `${message}. ${err.message}.\n`;
177+
const detail = _.get(err, 'response.body.message');
178+
if (detail) {
179+
resMsg += ` Detail: ${detail}`;
180+
}
181+
const apiError = new errors.ApiError(
182+
err.status || _.get(err, 'response.status', constants.SERVICE_ERROR_STATUS),
183+
_.get(err, 'response.body.message', constants.SERVICE_ERROR),
184+
resMsg
185+
);
186+
return apiError;
187+
}
188+
169189
/**
170190
* Ensure entity exists for given criteria. Return error if no result.
171191
* @param {Object} Model the mongoose model to query
@@ -270,6 +290,7 @@ module.exports = {
270290
buildController,
271291
convertGitHubError,
272292
convertGitLabError,
293+
convertAzureError,
273294
ensureExists,
274295
generateIdentifier,
275296
getProviderType,

src/config.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ const frontendConfigs = {
9393
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
9494
"TOPCODER_URL": "https://topcoder-dev.com",
9595
"GITHUB_TEAM_URL": "https://github.com/orgs/",
96-
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
96+
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
97+
"AZURE_TEAM_URL": "https://dev.azure.com/"
9798

9899
},
99100
"heroku":{
@@ -111,7 +112,8 @@ const frontendConfigs = {
111112
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
112113
"TOPCODER_URL": "https://topcoder-dev.com",
113114
"GITHUB_TEAM_URL": "https://github.com/orgs/",
114-
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
115+
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
116+
"AZURE_TEAM_URL": "https://dev.azure.com/"
115117
},
116118
"dev":{
117119
"JWT_V3_NAME":"v3jwt",
@@ -128,7 +130,8 @@ const frontendConfigs = {
128130
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
129131
"TOPCODER_URL": "https://topcoder-dev.com",
130132
"GITHUB_TEAM_URL": "https://github.com/orgs/",
131-
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
133+
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
134+
"AZURE_TEAM_URL": "https://dev.azure.com/"
132135
},
133136
"qa":{
134137
"JWT_V3_NAME":"v3jwt",
@@ -145,7 +148,8 @@ const frontendConfigs = {
145148
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
146149
"TOPCODER_URL": "https://topcoder-dev.com",
147150
"GITHUB_TEAM_URL": "https://github.com/orgs/",
148-
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
151+
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
152+
"AZURE_TEAM_URL": "https://dev.azure.com/"
149153
},
150154
"prod":{
151155
"JWT_V3_NAME":"v3jwt",
@@ -162,7 +166,8 @@ const frontendConfigs = {
162166
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
163167
"TOPCODER_URL": "https://topcoder-dev.com",
164168
"GITHUB_TEAM_URL": "https://github.com/orgs/",
165-
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
169+
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
170+
"AZURE_TEAM_URL": "https://dev.azure.com/"
166171
}
167172
};
168173

@@ -185,5 +190,6 @@ module.exports.frontendConfigs = {
185190
OWNER_LOGIN_AZURE_URL: process.env.OWNER_LOGIN_AZURE_URL || frontendConfigs[activeEnv].OWNER_LOGIN_AZURE_URL,
186191
TOPCODER_URL: process.env.TOPCODER_URL || frontendConfigs[activeEnv].TOPCODER_URL,
187192
GITHUB_TEAM_URL: process.env.GITHUB_TEAM_URL || frontendConfigs[activeEnv].GITHUB_TEAM_URL,
188-
GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || frontendConfigs[activeEnv].GITLAB_GROUP_URL
193+
GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || frontendConfigs[activeEnv].GITLAB_GROUP_URL,
194+
AZURE_TEAM_URL: process.env.AZURE_TEAM_URL || frontendConfigs[activeEnv].AZURE_TEAM_URL
189195
};

src/controllers/AzureController.js

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44

55
/**
6-
* This controller exposes Gitlab REST endpoints.
6+
* This controller exposes Azure REST endpoints.
77
*
88
* @author TCSCODER
99
* @version 1.0
@@ -17,12 +17,11 @@ const errors = require('../common/errors');
1717
const constants = require('../common/constants');
1818
const config = require('../config');
1919
const AzureService = require('../services/AzureService');
20-
const GitlabService = require('../services/GitlabService');
2120
const UserService = require('../services/UserService');
2221
const User = require('../models').User;
2322
const OwnerUserTeam = require('../models').OwnerUserTeam;
24-
// const UserMapping = require('../models').UserMapping;
25-
const UserGroupMapping = require('../models').UserGroupMapping;
23+
const UserMapping = require('../models').UserMapping;
24+
const UserTeamMapping = require('../models').UserTeamMapping;
2625

2726
const request = superagentPromise(superagent, Promise);
2827

@@ -40,7 +39,7 @@ async function ownerUserLogin(req, res) {
4039
if (!req.session.state) {
4140
req.session.state = helper.generateIdentifier();
4241
}
43-
// redirect to GitLab OAuth
42+
// redirect to Azure OAuth
4443
const callbackUri = `${config.WEBSITE_SECURE}${constants.AZURE_OWNER_CALLBACK_URL}`;
4544
res.redirect(`https://app.vssps.visualstudio.com/oauth2/authorize?client_id=${
4645
config.AZURE_APP_ID
@@ -50,7 +49,7 @@ async function ownerUserLogin(req, res) {
5049
}
5150

5251
/**
53-
* Owner user login callback, redirected by GitLab.
52+
* Owner user login callback, redirected by Azure.
5453
* @param {Object} req the request
5554
* @param {Object} res the response
5655
*/
@@ -104,7 +103,7 @@ async function ownerUserLoginCallback(req, res) {
104103
async function listOwnerUserTeams(req) {
105104
const user = await UserService.getAccessTokenByHandle(req.currentUser.handle, constants.USER_TYPES.AZURE);
106105
if (!user || !user.accessToken) {
107-
throw new errors.UnauthorizedError('You have not setup for Gitlab.');
106+
throw new errors.UnauthorizedError('You have not setup for Azure.');
108107
}
109108
return await AzureService.listOwnerUserTeams(user, req.query.page, req.query.perPage);
110109
}
@@ -136,7 +135,7 @@ async function addUserToTeam(req, res) {
136135
// store identifier to session, to be compared in callback
137136
req.session.identifier = identifier;
138137

139-
// redirect to GitLab OAuth
138+
// redirect to Azure OAuth
140139
const callbackUri = `${config.WEBSITE_SECURE}/api/${config.API_VERSION}/azure/normaluser/callback`;
141140
res.redirect(`https://app.vssps.visualstudio.com/oauth2/authorize?client_id=${
142141
config.AZURE_USER_APP_ID
@@ -146,7 +145,7 @@ async function addUserToTeam(req, res) {
146145
}
147146

148147
/**
149-
* Normal user callback, to be added to group. Redirected by GitLab.
148+
* Normal user callback, to be added to group. Redirected by Azure.
150149
* @param {Object} req the request
151150
* @param {Object} res the response
152151
*/
@@ -197,29 +196,15 @@ async function addUserToTeamCallback(req, res) {
197196
.end()
198197
.then((resp) => resp.body);
199198

200-
// PATCH https://vsaex.dev.azure.com/{organization}/_apis/userentitlements/{userId}?api-version=5.1-preview.2
201199
try {
202-
await request
203-
.patch(`https://vsaex.dev.azure.com/telagaid/_apis/userentitlements/${userProfile.id}?api-version=5.1-preview.2`)
200+
await request
201+
.patch(`https://vsaex.dev.azure.com/${team.organizationName}/_apis/UserEntitlements?doNotSendInviteForNewUsers=true&api-version=5.1-preview.3`)
204202
.send([{
205-
from: "",
203+
from: '',
206204
op: 0,
207-
path: "",
205+
path: `/${userProfile.id}/projectEntitlements/${team.githubOrgId}/teamRefs`,
208206
value: {
209-
projectEntitlements: {
210-
projectRef: {
211-
id: team.githubOrgId
212-
},
213-
teamRefs: [{
214-
id:team.teamId
215-
}]
216-
},
217-
user: {
218-
subjectKind: 'user',
219-
displayName: userProfile.emailAddress,
220-
principalName: userProfile.emailAddress,
221-
id: userProfile.id
222-
}
207+
id:team.teamId
223208
}
224209
}])
225210
.set('Content-Type', 'application/json-patch+json')
@@ -229,37 +214,70 @@ async function addUserToTeamCallback(req, res) {
229214
catch(err) {
230215
console.log(err); // eslint-disable-line no-console
231216
}
217+
218+
// associate azure username with TC username
219+
const mapping = await dbHelper.scanOne(UserMapping, {
220+
topcoderUsername: {eq: req.session.tcUsername},
221+
});
222+
if (mapping) {
223+
await dbHelper.update(UserMapping, mapping.id, {
224+
azureEmail: userProfile.emailAddress,
225+
azureUserId: userProfile.id
226+
});
227+
} else {
228+
await dbHelper.create(UserMapping, {
229+
id: helper.generateIdentifier(),
230+
topcoderUsername: req.session.tcUsername,
231+
azureEmail: userProfile.emailAddress,
232+
azureUserId: userProfile.id
233+
});
234+
}
235+
236+
const azureUserToTeamMapping = await dbHelper.scanOne(UserTeamMapping, {
237+
teamId: {eq: team.teamId},
238+
azureUserId: {eq: userProfile.id},
239+
});
240+
241+
if (!azureUserToTeamMapping) {
242+
await dbHelper.create(UserTeamMapping, {
243+
id: helper.generateIdentifier(),
244+
teamId: team.teamId,
245+
azureUserId: userProfile.id,
246+
azureProjectId: team.githubOrgId
247+
});
248+
}
249+
232250
// redirect to success page
233-
res.redirect(`${constants.USER_ADDED_TO_TEAM_SUCCESS_URL}/azure/path`);
251+
res.redirect(`${constants.USER_ADDED_TO_TEAM_SUCCESS_URL}/azure/${team.organizationName}_${team.githubOrgId}`);
234252
}
235253

236254

237255
/**
238-
* Delete users from a group.
256+
* Delete users from a team.
239257
* @param {Object} req the request
240258
* @param {Object} res the response
241259
*/
242260
async function deleteUsersFromTeam(req, res) {
243-
const groupId = req.params.id;
244-
let groupInDB;
261+
const teamId = req.params.id;
262+
let teamInDB;
245263
try {
246-
groupInDB = await helper.ensureExists(OwnerUserTeam, {groupId}, 'OwnerUserTeam');
264+
teamInDB = await helper.ensureExists(OwnerUserTeam, {teamId}, 'OwnerUserTeam');
247265
} catch (err) {
248266
if (!(err instanceof errors.NotFoundError)) {
249267
throw err;
250268
}
251269
}
252-
// If groupInDB not exists, then just return
253-
if (groupInDB) {
270+
// If teamInDB not exists, then just return
271+
if (teamInDB) {
254272
try {
255273
const ownerUser = await helper.ensureExists(User,
256-
{username: groupInDB.ownerUsername, type: constants.USER_TYPES.GITLAB, role: constants.USER_ROLES.OWNER}, 'User');
257-
await GitlabService.refreshGitlabUserAccessToken(ownerUser);
258-
const userGroupMappings = await dbHelper.scan(UserGroupMapping, {groupId});
274+
{username: teamInDB.ownerUsername, type: constants.USER_TYPES.AZURE, role: constants.USER_ROLES.OWNER}, 'User');
275+
await AzureService.refreshAzureUserAccessToken(ownerUser);
276+
const userTeamMappings = await dbHelper.scan(UserTeamMapping, {teamId});
259277
// eslint-disable-next-line no-restricted-syntax
260-
for (const userGroupMapItem of userGroupMappings) {
261-
await GitlabService.deleteUserFromGitlabGroup(ownerUser.accessToken, groupId, userGroupMapItem.gitlabUserId);
262-
await dbHelper.remove(UserGroupMapping, {id: userGroupMapItem.id});
278+
for (const userTeamMapItem of userTeamMappings) {
279+
await AzureService.deleteUserFromAzureTeam(ownerUser.accessToken, teamInDB, userTeamMapItem.azureUserId);
280+
await dbHelper.remove(UserTeamMapping, {id: userTeamMapItem.id});
263281
}
264282
} catch (err) {
265283
throw err;

src/front/src/app/git-access-control/access-control.service.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,15 @@ angular.module('topcoderX')
9595
});
9696
};
9797

98+
/**
99+
* remove all users from a azure team
100+
*
101+
*/
102+
service.removeAllAzureUsers = function (teamId) {
103+
return $http.delete(baseUrl + '/api/v1/azure/teams/' + teamId + '/users').then(function (response) {
104+
return response;
105+
});
106+
};
107+
98108
return service;
99109
}]);

src/front/src/app/members/member.controller.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ angular.module('topcoderX')
1111
const org = params[0];
1212
const team = url.replace(org, '').substring(1);
1313
$scope.link = $rootScope.appConfig.GITHUB_TEAM_URL + org + '/teams/' + team;
14-
} else {
14+
} else if (provider === 'github') {
1515
$scope.link = $rootScope.appConfig.GITLAB_GROUP_URL + url;
16+
} else if (provider === 'azure') {
17+
const params = url.split('_');
18+
$scope.link = $rootScope.appConfig.AZURE_TEAM_URL + params[0] + '/' + params[1];
1619
}
1720
};
1821
_getUrl($scope.provider, $stateParams.url);

src/front/src/app/members/member.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ <h2>{{title}}</h2>
1919
<p>You were successfully added to the group!</p>
2020
<a href="{{link}}">{{link}}</a>
2121
</div>
22+
<div class="text-center m-t-lg" ng-if="provider==='azure'">
23+
<p>You were successfully added to the Azure DevOps project team!</p>
24+
<a href="{{link}}">{{link}}</a>
25+
</div>
2226
</div>
2327
</div>
2428
</div>

src/front/src/app/projects/projects.controller.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,18 @@ angular.module('topcoderX')
6767
};
6868

6969
$scope.repoType = function (repo) {
70-
return (repo.toLocaleLowerCase().indexOf("gitlab") >= 0 ? "Gitlab" : "Github");
70+
if (repo.toLocaleLowerCase().indexOf("github") >= 0) {
71+
return "Github";
72+
}
73+
else if (repo.toLocaleLowerCase().indexOf("gitlab") >= 0) {
74+
return "Gitlab";
75+
}
76+
else if (repo.toLocaleLowerCase().indexOf("azure") >= 0) {
77+
return "Azure";
78+
}
79+
else {
80+
return "Other";
81+
}
7182
};
7283

7384
$scope.init = function () {

src/front/src/app/projects/projects.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ <h4>You don't have active projects right now. Please
5858
<tr>
5959
<th class="col-lg-2">Project Name</th>
6060
<th class="col-lg-2">Topcoder Direct ID</th>
61-
<th class="col-lg-2">Gitlab/Github Repo Url</th>
61+
<th class="col-lg-2">Service Provider</th>
6262
<th class="col-lg-2" ng-show="isAdminUser">Owner</th>
6363
<th class="col-lg-3" data-sort-ignore="true"></th>
6464
</tr>
@@ -117,7 +117,7 @@ <h4>You don't have active projects right now. Please
117117
<tr>
118118
<th class="col-lg-2">Project Name</th>
119119
<th class="col-lg-2">Topcoder Direct ID</th>
120-
<th class="col-lg-2">Gitlab/Github Repo Url</th>
120+
<th class="col-lg-2">Service Provider</th>
121121
<th class="col-lg-2" ng-show="isAdminUser">Owner</th>
122122
<th class="col-lg-2" data-sort-ignore="true"></th>
123123
</tr>

src/models/UserTeamMapping.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const schema = new Schema({
2323
},
2424
githubOrgId: {
2525
type: String,
26-
required: true,
26+
required: false,
2727
index: {
2828
global: true,
2929
project: true,
@@ -33,14 +33,16 @@ const schema = new Schema({
3333
},
3434
githubUserName: {
3535
type: String,
36-
required: true,
36+
required: false,
3737
index: {
3838
global: true,
3939
project: true,
4040
rangKey: 'id',
4141
name: 'GithubUserNameIndex',
4242
},
4343
},
44+
azureProjectId: { type: String, required: false },
45+
azureUserId: { type: String, required: false }
4446
});
4547

4648
module.exports = schema;

0 commit comments

Comments
 (0)