diff --git a/src/common/constants.js b/src/common/constants.js index c3aff1d..a1b06ed 100644 --- a/src/common/constants.js +++ b/src/common/constants.js @@ -30,8 +30,7 @@ const USER_ROLES = { // The user types const USER_TYPES = { GITHUB: 'github', - GITLAB: 'gitlab', - AZURE: 'azure' + GITLAB: 'gitlab' }; // The default page size for Gitlab API @@ -50,15 +49,8 @@ const GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION = 3600 * 24 * 14; // The Gitlab refresh token time in seconds before expiration const GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION = 300; -// The Azure access token default expiration in seconds -const AZURE_ACCESS_TOKEN_DEFAULT_EXPIRATION = 3600 * 24 * 14; - -// The Azure refresh token time in seconds before expiration -const AZURE_REFRESH_TOKEN_BEFORE_EXPIRATION = 300; - const GITHUB_OWNER_CALLBACK_URL = '/api/v1/github/owneruser/callback'; const GITLAB_OWNER_CALLBACK_URL = '/api/v1/gitlab/owneruser/callback'; -const AZURE_OWNER_CALLBACK_URL = '/api/v1/azure/owneruser/callback'; const OWNER_USER_LOGIN_SUCCESS_URL = '/#!/app/settings'; const USER_ADDED_TO_TEAM_SUCCESS_URL = '/#!/members'; @@ -78,11 +70,8 @@ module.exports = { GITLAB_DEFAULT_GROUP_ACCESS_LEVEL, GITLAB_ACCESS_TOKEN_DEFAULT_EXPIRATION, GITLAB_REFRESH_TOKEN_BEFORE_EXPIRATION, - AZURE_ACCESS_TOKEN_DEFAULT_EXPIRATION, - AZURE_REFRESH_TOKEN_BEFORE_EXPIRATION, GITHUB_OWNER_CALLBACK_URL, GITLAB_OWNER_CALLBACK_URL, - AZURE_OWNER_CALLBACK_URL, OWNER_USER_LOGIN_SUCCESS_URL, USER_ADDED_TO_TEAM_SUCCESS_URL, TC_LOGIN_CALLBACK_URL, diff --git a/src/common/helper.js b/src/common/helper.js index a8b5771..6aa6c62 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -159,26 +159,6 @@ 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 @@ -216,7 +196,7 @@ async function ensureExists(Model, criteria, modelName) { async function getProviderType(repoUrl) { const parsedDomain = await parseDomain(repoUrl); if (!parsedDomain || !parsedDomain.domain || - (parsedDomain.domain !== 'github' && parsedDomain.domain !== 'gitlab' && parsedDomain.domain !== 'azure')) { + (parsedDomain.domain !== 'github' && parsedDomain.domain !== 'gitlab')) { throw new ValidationError('Invalid git repo url'); } return parsedDomain.domain; @@ -237,21 +217,15 @@ async function getProjectCopilotOrOwner(models, project, provider, isCopilot) { if (!userMapping || (provider === 'github' && !userMapping.githubUserId) - || (provider === 'gitlab' && !userMapping.gitlabUserId) - || (provider === 'azure' && !userMapping.azureUserId)) { + || (provider === 'gitlab' && !userMapping.gitlabUserId)) { throw new Error(`Couldn't find ${isCopilot ? 'copilot' : 'owner'} username for '${provider}' for this repository.`); } let user = await dbHelper.scanOne(models.User, { username: provider === 'github' ? userMapping.githubUsername : // eslint-disable-line no-nested-ternary - provider === 'gitlab' ? userMapping.gitlabUsername : userMapping.azureEmail, + userMapping.gitlabUsername, type: provider, }); - - if (provider === 'azure') { - const azureService = require('../services/AzureService'); // eslint-disable-line global-require - user = azureService.refreshAzureUserAccessToken(user); - } return user; } @@ -283,7 +257,6 @@ module.exports = { buildController, convertGitHubError, convertGitLabError, - convertAzureError, ensureExists, generateIdentifier, getProviderType, diff --git a/src/config.js b/src/config.js index a6377d4..0ba1df4 100644 --- a/src/config.js +++ b/src/config.js @@ -19,17 +19,11 @@ module.exports = { GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET, GITLAB_CLIENT_ID: process.env.GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET: process.env.GITLAB_CLIENT_SECRET, - AZURE_APP_ID: process.env.AZURE_APP_ID, - AZURE_CLIENT_SECRET: process.env.AZURE_CLIENT_SECRET, - AZURE_USER_APP_ID: process.env.AZURE_APP_ID, - AZURE_USER_CLIENT_SECRET: process.env.AZURE_USER_CLIENT_SECRET, // used as base to construct various URLs WEBSITE: process.env.WEBSITE || 'http://topcoderx.topcoder-dev.com', WEBSITE_SECURE: process.env.WEBSITE_SECURE || 'https://topcoderx.topcoder-dev.com', GITLAB_API_BASE_URL: process.env.GITLAB_API_BASE_URL || 'https://gitlab.com', - AZURE_API_BASE_URL: process.env.AZURE_API_BASE_URL || 'https://app.vssps.visualstudio.com', - AZURE_DEVOPS_API_BASE_URL: process.env.AZURE_DEVOPS_API_BASE_URL || 'https://dev.azure.com', // kafka configuration TOPIC: process.env.TOPIC || 'tc-x-events', @@ -90,12 +84,9 @@ const frontendConfigs = { "DIRECT_URL_BASE": "https://www.topcoder-dev/direct/projectOverview?formData.projectId=", "OWNER_LOGIN_GITHUB_URL":"/api/v1/github/owneruser/login", "OWNER_LOGIN_GITLAB_URL":"/api/v1/gitlab/owneruser/login", - "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/", - "AZURE_TEAM_URL": "https://dev.azure.com/" - + "GITLAB_GROUP_URL": "https://gitlab.com/groups/" }, "heroku":{ "JWT_V3_NAME":"v3jwt", @@ -109,11 +100,9 @@ const frontendConfigs = { "DIRECT_URL_BASE": "https://www.topcoder-dev.com/direct/projectOverview?formData.projectId=", "OWNER_LOGIN_GITHUB_URL":"/api/v1/github/owneruser/login", "OWNER_LOGIN_GITLAB_URL":"/api/v1/gitlab/owneruser/login", - "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/", - "AZURE_TEAM_URL": "https://dev.azure.com/" + "GITLAB_GROUP_URL": "https://gitlab.com/groups/" }, "dev":{ "JWT_V3_NAME":"v3jwt", @@ -127,11 +116,9 @@ const frontendConfigs = { "DIRECT_URL_BASE": "https://www.topcoder-dev.com/direct/projectOverview?formData.projectId=", "OWNER_LOGIN_GITHUB_URL":"/api/v1/github/owneruser/login", "OWNER_LOGIN_GITLAB_URL":"/api/v1/gitlab/owneruser/login", - "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/", - "AZURE_TEAM_URL": "https://dev.azure.com/" + "GITLAB_GROUP_URL": "https://gitlab.com/groups/" }, "qa":{ "JWT_V3_NAME":"v3jwt", @@ -145,11 +132,9 @@ const frontendConfigs = { "DIRECT_URL_BASE": "https://www.topcoder-dev.com/direct/projectOverview?formData.projectId=", "OWNER_LOGIN_GITHUB_URL":"/api/v1/github/owneruser/login", "OWNER_LOGIN_GITLAB_URL":"/api/v1/gitlab/owneruser/login", - "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/", - "AZURE_TEAM_URL": "https://dev.azure.com/" + "GITLAB_GROUP_URL": "https://gitlab.com/groups/" }, "prod":{ "JWT_V3_NAME":"v3jwt", @@ -163,11 +148,9 @@ const frontendConfigs = { "DIRECT_URL_BASE": "https://www.topcoder.com/direct/projectOverview?formData.projectId=", "OWNER_LOGIN_GITHUB_URL":"/api/v1/github/owneruser/login", "OWNER_LOGIN_GITLAB_URL":"/api/v1/gitlab/owneruser/login", - "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/", - "AZURE_TEAM_URL": "https://dev.azure.com/" + "GITLAB_GROUP_URL": "https://gitlab.com/groups/" } }; @@ -187,9 +170,7 @@ module.exports.frontendConfigs = { DIRECT_URL_BASE: process.env.DIRECT_URL_BASE || frontendConfigs[activeEnv].DIRECT_URL_BASE, OWNER_LOGIN_GITHUB_URL: process.env.OWNER_LOGIN_GITHUB_URL || frontendConfigs[activeEnv].OWNER_LOGIN_GITHUB_URL, OWNER_LOGIN_GITLAB_URL: process.env.OWNER_LOGIN_GITLAB_URL || frontendConfigs[activeEnv].OWNER_LOGIN_GITLAB_URL, - 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, - AZURE_TEAM_URL: process.env.AZURE_TEAM_URL || frontendConfigs[activeEnv].AZURE_TEAM_URL + GITLAB_GROUP_URL: process.env.GITLAB_GROUP_URL || frontendConfigs[activeEnv].GITLAB_GROUP_URL }; \ No newline at end of file diff --git a/src/controllers/AzureController.js b/src/controllers/AzureController.js deleted file mode 100644 index a257e0a..0000000 --- a/src/controllers/AzureController.js +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2017 TopCoder, Inc. All rights reserved. - */ - -/** - * This controller exposes Azure REST endpoints. - * - * @author TCSCODER - * @version 1.0 - */ -// const _ = require('lodash'); -const superagent = require('superagent'); -const superagentPromise = require('superagent-promise'); -const helper = require('../common/helper'); -const dbHelper = require('../common/db-helper'); -const errors = require('../common/errors'); -const constants = require('../common/constants'); -const config = require('../config'); -const AzureService = require('../services/AzureService'); -const UserService = require('../services/UserService'); -const User = require('../models').User; -const OwnerUserTeam = require('../models').OwnerUserTeam; -const UserMapping = require('../models').UserMapping; -const UserTeamMapping = require('../models').UserTeamMapping; - -const request = superagentPromise(superagent, Promise); - -// milliseconds per second -const MS_PER_SECOND = 1000; - -/** - * Owner user login. - * @param {Object} req the request - * @param {Object} res the response - */ -async function ownerUserLogin(req, res) { - // generate an identifier if not present, - // the identifier is used as OAuth state - if (!req.session.state) { - req.session.state = helper.generateIdentifier(); - } - // 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 - }&redirect_uri=${ - encodeURIComponent(callbackUri) - }&response_type=Assertion&state=${req.session.state}&scope=vso.identity_manage vso.memberentitlementmanagement_write vso.notification_manage vso.profile_write vso.project_manage vso.wiki_write vso.work_full`); -} - -/** - * Owner user login callback, redirected by Azure. - * @param {Object} req the request - * @param {Object} res the response - */ -async function ownerUserLoginCallback(req, res) { - if (!req.session.state || req.query.state !== req.session.state) { - throw new errors.ForbiddenError('Invalid state.'); - } - const code = req.query.code; - if (!code) { - throw new errors.ValidationError('Missing code.'); - } - - // exchange code to get token - const result = await request - .post('https://app.vssps.visualstudio.com/oauth2/token') - .send({ - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: encodeURIComponent(config.AZURE_CLIENT_SECRET), - assertion: encodeURIComponent(code), - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - redirect_uri: `${config.WEBSITE_SECURE}${constants.AZURE_OWNER_CALLBACK_URL}`, - }) - .set('Content-Type', 'application/x-www-form-urlencoded') - .end(); - const topcoderUsername = req.currentUser.handle; - const accessToken = result.body.access_token; - const refreshToken = result.body.refresh_token; - const expiresIn = result.body.expires_in || constants.AZURE_ACCESS_TOKEN_DEFAULT_EXPIRATION; - - // ensure the user is valid owner user - const ownerUser = await AzureService.ensureOwnerUser(accessToken, topcoderUsername); - // save user token data - await dbHelper.update(User, ownerUser.id, { - accessToken, - accessTokenExpiration: new Date(new Date().getTime() + expiresIn * MS_PER_SECOND), - refreshToken, - }); - - // refresh token periodically - // store username to session - req.session.azureOwnerUsername = ownerUser.username; - // redirect to success page - res.redirect(constants.OWNER_USER_LOGIN_SUCCESS_URL); -} - -/** - * List teams of owner user. - * @param {Object} req the request - * @returns {Object} the owner user groups - */ -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 Azure.'); - } - return await AzureService.listOwnerUserTeams(user, req.query.page, req.query.perPage); -} - -/** - * Get group registration URL. - * @param {Object} req the request - * @returns {Object} the group registration URL - */ -async function getTeamRegistrationUrl(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 Azure.'); - } - return await AzureService.getTeamRegistrationUrl(user.accessToken, user.username, req.params.id, - req.params.orgname, req.params.projectId); -} - -/** - * Add user to group. - * @param {Object} req the request - * @param {Object} res the response - */ -async function addUserToTeam(req, res) { - const identifier = req.params.identifier; - // validate the identifier - await helper.ensureExists(OwnerUserTeam, {identifier}, 'OwnerUserTeam'); - - // store identifier to session, to be compared in callback - req.session.identifier = identifier; - - // 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 - }&redirect_uri=${ - encodeURIComponent(callbackUri) - }&response_type=Assertion&state=${identifier}&scope=vso.profile`); -} - -/** - * Normal user callback, to be added to group. Redirected by Azure. - * @param {Object} req the request - * @param {Object} res the response - */ -async function addUserToTeamCallback(req, res) { - if (req.query.error_description) { - throw new errors.ForbiddenError(req.query.error_description.replace(/\+/g, ' ')); - } - if (!req.session.identifier || req.query.state !== req.session.identifier) { - throw new errors.ForbiddenError('Invalid state.'); - } - const identifier = req.session.identifier; - const code = req.query.code; - if (!code) { - throw new errors.ValidationError('Missing code.'); - } - const team = await helper.ensureExists(OwnerUserTeam, {identifier}, 'OwnerUserTeam'); - - if (!team) { - throw new errors.NotFoundError('The group is not found or not accessible.'); - } - - // get owner user - const ownerUser = await helper.ensureExists(User, - {username: team.ownerUsername, type: constants.USER_TYPES.AZURE, role: constants.USER_ROLES.OWNER}, 'User'); - - if (!ownerUser) { - throw new errors.NotFoundError('The owner user is not found or not accessible.'); - } - - await AzureService.refreshAzureUserAccessToken(ownerUser); - - const result = await request - .post('https://app.vssps.visualstudio.com/oauth2/token') - .send({ - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: encodeURIComponent(config.AZURE_USER_CLIENT_SECRET), - assertion: encodeURIComponent(code), - grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', - redirect_uri: `${config.WEBSITE_SECURE}/api/${config.API_VERSION}/azure/normaluser/callback`, - }) - .set('Content-Type', 'application/x-www-form-urlencoded') - .end(); - const token = result.body.access_token; - - const userProfile = await request - .get(`${config.AZURE_API_BASE_URL}/_apis/profile/profiles/me?api-version=5.1`) - .set('Authorization', `Bearer ${token}`) - .end() - .then((resp) => resp.body); - - try { - await request - .patch(`https://vsaex.dev.azure.com/${team.organizationName}/_apis/UserEntitlements?doNotSendInviteForNewUsers=true&api-version=5.1-preview.3`) - .send([{ - from: '', - op: 0, - path: `/${userProfile.id}/projectEntitlements/${team.githubOrgId}/teamRefs`, - value: { - id:team.teamId - } - }]) - .set('Content-Type', 'application/json-patch+json') - .set('Authorization', `Bearer ${team.ownerToken}`) - .end(); - } - 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/${team.organizationName}_${team.githubOrgId}`); -} - - -/** - * Delete users from a team. - * @param {Object} req the request - * @param {Object} res the response - */ -async function deleteUsersFromTeam(req, res) { - const teamId = req.params.id; - let teamInDB; - try { - teamInDB = await helper.ensureExists(OwnerUserTeam, {teamId}, 'OwnerUserTeam'); - } catch (err) { - if (!(err instanceof errors.NotFoundError)) { - throw err; - } - } - // If teamInDB not exists, then just return - if (teamInDB) { - try { - const ownerUser = await helper.ensureExists(User, - {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 userTeamMapItem of userTeamMappings) { - await AzureService.deleteUserFromAzureTeam(ownerUser.accessToken, teamInDB, userTeamMapItem.azureUserId); - await dbHelper.remove(UserTeamMapping, {id: userTeamMapItem.id}); - } - } catch (err) { - throw err; - } - } - res.send({}); -} - -module.exports = { - ownerUserLogin, - ownerUserLoginCallback, - listOwnerUserTeams, - getTeamRegistrationUrl, - addUserToTeam, - addUserToTeamCallback, - deleteUsersFromTeam, -}; - -helper.buildController(module.exports); diff --git a/src/front/src/app/git-access-control/access-control.html b/src/front/src/app/git-access-control/access-control.html index 77c62f6..ce37622 100644 --- a/src/front/src/app/git-access-control/access-control.html +++ b/src/front/src/app/git-access-control/access-control.html @@ -207,102 +207,6 @@

Git Access Control

- -
-
-
-
-
-
- - - - -
-
-
- - - - - - - - - - - - - - - - - - - -
Team NameGet Link
{{item.name}} - - -

{{alert}}

-

{{alert}}

-
- - - - -
-
- - -
- -
-
-
- You don't appear to have any azure groups available to manage through Topcoder X. Topcoder X is used to give Topcoder - members access to specific group using your ownership credentials. If you - don't have any group, Topcoder X won't be able to manage access for you. - Please see this link for information on azure group: - here -
-
-
-

Your account isn't registered in the Topcoder X tool.

- - - Go To Settings - -
-
diff --git a/src/front/src/app/git-access-control/access-control.service.js b/src/front/src/app/git-access-control/access-control.service.js index edbad10..fcc9eec 100644 --- a/src/front/src/app/git-access-control/access-control.service.js +++ b/src/front/src/app/git-access-control/access-control.service.js @@ -35,26 +35,6 @@ angular.module('topcoderX') }); }; - /** - * get azure owner teams - * - */ - service.getAzureOwnerTeams = function (pageNo, pageSize) { - return $http.get(baseUrl + '/api/v1/azure/owneruser/teams?page=' + pageNo + '&perPage=' + pageSize).then(function (response) { - return response; - }); - }; - - /** - * get azure shareable link - * - */ - service.getAzureShareableLink = function (teamId, orgname, projectId) { - return $http.get(baseUrl + '/api/v1/azure/teams/' + teamId + '/registrationurl/' + orgname + '/' + projectId).then(function (response) { - return response; - }); - }; - /** * get github owner teams * @@ -95,15 +75,5 @@ 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; }]); diff --git a/src/front/src/app/git-access-control/gitAccessControl.controller.js b/src/front/src/app/git-access-control/gitAccessControl.controller.js index 1e1727a..74175cb 100644 --- a/src/front/src/app/git-access-control/gitAccessControl.controller.js +++ b/src/front/src/app/git-access-control/gitAccessControl.controller.js @@ -33,19 +33,6 @@ angular.module('topcoderX').controller('GitAccessController', ['currentUser', '$ accessLinkMethod: GitAccessControlService.getGitlabShareableLink, removeAllUsersMethod: GitAccessControlService.removeAllGitlabUsers, query: '', - }, - azure: { - pageNumber: 1, - pageSize: 10, - isLoading: false, - items: [], - allItems: [], - totalPages: 1, - searchMethod: GitAccessControlService.getAzureOwnerTeams, - initialized: false, - accessLinkMethod: GitAccessControlService.getAzureShareableLink, - removeAllUsersMethod: GitAccessControlService.removeAllAzureUsers, - query: '', } } @@ -87,19 +74,6 @@ angular.module('topcoderX').controller('GitAccessController', ['currentUser', '$ */ $scope.getSharableLink = function (team, provider) { team.gettingLink = true; - if (provider === 'azure') { - var azconfig = $scope.tableConfig[provider]; - var azparams = [team.id, team.orgName, team.projectId]; - azconfig.accessLinkMethod.apply(vm, azparams).then(function (response) { - team.accessLink = response.data.url; - team.showLink = true; - team.gettingLink = false; - }).catch(function (err) { - team.gettingLink = false; - _handleError(err); - }); - return; - } const modalInstance = $uibModal.open({ size: 'md', templateUrl: 'app/git-access-control/git-access-dialog.html', diff --git a/src/front/src/app/members/member.controller.js b/src/front/src/app/members/member.controller.js index 7c9634f..350ed87 100644 --- a/src/front/src/app/members/member.controller.js +++ b/src/front/src/app/members/member.controller.js @@ -13,9 +13,6 @@ angular.module('topcoderX') $scope.link = $rootScope.appConfig.GITHUB_TEAM_URL + org + '/teams/' + team; } 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); diff --git a/src/front/src/app/members/member.html b/src/front/src/app/members/member.html index bf3f4ff..5d20ef3 100644 --- a/src/front/src/app/members/member.html +++ b/src/front/src/app/members/member.html @@ -19,10 +19,6 @@

{{title}}

You were successfully added to the group!

{{link}} -
-

You were successfully added to the Azure DevOps project team!

- {{link}} -
diff --git a/src/front/src/app/projects/projects.controller.js b/src/front/src/app/projects/projects.controller.js index 9857646..e9a0fbc 100644 --- a/src/front/src/app/projects/projects.controller.js +++ b/src/front/src/app/projects/projects.controller.js @@ -73,9 +73,6 @@ angular.module('topcoderX') else if (repo.toLocaleLowerCase().indexOf("gitlab") >= 0) { return "Gitlab"; } - else if (repo.toLocaleLowerCase().indexOf("azure") >= 0) { - return "Azure"; - } else { return "Other"; } diff --git a/src/front/src/app/settings/settings.controller.js b/src/front/src/app/settings/settings.controller.js index 83673c3..f3d0e6d 100644 --- a/src/front/src/app/settings/settings.controller.js +++ b/src/front/src/app/settings/settings.controller.js @@ -21,8 +21,7 @@ angular.module('topcoderX').controller('SettingController', ['currentUser', '$sc $scope.loginUrl = { github: Helper.baseUrl + $rootScope.appConfig.OWNER_LOGIN_GITHUB_URL, - gitlab: Helper.baseUrl + $rootScope.appConfig.OWNER_LOGIN_GITLAB_URL, - azure: Helper.baseUrl + $rootScope.appConfig.OWNER_LOGIN_AZURE_URL + gitlab: Helper.baseUrl + $rootScope.appConfig.OWNER_LOGIN_GITLAB_URL } $scope.$on('dialog.finished', function (event, args) { diff --git a/src/front/src/app/settings/settings.html b/src/front/src/app/settings/settings.html index a000c86..2056689 100644 --- a/src/front/src/app/settings/settings.html +++ b/src/front/src/app/settings/settings.html @@ -65,31 +65,6 @@

Settings

- -

Azure DevOps

- -
-
- - -
-
- - Setup - -
-
- - -
-
- - Revoke - -
-
- - diff --git a/src/models/UserMapping.js b/src/models/UserMapping.js index c50d551..f1be34c 100644 --- a/src/models/UserMapping.js +++ b/src/models/UserMapping.js @@ -23,10 +23,8 @@ const schema = new Schema({ }, githubUsername: String, gitlabUsername: String, - azureEmail: String, githubUserId: Number, - gitlabUserId: Number, - azureUserId: String + gitlabUserId: Number }); module.exports = schema; diff --git a/src/models/UserTeamMapping.js b/src/models/UserTeamMapping.js index 06bda59..97d26c2 100644 --- a/src/models/UserTeamMapping.js +++ b/src/models/UserTeamMapping.js @@ -40,9 +40,7 @@ const schema = new Schema({ rangKey: 'id', name: 'GithubUserNameIndex', }, - }, - azureProjectId: { type: String, required: false }, - azureUserId: { type: String, required: false } + } }); module.exports = schema; diff --git a/src/routes.js b/src/routes.js index bcaca2e..8832a8b 100644 --- a/src/routes.js +++ b/src/routes.js @@ -115,55 +115,6 @@ module.exports = { allowAnonymous: true, }, }, - - '/azure/owneruser/login': { - get: { - controller: 'AzureController', - method: 'ownerUserLogin', - }, - }, - '/azure/owneruser/callback': { - get: { - controller: 'AzureController', - method: 'ownerUserLoginCallback', - }, - }, - '/azure/owneruser/teams': { - get: { - controller: 'AzureController', - method: 'listOwnerUserTeams', - }, - }, - '/azure/teams/:id/registrationurl/:orgname/:projectId': { - get: { - controller: 'AzureController', - method: 'getTeamRegistrationUrl', - }, - }, - '/azure/teams/:id/users': { - delete: { - controller: 'AzureController', - method: 'deleteUsersFromTeam', - }, - }, - '/azure/teams/registration/:identifier': { - get: { - controller: 'AzureController', - method: 'addUserToTeam', - allowNormalUser: true, - tcLogin: true, - allowAnonymous: true, - }, - }, - '/azure/normaluser/callback': { - get: { - controller: 'AzureController', - method: 'addUserToTeamCallback', - allowNormalUser: true, - allowAnonymous: true, - }, - }, - '/tclogin': { get: { controller: 'TCUserController', diff --git a/src/services/AzureService.js b/src/services/AzureService.js deleted file mode 100644 index 0b31da9..0000000 --- a/src/services/AzureService.js +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (c) 2017 TopCoder, Inc. All rights reserved. - */ - -/** - * This service will provide Azure operations. - * - * @author TCSCODER - * @version 1.0 - */ - -const Joi = require('joi'); -const superagent = require('superagent'); -const superagentPromise = require('superagent-promise'); -const config = require('../config'); -const constants = require('../common/constants'); -const helper = require('../common/helper'); -const dbHelper = require('../common/db-helper'); -const errors = require('../common/errors'); -const User = require('../models').User; -const UserMapping = require('../models').UserMapping; -const OwnerUserTeam = require('../models').OwnerUserTeam; - -const request = superagentPromise(superagent, Promise); -// milliseconds per second -const MS_PER_SECOND = 1000; - -/** - * Ensure the owner user is in database. - * @param {String} token the access token of owner user - * @param {String} topcoderUsername the topcoder handle of owner user - * @returns {Promise} the promise result of found owner user - */ -async function ensureOwnerUser(token, topcoderUsername) { - let userProfile; - try { - // get current user name - userProfile = await request - .get(`${config.AZURE_API_BASE_URL}/_apis/profile/profiles/me?api-version=5.1`) - .set('Authorization', `Bearer ${token}`) - .end() - .then((res) => res.body); - } catch (err) { - throw helper.convertAzureError(err, 'Failed to ensure valid owner user.'); - } - if (!userProfile) { - throw new errors.UnauthorizedError('Can not get user from the access token.'); - } - const user = await dbHelper.scanOne(User, { - username: userProfile.emailAddress, - type: constants.USER_TYPES.AZURE, - role: constants.USER_ROLES.OWNER, - }); - - const userMapping = await dbHelper.scanOne(UserMapping, {topcoderUsername}); - if (!userMapping) { - await dbHelper.create(UserMapping, { - id: helper.generateIdentifier(), - topcoderUsername, - azureUserId: userProfile.id, - azureEmail: userProfile.emailAddress, - }); - } else { - await dbHelper.update(UserMapping, userMapping.id, { - azureUserId: userProfile.id, - azureEmail: userProfile.emailAddress, - }); - } - - if (!user) { - return await dbHelper.create(User, { - id: helper.generateIdentifier(), - role: constants.USER_ROLES.OWNER, - type: constants.USER_TYPES.AZURE, - userProviderId: helper.hashCode(userProfile.id), - userProviderIdStr: userProfile.id, - username: userProfile.emailAddress, - accessToken: token, - }); - } - // save user token data - return await dbHelper.update(User, user.id, { - userProviderId: helper.hashCode(userProfile.id), - userProviderIdStr: userProfile.id, - username: userProfile.emailAddress, - accessToken: token, - }); -} - -ensureOwnerUser.schema = Joi.object().keys({ - token: Joi.string().required(), - topcoderUsername: Joi.string().required(), -}); - -/** - * List groups of owner user. - * @param {Object} user the token - * @param {Number} page the page number (default to be 1). Must be >= 1 - * @param {Number} perPage the page size (default to be constants.GITLAB_DEFAULT_PER_PAGE). - * Must be within range [1, constants.GITLAB_MAX_PER_PAGE] - * @param {Boolean} getAll get all groups - * @returns {Promise} the promise result - */ -async function listOwnerUserTeams(user, page = 1, perPage = constants.GITLAB_DEFAULT_PER_PAGE) { - try { - // https://app.vssps.visualstudio.com/_apis/accounts?api-version=5.0&ownerId=facd2f29-c239-6cd6-b374-d987a9f38d5c - const orgs = await request - .get(`${config.AZURE_API_BASE_URL}/_apis/accounts?ownerId=${user.userProviderIdStr}&api-version=5.0`) - .set('Authorization', `Bearer ${user.accessToken}`) - .end() - .then((res) => res.body); - if (orgs.count === 0) { - return { - page, - perPage, - lastPage: 1, - teams: [] - }; - } - const org = orgs.value[0]; - const orgName = org.accountName; - // https://dev.azure.com/telagaid/_apis/teams?api-version=5.1-preview.3 - const teams = await request - .get(`${config.AZURE_DEVOPS_API_BASE_URL}/${orgName}/_apis/teams?api-version=5.1-preview.3`) - .set('Authorization', `Bearer ${user.accessToken}`) - .end() - .then((res) => res.body); - - return { - page, - perPage, - lastPage: 1, - teams: teams.value.map((team) => { - team.orgName = orgName; - const urlComponent = team.url.split('/'); - team.projectId = urlComponent[urlComponent.length - 3]; // eslint-disable-line no-magic-numbers - return team; - }) - }; - } catch (err) { - throw helper.convertAzureError(err, 'Failed to list user groups'); - } -} - -listOwnerUserTeams.schema = Joi.object().keys({ - user: Joi.object().required(), - page: Joi.number().integer().min(1).optional(), - perPage: Joi.number().integer().min(1).max(constants.GITLAB_MAX_PER_PAGE) - .optional(), - getAll: Joi.boolean().optional(), -}); - -/** - * Get owner user group registration URL. - * @param {String} token the access token of owner user - * @param {String} ownerUsername the owner user name - * @param {String} teamId the group id - * @param {String} orgname the group access level - * @param {String} projectId the projectid - * @returns {Promise} the promise result - */ -async function getTeamRegistrationUrl(token, ownerUsername, teamId, orgname, projectId) { - // generate identifier - const identifier = helper.generateIdentifier(); - - // create owner user group - await dbHelper.create(OwnerUserTeam, { - id: helper.generateIdentifier(), - ownerUsername, - type: constants.USER_TYPES.AZURE, - teamId, - identifier, - ownerToken: token, - githubOrgId: projectId, - organizationName: orgname - }); - - // construct URL - const url = `${config.WEBSITE}/api/${config.API_VERSION}/azure/teams/registration/${identifier}`; - return {url}; -} - -getTeamRegistrationUrl.schema = Joi.object().keys({ - token: Joi.string().required(), - ownerUsername: Joi.string().required(), - teamId: Joi.string().required(), - orgname: Joi.string().required(), - projectId: Joi.string() -}); - -/** - * Refresh the owner user access token if needed - * @param {Object} azureOwner the azure owner - * @returns {Object} the user object - */ -async function refreshAzureUserAccessToken(azureOwner) { - // if (azureOwner.accessTokenExpiration && azureOwner.accessTokenExpiration.getTime() <= - // new Date().getTime() + constants.AZURE_REFRESH_TOKEN_BEFORE_EXPIRATION * MS_PER_SECOND) { - const refreshTokenResult = await request - .post('https://app.vssps.visualstudio.com/oauth2/token') - .send({ - client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', - client_assertion: encodeURIComponent(config.AZURE_CLIENT_SECRET), - assertion: encodeURIComponent(azureOwner.refreshToken), - grant_type: 'refresh_token', - redirect_uri: `${config.WEBSITE_SECURE}${constants.AZURE_OWNER_CALLBACK_URL}`, - }) - .set('Content-Type', 'application/x-www-form-urlencoded') - .end(); - // save user token data - const expiresIn = refreshTokenResult.body.expires_in || constants.AZURE_ACCESS_TOKEN_DEFAULT_EXPIRATION; - return await dbHelper.update(User, azureOwner.id, { - accessToken: refreshTokenResult.body.access_token, - accessTokenExpiration: new Date(new Date().getTime() + expiresIn * MS_PER_SECOND), - refreshToken: refreshTokenResult.body.refresh_token, - }); - // } - // return azureOwner; -} - -refreshAzureUserAccessToken.schema = Joi.object().keys({ - azureOwner: Joi.object().keys({ - id: Joi.string().required(), - accessTokenExpiration: Joi.date().required(), - refreshToken: Joi.string().required(), - role: Joi.string(), - userProviderId: Joi.number(), - userProviderIdStr: Joi.string(), - type: Joi.string(), - accessToken: Joi.string(), - username: Joi.string(), - }), -}); - -/** - * delete user fromgroup - * @param {String} ownerUserToken the azure owner token - * @param {Object} team the azure team - * @param {String} userId the normal user id - */ -async function deleteUserFromAzureTeam(ownerUserToken, team, userId) { - try { - await request - .patch(`https://vsaex.dev.azure.com/${team.organizationName}/_apis/UserEntitlements?doNotSendInviteForNewUsers=true&api-version=5.1-preview.3`) - .send([{ - from: '', - op: 'remove', - path: `/${userId}/projectEntitlements/${team.githubOrgId}`, - value: { - id:team.teamId - } - }]) - .set('Content-Type', 'application/json-patch+json') - .set('Authorization', `Bearer ${ownerUserToken}`) - .end(); - } catch (err) { - // If a user is not found from azure, then ignore the error - // eslint-disable-next-line no-magic-numbers - if (err.status !== 404) { - throw helper.convertAzureError(err, `Failed to delete user from group, userId is ${userId}, teamId is ${team.teamId}.`); - } - } -} - -deleteUserFromAzureTeam.schema = Joi.object().keys({ - ownerUserToken: Joi.string().required(), - team: Joi.object().required(), - userId: Joi.string().required(), -}); - -module.exports = { - ensureOwnerUser, - listOwnerUserTeams, - getTeamRegistrationUrl, - refreshAzureUserAccessToken, - deleteUserFromAzureTeam -}; - -helper.buildService(module.exports); \ No newline at end of file diff --git a/src/services/ProjectService.js b/src/services/ProjectService.js index 78ced83..6eafe48 100644 --- a/src/services/ProjectService.js +++ b/src/services/ProjectService.js @@ -17,8 +17,6 @@ const GitHub = require('github-api'); const Gitlab = require('gitlab/dist/es5').default; const _ = require('lodash'); const guid = require('guid'); -const superagent = require('superagent'); -const superagentPromise = require('superagent-promise'); const kafka = require('../utils/kafka'); const helper = require('../common/helper'); const dbHelper = require('../common/db-helper'); @@ -29,8 +27,6 @@ const errors = require('../common/errors'); const userService = require('./UserService'); const securityService = require('./SecurityService'); -const request = superagentPromise(superagent, Promise); - const currentUserSchema = Joi.object().keys({ handle: Joi.string().required(), roles: Joi.array().required(), @@ -130,33 +126,6 @@ async function create(project, currentUser) { project.copilot = project.copilot ? project.copilot.toLowerCase() : null; project.id = helper.generateIdentifier(); - const provider = await helper.getProviderType(project.repoUrl); - - if (provider === 'azure') { - const repoUrlObj = new URL(project.repoUrl); - const pathCount = 3; - const paths = repoUrlObj.pathname.split('/'); - if (paths.length > pathCount) { - project.repoUrl = decodeURIComponent(`${repoUrlObj.origin}/${paths[1]}/${paths[2]}`); // eslint-disable-line no-magic-numbers - } - else { - project.repoUrl = decodeURIComponent(`${repoUrlObj.origin}${repoUrlObj.pathname}`); - } - const userRole = await helper.getProjectCopilotOrOwner(models, project, provider, false); - const results = project.repoUrl.split('/'); - const index = 1; - const repoName = results[results.length - index]; - const repoOwner = _(results).slice(pathCount, results.length - 1).join('/'); - - let result = await request - .get(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/_apis/projects/${repoName}?api-version=5.1`) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .end() - .then((res) => res.body); - - project.repoId = result.id; - } - const createdProject = await dbHelper.create(models.Project, project); try { @@ -327,35 +296,6 @@ async function createLabel(body, currentUser) { throw helper.convertGitLabError(err, 'Failed to create labels.'); } } - } else if (provider === 'azure') { - try { - // POST https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/${type}?api-version=6.0-preview.3 - const result = await request - .post(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/${repoName}/_apis/wit/workitems/$issue?api-version=6.0-preview.3`) - .send([{ - op: 'add', - path: '/fields/System.Title', - from: null, - value: 'Issue For Creating Labels' - }, { - op: 'add', - path: '/fields/System.Tags', - value: _.join(config.LABELS.map((label) => label.name), '; ') - }]) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .set('Content-Type', 'application/json-patch+json') - .end() - .then((res) => res.body); - // DELETE https://dev.azure.com/{organization}/{project}/_apis/wit/workitems/{id}?api-version=6.0-preview.3 - await request - .del(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/${repoName}/_apis/wit/workitems/${result.id}?api-version=6.0-preview.3`) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .end(); - } catch (err) { - if (_.get(err, 'error.message') !== 'Label already exists') { - throw helper.convertGitLabError(err, 'Failed to create labels.'); - } - } } return { success: true, @@ -474,49 +414,6 @@ async function createHook(body, currentUser) { } throw helper.convertGitLabError(err, errMsg); } - } else if (provider === 'azure') { - try { - // https://dev.azure.com/telagaid/_apis/projects/Test%20Second?api-version=5.1 - let project = await request - .get(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/_apis/projects/${repoName}?api-version=5.1`) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .end() - .then((res) => res.body); - - // https://dev.azure.com/telagaid/_apis/hooks/subscriptions?api-version=5.1 - const eventTypes = [ - 'workitem.created', - 'workitem.updated', - 'workitem.deleted', - 'workitem.restored', - 'workitem.commented' - ]; - for (const eventType of eventTypes) { // eslint-disable-line no-restricted-syntax - await request - .post(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/_apis/hooks/subscriptions?api-version=5.1`) - .send({ - publisherId: 'tfs', - eventType: eventType, - resourceVersion: '5.1-preview.3', - consumerId: 'webHooks', - consumerActionId: 'httpRequest', - publisherInputs: { - projectId: project.id - }, - consumerInputs: { - basicAuthUsername: 'tcx', - url: `${config.HOOK_BASE_URL}/webhooks/azure`, - basicAuthPassword: dbProject.secretWebhookKey - } - }) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .set('Content-Type', 'application/json') - .end() - .then((res) => res.body); - } - } catch (err) { - throw helper.convertGitLabError(err, 'Failed to ensure valid owner user.'); - } } else { return { success: false @@ -584,38 +481,6 @@ async function addWikiRules(body, currentUser) { } catch (err) { throw helper.convertGitLabError(err, 'Failed to add wiki rules.'); } - } else if (provider === 'azure') { - try { - // PUT https://dev.azure.com/{organization}/{project}/_apis/wiki/wikis/{wikiIdentifier}/pages?path={path}&api-version=5.1 - const project = await request - .get(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/_apis/projects/${repoName}?api-version=5.1`) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .end() - .then((res) => res.body); - - // POST https://dev.azure.com/fabrikam/_apis/wiki/wikis?api-version=5.1 - await request - .post(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/${repoName}/_apis/wiki/wikis?api-version=5.1`) - .send({ - type: 'projectWiki', - name: `${repoName.replace(/ /g, '-')}.wiki`, - projectId: project.id - }) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .set('Content-Type', 'application/json') - .end(); - - await request - .put(`${config.AZURE_DEVOPS_API_BASE_URL}/${repoOwner}/${repoName}/_apis/wiki/wikis/${repoName.replace(/ /g, '-')}.wiki/pages?path=Azure%20ticket%20rules&api-version=5.1`) - .send({ - content - }) - .set('Authorization', `Bearer ${userRole.accessToken}`) - .set('Content-Type', 'application/json') - .end(); - } catch (err) { - throw helper.convertGitLabError(err, 'Failed to add wiki rules.'); - } } return { success: true, diff --git a/src/services/UserService.js b/src/services/UserService.js index 8bea0dc..b7b2000 100644 --- a/src/services/UserService.js +++ b/src/services/UserService.js @@ -29,7 +29,6 @@ async function getUserSetting(handle) { const setting = { github: false, gitlab: false, - azure: false, expired: {} }; @@ -58,16 +57,6 @@ async function getUserSetting(handle) { } } - if (mapping.azureEmail) { - const azure = await dbHelper.scanOne(User, { - username: mapping.azureEmail, - type: constants.USER_TYPES.AZURE, - }); - if (!_.isNil(azure)) { - users.push(azure); - } - } - _.forEach(constants.USER_TYPES, (item) => { setting[item] = !!users.find((i) => i.type === item && i.accessToken); if (setting[item]) { @@ -115,14 +104,6 @@ async function revokeUserSetting(handle, provider) { return true; } - if (provider === 'azure' && mapping.azureEmail) { - dbHelper.remove(User, { - username: mapping.azureEmail, - type: constants.USER_TYPES.AZURE, - }); - return true; - } - return false; } @@ -166,7 +147,7 @@ async function getAccessTokenByHandle(handle, provider) { let gitUserName; if (mapping) { gitUserName = provider === constants.USER_TYPES.GITHUB ? 'githubUsername' : //eslint-disable-line no-nested-ternary - provider === constants.USER_TYPES.GITLAB ? 'gitlabUsername' : 'azureEmail'; + 'gitlabUsername'; return await dbHelper.scanOne(User, { username: mapping[gitUserName], type: provider,