mirror of
https://github.com/hanzoai/team.git
synced 2026-10-30 14:49:35 +00:00
* Validate Azure storage account name format For the commercial and government clouds the storage account name becomes part of the service hostname, so enforce Azure's documented format (3 to 24 lowercase letters and digits) both in config validation and when building the service URL. Also treat an empty AzureCloud as commercial, consistent with the URL builder. * Route Azure custom endpoints through httpservice The Azure custom-cloud backend now issues requests through httpservice's transport rather than the SDK default, so they honor ServiceSettings.AllowedUntrustedInternalConnections like other outbound connections. The commercial and government clouds keep the SDK default transport, which Private Link deployments rely on. The allowlist value is plumbed into FileBackendSettings, and the host/IP checks are factored into a shared httpservice helper that MakeTransport now uses too.
205 lines
4.8 KiB
Go
205 lines
4.8 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package users
|
|
|
|
import (
|
|
"bytes"
|
|
"hash/fnv"
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/png"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/golang/freetype"
|
|
"github.com/golang/freetype/truetype"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
|
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
|
xfont "golang.org/x/image/font"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
const (
|
|
imageProfilePixelDimension = 128
|
|
)
|
|
|
|
func (us *UserService) GetProfileImage(user *model.User) ([]byte, bool, error) {
|
|
if *us.config().FileSettings.DriverName == "" {
|
|
img, err := us.GetDefaultProfileImage(user)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
return img, false, nil
|
|
}
|
|
|
|
path := path.Join("users", user.Id, "profile.png")
|
|
data, err := us.ReadFile(path)
|
|
if err != nil {
|
|
img, appErr := us.GetDefaultProfileImage(user)
|
|
if appErr != nil {
|
|
return nil, false, appErr
|
|
}
|
|
|
|
if user.LastPictureUpdate == 0 {
|
|
if _, err := us.writeFile(bytes.NewReader(img), path); err != nil {
|
|
return nil, false, err
|
|
}
|
|
}
|
|
return img, true, nil
|
|
}
|
|
|
|
return data, false, nil
|
|
}
|
|
|
|
func (us *UserService) FileBackend() (filestore.FileBackend, error) {
|
|
license := us.license()
|
|
insecure := us.config().ServiceSettings.EnableInsecureOutgoingConnections
|
|
allowedUntrustedInternalConnections := model.SafeDereference(us.config().ServiceSettings.AllowedUntrustedInternalConnections)
|
|
backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&us.config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure, allowedUntrustedInternalConnections))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return backend, nil
|
|
}
|
|
|
|
func (us *UserService) ReadFile(path string) ([]byte, error) {
|
|
backend, err := us.FileBackend()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result, nErr := backend.ReadFile(path)
|
|
if nErr != nil {
|
|
return nil, nErr
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (us *UserService) writeFile(fr io.Reader, path string) (int64, error) {
|
|
backend, err := us.FileBackend()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
result, nErr := backend.WriteFile(fr, path)
|
|
if nErr != nil {
|
|
return result, nErr
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (us *UserService) GetDefaultProfileImage(user *model.User) ([]byte, error) {
|
|
if user.IsBot {
|
|
return botDefaultImage, nil
|
|
}
|
|
|
|
return createProfileImage(user.Username, user.Id, *us.config().FileSettings.InitialFont)
|
|
}
|
|
|
|
func createProfileImage(username string, userID string, initialFont string) ([]byte, error) {
|
|
colors := []color.NRGBA{
|
|
{197, 8, 126, 255},
|
|
{227, 207, 18, 255},
|
|
{28, 181, 105, 255},
|
|
{35, 188, 224, 255},
|
|
{116, 49, 196, 255},
|
|
{197, 8, 126, 255},
|
|
{197, 19, 19, 255},
|
|
{250, 134, 6, 255},
|
|
{227, 207, 18, 255},
|
|
{123, 201, 71, 255},
|
|
{28, 181, 105, 255},
|
|
{35, 188, 224, 255},
|
|
{116, 49, 196, 255},
|
|
{197, 8, 126, 255},
|
|
{197, 19, 19, 255},
|
|
{250, 134, 6, 255},
|
|
{227, 207, 18, 255},
|
|
{123, 201, 71, 255},
|
|
{28, 181, 105, 255},
|
|
{35, 188, 224, 255},
|
|
{116, 49, 196, 255},
|
|
{197, 8, 126, 255},
|
|
{197, 19, 19, 255},
|
|
{250, 134, 6, 255},
|
|
{227, 207, 18, 255},
|
|
{123, 201, 71, 255},
|
|
}
|
|
|
|
h := fnv.New32a()
|
|
h.Write([]byte(userID))
|
|
seed := h.Sum32()
|
|
|
|
initial := string(strings.ToUpper(username)[0])
|
|
|
|
font, err := getFont(initialFont)
|
|
if err != nil {
|
|
return nil, DefaultFontError
|
|
}
|
|
|
|
color := colors[int64(seed)%int64(len(colors))]
|
|
|
|
dstImg := image.NewRGBA(image.Rect(0, 0, imageProfilePixelDimension, imageProfilePixelDimension))
|
|
|
|
draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.Point{}, draw.Src)
|
|
|
|
size := float64(imageProfilePixelDimension / 2)
|
|
|
|
opts := truetype.Options{}
|
|
opts.Size = size
|
|
face := truetype.NewFace(font, &opts)
|
|
|
|
d := &xfont.Drawer{
|
|
Dst: dstImg,
|
|
Src: image.White,
|
|
Face: face,
|
|
}
|
|
|
|
bounds, advance, ok := face.GlyphBounds([]rune(initial)[0])
|
|
|
|
if !ok {
|
|
return nil, GlyphError
|
|
}
|
|
|
|
x := (fixed.I(imageProfilePixelDimension) - advance) / 2
|
|
y := (fixed.I(imageProfilePixelDimension) + (bounds.Max.Y - bounds.Min.Y)) / 2
|
|
|
|
d.Dot = fixed.Point26_6{
|
|
X: x,
|
|
Y: y,
|
|
}
|
|
d.DrawString(initial)
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
enc := png.Encoder{
|
|
CompressionLevel: png.BestCompression,
|
|
}
|
|
if imgErr := enc.Encode(buf, dstImg); imgErr != nil {
|
|
return nil, ImageEncodingError
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func getFont(initialFont string) (*truetype.Font, error) {
|
|
// Some people have the old default font still set, so just treat that as if they're using the new default
|
|
if initialFont == "luximbi.ttf" {
|
|
initialFont = "nunito-bold.ttf"
|
|
}
|
|
|
|
fontDir, _ := fileutils.FindDir("fonts")
|
|
fontBytes, err := os.ReadFile(filepath.Join(fontDir, initialFont))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return freetype.ParseFont(fontBytes)
|
|
}
|