add audit logs to DCR (#34598)

This commit is contained in:
Ben Cooke
2025-11-28 11:44:15 -05:00
committed by GitHub
parent 758bdd785f
commit c78ebc5ec1
3 changed files with 102 additions and 0 deletions
+20
View File
@@ -342,6 +342,9 @@ func getAuthorizedOAuthApps(c *Context, w http.ResponseWriter, r *http.Request)
func registerOAuthClient(c *Context, w http.ResponseWriter, r *http.Request) {
// Session and permission checks removed for DCR endpoint to allow external client registration
auditRec := c.MakeAuditRecord(model.AuditEventRegisterOAuthClient, model.AuditStatusFail)
defer c.LogAuditRec(auditRec)
var clientRequest model.ClientRegistrationRequest
if jsonErr := json.NewDecoder(r.Body).Decode(&clientRequest); jsonErr != nil {
dcrError := model.NewDCRError(model.DCRErrorInvalidClientMetadata, "Invalid JSON in request body")
@@ -353,6 +356,18 @@ func registerOAuthClient(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
// Add DCR request parameters to audit record
model.AddEventParameterToAuditRec(auditRec, "redirect_uris", clientRequest.RedirectURIs)
if clientRequest.ClientName != nil {
model.AddEventParameterToAuditRec(auditRec, "client_name", *clientRequest.ClientName)
}
if clientRequest.TokenEndpointAuthMethod != nil {
model.AddEventParameterToAuditRec(auditRec, "token_endpoint_auth_method", *clientRequest.TokenEndpointAuthMethod)
}
if clientRequest.ClientURI != nil {
model.AddEventParameterToAuditRec(auditRec, "client_uri", *clientRequest.ClientURI)
}
// Check if OAuth service provider is enabled
if !*c.App.Config().ServiceSettings.EnableOAuthServiceProvider {
dcrError := model.NewDCRError(model.DCRErrorUnsupportedOperation, "OAuth service provider is disabled")
@@ -400,6 +415,11 @@ func registerOAuthClient(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
auditRec.Success()
auditRec.AddEventResultState(app)
auditRec.AddEventObjectType("oauth_app")
c.LogAudit("client_id=" + app.Id)
siteURL := *c.App.Config().ServiceSettings.SiteURL
response := app.ToClientRegistrationResponse(siteURL)
+81
View File
@@ -5,7 +5,9 @@ package api4
import (
"context"
"io"
"net/http"
"os"
"testing"
"time"
@@ -13,6 +15,7 @@ import (
"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) {
@@ -810,3 +813,81 @@ func TestRegisterOAuthClient_PublicClient_Success(t *testing.T) {
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())
os.Setenv("MM_EXPERIMENTALAUDITSETTINGS_FILEENABLED", "true")
os.Setenv("MM_EXPERIMENTALAUDITSETTINGS_FILENAME", logFile.Name())
defer os.Unsetenv("MM_EXPERIMENTALAUDITSETTINGS_FILEENABLED")
defer os.Unsetenv("MM_EXPERIMENTALAUDITSETTINGS_FILENAME")
options := []app.Option{app.WithLicense(model.NewTestLicense("advanced_logging"))}
th := SetupWithServerOptions(t, options)
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ServiceSettings.EnableOAuthServiceProvider = true
cfg.ServiceSettings.EnableDynamicClientRegistration = model.NewPointer(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: model.NewPointer(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)
auditLog := string(data)
assert.Contains(t, auditLog, "registerOAuthClient")
assert.Contains(t, auditLog, clientName)
assert.Contains(t, auditLog, response.ClientID)
assert.Contains(t, auditLog, "success")
})
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: model.NewPointer("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)
auditLog := string(data)
assert.Contains(t, auditLog, "registerOAuthClient")
assert.Contains(t, auditLog, "fail")
})
}
+1
View File
@@ -222,6 +222,7 @@ const (
AuditEventLoginWithOAuth = "loginWithOAuth" // login using OAuth authentication provider
AuditEventMobileLoginWithOAuth = "mobileLoginWithOAuth" // mobile application login using OAuth authentication provider
AuditEventRegenerateOAuthAppSecret = "regenerateOAuthAppSecret" // regenerate secret key for OAuth app
AuditEventRegisterOAuthClient = "registerOAuthClient" // register OAuth client via dynamic client registration (RFC 7591)
AuditEventSignupWithOAuth = "signupWithOAuth" // create account using OAuth authentication provider
AuditEventUpdateOAuthApp = "updateOAuthApp" // update OAuth app
AuditEventUpdateOutgoingOAuthConnection = "updateOutgoingOAuthConnection" // update outgoing OAuth connection