diff options
| -rw-r--r-- | docs/configuration/accounts.md | 7 | ||||
| -rw-r--r-- | example/config.yaml | 7 | ||||
| -rw-r--r-- | internal/config/config.go | 1 | ||||
| -rw-r--r-- | internal/config/defaults.go | 1 | ||||
| -rw-r--r-- | internal/config/helpers.gen.go | 75 | ||||
| -rw-r--r-- | internal/validate/formvalidation.go | 3 | ||||
| -rw-r--r-- | internal/validate/formvalidation_test.go | 166 | ||||
| -rwxr-xr-x | test/envparsing.sh | 2 | ||||
| -rw-r--r-- | testrig/config.go | 1 | 
9 files changed, 174 insertions, 89 deletions
diff --git a/docs/configuration/accounts.md b/docs/configuration/accounts.md index 3f0c6d5f8..0f4fecde0 100644 --- a/docs/configuration/accounts.md +++ b/docs/configuration/accounts.md @@ -39,4 +39,11 @@ accounts-reason-required: true  # Options: [true, false]  # Default: false  accounts-allow-custom-css: false + +# Int. If accounts-allow-custom-css is true, this is the permitted length in characters for +# CSS uploaded by accounts on this instance. No effect if accounts-allow-custom-css is false. +# +# Examples: [500, 5000, 9999] +# Default: 10000 +accounts-custom-css-length: 10000  ``` diff --git a/example/config.yaml b/example/config.yaml index 7f119ab16..c0b850ad9 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -433,6 +433,13 @@ accounts-reason-required: true  # Default: false  accounts-allow-custom-css: false +# Int. If accounts-allow-custom-css is true, this is the permitted length in characters for +# CSS uploaded by accounts on this instance. No effect if accounts-allow-custom-css is false. +# +# Examples: [500, 5000, 9999] +# Default: 10000 +accounts-custom-css-length: 10000 +  ########################  ##### MEDIA CONFIG #####  ######################## diff --git a/internal/config/config.go b/internal/config/config.go index 0b7b527ea..8dcbcaf97 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -85,6 +85,7 @@ type Configuration struct {  	AccountsApprovalRequired bool `name:"accounts-approval-required" usage:"Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved."`  	AccountsReasonRequired   bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"`  	AccountsAllowCustomCSS   bool `name:"accounts-allow-custom-css" usage:"Allow accounts to enable custom CSS for their profile pages and statuses."` +	AccountsCustomCSSLength  int  `name:"accounts-custom-css-length" usage:"Maximum permitted length (characters) of custom CSS for accounts."`  	MediaImageMaxSize        bytesize.Size `name:"media-image-max-size" usage:"Max size of accepted images in bytes"`  	MediaVideoMaxSize        bytesize.Size `name:"media-video-max-size" usage:"Max size of accepted videos in bytes"` diff --git a/internal/config/defaults.go b/internal/config/defaults.go index 53d994cda..c11f436d6 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -65,6 +65,7 @@ var Defaults = Configuration{  	AccountsApprovalRequired: true,  	AccountsReasonRequired:   true,  	AccountsAllowCustomCSS:   false, +	AccountsCustomCSSLength:  10000,  	MediaImageMaxSize:        10 * bytesize.MiB,  	MediaVideoMaxSize:        40 * bytesize.MiB, diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index 2a70488e8..16865f32c 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -74,6 +74,31 @@ func GetLogDbQueries() bool { return global.GetLogDbQueries() }  // SetLogDbQueries safely sets the value for global configuration 'LogDbQueries' field  func SetLogDbQueries(v bool) { global.SetLogDbQueries(v) } +// GetLogClientIP safely fetches the Configuration value for state's 'LogClientIP' field +func (st *ConfigState) GetLogClientIP() (v bool) { +	st.mutex.Lock() +	v = st.config.LogClientIP +	st.mutex.Unlock() +	return +} + +// SetLogClientIP safely sets the Configuration value for state's 'LogClientIP' field +func (st *ConfigState) SetLogClientIP(v bool) { +	st.mutex.Lock() +	defer st.mutex.Unlock() +	st.config.LogClientIP = v +	st.reloadToViper() +} + +// LogClientIPFlag returns the flag name for the 'LogClientIP' field +func LogClientIPFlag() string { return "log-client-ip" } + +// GetLogClientIP safely fetches the value for global configuration 'LogClientIP' field +func GetLogClientIP() bool { return global.GetLogClientIP() } + +// SetLogClientIP safely sets the value for global configuration 'LogClientIP' field +func SetLogClientIP(v bool) { global.SetLogClientIP(v) } +  // GetApplicationName safely fetches the Configuration value for state's 'ApplicationName' field  func (st *ConfigState) GetApplicationName() (v string) {  	st.mutex.Lock() @@ -924,6 +949,31 @@ func GetAccountsAllowCustomCSS() bool { return global.GetAccountsAllowCustomCSS(  // SetAccountsAllowCustomCSS safely sets the value for global configuration 'AccountsAllowCustomCSS' field  func SetAccountsAllowCustomCSS(v bool) { global.SetAccountsAllowCustomCSS(v) } +// GetAccountsCustomCSSLength safely fetches the Configuration value for state's 'AccountsCustomCSSLength' field +func (st *ConfigState) GetAccountsCustomCSSLength() (v int) { +	st.mutex.Lock() +	v = st.config.AccountsCustomCSSLength +	st.mutex.Unlock() +	return +} + +// SetAccountsCustomCSSLength safely sets the Configuration value for state's 'AccountsCustomCSSLength' field +func (st *ConfigState) SetAccountsCustomCSSLength(v int) { +	st.mutex.Lock() +	defer st.mutex.Unlock() +	st.config.AccountsCustomCSSLength = v +	st.reloadToViper() +} + +// AccountsCustomCSSLengthFlag returns the flag name for the 'AccountsCustomCSSLength' field +func AccountsCustomCSSLengthFlag() string { return "accounts-custom-css-length" } + +// GetAccountsCustomCSSLength safely fetches the value for global configuration 'AccountsCustomCSSLength' field +func GetAccountsCustomCSSLength() int { return global.GetAccountsCustomCSSLength() } + +// SetAccountsCustomCSSLength safely sets the value for global configuration 'AccountsCustomCSSLength' field +func SetAccountsCustomCSSLength(v int) { global.SetAccountsCustomCSSLength(v) } +  // GetMediaImageMaxSize safely fetches the Configuration value for state's 'MediaImageMaxSize' field  func (st *ConfigState) GetMediaImageMaxSize() (v bytesize.Size) {  	st.mutex.Lock() @@ -3829,28 +3879,3 @@ func GetRequestIDHeader() string { return global.GetRequestIDHeader() }  // SetRequestIDHeader safely sets the value for global configuration 'RequestIDHeader' field  func SetRequestIDHeader(v string) { global.SetRequestIDHeader(v) } - -// GetLogClientIP safely fetches the Configuration value for state's 'LogClientIP' field -func (st *ConfigState) GetLogClientIP() (v bool) { -	st.mutex.Lock() -	v = st.config.LogClientIP -	st.mutex.Unlock() -	return -} - -// SetLogClientIP safely sets the Configuration value for state's 'LogClientIP' field -func (st *ConfigState) SetLogClientIP(v bool) { -	st.mutex.Lock() -	defer st.mutex.Unlock() -	st.config.LogClientIP = v -	st.reloadToViper() -} - -// LogClientIPFlag returns the flag name for the 'LogClientIP' field -func LogClientIPFlag() string { return "log-client-ip" } - -// GetLogClientIP safely fetches the value for global configuration 'LogClientIP' field -func GetLogClientIP() bool { return global.GetLogClientIP() } - -// SetLogClientIP safely sets the value for global configuration 'LogClientIP' field -func SetLogClientIP(v bool) { global.SetLogClientIP(v) } diff --git a/internal/validate/formvalidation.go b/internal/validate/formvalidation.go index f9328dc1f..c122bf2bc 100644 --- a/internal/validate/formvalidation.go +++ b/internal/validate/formvalidation.go @@ -41,7 +41,6 @@ const (  	maximumDescriptionLength      = 5000  	maximumSiteTermsLength        = 5000  	maximumUsernameLength         = 64 -	maximumCustomCSSLength        = 5000  	maximumEmojiCategoryLength    = 64  	maximumProfileFieldLength     = 255  	maximumProfileFields          = 6 @@ -170,9 +169,11 @@ func CustomCSS(customCSS string) error {  		return errors.New("accounts-allow-custom-css is not enabled for this instance")  	} +	maximumCustomCSSLength := config.GetAccountsCustomCSSLength()  	if length := len([]rune(customCSS)); length > maximumCustomCSSLength {  		return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length)  	} +  	return nil  } diff --git a/internal/validate/formvalidation_test.go b/internal/validate/formvalidation_test.go index 4dff5b60d..a594c3763 100644 --- a/internal/validate/formvalidation_test.go +++ b/internal/validate/formvalidation_test.go @@ -22,8 +22,8 @@ import (  	"fmt"  	"testing" -	"github.com/stretchr/testify/assert"  	"github.com/stretchr/testify/suite" +	"github.com/superseriousbusiness/gotosocial/internal/config"  	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"  	"github.com/superseriousbusiness/gotosocial/internal/validate"  ) @@ -44,43 +44,43 @@ func (suite *ValidationTestSuite) TestCheckPasswordStrength() {  	var err error  	err = validate.NewPassword(empty) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("no password provided"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("no password provided"), err)  	}  	err = validate.NewPassword(terriblePassword) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("password is only 62% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("password is only 62% strength, try including more special characters, using uppercase letters, using numbers or using a longer password"), err)  	}  	err = validate.NewPassword(weakPassword) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("password is only 95% strength, try including more special characters, using numbers or using a longer password"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("password is only 95% strength, try including more special characters, using numbers or using a longer password"), err)  	}  	err = validate.NewPassword(shortPassword) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("password is only 39% strength, try including more special characters or using a longer password"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("password is only 39% strength, try including more special characters or using a longer password"), err)  	}  	err = validate.NewPassword(specialPassword) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("password is only 53% strength, try including more special characters or using a longer password"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("password is only 53% strength, try including more special characters or using a longer password"), err)  	}  	err = validate.NewPassword(longPassword) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.NewPassword(tooLong) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("password should be no more than 256 chars"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("password should be no more than 256 chars"), err)  	}  	err = validate.NewPassword(strongPassword) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  } @@ -133,28 +133,28 @@ func (suite *ValidationTestSuite) TestValidateEmail() {  	var err error  	err = validate.Email(empty) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("no email provided"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("no email provided"), err)  	}  	err = validate.Email(notAnEmailAddress) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("mail: missing '@' or angle-addr"), err)  	}  	err = validate.Email(almostAnEmailAddress) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("mail: no angle-addr"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("mail: no angle-addr"), err)  	}  	err = validate.Email(aWebsite) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("mail: missing '@' or angle-addr"), err)  	}  	err = validate.Email(emailAddress) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  } @@ -171,48 +171,48 @@ func (suite *ValidationTestSuite) TestValidateLanguage() {  	var err error  	err = validate.Language(empty) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("no language provided"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("no language provided"), err)  	}  	err = validate.Language(notALanguage) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("language: tag is not well-formed"), err)  	}  	err = validate.Language(english) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.Language(capitalEnglish) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.Language(arabic3Letters) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.Language(mixedCapsEnglish) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.Language(englishUS) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("language: tag is not well-formed"), err)  	}  	err = validate.Language(dutch) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.Language(german) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  } @@ -226,49 +226,49 @@ func (suite *ValidationTestSuite) TestValidateReason() {  	// check with no reason required  	err = validate.SignUpReason(empty, false) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.SignUpReason(badReason, false) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.SignUpReason(tooLong, false) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.SignUpReason(goodReason, false) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	err = validate.SignUpReason(unicode, false) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  	// check with reason required  	err = validate.SignUpReason(empty, true) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("no reason provided"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("no reason provided"), err)  	}  	err = validate.SignUpReason(badReason, true) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("reason should be at least 40 chars but 'because' was 7"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("reason should be at least 40 chars but 'because' was 7"), err)  	}  	err = validate.SignUpReason(tooLong, true) -	if assert.Error(suite.T(), err) { -		assert.Equal(suite.T(), errors.New("reason should be no more than 500 chars but given reason was 600"), err) +	if suite.Error(err) { +		suite.Equal(errors.New("reason should be no more than 500 chars but given reason was 600"), err)  	}  	err = validate.SignUpReason(goodReason, true) -	if assert.NoError(suite.T(), err) { -		assert.Equal(suite.T(), nil, err) +	if suite.NoError(err) { +		suite.Equal(nil, err)  	}  } @@ -302,6 +302,46 @@ func (suite *ValidationTestSuite) TestValidateProfileField() {  	suite.Len(dodgyFields[0].Value, 255)  } +func (suite *ValidationTestSuite) TestValidateCustomCSSDisabled() { +	config.SetAccountsAllowCustomCSS(false) + +	err := validate.CustomCSS("this will fail") +	suite.EqualError(err, "accounts-allow-custom-css is not enabled for this instance") +} + +func (suite *ValidationTestSuite) TestValidateCustomCSSEnabled() { +	config.SetAccountsAllowCustomCSS(true) + +	err := validate.CustomCSS("this will pass") +	suite.NoError(err) +} + +func (suite *ValidationTestSuite) TestValidateCustomCSSTooLong() { +	config.SetAccountsAllowCustomCSS(true) +	config.SetAccountsCustomCSSLength(5) + +	err := validate.CustomCSS("this will fail") +	suite.EqualError(err, "custom_css must be less than 5 characters, but submitted custom_css was 14 characters") +} + +func (suite *ValidationTestSuite) TestValidateCustomCSSTooLongZalgo() { +	config.SetAccountsAllowCustomCSS(true) +	config.SetAccountsCustomCSSLength(5) +	zalgo := "p̵̹̜͇̺̜̱͊̓̈́͛̀͊͘͜e̷̡̱̲̼̪̗̙̐͐̃́̄̉͛̔e̷̞̰̜̲̥̘̻͔̜̞̬͚͋̊͑͗̅̓͛͗̎̃̈́̐̂̕͝ ̷̨̢̡̱̖̤͇̻͕̲̤̞̑ͅp̶̰̜̟̠̏̇̇̆̐̒͋̔͘ḛ̵̾͘ę̷̝͙͕͓͓̱̠̤̳̻̜̗͖̞͙̻̆̓̄͋̎͊̀̋̿́̐͛͗̄̈́̚͠ ̵̨̨̫͕̲͚̮͕̳̉̾̔̍͐p̶̘̞̠̘̎̓̍̑̀͗̃̈́͂́̈́͆͘͜͝͝o̶̜͛̒͒̉̑͒̿͗̐̃͝o̵̼̒͌̓ ̵̢̗̦͔͉͈̰̘̋̃̐̑̅̽̏̄̅͐͆̔͊̃̋͝p̵̩̱̆̆͂̂͛̓̋̅͝o̶̪̰̲̝̻̳̦̮̮͔̒ͅơ̸̧̨̟͇̪̰̜̠̦͇̇̎͗̏̏̈́͂̉̏͐́̃̀͆͠ͅ" + +	err := validate.CustomCSS(zalgo) +	suite.EqualError(err, "custom_css must be less than 5 characters, but submitted custom_css was 275 characters") +} + +func (suite *ValidationTestSuite) TestValidateCustomCSSTooLongUnicode() { +	config.SetAccountsAllowCustomCSS(true) +	config.SetAccountsCustomCSSLength(5) +	unicode := "⎾⎿⏀⏁⏂⏃⏄⏅⏆⏇" + +	err := validate.CustomCSS(unicode) +	suite.EqualError(err, "custom_css must be less than 5 characters, but submitted custom_css was 10 characters") +} +  func TestValidationTestSuite(t *testing.T) {  	suite.Run(t, new(ValidationTestSuite))  } diff --git a/test/envparsing.sh b/test/envparsing.sh index 8ceabde8f..01a0877b0 100755 --- a/test/envparsing.sh +++ b/test/envparsing.sh @@ -7,6 +7,7 @@ EXPECT=$(cat <<"EOF"      "account-domain": "peepee",      "accounts-allow-custom-css": true,      "accounts-approval-required": false, +    "accounts-custom-css-length": 5000,      "accounts-reason-required": false,      "accounts-registration-open": true,      "advanced-cookies-samesite": "strict", @@ -207,6 +208,7 @@ GTS_INSTANCE_EXPOSE_SUSPENDED_WEB=true \  GTS_INSTANCE_EXPOSE_PUBLIC_TIMELINE=true \  GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \  GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \ +GTS_ACCOUNTS_CUSTOM_CSS_LENGTH=5000 \  GTS_ACCOUNTS_REGISTRATION_OPEN=true \  GTS_ACCOUNTS_APPROVAL_REQUIRED=false \  GTS_ACCOUNTS_REASON_REQUIRED=false \ diff --git a/testrig/config.go b/testrig/config.go index aeac78e3f..36de99338 100644 --- a/testrig/config.go +++ b/testrig/config.go @@ -71,6 +71,7 @@ var testDefaults = config.Configuration{  	AccountsApprovalRequired: true,  	AccountsReasonRequired:   true,  	AccountsAllowCustomCSS:   true, +	AccountsCustomCSSLength:  10000,  	MediaImageMaxSize:        10485760, // 10mb  	MediaVideoMaxSize:        41943040, // 40mb  | 
