mattermost/server/platform/services/sharedchannel/permalink.go
Doug Lauder e694e86d63
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
MM-68204: Use multi-level logging for shared channel and remote cluster service errors (#35949)
* Use multi-level logging for shared channel and remote cluster service errors

  Service-specific log levels (LvlRemoteClusterServiceError, LvlSharedChannelServiceError)
  were hidden from the main log by default, requiring explicit configuration to see them.
  Switch all call sites to use LogM with multi-level combos so each log line is attributed
  to both the standard level (error/warn) and the service-specific level. This surfaces
  errors in the main log while preserving the ability to isolate them into dedicated files.

  Each instance was reviewed and either kept as error (DB failures, security issues, config
  errors, exhausted retries on critical data) or downgraded to warn (transient network
  failures, remote-side reported errors with retry logic, non-critical data like profile
  images, reactions, acknowledgements, and status).
2026-04-06 12:17:47 -04:00

88 lines
3.2 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sharedchannel
import (
"net/url"
"regexp"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/i18n"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
)
var (
// Team name regex taken from model.IsValidTeamName
permaLinkRegex = regexp.MustCompile(`https?://[0-9.\-A-Za-z]+/[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+/pl/([a-zA-Z0-9]+)`)
permaLinkSharedRegex = regexp.MustCompile(`https?://[0-9.\-A-Za-z]+/[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+/plshared/([a-zA-Z0-9]+)`)
)
const (
permalinkMarker = "plshared"
)
// processPermalinkToRemote processes all permalinks going towards a remote site.
func (scs *Service) processPermalinkToRemote(p *model.Post) string {
// request.CTX is not widely used in the shared channels code.
// Just create a new request.CTX for now.
rctx := request.EmptyContext(scs.server.Log())
var sent bool
return permaLinkRegex.ReplaceAllStringFunc(p.Message, func(msg string) string {
// Extract the postID (This is simple enough not to warrant full-blown URL parsing.)
lastSlash := strings.LastIndexByte(msg, '/')
postID := msg[lastSlash+1:]
opts := model.GetPostsOptions{
SkipFetchThreads: true,
}
postList, err := scs.server.GetStore().Post().Get(rctx, postID, opts, "", map[string]bool{})
if err != nil {
scs.server.Log().LogM(mlog.MlvlSharedChannelServiceWarn, "Unable to get post during replacing permalinks", mlog.Err(err))
return msg
}
if len(postList.Order) == 0 {
scs.server.Log().LogM(mlog.MlvlSharedChannelServiceWarn, "No post found for permalink", mlog.String("postID", postID))
return msg
}
// If postID is for a different channel
if postList.Posts[postList.Order[0]].ChannelId != p.ChannelId {
// Send ephemeral message to OP (only once per message).
if !sent {
scs.sendEphemeralPost(p.ChannelId, p.UserId, i18n.T("sharedchannel.permalink.not_found"))
sent = true
}
// But don't modify msg
return msg
}
// Otherwise, modify pl to plshared as a marker to be replaced by remote sites
return strings.Replace(msg, "/pl/", "/"+permalinkMarker+"/", 1)
})
}
// processPermalinkFromRemote processes all permalinks coming from a remote site.
func (scs *Service) processPermalinkFromRemote(p *model.Post, team *model.Team) string {
return permaLinkSharedRegex.ReplaceAllStringFunc(p.Message, func(remoteLink string) string {
// Extract host name
parsed, err := url.Parse(remoteLink)
if err != nil {
scs.server.Log().LogM(mlog.MlvlSharedChannelServiceWarn, "Unable to parse the remote link during replacing permalinks", mlog.Err(err))
return remoteLink
}
// Replace with local SiteURL
parsed.Scheme = scs.siteURL.Scheme
parsed.Host = scs.siteURL.Host
// Replace team name with local team
teamEnd := strings.Index(parsed.Path, "/"+permalinkMarker)
parsed.Path = "/" + team.Name + parsed.Path[teamEnd:]
// Replace plshared with pl
return strings.Replace(parsed.String(), "/"+permalinkMarker+"/", "/pl/", 1)
})
}