Skip to content

Commit

Permalink
[MI-2748]: Implemented 1:1 mapping between MM user and Azure DevOps u…
Browse files Browse the repository at this point in the history
…ser for oAuth. (#35)

* [MI-2748]: Implemented 1:1 mapping between MM user and Azure DevOps user for oAuth.

* [MI-2748]: Added 1 test case and http response code

* [MI-2748]: Fixed condition

* [MI-2748]: Fixed token refresh logic

* [MI-2748]: Fixed CI

* [MI-2748]: Fixed CI

* [MI-2748]: Review fixes

* [MI-2748]: Fixed CI

* [MI-2748]: Review fixes

* [MI-2748]: Review fixes

---------

Co-authored-by: Abhishek Verma <[email protected]>
  • Loading branch information
avas27JTG and avas27JTG authored Feb 10, 2023
1 parent 6d00878 commit b439dbc
Show file tree
Hide file tree
Showing 20 changed files with 341 additions and 134 deletions.
16 changes: 16 additions & 0 deletions mocks/mock_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 27 additions & 12 deletions mocks/mock_store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

89 changes: 45 additions & 44 deletions server/constants/messages.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package constants

const (
// TODO: all these messages are to be verified from Mike at the end
// Generic
GenericErrorMessage = "Something went wrong, please try again later"
SessionExpiredMessage = "Session expired. Please connect your Azure DevOps account again"
ConnectAccount = "[Click here to connect your Azure DevOps account](%s%s)"
ConnectAccountFirst = "Your Azure DevOps account is not connected \n%s"
UserConnected = "Your Azure DevOps account is successfully connected!"
UserAlreadyConnected = "Your Azure DevOps account is already connected"
MattermostUserAlreadyConnected = "Your Azure DevOps account is already connected"
UserDisconnected = "Your Azure DevOps account is now disconnected"
CreatedTask = "Work item [#%d: \"%s\"](%s) of type \"%s\" was successfully created by %s."
TaskTitle = "[%s #%d: %s](%s)"
Expand Down Expand Up @@ -43,46 +42,48 @@ const (

const (
// Error messages
Error = "Error"
NotAuthorized = "Not authorized"
UnableToDisconnectUser = "Unable to disconnect user"
UnableToCheckIfAlreadyConnected = "Unable to check if user account is already connected"
UnableToStoreOauthState = "Unable to store oAuth state for the userID %s"
AuthAttemptExpired = "Authentication attempt expired, please try again"
InvalidAuthState = "Invalid oauth state, please try again"
GetProjectListError = "Error in getting project list"
ErrorFetchProjectList = "Error in fetching project list"
ErrorDecodingBody = "Error in decoding body"
ErrorCreateTask = "Error in creating task"
ErrorCreateSubscription = "Error in creating subscription"
ErrorLinkProject = "Error in linking the project"
FetchSubscriptionListError = "Error in fetching subscription list"
FetchFilteredSubscriptionListError = "Error in fetching filtered subscription list"
CreateSubscriptionError = "Error in creating subscription"
ErrorCheckingProjectAdmin = "Error in checking if user is an admin on the project %s"
ProjectNotLinked = "Requested project is not linked"
GetSubscriptionListError = "Error getting subscription list"
SubscriptionAlreadyPresent = "Requested subscription already exists"
SubscriptionNotFound = "Requested subscription does not exists"
ErrorLoadingUserData = "Error in loading user data"
ErrorLoadingDataFromKVStore = "Error in loading data from KV store"
ProjectNotFound = "Requested project does not exist"
ErrorUnlinkProject = "Error in unlinking the project"
InvalidChannelID = "Invalid channel ID"
DeleteSubscriptionError = "Error in deleting subscription"
GetChannelError = "Error in getting channels for team and user"
GetUserError = "Error in getting Mattermost user details"
InvalidPaginationQueryParam = "Invalid value for query param(s) page or per_page"
ErrorAdminAccess = "Cannot delete the subscription, looks like you do not have access to add/delete a subscription for this project. Please make sure you are a project or team administrator for this project"
ErrorFetchSubscriptionList = "Error in fetching subscription list"
ErrorMessageForAdmin = "There is no registered handler for the service hooks event type %s"
AccessDenied = "Access Denied"
ErrorOrganizationOrProjectQueryParam = "Invalid organization or project name"
ErrorRepositoryPathParam = "Invalid organization, project or repository params"
ErrorInvalidOrganizationOrProject = "Invalid organization or project name"
ErrorUpdatingPipelineApprovalRequest = "Failed to update pipeline approval request"
ErrorUpdatingNonPendingPipelineRequest = "Approval(s) %d are not in a pending state. Only pending approval(s) can be updated"
UnableToDMBot = "Unable to send DM to bot"
ErrorFetchSubscriptionFilterPossibleValues = "Error in fetching subscription filter possible values"
ErrorUnauthorisedSubscriptionsWebhookRequest = "missing or invalid webhook secret for subscriptions notification"
Error = "Error"
NotAuthorized = "Not authorized"
UnableToDisconnectUser = "Unable to disconnect user"
UnableToCheckIfAlreadyConnected = "Unable to check if user account is already connected"
UnableToStoreOauthState = "Unable to store oAuth state for the userID %s"
UnableToCompleteOAuth = "Unable to complete oAuth"
AuthAttemptExpired = "Authentication attempt expired, please try again"
InvalidAuthState = "Invalid oauth state, please try again"
GetProjectListError = "Error in getting project list"
ErrorFetchProjectList = "Error in fetching project list"
ErrorDecodingBody = "Error in decoding body"
ErrorCreateTask = "Error in creating task"
ErrorCreateSubscription = "Error in creating subscription"
ErrorLinkProject = "Error in linking the project"
FetchSubscriptionListError = "Error in fetching subscription list"
FetchFilteredSubscriptionListError = "Error in fetching filtered subscription list"
CreateSubscriptionError = "Error in creating subscription"
ErrorCheckingProjectAdmin = "Error in checking if user is an admin on the project %s"
ProjectNotLinked = "Requested project is not linked"
GetSubscriptionListError = "Error getting subscription list"
SubscriptionAlreadyPresent = "Requested subscription already exists"
SubscriptionNotFound = "Requested subscription does not exists"
ErrorLoadingUserData = "Error in loading user data"
ErrorLoadingDataFromKVStore = "Error in loading data from KV store"
ProjectNotFound = "Requested project does not exist"
ErrorUnlinkProject = "Error in unlinking the project"
InvalidChannelID = "Invalid channel ID"
DeleteSubscriptionError = "Error in deleting subscription"
GetChannelError = "Error in getting channels for team and user"
GetUserError = "Error in getting Mattermost user details"
InvalidPaginationQueryParam = "Invalid value for query param(s) page or per_page"
ErrorAdminAccess = "Cannot delete the subscription, looks like you do not have access to add/delete a subscription for this project. Please make sure you are a project or team administrator for this project"
ErrorFetchSubscriptionList = "Error in fetching subscription list"
ErrorMessageForAdmin = "There is no registered handler for the service hooks event type %s"
AccessDenied = "Access Denied"
ErrorOrganizationOrProjectQueryParam = "Invalid organization or project name"
ErrorRepositoryPathParam = "Invalid organization, project or repository params"
ErrorInvalidOrganizationOrProject = "Invalid organization or project name"
ErrorUpdatingPipelineApprovalRequest = "Failed to update pipeline approval request"
ErrorUpdatingNonPendingPipelineRequest = "Approval(s) %d are not in a pending state. Only pending approval(s) can be updated"
UnableToDMBot = "Unable to send DM to bot"
ErrorFetchSubscriptionFilterPossibleValues = "Error in fetching subscription filter possible values"
ErrorUnauthorisedSubscriptionsWebhookRequest = "missing or invalid webhook secret for subscriptions notification"
ErrorMessageAzureDevopsAccountAlreadyConnected = "azure devops account for %s is already connected"
)
5 changes: 4 additions & 1 deletion server/constants/oauth_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ const (
// Paths
PathAuth = "/oauth2/authorize"
// #nosec G101 -- This is a false positive
PathToken = "/oauth2/token"
PathToken = "/oauth2/token"
PathUserProfile = "/_apis/profile/profiles/%s"

CurrentAzureDevopsUserProfileID = "me"
)
11 changes: 6 additions & 5 deletions server/constants/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ const (
UsersPerPage = 100

// KV store prefix keys
OAuthPrefix = "oAuth_%s"
ProjectKey = "%s_%s"
ProjectPrefix = "project_list"
SubscriptionPrefix = "subscription_list"
UserIDPrefix = "oAuth"
OAuthPrefix = "oAuth_%s"
ProjectKey = "%s_%s"
ProjectPrefix = "project_list"
SubscriptionPrefix = "subscription_list"
UserIDPrefix = "oAuth"
AzureDevOpsUserPrefix = "azd_userID_%s"
)
18 changes: 16 additions & 2 deletions server/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1314,7 +1314,14 @@ func (p *Plugin) getUserChannelsForTeam(w http.ResponseWriter, r *http.Request)
func (p *Plugin) checkOAuth(handler http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mattermostUserID := r.Header.Get(constants.HeaderMattermostUserID)
user, err := p.Store.LoadUser(mattermostUserID)
azureDevopsUserID, err := p.Store.LoadAzureDevopsUserIDFromMattermostUser(mattermostUserID)
if err != nil {
p.API.LogError(constants.ErrorLoadingUserData, "Error", err.Error())
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: constants.GenericErrorMessage})
return
}

user, err := p.Store.LoadAzureDevopsUserDetails(azureDevopsUserID)
if err != nil || user.AccessToken == "" {
if errors.Is(err, ErrNotFound) || user.AccessToken == "" {
p.handleError(w, r, &serializers.Error{Code: http.StatusUnauthorized, Message: constants.ConnectAccountFirst})
Expand Down Expand Up @@ -1378,7 +1385,14 @@ func (p *Plugin) handleError(w http.ResponseWriter, r *http.Request, error *seri
// handleGetUserAccountDetails provides user details
func (p *Plugin) handleGetUserAccountDetails(w http.ResponseWriter, r *http.Request) {
mattermostUserID := r.Header.Get(constants.HeaderMattermostUserID)
userDetails, err := p.Store.LoadUser(mattermostUserID)
azureDevopsUserID, err := p.Store.LoadAzureDevopsUserIDFromMattermostUser(mattermostUserID)
if err != nil {
p.API.LogError(constants.ErrorLoadingDataFromKVStore, "Error", err.Error())
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}

userDetails, err := p.Store.LoadAzureDevopsUserDetails(azureDevopsUserID)
if err != nil {
p.API.LogError(constants.ErrorLoadingDataFromKVStore, "Error", err.Error())
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
Expand Down
4 changes: 2 additions & 2 deletions server/plugin/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,8 @@ func TestHandleGetUserAccountDetails(t *testing.T) {
t.Run(testCase.description, func(t *testing.T) {
mockAPI.On("LogError", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"))
mockAPI.On("PublishWebSocketEvent", mock.AnythingOfType("string"), mock.Anything, mock.AnythingOfType("*model.WebsocketBroadcast")).Return(nil)

mockedStore.EXPECT().LoadUser(testutils.MockMattermostUserID).Return(testCase.user, testCase.loadUserError)
mockedStore.EXPECT().LoadAzureDevopsUserIDFromMattermostUser(testutils.MockMattermostUserID).Return(testutils.MockAzureDevopsUserID, nil)
mockedStore.EXPECT().LoadAzureDevopsUserDetails(testutils.MockAzureDevopsUserID).Return(testCase.user, testCase.loadUserError)

monkey.Patch(json.Marshal, func(interface{}) ([]byte, error) {
return []byte{}, testCase.marshalError
Expand Down
Loading

0 comments on commit b439dbc

Please sign in to comment.