Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

1042 lines
37 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package api4
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/app"
)
func TestCreateOAuthApp(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}, IsTrusted: true}
rapp, resp, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
assert.Equal(t, oapp.Name, rapp.Name, "names did not match")
assert.Equal(t, oapp.IsTrusted, rapp.IsTrusted, "trusted did no match")
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, resp, err = client.CreateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
rapp, resp, err = client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
assert.False(t, rapp.IsTrusted, "trusted should be false - created by non admin")
oapp.Name = ""
_, resp, err = adminClient.CreateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
r, err := client.DoAPIPost(context.Background(), "/oauth/apps", "garbage")
require.Error(t, err, "expected error from garbage post")
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.CreateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
oapp.Name = GenerateTestAppName()
_, resp, err = adminClient.CreateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestUpdateOAuthApp(t *testing.T) {
// MM-62895: re-enabled to collect failure data (14mo, empty Jira description).
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{
Name: "oapp",
IsTrusted: false,
IconURL: "https://nowhere.com/img",
Homepage: "https://nowhere.com",
Description: "test",
CallbackUrls: []string{"https://callback.com"},
}
oapp, _, _ = adminClient.CreateOAuthApp(context.Background(), oapp)
oapp.Name = "oapp_update"
oapp.IsTrusted = true
oapp.IconURL = "https://nowhere.com/img_update"
oapp.Homepage = "https://nowhere_update.com"
oapp.Description = "test_update"
oapp.CallbackUrls = []string{"https://callback_update.com", "https://another_callback.com"}
updatedApp, _, err := adminClient.UpdateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
assert.Equal(t, oapp.Id, updatedApp.Id, "Id should have not updated")
assert.Equal(t, oapp.CreatorId, updatedApp.CreatorId, "CreatorId should have not updated")
assert.Equal(t, oapp.CreateAt, updatedApp.CreateAt, "CreateAt should have not updated")
assert.NotEqual(t, oapp.UpdateAt, updatedApp.UpdateAt, "UpdateAt should have updated")
assert.Equal(t, oapp.ClientSecret, updatedApp.ClientSecret, "ClientSecret should have not updated")
assert.Equal(t, oapp.Name, updatedApp.Name, "Name should have updated")
assert.Equal(t, oapp.Description, updatedApp.Description, "Description should have updated")
assert.Equal(t, oapp.IconURL, updatedApp.IconURL, "IconURL should have updated")
if len(updatedApp.CallbackUrls) == len(oapp.CallbackUrls) {
for i, callbackURL := range updatedApp.CallbackUrls {
assert.Equal(t, oapp.CallbackUrls[i], callbackURL, "Description should have updated")
}
}
assert.Equal(t, oapp.Homepage, updatedApp.Homepage, "Homepage should have updated")
assert.Equal(t, oapp.IsTrusted, updatedApp.IsTrusted, "IsTrusted should have updated")
th.LoginBasic2(t)
updatedApp.CreatorId = th.BasicUser2.Id
_, resp, err := client.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
th.LoginBasic(t)
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, resp, err = client.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
oapp.Id = "zhk9d1ggatrqz236c7h87im7bc"
_, resp, err = adminClient.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckNotFoundStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
_, resp, err = adminClient.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
oapp.Id = "junk"
_, resp, err = adminClient.UpdateOAuthApp(context.Background(), oapp)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.LoginBasic(t)
userOapp := &model.OAuthApp{
Name: "useroapp",
IsTrusted: false,
IconURL: "https://nowhere.com/img",
Homepage: "https://nowhere.com",
Description: "test",
CallbackUrls: []string{"https://callback.com"},
}
userOapp, _, err = client.CreateOAuthApp(context.Background(), userOapp)
require.NoError(t, err)
userOapp.IsTrusted = true
userOapp, _, err = client.UpdateOAuthApp(context.Background(), userOapp)
require.NoError(t, err)
assert.False(t, userOapp.IsTrusted)
userOapp.IsTrusted = true
userOapp, _, err = adminClient.UpdateOAuthApp(context.Background(), userOapp)
require.NoError(t, err)
assert.True(t, userOapp.IsTrusted)
userOapp.IsTrusted = false
userOapp, _, err = client.UpdateOAuthApp(context.Background(), userOapp)
require.NoError(t, err)
assert.True(t, userOapp.IsTrusted)
}
func TestGetOAuthApps(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err := client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
apps, _, err := adminClient.GetOAuthApps(context.Background(), 0, 1000)
require.NoError(t, err)
found1 := false
found2 := false
for _, a := range apps {
if a.Id == rapp.Id {
found1 = true
}
if a.Id == rapp2.Id {
found2 = true
}
}
assert.Truef(t, found1, "missing oauth app %v", rapp.Id)
assert.Truef(t, found2, "missing oauth app %v", rapp2.Id)
apps, _, err = adminClient.GetOAuthApps(context.Background(), 1, 1)
require.NoError(t, err)
require.Equal(t, 1, len(apps), "paging failed")
apps, _, err = client.GetOAuthApps(context.Background(), 0, 1000)
require.NoError(t, err)
require.True(t, len(apps) == 1 || apps[0].Id == rapp2.Id, "wrong apps returned")
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, resp, err := client.GetOAuthApps(context.Background(), 0, 1000)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.GetOAuthApps(context.Background(), 0, 1000)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
_, resp, err = adminClient.GetOAuthApps(context.Background(), 0, 1000)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestGetOAuthApp(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err := client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
rrapp, _, err := adminClient.GetOAuthApp(context.Background(), rapp.Id)
require.NoError(t, err)
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
assert.NotEqual(t, "", rrapp.ClientSecret, "should not be sanitized")
rrapp2, _, err := adminClient.GetOAuthApp(context.Background(), rapp2.Id)
require.NoError(t, err)
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
assert.NotEqual(t, "", rrapp2.ClientSecret, "should not be sanitized")
_, _, err = client.GetOAuthApp(context.Background(), rapp2.Id)
require.NoError(t, err)
_, resp, err := client.GetOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, resp, err = client.GetOAuthApp(context.Background(), rapp2.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.GetOAuthApp(context.Background(), rapp2.Id)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, resp, err = adminClient.GetOAuthApp(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = adminClient.GetOAuthApp(context.Background(), model.NewId())
require.Error(t, err)
CheckNotFoundStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
_, resp, err = adminClient.GetOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestGetOAuthAppInfo(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err := client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
rrapp, _, err := adminClient.GetOAuthAppInfo(context.Background(), rapp.Id)
require.NoError(t, err)
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
assert.Equal(t, "", rrapp.ClientSecret, "should be sanitized")
rrapp2, _, err := adminClient.GetOAuthAppInfo(context.Background(), rapp2.Id)
require.NoError(t, err)
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
assert.Equal(t, "", rrapp2.ClientSecret, "should be sanitized")
_, _, err = client.GetOAuthAppInfo(context.Background(), rapp2.Id)
require.NoError(t, err)
_, _, err = client.GetOAuthAppInfo(context.Background(), rapp.Id)
require.NoError(t, err)
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, _, err = client.GetOAuthAppInfo(context.Background(), rapp2.Id)
require.NoError(t, err)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err := client.GetOAuthAppInfo(context.Background(), rapp2.Id)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, resp, err = adminClient.GetOAuthAppInfo(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = adminClient.GetOAuthAppInfo(context.Background(), model.NewId())
require.Error(t, err)
CheckNotFoundStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
_, resp, err = adminClient.GetOAuthAppInfo(context.Background(), rapp.Id)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestDeleteOAuthApp(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err := client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
_, err = adminClient.DeleteOAuthApp(context.Background(), rapp.Id)
require.NoError(t, err)
_, err = adminClient.DeleteOAuthApp(context.Background(), rapp2.Id)
require.NoError(t, err)
rapp, _, err = adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err = client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
resp, err := client.DeleteOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, err = client.DeleteOAuthApp(context.Background(), rapp2.Id)
require.NoError(t, err)
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
resp, err = client.DeleteOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
resp, err = client.DeleteOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
resp, err = adminClient.DeleteOAuthApp(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
resp, err = adminClient.DeleteOAuthApp(context.Background(), model.NewId())
require.Error(t, err)
CheckNotFoundStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
resp, err = adminClient.DeleteOAuthApp(context.Background(), rapp.Id)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestRegenerateOAuthAppSecret(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
}()
// Grant permission to regular users.
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err := client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
rrapp, _, err := adminClient.RegenerateOAuthAppSecret(context.Background(), rapp.Id)
require.NoError(t, err)
assert.Equal(t, rrapp.Id, rapp.Id, "wrong app")
assert.NotEqual(t, rapp.ClientSecret, rrapp.ClientSecret, "secret didn't change")
_, _, err = adminClient.RegenerateOAuthAppSecret(context.Background(), rapp2.Id)
require.NoError(t, err)
rapp, _, err = adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
oapp.Name = GenerateTestAppName()
rapp2, _, err = client.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
_, resp, err := client.RegenerateOAuthAppSecret(context.Background(), rapp.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, _, err = client.RegenerateOAuthAppSecret(context.Background(), rapp2.Id)
require.NoError(t, err)
// Revoke permission from regular users.
th.RemovePermissionFromRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
_, resp, err = client.RegenerateOAuthAppSecret(context.Background(), rapp.Id)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.RegenerateOAuthAppSecret(context.Background(), rapp.Id)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, resp, err = adminClient.RegenerateOAuthAppSecret(context.Background(), "junk")
require.Error(t, err)
CheckBadRequestStatus(t, resp)
_, resp, err = adminClient.RegenerateOAuthAppSecret(context.Background(), model.NewId())
require.Error(t, err)
CheckNotFoundStatus(t, resp)
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
_, resp, err = adminClient.RegenerateOAuthAppSecret(context.Background(), rapp.Id)
require.Error(t, err)
CheckNotImplementedStatus(t, resp)
}
func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
client := th.Client
adminClient := th.SystemAdminClient
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
defer func() {
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
}()
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
oapp := &model.OAuthApp{Name: GenerateTestAppName(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
rapp, _, err := adminClient.CreateOAuthApp(context.Background(), oapp)
require.NoError(t, err)
authRequest := &model.AuthorizeRequest{
ResponseType: model.AuthCodeResponseType,
ClientId: rapp.Id,
RedirectURI: rapp.CallbackUrls[0],
Scope: "",
State: "123",
}
_, _, err = client.AuthorizeOAuthApp(context.Background(), authRequest)
require.NoError(t, err)
apps, _, err := client.GetAuthorizedOAuthAppsForUser(context.Background(), th.BasicUser.Id, 0, 1000)
require.NoError(t, err)
found := false
for _, a := range apps {
if a.Id == rapp.Id {
found = true
}
assert.Equal(t, "", a.ClientSecret, "not sanitized")
}
require.True(t, found, "missing app")
_, resp, err := client.GetAuthorizedOAuthAppsForUser(context.Background(), th.BasicUser2.Id, 0, 1000)
require.Error(t, err)
CheckForbiddenStatus(t, resp)
_, resp, err = client.GetAuthorizedOAuthAppsForUser(context.Background(), "junk", 0, 1000)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
_, err = client.Logout(context.Background())
require.NoError(t, err)
_, resp, err = client.GetAuthorizedOAuthAppsForUser(context.Background(), th.BasicUser.Id, 0, 1000)
require.Error(t, err)
CheckUnauthorizedStatus(t, resp)
_, _, err = adminClient.GetAuthorizedOAuthAppsForUser(context.Background(), th.BasicUser.Id, 0, 1000)
require.NoError(t, err)
}
func TestNilAuthorizeOAuthApp(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
client := th.Client
_, _, err := client.AuthorizeOAuthApp(context.Background(), nil)
require.Error(t, err)
CheckErrorID(t, err, "api.context.invalid_body_param.app_error")
}
func TestRegisterOAuthClient(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)
// Configure server for DCR
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.EnableOAuthServiceProvider = new(true)
cfg.ServiceSettings.EnableDynamicClientRegistration = new(true)
})
t.Run("Valid DCR request", func(t *testing.T) {
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client"),
}
response, resp, err := th.SystemAdminClient.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
require.NotNil(t, response)
CheckCreatedStatus(t, resp)
assert.Equal(t, request.RedirectURIs, response.RedirectURIs)
assert.NotEmpty(t, response.ClientID)
assert.NotNil(t, response.ClientSecret)
assert.NotEmpty(t, *response.ClientSecret)
})
t.Run("Missing redirect URIs", func(t *testing.T) {
request := &model.ClientRegistrationRequest{
ClientName: new("Test Client"),
}
_, resp, err := th.SystemAdminClient.RegisterOAuthClient(context.Background(), request)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
})
t.Run("Works without authentication", func(t *testing.T) {
// Sleep to avoid rate limiting issues
time.Sleep(time.Second)
// Log out to demonstrate DCR works without session
_, err := th.Client.Logout(context.Background())
require.NoError(t, err)
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client No Auth"),
}
response, resp, err := th.Client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
require.NotNil(t, response)
CheckCreatedStatus(t, resp)
assert.Equal(t, request.RedirectURIs, response.RedirectURIs)
assert.NotEmpty(t, response.ClientID)
assert.NotNil(t, response.ClientSecret)
assert.NotEmpty(t, *response.ClientSecret)
})
t.Run("Works with client_uri", func(t *testing.T) {
// Sleep to avoid rate limiting issues
time.Sleep(time.Second)
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client with URI"),
ClientURI: new("https://example.com"),
}
response, resp, err := th.Client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
require.NotNil(t, response)
CheckCreatedStatus(t, resp)
assert.Equal(t, request.RedirectURIs, response.RedirectURIs)
assert.NotEmpty(t, response.ClientID)
assert.NotNil(t, response.ClientSecret)
assert.NotEmpty(t, *response.ClientSecret)
assert.Equal(t, request.ClientName, response.ClientName)
assert.Equal(t, request.ClientURI, response.ClientURI)
})
}
func TestRegisterOAuthClient_DisabledFeatures(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
adminClient := th.SystemAdminClient
defaultRolePermissions := th.SaveDefaultRolePermissions(t)
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
enableDCR := th.App.Config().ServiceSettings.EnableDynamicClientRegistration
defer func() {
th.RestoreDefaultRolePermissions(t, defaultRolePermissions)
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider
cfg.ServiceSettings.EnableDynamicClientRegistration = enableDCR
})
}()
th.AddPermissionToRole(t, model.PermissionManageOAuth.Id, model.SystemUserRoleId)
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client"),
}
// Test with OAuth disabled
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.EnableOAuthServiceProvider = new(false)
cfg.ServiceSettings.EnableDynamicClientRegistration = new(true)
})
_, resp, err := adminClient.RegisterOAuthClient(context.Background(), request)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
// Sleep to avoid rate limiting issues
time.Sleep(time.Second)
// Test with DCR disabled
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.EnableOAuthServiceProvider = new(true)
cfg.ServiceSettings.EnableDynamicClientRegistration = new(false)
})
_, resp, err = adminClient.RegisterOAuthClient(context.Background(), request)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
// Sleep to avoid rate limiting issues
time.Sleep(time.Second)
// Test with nil config values (should be disabled by default)
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.EnableOAuthServiceProvider = nil
cfg.ServiceSettings.EnableDynamicClientRegistration = nil
})
_, resp, err = adminClient.RegisterOAuthClient(context.Background(), request)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
}
func TestRegisterOAuthClient_RedirectURIAllowlist(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableOAuthServiceProvider = true
cfg.ServiceSettings.EnableDynamicClientRegistration = new(true)
})
t.Run("allowlist empty registration succeeds", func(t *testing.T) {
cfg := th.App.Config()
cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{}
th.App.UpdateConfig(func(c *model.Config) { *c = *cfg })
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client"),
}
response, resp, err := client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, response)
assert.NotEmpty(t, response.ClientID)
})
t.Run("wildcard allowed URI succeeds", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"https://example.com/*", "https://*.test.com/**"}
})
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new("Test Client"),
}
response, resp, err := client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, response)
time.Sleep(time.Second) // avoid rate limit
request2 := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://app.test.com/deep/path/cb"},
ClientName: new("Test Client 2"),
}
response2, resp2, err2 := client.RegisterOAuthClient(context.Background(), request2)
require.NoError(t, err2)
CheckCreatedStatus(t, resp2)
require.NotNil(t, response2)
})
t.Run("disallowed URI returns 400 invalid_redirect_uri", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"https://allowed.com/**"}
})
body, _ := json.Marshal(&model.ClientRegistrationRequest{
RedirectURIs: []string{"https://disallowed.com/callback"},
ClientName: new("Test Client"),
})
req, err := http.NewRequest(http.MethodPost, client.APIURL+"/oauth/apps/register", bytes.NewReader(body))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
if client.AuthToken != "" {
req.Header.Set(model.HeaderAuth, model.HeaderBearer+" "+client.AuthToken)
}
httpResp, err := client.HTTPClient.Do(req)
require.NoError(t, err)
defer httpResp.Body.Close()
require.Equal(t, http.StatusBadRequest, httpResp.StatusCode)
var dcrErr model.DCRError
jsonErr := json.NewDecoder(httpResp.Body).Decode(&dcrErr)
require.NoError(t, jsonErr)
assert.Equal(t, model.DCRErrorInvalidRedirectURI, dcrErr.Error)
assert.NotEmpty(t, dcrErr.ErrorDescription)
})
t.Run("wildcard host cannot be satisfied by query string", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"https://*.example.com/**"}
})
registerRedirectURI := func(redirectURI string) (*http.Response, model.DCRError) {
body, _ := json.Marshal(&model.ClientRegistrationRequest{
RedirectURIs: []string{redirectURI},
ClientName: model.NewPointer("Test Client"),
TokenEndpointAuthMethod: model.NewPointer(model.ClientAuthMethodNone),
})
req, err := http.NewRequest(http.MethodPost, client.APIURL+"/oauth/apps/register", bytes.NewReader(body))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
httpResp, err := client.HTTPClient.Do(req)
require.NoError(t, err)
var dcrErr model.DCRError
if httpResp.StatusCode == http.StatusBadRequest {
jsonErr := json.NewDecoder(httpResp.Body).Decode(&dcrErr)
require.NoError(t, jsonErr)
}
require.NoError(t, httpResp.Body.Close())
return httpResp, dcrErr
}
time.Sleep(time.Second)
httpResp, dcrErr := registerRedirectURI("https://attacker.example.net/cb")
require.Equal(t, http.StatusBadRequest, httpResp.StatusCode)
assert.Equal(t, model.DCRErrorInvalidRedirectURI, dcrErr.Error)
time.Sleep(time.Second)
httpResp, dcrErr = registerRedirectURI("https://attacker.example.net?x=.example.com/cb")
require.Equal(t, http.StatusBadRequest, httpResp.StatusCode)
assert.Equal(t, model.DCRErrorInvalidRedirectURI, dcrErr.Error)
time.Sleep(time.Second)
httpResp, dcrErr = registerRedirectURI("https://app.example.com/cb")
require.Equal(t, http.StatusCreated, httpResp.StatusCode)
assert.Empty(t, dcrErr.Error)
})
t.Run("multi redirect partial mismatch rejects request", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"https://allowed.com/**"}
})
time.Sleep(time.Second)
body, _ := json.Marshal(&model.ClientRegistrationRequest{
RedirectURIs: []string{"https://allowed.com/cb1", "https://disallowed.com/cb2"},
ClientName: new("Test Client"),
})
req, err := http.NewRequest(http.MethodPost, client.APIURL+"/oauth/apps/register", bytes.NewReader(body))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/json")
if client.AuthToken != "" {
req.Header.Set(model.HeaderAuth, model.HeaderBearer+" "+client.AuthToken)
}
httpResp, err := client.HTTPClient.Do(req)
require.NoError(t, err)
defer httpResp.Body.Close()
require.Equal(t, http.StatusBadRequest, httpResp.StatusCode)
var dcrErr model.DCRError
jsonErr := json.NewDecoder(httpResp.Body).Decode(&dcrErr)
require.NoError(t, jsonErr)
assert.Equal(t, model.DCRErrorInvalidRedirectURI, dcrErr.Error)
assert.NotEmpty(t, dcrErr.ErrorDescription)
})
}
func TestRegisterOAuthClient_PublicClient_Success(t *testing.T) {
// Test successful public client DCR registration
mainHelper.Parallel(t)
th := Setup(t)
client := th.Client
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableOAuthServiceProvider = true
cfg.ServiceSettings.EnableDynamicClientRegistration = new(true)
})
// DCR request for public client
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
TokenEndpointAuthMethod: model.NewPointer(model.ClientAuthMethodNone),
ClientName: new("Test Public Client"),
ClientURI: new("https://example.com"),
}
// Register public client
response, resp, err := client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, response)
// Verify response properties for public client
assert.NotEmpty(t, response.ClientID)
assert.Nil(t, response.ClientSecret) // No client secret for public clients
assert.Equal(t, request.RedirectURIs, response.RedirectURIs)
assert.Equal(t, model.ClientAuthMethodNone, response.TokenEndpointAuthMethod)
assert.Equal(t, *request.ClientName, *response.ClientName)
assert.Equal(t, *request.ClientURI, *response.ClientURI)
assert.Equal(t, "user", response.Scope)
}
func TestRegisterOAuthClientAudit(t *testing.T) {
logFile, err := os.CreateTemp("", "dcr_audit.log")
require.NoError(t, err)
defer os.Remove(logFile.Name())
options := []app.Option{app.WithLicense(model.NewTestLicense("advanced_logging"))}
th := SetupWithServerOptionsAndConfig(t, options, func(cfg *model.Config) {
cfg.ExperimentalAuditSettings.FileEnabled = new(true)
cfg.ExperimentalAuditSettings.FileName = new(logFile.Name())
*cfg.ServiceSettings.EnableOAuthServiceProvider = true
cfg.ServiceSettings.EnableDynamicClientRegistration = new(true)
})
t.Run("Successful DCR registration is audited", func(t *testing.T) {
clientName := "Test Audit Client"
request := &model.ClientRegistrationRequest{
RedirectURIs: []string{"https://example.com/callback"},
ClientName: new(clientName),
}
response, resp, err := th.Client.RegisterOAuthClient(context.Background(), request)
require.NoError(t, err)
CheckCreatedStatus(t, resp)
require.NotNil(t, response)
// Flush audit logs
err = th.Server.Audit.Flush()
require.NoError(t, err)
require.NoError(t, logFile.Sync())
// Read and verify audit log
data, err := io.ReadAll(logFile)
require.NoError(t, err)
require.NotEmpty(t, data)
entry := FindAuditEntry(string(data), "registerOAuthClient", "")
require.NotNil(t, entry, "should find a registerOAuthClient audit entry")
assert.Equal(t, "success", entry.Status)
// Verify client details are in the raw entry
assert.Contains(t, fmt.Sprintf("%v", entry.Raw), clientName)
assert.Contains(t, fmt.Sprintf("%v", entry.Raw), response.ClientID)
})
t.Run("Failed DCR registration is audited", func(t *testing.T) {
// Truncate log file for this test
require.NoError(t, logFile.Truncate(0))
_, err := logFile.Seek(0, 0)
require.NoError(t, err)
// Invalid request (missing redirect URIs)
request := &model.ClientRegistrationRequest{
ClientName: new("Invalid Client"),
}
_, resp, err := th.Client.RegisterOAuthClient(context.Background(), request)
require.Error(t, err)
CheckBadRequestStatus(t, resp)
// Flush audit logs
err = th.Server.Audit.Flush()
require.NoError(t, err)
require.NoError(t, logFile.Sync())
// Read and verify audit log
data, err := io.ReadAll(logFile)
require.NoError(t, err)
require.NotEmpty(t, data)
entry := FindAuditEntry(string(data), "registerOAuthClient", "")
require.NotNil(t, entry, "should find a registerOAuthClient audit entry")
assert.Equal(t, "fail", entry.Status)
})
}