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

Azure cleanup. Add user to team. #312

Merged
merged 1 commit into from
Apr 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/common/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,26 @@ function convertGitLabError(err, message) {
return apiError;
}

/**
* Convert azure api error.
* @param {Error} err the azure api error
* @param {String} message the error message
* @returns {Error} converted error
*/
function convertAzureError(err, message) {
let resMsg = `${message}. ${err.message}.\n`;
const detail = _.get(err, 'response.body.message');
if (detail) {
resMsg += ` Detail: ${detail}`;
}
const apiError = new errors.ApiError(
err.status || _.get(err, 'response.status', constants.SERVICE_ERROR_STATUS),
_.get(err, 'response.body.message', constants.SERVICE_ERROR),
resMsg
);
return apiError;
}

/**
* Ensure entity exists for given criteria. Return error if no result.
* @param {Object} Model the mongoose model to query
Expand Down Expand Up @@ -270,6 +290,7 @@ module.exports = {
buildController,
convertGitHubError,
convertGitLabError,
convertAzureError,
ensureExists,
generateIdentifier,
getProviderType,
Expand Down
18 changes: 12 additions & 6 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ const frontendConfigs = {
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
"TOPCODER_URL": "https://topcoder-dev.com",
"GITHUB_TEAM_URL": "https://github.com/orgs/",
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
"AZURE_TEAM_URL": "https://dev.azure.com/"

},
"heroku":{
Expand All @@ -111,7 +112,8 @@ const frontendConfigs = {
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
"TOPCODER_URL": "https://topcoder-dev.com",
"GITHUB_TEAM_URL": "https://github.com/orgs/",
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
"AZURE_TEAM_URL": "https://dev.azure.com/"
},
"dev":{
"JWT_V3_NAME":"v3jwt",
Expand All @@ -128,7 +130,8 @@ const frontendConfigs = {
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
"TOPCODER_URL": "https://topcoder-dev.com",
"GITHUB_TEAM_URL": "https://github.com/orgs/",
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
"AZURE_TEAM_URL": "https://dev.azure.com/"
},
"qa":{
"JWT_V3_NAME":"v3jwt",
Expand All @@ -145,7 +148,8 @@ const frontendConfigs = {
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
"TOPCODER_URL": "https://topcoder-dev.com",
"GITHUB_TEAM_URL": "https://github.com/orgs/",
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
"AZURE_TEAM_URL": "https://dev.azure.com/"
},
"prod":{
"JWT_V3_NAME":"v3jwt",
Expand All @@ -162,7 +166,8 @@ const frontendConfigs = {
"OWNER_LOGIN_AZURE_URL":"/api/v1/azure/owneruser/login",
"TOPCODER_URL": "https://topcoder-dev.com",
"GITHUB_TEAM_URL": "https://github.com/orgs/",
"GITLAB_GROUP_URL": "https://gitlab.com/groups/"
"GITLAB_GROUP_URL": "https://gitlab.com/groups/",
"AZURE_TEAM_URL": "https://dev.azure.com/"
}
};

Expand All @@ -185,5 +190,6 @@ module.exports.frontendConfigs = {
OWNER_LOGIN_AZURE_URL: process.env.OWNER_LOGIN_AZURE_URL || frontendConfigs[activeEnv].OWNER_LOGIN_AZURE_URL,
TOPCODER_URL: process.env.TOPCODER_URL || frontendConfigs[activeEnv].TOPCODER_URL,
GITHUB_TEAM_URL: process.env.GITHUB_TEAM_URL || frontendConfigs[activeEnv].GITHUB_TEAM_URL,
GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || frontendConfigs[activeEnv].GITLAB_GROUP_URL
GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || frontendConfigs[activeEnv].GITLAB_GROUP_URL,
AZURE_TEAM_URL: process.env.AZURE_TEAM_URL || frontendConfigs[activeEnv].AZURE_TEAM_URL
};
100 changes: 59 additions & 41 deletions src/controllers/AzureController.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

/**
* This controller exposes Gitlab REST endpoints.
* This controller exposes Azure REST endpoints.
*
* @author TCSCODER
* @version 1.0
Expand All @@ -17,12 +17,11 @@ const errors = require('../common/errors');
const constants = require('../common/constants');
const config = require('../config');
const AzureService = require('../services/AzureService');
const GitlabService = require('../services/GitlabService');
const UserService = require('../services/UserService');
const User = require('../models').User;
const OwnerUserTeam = require('../models').OwnerUserTeam;
// const UserMapping = require('../models').UserMapping;
const UserGroupMapping = require('../models').UserGroupMapping;
const UserMapping = require('../models').UserMapping;
const UserTeamMapping = require('../models').UserTeamMapping;

const request = superagentPromise(superagent, Promise);

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

/**
* Owner user login callback, redirected by GitLab.
* Owner user login callback, redirected by Azure.
* @param {Object} req the request
* @param {Object} res the response
*/
Expand Down Expand Up @@ -104,7 +103,7 @@ async function ownerUserLoginCallback(req, res) {
async function listOwnerUserTeams(req) {
const user = await UserService.getAccessTokenByHandle(req.currentUser.handle, constants.USER_TYPES.AZURE);
if (!user || !user.accessToken) {
throw new errors.UnauthorizedError('You have not setup for Gitlab.');
throw new errors.UnauthorizedError('You have not setup for Azure.');
}
return await AzureService.listOwnerUserTeams(user, req.query.page, req.query.perPage);
}
Expand Down Expand Up @@ -136,7 +135,7 @@ async function addUserToTeam(req, res) {
// store identifier to session, to be compared in callback
req.session.identifier = identifier;

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

/**
* Normal user callback, to be added to group. Redirected by GitLab.
* Normal user callback, to be added to group. Redirected by Azure.
* @param {Object} req the request
* @param {Object} res the response
*/
Expand Down Expand Up @@ -197,29 +196,15 @@ async function addUserToTeamCallback(req, res) {
.end()
.then((resp) => resp.body);

// PATCH https://vsaex.dev.azure.com/{organization}/_apis/userentitlements/{userId}?api-version=5.1-preview.2
try {
await request
.patch(`https://vsaex.dev.azure.com/telagaid/_apis/userentitlements/${userProfile.id}?api-version=5.1-preview.2`)
await request
.patch(`https://vsaex.dev.azure.com/${team.organizationName}/_apis/UserEntitlements?doNotSendInviteForNewUsers=true&api-version=5.1-preview.3`)
.send([{
from: "",
from: '',
op: 0,
path: "",
path: `/${userProfile.id}/projectEntitlements/${team.githubOrgId}/teamRefs`,
value: {
projectEntitlements: {
projectRef: {
id: team.githubOrgId
},
teamRefs: [{
id:team.teamId
}]
},
user: {
subjectKind: 'user',
displayName: userProfile.emailAddress,
principalName: userProfile.emailAddress,
id: userProfile.id
}
id:team.teamId
}
}])
.set('Content-Type', 'application/json-patch+json')
Expand All @@ -229,37 +214,70 @@ async function addUserToTeamCallback(req, res) {
catch(err) {
console.log(err); // eslint-disable-line no-console
}

// associate azure username with TC username
const mapping = await dbHelper.scanOne(UserMapping, {
topcoderUsername: {eq: req.session.tcUsername},
});
if (mapping) {
await dbHelper.update(UserMapping, mapping.id, {
azureEmail: userProfile.emailAddress,
azureUserId: userProfile.id
});
} else {
await dbHelper.create(UserMapping, {
id: helper.generateIdentifier(),
topcoderUsername: req.session.tcUsername,
azureEmail: userProfile.emailAddress,
azureUserId: userProfile.id
});
}

const azureUserToTeamMapping = await dbHelper.scanOne(UserTeamMapping, {
teamId: {eq: team.teamId},
azureUserId: {eq: userProfile.id},
});

if (!azureUserToTeamMapping) {
await dbHelper.create(UserTeamMapping, {
id: helper.generateIdentifier(),
teamId: team.teamId,
azureUserId: userProfile.id,
azureProjectId: team.githubOrgId
});
}

// redirect to success page
res.redirect(`${constants.USER_ADDED_TO_TEAM_SUCCESS_URL}/azure/path`);
res.redirect(`${constants.USER_ADDED_TO_TEAM_SUCCESS_URL}/azure/${team.organizationName}_${team.githubOrgId}`);
}


/**
* Delete users from a group.
* Delete users from a team.
* @param {Object} req the request
* @param {Object} res the response
*/
async function deleteUsersFromTeam(req, res) {
const groupId = req.params.id;
let groupInDB;
const teamId = req.params.id;
let teamInDB;
try {
groupInDB = await helper.ensureExists(OwnerUserTeam, {groupId}, 'OwnerUserTeam');
teamInDB = await helper.ensureExists(OwnerUserTeam, {teamId}, 'OwnerUserTeam');
} catch (err) {
if (!(err instanceof errors.NotFoundError)) {
throw err;
}
}
// If groupInDB not exists, then just return
if (groupInDB) {
// If teamInDB not exists, then just return
if (teamInDB) {
try {
const ownerUser = await helper.ensureExists(User,
{username: groupInDB.ownerUsername, type: constants.USER_TYPES.GITLAB, role: constants.USER_ROLES.OWNER}, 'User');
await GitlabService.refreshGitlabUserAccessToken(ownerUser);
const userGroupMappings = await dbHelper.scan(UserGroupMapping, {groupId});
{username: teamInDB.ownerUsername, type: constants.USER_TYPES.AZURE, role: constants.USER_ROLES.OWNER}, 'User');
await AzureService.refreshAzureUserAccessToken(ownerUser);
const userTeamMappings = await dbHelper.scan(UserTeamMapping, {teamId});
// eslint-disable-next-line no-restricted-syntax
for (const userGroupMapItem of userGroupMappings) {
await GitlabService.deleteUserFromGitlabGroup(ownerUser.accessToken, groupId, userGroupMapItem.gitlabUserId);
await dbHelper.remove(UserGroupMapping, {id: userGroupMapItem.id});
for (const userTeamMapItem of userTeamMappings) {
await AzureService.deleteUserFromAzureTeam(ownerUser.accessToken, teamInDB, userTeamMapItem.azureUserId);
await dbHelper.remove(UserTeamMapping, {id: userTeamMapItem.id});
}
} catch (err) {
throw err;
Expand Down
10 changes: 10 additions & 0 deletions src/front/src/app/git-access-control/access-control.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,15 @@ angular.module('topcoderX')
});
};

/**
* remove all users from a azure team
*
*/
service.removeAllAzureUsers = function (teamId) {
return $http.delete(baseUrl + '/api/v1/azure/teams/' + teamId + '/users').then(function (response) {
return response;
});
};

return service;
}]);
5 changes: 4 additions & 1 deletion src/front/src/app/members/member.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ angular.module('topcoderX')
const org = params[0];
const team = url.replace(org, '').substring(1);
$scope.link = $rootScope.appConfig.GITHUB_TEAM_URL + org + '/teams/' + team;
} else {
} else if (provider === 'github') {
$scope.link = $rootScope.appConfig.GITLAB_GROUP_URL + url;
} else if (provider === 'azure') {
const params = url.split('_');
$scope.link = $rootScope.appConfig.AZURE_TEAM_URL + params[0] + '/' + params[1];
}
};
_getUrl($scope.provider, $stateParams.url);
Expand Down
4 changes: 4 additions & 0 deletions src/front/src/app/members/member.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ <h2>{{title}}</h2>
<p>You were successfully added to the group!</p>
<a href="{{link}}">{{link}}</a>
</div>
<div class="text-center m-t-lg" ng-if="provider==='azure'">
<p>You were successfully added to the Azure DevOps project team!</p>
<a href="{{link}}">{{link}}</a>
</div>
</div>
</div>
</div>
Expand Down
13 changes: 12 additions & 1 deletion src/front/src/app/projects/projects.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,18 @@ angular.module('topcoderX')
};

