From e9ae890a013bb57989fcbdb548d8b7b86b240237 Mon Sep 17 00:00:00 2001 From: Ben Cooke Date: Wed, 18 Mar 2026 13:31:48 -0400 Subject: [PATCH] oauth check (#35553) --- server/channels/app/oauth.go | 8 +++++ server/channels/app/oauth_test.go | 58 +++++++++++++++++++++++++++++++ server/i18n/en.json | 4 +++ 3 files changed, 70 insertions(+) diff --git a/server/channels/app/oauth.go b/server/channels/app/oauth.go index ecfe2118908..fc48bb6282a 100644 --- a/server/channels/app/oauth.go +++ b/server/channels/app/oauth.go @@ -341,6 +341,10 @@ func (a *App) handleAuthorizationCodeGrant(rctx request.CTX, oauthApp *model.OAu return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusForbidden) } + if authData.ClientId != clientId { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.client_id_mismatch.app_error", nil, "", http.StatusBadRequest) + } + if authData.RedirectUri != redirectURI { return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest) } @@ -395,6 +399,10 @@ func (a *App) handleRefreshTokenGrant(rctx request.CTX, oauthApp *model.OAuthApp return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "", http.StatusNotFound).Wrap(nErr) } + if accessData.ClientId != oauthApp.Id { + return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.client_id_mismatch.app_error", nil, "", http.StatusBadRequest) + } + user, nErr := a.Srv().Store().User().Get(context.Background(), accessData.UserId) if nErr != nil { return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound).Wrap(nErr) diff --git a/server/channels/app/oauth_test.go b/server/channels/app/oauth_test.go index 913b737c3d6..2ceee58938b 100644 --- a/server/channels/app/oauth_test.go +++ b/server/channels/app/oauth_test.go @@ -1400,6 +1400,64 @@ func TestGetOAuthAccessTokenForCodeFlow(t *testing.T) { require.Contains(t, appErr.Id, "resource_mismatch") }) }) + + t.Run("DifferentClient_CannotRedeemCode", func(t *testing.T) { + appA := createConfidentialOAuthApp("TestClientA") + appB := createConfidentialOAuthApp("TestClientB") + code := getAuthorizationCode(appA, "") + + _, appErr := th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appB.Id, + model.AccessTokenGrantType, + appA.CallbackUrls[0], + code, + appB.ClientSecret, + "", + "", + "", + ) + require.NotNil(t, appErr) + require.Contains(t, appErr.Id, "client_id_mismatch") + require.Equal(t, http.StatusBadRequest, appErr.StatusCode) + }) + + t.Run("DifferentClient_CannotUseRefreshToken", func(t *testing.T) { + appA := createConfidentialOAuthApp("TestClientA") + appB := createConfidentialOAuthApp("TestClientB") + code := getAuthorizationCode(appA, "") + + // Get a valid refresh token for appA + tokenResp, appErr := th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appA.Id, + model.AccessTokenGrantType, + appA.CallbackUrls[0], + code, + appA.ClientSecret, + "", + "", + "", + ) + require.Nil(t, appErr) + require.NotEmpty(t, tokenResp.RefreshToken) + + // Try to use appA's refresh token with appB's credentials + _, appErr = th.App.GetOAuthAccessTokenForCodeFlow( + th.Context, + appB.Id, + model.RefreshTokenGrantType, + appB.CallbackUrls[0], + "", + appB.ClientSecret, + tokenResp.RefreshToken, + "", + "", + ) + require.NotNil(t, appErr) + require.Contains(t, appErr.Id, "client_id_mismatch") + require.Equal(t, http.StatusBadRequest, appErr.StatusCode) + }) } func TestParseOAuthStateTokenExtra(t *testing.T) { t.Run("valid token with normal values", func(t *testing.T) { diff --git a/server/i18n/en.json b/server/i18n/en.json index 490be82721f..503c4618df6 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -2596,6 +2596,10 @@ "id": "api.oauth.get_access_token.bad_request.app_error", "translation": "invalid_request: Bad request." }, + { + "id": "api.oauth.get_access_token.client_id_mismatch.app_error", + "translation": "invalid_grant: Token grant was not issued to this client." + }, { "id": "api.oauth.get_access_token.credentials.app_error", "translation": "invalid_client: Invalid client credentials."