allow to customize name and log from the WebUI

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2024-07-23 20:52:36 +02:00
parent b2926377b7
commit b5c821795a
16 changed files with 996 additions and 101 deletions

View File

@@ -15,8 +15,11 @@
package dataprovider
import (
"bytes"
"encoding/json"
"fmt"
"image/png"
"net/url"
"golang.org/x/crypto/ssh"
@@ -305,6 +308,27 @@ func (c *SMTPConfigs) TryDecrypt() error {
return nil
}
func (c *SMTPConfigs) prepareForRendering() {
if c.Password != nil {
c.Password.Hide()
if c.Password.IsEmpty() {
c.Password = nil
}
}
if c.OAuth2.ClientSecret != nil {
c.OAuth2.ClientSecret.Hide()
if c.OAuth2.ClientSecret.IsEmpty() {
c.OAuth2.ClientSecret = nil
}
}
if c.OAuth2.RefreshToken != nil {
c.OAuth2.RefreshToken.Hide()
if c.OAuth2.RefreshToken.IsEmpty() {
c.OAuth2.RefreshToken = nil
}
}
}
func (c *SMTPConfigs) getACopy() *SMTPConfigs {
var password *kms.Secret
if c.Password != nil {
@@ -387,13 +411,137 @@ func (c *ACMEConfigs) getACopy() *ACMEConfigs {
}
}
// BrandingConfig defines the branding configuration
type BrandingConfig struct {
Name string `json:"name"`
ShortName string `json:"short_name"`
Logo []byte `json:"logo"`
Favicon []byte `json:"favicon"`
DisclaimerName string `json:"disclaimer_name"`
DisclaimerURL string `json:"disclaimer_url"`
}
func (c *BrandingConfig) isEmpty() bool {
if c.Name != "" {
return false
}
if c.ShortName != "" {
return false
}
if len(c.Logo) > 0 {
return false
}
if len(c.Favicon) > 0 {
return false
}
if c.DisclaimerName != "" && c.DisclaimerURL != "" {
return false
}
return true
}
func (*BrandingConfig) validatePNG(b []byte, maxWidth, maxHeight int) error {
if len(b) == 0 {
return nil
}
// DecodeConfig is more efficient, but I'm not sure if this would lead to
// accepting invalid images in some edge cases and performance does not
// matter here.
img, err := png.Decode(bytes.NewBuffer(b))
if err != nil {
return util.NewI18nError(
util.NewValidationError("invalid PNG image"),
util.I18nErrorInvalidPNG,
)
}
bounds := img.Bounds()
if bounds.Dx() > maxWidth || bounds.Dy() > maxHeight {
return util.NewI18nError(
util.NewValidationError("invalid PNG image size"),
util.I18nErrorInvalidPNGSize,
)
}
return nil
}
func (c *BrandingConfig) validateDisclaimerURL() error {
if c.DisclaimerURL == "" {
return nil
}
u, err := url.Parse(c.DisclaimerURL)
if err != nil {
return util.NewI18nError(
util.NewValidationError("invalid disclaimer URL"),
util.I18nErrorInvalidDisclaimerURL,
)
}
if u.Scheme != "http" && u.Scheme != "https" {
return util.NewI18nError(
util.NewValidationError("invalid disclaimer URL scheme"),
util.I18nErrorInvalidDisclaimerURL,
)
}
return nil
}
func (c *BrandingConfig) validate() error {
if err := c.validateDisclaimerURL(); err != nil {
return err
}
if err := c.validatePNG(c.Logo, 512, 512); err != nil {
return err
}
return c.validatePNG(c.Favicon, 256, 256)
}
func (c *BrandingConfig) getACopy() BrandingConfig {
logo := make([]byte, len(c.Logo))
copy(logo, c.Logo)
favicon := make([]byte, len(c.Favicon))
copy(favicon, c.Favicon)
return BrandingConfig{
Name: c.Name,
ShortName: c.ShortName,
Logo: logo,
Favicon: favicon,
DisclaimerName: c.DisclaimerName,
DisclaimerURL: c.DisclaimerURL,
}
}
// BrandingConfigs defines the branding configuration for WebAdmin and WebClient UI
type BrandingConfigs struct {
WebAdmin BrandingConfig
WebClient BrandingConfig
}
func (c *BrandingConfigs) isEmpty() bool {
return c.WebAdmin.isEmpty() && c.WebClient.isEmpty()
}
func (c *BrandingConfigs) validate() error {
if err := c.WebAdmin.validate(); err != nil {
return err
}
return c.WebClient.validate()
}
func (c *BrandingConfigs) getACopy() *BrandingConfigs {
return &BrandingConfigs{
WebAdmin: c.WebAdmin.getACopy(),
WebClient: c.WebClient.getACopy(),
}
}
// Configs allows to set configuration keys disabled by default without
// modifying the config file or setting env vars
type Configs struct {
SFTPD *SFTPDConfigs `json:"sftpd,omitempty"`
SMTP *SMTPConfigs `json:"smtp,omitempty"`
ACME *ACMEConfigs `json:"acme,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
SFTPD *SFTPDConfigs `json:"sftpd,omitempty"`
SMTP *SMTPConfigs `json:"smtp,omitempty"`
ACME *ACMEConfigs `json:"acme,omitempty"`
Branding *BrandingConfigs `json:"branding,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
}
func (c *Configs) validate() error {
@@ -412,6 +560,11 @@ func (c *Configs) validate() error {
return err
}
}
if c.Branding != nil {
if err := c.Branding.validate(); err != nil {
return err
}
}
return nil
}
@@ -428,25 +581,11 @@ func (c *Configs) PrepareForRendering() {
if c.ACME != nil && c.ACME.isEmpty() {
c.ACME = nil
}
if c.Branding != nil && c.Branding.isEmpty() {
c.Branding = nil
}
if c.SMTP != nil {
if c.SMTP.Password != nil {
c.SMTP.Password.Hide()
if c.SMTP.Password.IsEmpty() {
c.SMTP.Password = nil
}
}
if c.SMTP.OAuth2.ClientSecret != nil {
c.SMTP.OAuth2.ClientSecret.Hide()
if c.SMTP.OAuth2.ClientSecret.IsEmpty() {
c.SMTP.OAuth2.ClientSecret = nil
}
}
if c.SMTP.OAuth2.RefreshToken != nil {
c.SMTP.OAuth2.RefreshToken.Hide()
if c.SMTP.OAuth2.RefreshToken.IsEmpty() {
c.SMTP.OAuth2.RefreshToken = nil
}
}
c.SMTP.prepareForRendering()
}
}
@@ -470,6 +609,9 @@ func (c *Configs) SetNilsToEmpty() {
if c.ACME == nil {
c.ACME = &ACMEConfigs{}
}
if c.Branding == nil {
c.Branding = &BrandingConfigs{}
}
}
// RenderAsJSON implements the renderer interface used within plugins
@@ -498,6 +640,9 @@ func (c *Configs) getACopy() Configs {
if c.ACME != nil {
result.ACME = c.ACME.getACopy()
}
if c.Branding != nil {
result.Branding = c.Branding.getACopy()
}
result.UpdatedAt = c.UpdatedAt
return result
}