$scope.repoType = function (repo) {
return (repo.toLocaleLowerCase().indexOf("gitlab") >= 0 ? "Gitlab" : "Github");
if (repo.toLocaleLowerCase().indexOf("github") >= 0) {
return "Github";
}
else if (repo.toLocaleLowerCase().indexOf("gitlab") >= 0) {
return "Gitlab";
}
else if (repo.toLocaleLowerCase().indexOf("azure") >= 0) {
return "Azure";
}
else {
return "Other";
}
};

$scope.init = function () {
Expand Down
4 changes: 2 additions & 2 deletions src/front/src/app/projects/projects.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ <h4>You don't have active projects right now. Please
<tr>
<th class="col-lg-2">Project Name</th>
<th class="col-lg-2">Topcoder Direct ID</th>
<th class="col-lg-2">Gitlab/Github Repo Url</th>
<th class="col-lg-2">Service Provider</th>
<th class="col-lg-2" ng-show="isAdminUser">Owner</th>
<th class="col-lg-3" data-sort-ignore="true"></th>
</tr>
Expand Down Expand Up @@ -117,7 +117,7 @@ <h4>You don't have active projects right now. Please
<tr>
<th class="col-lg-2">Project Name</th>
<th class="col-lg-2">Topcoder Direct ID</th>
<th class="col-lg-2">Gitlab/Github Repo Url</th>
<th class="col-lg-2">Service Provider</th>
<th class="col-lg-2" ng-show="isAdminUser">Owner</th>
<th class="col-lg-2" data-sort-ignore="true"></th>
</tr>
Expand Down
6 changes: 4 additions & 2 deletions src/models/UserTeamMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const schema = new Schema({
},
githubOrgId: {
type: String,
required: true,
required: false,
index: {
global: true,
project: true,
Expand All @@ -33,14 +33,16 @@ const schema = new Schema({
},
githubUserName: {
type: String,
required: true,
required: false,
index: {
global: true,
project: true,
rangKey: 'id',
name: 'GithubUserNameIndex',
},
},
azureProjectId: { type: String, required: false },
azureUserId: { type: String, required: false }
});

module.exports = schema;
Loading