From 34b0b793babb78c0a0ce5708733e01f6c02d26ec Mon Sep 17 00:00:00 2001 From: MaysWind Date: Sun, 10 Nov 2024 15:24:09 +0800 Subject: [PATCH] support default feature restrictions after user registration --- cmd/user_data.go | 1 + conf/ezbookkeeping.ini | 15 +++ pkg/api/data_managements.go | 4 +- pkg/api/forget_passwords.go | 4 +- pkg/api/tokens.go | 4 +- pkg/api/transactions.go | 4 +- pkg/api/twofactor_authorizations.go | 6 +- pkg/api/users.go | 11 +- pkg/cli/user_data.go | 1 + pkg/core/user_feature_restriction.go | 120 ++++++++++++++++++++++ pkg/core/user_feature_restriction_test.go | 118 +++++++++++++++++++++ pkg/models/user.go | 91 +--------------- pkg/models/user_test.go | 89 ---------------- pkg/settings/setting.go | 2 + 14 files changed, 275 insertions(+), 195 deletions(-) create mode 100644 pkg/core/user_feature_restriction.go create mode 100644 pkg/core/user_feature_restriction_test.go diff --git a/cmd/user_data.go b/cmd/user_data.go index b3a93919..b71b820c 100644 --- a/cmd/user_data.go +++ b/cmd/user_data.go @@ -769,6 +769,7 @@ func printUserInfo(user *models.User) { fmt.Printf("[CurrencyDisplayType] %s (%d)\n", user.CurrencyDisplayType, user.CurrencyDisplayType) fmt.Printf("[ExpenseAmountColor] %s (%d)\n", user.ExpenseAmountColor, user.ExpenseAmountColor) fmt.Printf("[IncomeAmountColor] %s (%d)\n", user.IncomeAmountColor, user.IncomeAmountColor) + fmt.Printf("[FeatureRestriction] %s (%d)\n", user.FeatureRestriction, user.FeatureRestriction) fmt.Printf("[Deleted] %t\n", user.Deleted) fmt.Printf("[EmailVerified] %t\n", user.EmailVerified) fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime) diff --git a/conf/ezbookkeeping.ini b/conf/ezbookkeeping.ini index 6c04101a..893d283e 100644 --- a/conf/ezbookkeeping.ini +++ b/conf/ezbookkeeping.ini @@ -217,6 +217,21 @@ avatar_provider = internal # For "internal" avatar provider only, maximum allowed user avatar file size (1 - 4294967295 bytes) max_user_avatar_size = 1048576 +# The default feature restrictions after user registration (feature types separated by commas), leave blank for no restrictions +# Supports the following feature types: +# 1: Update Password +# 2: Update Email +# 3: Update Profile Basic Info +# 4: Update Avatar +# 5: Logout Other Session +# 6: Enable Two-Factor Authentication +# 7: Disable Enable Two-Factor Authentication +# 8: Forget Password +# 9: Import Transactions +# 10: Export Transactions +# 11: Clear All Data +default_feature_restrictions = + [data] # Set to true to allow users to export their data enable_export = true diff --git a/pkg/api/data_managements.go b/pkg/api/data_managements.go index 08c68564..32b26e1a 100644 --- a/pkg/api/data_managements.go +++ b/pkg/api/data_managements.go @@ -147,7 +147,7 @@ func (a *DataManagementsApi) ClearDataHandler(c *core.WebContext) (any, *errs.Er return nil, errs.ErrUserPasswordWrong } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -208,7 +208,7 @@ func (a *DataManagementsApi) getExportedFileContent(c *core.WebContext, fileType return nil, "", errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION) { return nil, "", errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/api/forget_passwords.go b/pkg/api/forget_passwords.go index bdf62fb2..fe29e7de 100644 --- a/pkg/api/forget_passwords.go +++ b/pkg/api/forget_passwords.go @@ -56,7 +56,7 @@ func (a *ForgetPasswordsApi) UserForgetPasswordRequestHandler(c *core.WebContext return nil, errs.ErrUserIsDisabled } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -113,7 +113,7 @@ func (a *ForgetPasswordsApi) UserResetPasswordHandler(c *core.WebContext) (any, return nil, errs.ErrUserIsDisabled } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) { return nil, errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/api/tokens.go b/pkg/api/tokens.go index 59de69ad..7deeb735 100644 --- a/pkg/api/tokens.go +++ b/pkg/api/tokens.go @@ -146,7 +146,7 @@ func (a *TokensApi) TokenRevokeHandler(c *core.WebContext) (any, *errs.Error) { return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) { return nil, errs.ErrNotPermittedToPerformThisAction } } @@ -200,7 +200,7 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.WebContext) (any, *errs.Error) return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) { return nil, errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index 077498ba..4cf577a6 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -1077,7 +1077,7 @@ func (a *TransactionsApi) TransactionParseImportFileHandler(c *core.WebContext) return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -1205,7 +1205,7 @@ func (a *TransactionsApi) TransactionImportHandler(c *core.WebContext) (any, *er return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) { return nil, errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/api/twofactor_authorizations.go b/pkg/api/twofactor_authorizations.go index e472ff8e..c9b42279 100644 --- a/pkg/api/twofactor_authorizations.go +++ b/pkg/api/twofactor_authorizations.go @@ -81,7 +81,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableRequestHandler(c *core.WebCo return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -145,7 +145,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorEnableConfirmHandler(c *core.WebCo return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -237,7 +237,7 @@ func (a *TwoFactorAuthorizationsApi) TwoFactorDisableHandler(c *core.WebContext) return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) { return nil, errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/api/users.go b/pkg/api/users.go index 482cd0cd..c9da8015 100644 --- a/pkg/api/users.go +++ b/pkg/api/users.go @@ -79,6 +79,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.WebContext) (any, *errs.Error) { DefaultCurrency: userRegisterReq.DefaultCurrency, FirstDayOfWeek: userRegisterReq.FirstDayOfWeek, TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL, + FeatureRestriction: a.CurrentConfig().DefaultFeatureRestrictions, } err = a.users.CreateUser(c, user) @@ -259,7 +260,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro } if userUpdateReq.Email != "" && userUpdateReq.Email != user.Email { - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -269,7 +270,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro } if userUpdateReq.Password != "" { - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -438,7 +439,7 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.WebContext) (any, *errs.Erro userNew.IncomeAmountColor = models.AMOUNT_COLOR_TYPE_INVALID } - if modifyProfileBasicInfo && user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) { + if modifyProfileBasicInfo && user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -554,7 +555,7 @@ func (a *UsersApi) UserUpdateAvatarHandler(c *core.WebContext) (any, *errs.Error return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) { return nil, errs.ErrNotPermittedToPerformThisAction } @@ -621,7 +622,7 @@ func (a *UsersApi) UserRemoveAvatarHandler(c *core.WebContext) (any, *errs.Error return nil, errs.ErrUserNotFound } - if user.FeatureRestriction.Contains(models.USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) { + if user.FeatureRestriction.Contains(core.USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) { return nil, errs.ErrNotPermittedToPerformThisAction } diff --git a/pkg/cli/user_data.go b/pkg/cli/user_data.go index cd58ec2a..f5497f63 100644 --- a/pkg/cli/user_data.go +++ b/pkg/cli/user_data.go @@ -88,6 +88,7 @@ func (l *UserDataCli) AddNewUser(c *core.CliContext, username string, email stri DefaultCurrency: defaultCurrency, FirstDayOfWeek: core.WEEKDAY_SUNDAY, TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL, + FeatureRestriction: l.CurrentConfig().DefaultFeatureRestrictions, } err := l.users.CreateUser(c, user) diff --git a/pkg/core/user_feature_restriction.go b/pkg/core/user_feature_restriction.go new file mode 100644 index 00000000..8a8f13b2 --- /dev/null +++ b/pkg/core/user_feature_restriction.go @@ -0,0 +1,120 @@ +package core + +import ( + "fmt" + "strconv" + "strings" +) + +// UserFeatureRestrictions represents all the restrictions of user features +type UserFeatureRestrictions uint64 + +// Add returns a new feature restrictions with the specified feature +func (r UserFeatureRestrictions) Add(featureRestrictionType UserFeatureRestrictionType) UserFeatureRestrictions { + typeValue := uint64(1 << (featureRestrictionType - 1)) + return UserFeatureRestrictions(uint64(r) | typeValue) +} + +// Remove returns a new feature restrictions without the specified feature +func (r UserFeatureRestrictions) Remove(featureRestrictionType UserFeatureRestrictionType) UserFeatureRestrictions { + typeValue := uint64(1 << (featureRestrictionType - 1)) + return UserFeatureRestrictions(uint64(r) & (^typeValue)) +} + +// Contains returns whether contains the specified feature +func (r UserFeatureRestrictions) Contains(featureRestrictionType UserFeatureRestrictionType) bool { + typeValue := uint64(1 << (featureRestrictionType - 1)) + return uint64(r)&typeValue == typeValue +} + +// String returns a textual representation of all the restrictions of user features +func (r UserFeatureRestrictions) String() string { + builder := strings.Builder{} + + for restrictionType := USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD; restrictionType <= USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA; restrictionType++ { + if !r.Contains(restrictionType) { + continue + } + + if builder.Len() > 0 { + builder.WriteRune(',') + } + + builder.WriteString(restrictionType.String()) + } + + return builder.String() +} + +// ParseUserFeatureRestrictions returns restrictions of user features according to the textual restrictions of user features separated by commas +func ParseUserFeatureRestrictions(featureRestrictions string) UserFeatureRestrictions { + if len(featureRestrictions) < 1 { + return 0 + } + + restrictions := uint64(0) + typeValues := strings.Split(featureRestrictions, ",") + + for i := 0; i < len(typeValues); i++ { + value, err := strconv.ParseInt(typeValues[i], 10, 64) + + if err != nil { + continue + } + + if uint64(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) <= uint64(value) && uint64(value) <= uint64(USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) { + typeValue := uint64(1 << (value - 1)) + restrictions = restrictions | typeValue + } + } + + return UserFeatureRestrictions(restrictions) +} + +// UserFeatureRestrictionType represents the restriction type of user features +type UserFeatureRestrictionType uint64 + +// User Feature Restriction Type +const ( + USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD UserFeatureRestrictionType = 1 + USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL UserFeatureRestrictionType = 2 + USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO UserFeatureRestrictionType = 3 + USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR UserFeatureRestrictionType = 4 + USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION UserFeatureRestrictionType = 5 + USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA UserFeatureRestrictionType = 6 + USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA UserFeatureRestrictionType = 7 + USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD UserFeatureRestrictionType = 8 + USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION UserFeatureRestrictionType = 9 + USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION UserFeatureRestrictionType = 10 + USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA UserFeatureRestrictionType = 11 +) + +// String returns a textual representation of the restriction type of user features +func (t UserFeatureRestrictionType) String() string { + switch t { + case USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD: + return "Update Password" + case USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL: + return "Update Email" + case USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO: + return "Update Profile Basic Info" + case USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR: + return "Update Avatar" + case USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION: + return "Logout Other Session" + case USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA: + return "Enable Two-Factor Authentication" + case USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA: + return "Disable Enable Two-Factor Authentication" + case USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD: + return "Forget Password" + case USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION: + return "Import Transactions" + case USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION: + return "Export Transactions" + case USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA: + return "Clear All Data" + default: + return fmt.Sprintf("Invalid(%d)", int(t)) + } +} diff --git a/pkg/core/user_feature_restriction_test.go b/pkg/core/user_feature_restriction_test.go new file mode 100644 index 00000000..75bd8f1e --- /dev/null +++ b/pkg/core/user_feature_restriction_test.go @@ -0,0 +1,118 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUserFeatureRestrictionsAdd(t *testing.T) { + var featureRestrictions UserFeatureRestrictions + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) + expectedValue := UserFeatureRestrictions(1) + assert.Equal(t, expectedValue, featureRestrictions) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) + expectedValue = UserFeatureRestrictions(255) + assert.Equal(t, expectedValue, featureRestrictions) +} + +func TestUserFeatureRestrictionsRemove(t *testing.T) { + var featureRestrictions UserFeatureRestrictions + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) + expectedValue := UserFeatureRestrictions(1) + assert.Equal(t, expectedValue, featureRestrictions) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) + featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) + expectedValue = UserFeatureRestrictions(153) + assert.Equal(t, expectedValue, featureRestrictions) +} + +func TestUserFeatureRestrictionsContains(t *testing.T) { + var featureRestrictions UserFeatureRestrictions + assert.False(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD)) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) + assert.True(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD)) + assert.False(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO)) +} + +func TestUserFeatureRestrictionsString(t *testing.T) { + var featureRestrictions UserFeatureRestrictions + expectedValue := "" + actualValue := featureRestrictions.String() + assert.Equal(t, expectedValue, actualValue) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) + expectedValue = "Update Password" + actualValue = featureRestrictions.String() + assert.Equal(t, expectedValue, actualValue) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) + expectedValue = "Update Password,Forget Password" + actualValue = featureRestrictions.String() + assert.Equal(t, expectedValue, actualValue) + + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION) + featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) + expectedValue = "Update Password," + + "Update Email," + + "Update Profile Basic Info," + + "Update Avatar," + + "Logout Other Session," + + "Enable Two-Factor Authentication," + + "Disable Enable Two-Factor Authentication," + + "Forget Password," + + "Import Transactions," + + "Export Transactions," + + "Clear All Data" + actualValue = featureRestrictions.String() + assert.Equal(t, expectedValue, actualValue) +} + +func TestParseUserFeatureRestrictions(t *testing.T) { + expectedValue := UserFeatureRestrictions(0) + actualValue := ParseUserFeatureRestrictions("") + assert.Equal(t, expectedValue, actualValue) + + expectedValue = UserFeatureRestrictions(1) + actualValue = ParseUserFeatureRestrictions("1") + assert.Equal(t, expectedValue, actualValue) + + expectedValue = UserFeatureRestrictions(1) + actualValue = ParseUserFeatureRestrictions("1,20") + assert.Equal(t, expectedValue, actualValue) + + expectedValue = UserFeatureRestrictions(255) + actualValue = ParseUserFeatureRestrictions("1,2,3,4,5,6,7,8,20,21,22") + assert.Equal(t, expectedValue, actualValue) + + expectedValue = UserFeatureRestrictions(255) + actualValue = ParseUserFeatureRestrictions("1,2,3,4,5,6,7,8,a,b,20") + assert.Equal(t, expectedValue, actualValue) +} diff --git a/pkg/models/user.go b/pkg/models/user.go index c5dbea18..8f96d026 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -2,7 +2,6 @@ package models import ( "fmt" - "strings" "time" "github.com/mayswind/ezbookkeeping/pkg/core" @@ -81,94 +80,6 @@ func (s AmountColorType) String() string { } } -// UserFeatureRestrictions represents all the restrictions of user features -type UserFeatureRestrictions int64 - -// Add returns a new feature restrictions with the specified feature -func (r UserFeatureRestrictions) Add(featureRestrictionType UserFeatureRestrictionType) UserFeatureRestrictions { - typeValue := int64(1 << (featureRestrictionType - 1)) - return UserFeatureRestrictions(int64(r) | typeValue) -} - -// Remove returns a new feature restrictions without the specified feature -func (r UserFeatureRestrictions) Remove(featureRestrictionType UserFeatureRestrictionType) UserFeatureRestrictions { - typeValue := int64(1 << (featureRestrictionType - 1)) - return UserFeatureRestrictions(int64(r) & (^typeValue)) -} - -// Contains returns whether contains the specified feature -func (r UserFeatureRestrictions) Contains(featureRestrictionType UserFeatureRestrictionType) bool { - typeValue := int64(1 << (featureRestrictionType - 1)) - return int64(r)&typeValue == typeValue -} - -// String returns a textual representation of all the restrictions of user features -func (r UserFeatureRestrictions) String() string { - builder := strings.Builder{} - - for restrictionType := USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD; restrictionType <= USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA; restrictionType++ { - if !r.Contains(restrictionType) { - continue - } - - if builder.Len() > 0 { - builder.WriteRune(',') - } - - builder.WriteString(restrictionType.String()) - } - - return builder.String() -} - -// UserFeatureRestrictionType represents the restriction type of user features -type UserFeatureRestrictionType int64 - -// User Feature Restriction Type -const ( - USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD UserFeatureRestrictionType = 1 - USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL UserFeatureRestrictionType = 2 - USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO UserFeatureRestrictionType = 3 - USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR UserFeatureRestrictionType = 4 - USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION UserFeatureRestrictionType = 5 - USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA UserFeatureRestrictionType = 6 - USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA UserFeatureRestrictionType = 7 - USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD UserFeatureRestrictionType = 8 - USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION UserFeatureRestrictionType = 9 - USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION UserFeatureRestrictionType = 10 - USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA UserFeatureRestrictionType = 11 -) - -// String returns a textual representation of the restriction type of user features -func (t UserFeatureRestrictionType) String() string { - switch t { - case USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD: - return "Update Password" - case USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL: - return "Update Email" - case USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO: - return "Update Profile Basic Info" - case USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR: - return "Update Avatar" - case USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION: - return "Logout Other Session" - case USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA: - return "Enable Two-Factor Authentication" - case USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA: - return "Disable Enable Two-Factor Authentication" - case USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD: - return "Forget Password" - case USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION: - return "Import Transactions" - case USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION: - return "Export Transactions" - case USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA: - return "Clear All Data" - default: - return fmt.Sprintf("Invalid(%d)", int(t)) - } -} - // User represents user data stored in database type User struct { Uid int64 `xorm:"PK"` @@ -193,7 +104,7 @@ type User struct { CurrencyDisplayType core.CurrencyDisplayType `xorm:"TINYINT"` ExpenseAmountColor AmountColorType `xorm:"TINYINT"` IncomeAmountColor AmountColorType `xorm:"TINYINT"` - FeatureRestriction UserFeatureRestrictions + FeatureRestriction core.UserFeatureRestrictions Disabled bool Deleted bool `xorm:"NOT NULL"` EmailVerified bool `xorm:"NOT NULL"` diff --git a/pkg/models/user_test.go b/pkg/models/user_test.go index b58b2d8a..65a0f369 100644 --- a/pkg/models/user_test.go +++ b/pkg/models/user_test.go @@ -10,95 +10,6 @@ import ( "github.com/mayswind/ezbookkeeping/pkg/utils" ) -func TestUserFeatureRestrictionsAdd(t *testing.T) { - var featureRestrictions UserFeatureRestrictions - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) - expectedValue := UserFeatureRestrictions(1) - assert.Equal(t, expectedValue, featureRestrictions) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) - expectedValue = UserFeatureRestrictions(255) - assert.Equal(t, expectedValue, featureRestrictions) -} - -func TestUserFeatureRestrictionsRemove(t *testing.T) { - var featureRestrictions UserFeatureRestrictions - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) - expectedValue := UserFeatureRestrictions(1) - assert.Equal(t, expectedValue, featureRestrictions) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) - featureRestrictions = featureRestrictions.Remove(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) - expectedValue = UserFeatureRestrictions(153) - assert.Equal(t, expectedValue, featureRestrictions) -} - -func TestUserFeatureRestrictionsContains(t *testing.T) { - var featureRestrictions UserFeatureRestrictions - assert.False(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD)) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) - assert.True(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD)) - assert.False(t, featureRestrictions.Contains(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO)) -} - -func TestUserFeatureRestrictionsString(t *testing.T) { - var featureRestrictions UserFeatureRestrictions - expectedValue := "" - actualValue := featureRestrictions.String() - assert.Equal(t, expectedValue, actualValue) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PASSWORD) - expectedValue = "Update Password" - actualValue = featureRestrictions.String() - assert.Equal(t, expectedValue, actualValue) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_FORGET_PASSWORD) - expectedValue = "Update Password,Forget Password" - actualValue = featureRestrictions.String() - assert.Equal(t, expectedValue, actualValue) - - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_EMAIL) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_PROFILE_BASIC_INFO) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_UPDATE_AVATAR) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_REVOKE_OTHER_SESSION) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_ENABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_DISABLE_2FA) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_IMPORT_TRANSACTION) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_EXPORT_TRANSACTION) - featureRestrictions = featureRestrictions.Add(USER_FEATURE_RESTRICTION_TYPE_CLEAR_ALL_DATA) - expectedValue = "Update Password," + - "Update Email," + - "Update Profile Basic Info," + - "Update Avatar," + - "Logout Other Session," + - "Enable Two-Factor Authentication," + - "Disable Enable Two-Factor Authentication," + - "Forget Password," + - "Import Transactions," + - "Export Transactions," + - "Clear All Data" - actualValue = featureRestrictions.String() - assert.Equal(t, expectedValue, actualValue) -} - func TestUserCanEditTransactionByTransactionTime_ScopeIsNone(t *testing.T) { user := &User{ TransactionEditScope: TRANSACTION_EDIT_SCOPE_NONE, diff --git a/pkg/settings/setting.go b/pkg/settings/setting.go index 3b26e837..95e566e6 100644 --- a/pkg/settings/setting.go +++ b/pkg/settings/setting.go @@ -282,6 +282,7 @@ type Config struct { EnableScheduledTransaction bool AvatarProvider core.UserAvatarProviderType MaxAvatarFileSize uint32 + DefaultFeatureRestrictions core.UserFeatureRestrictions // Data EnableDataExport bool @@ -766,6 +767,7 @@ func loadUserConfiguration(config *Config, configFile *ini.File, sectionName str } config.MaxAvatarFileSize = getConfigItemUint32Value(configFile, sectionName, "max_user_avatar_size", defaultUserAvatarFileMaxSize) + config.DefaultFeatureRestrictions = core.ParseUserFeatureRestrictions(getConfigItemStringValue(configFile, sectionName, "default_feature_restrictions", "")) return nil }