]> git.r.bdr.sh - rbdr/mobius/commitdiff
Improve human readability of account config files
authorJeff Halter <redacted>
Sun, 28 Jul 2024 19:43:23 +0000 (12:43 -0700)
committerJeff Halter <redacted>
Sun, 28 Jul 2024 19:49:05 +0000 (12:49 -0700)
cmd/mobius-hotline-server/mobius/config/Users/admin.yaml
cmd/mobius-hotline-server/mobius/config/Users/guest.yaml
hotline/access.go
hotline/account.go
internal/mobius/account_manager.go
internal/mobius/account_manager_test.go [new file with mode: 0644]
internal/mobius/test/config/Users/admin.yaml
internal/mobius/test/config/Users/guest.yaml
internal/mobius/test/config/Users/user-with-old-access-format.yaml [new file with mode: 0644]
internal/mobius/transaction_handlers.go
internal/mobius/transaction_handlers_test.go

index 5413735e60bab8b6d513f671140504a6eed488a6..1e72e6b0b092c443152e58a4dc5f886fda6a047b 100644 (file)
@@ -2,11 +2,44 @@ Login: admin
 Name: admin
 Password: $2a$04$2itGEYx8C1N5bsfRSoC9JuonS3I4YfnyVPZHLSwp7kEInRX0yoB.a
 Access:
-- 255
-- 255
-- 255
-- 255
-- 255
-- 255
-- 255
-- 255
+    DownloadFile: true
+    DownloadFolder: true
+    UploadFile: true
+    UploadFolder: true
+    DeleteFile: true
+    RenameFile: true
+    MoveFile: true
+    CreateFolder: true
+    DeleteFolder: true
+    RenameFolder: true
+    MoveFolder: true
+    ReadChat: true
+    SendChat: true
+    OpenChat: true
+    CloseChat: true
+    ShowInList: true
+    CreateUser: true
+    DeleteUser: true
+    OpenUser: true
+    ModifyUser: true
+    ChangeOwnPass: true
+    NewsReadArt: true
+    NewsPostArt: true
+    DisconnectUser: true
+    CannotBeDisconnected: true
+    GetClientInfo: true
+    UploadAnywhere: true
+    AnyName: true
+    NoAgreement: true
+    SetFileComment: true
+    SetFolderComment: true
+    ViewDropBoxes: true
+    MakeAlias: true
+    Broadcast: false
+    NewsDeleteArt: true
+    NewsCreateCat: true
+    NewsDeleteCat: true
+    NewsCreateFldr: true
+    NewsDeleteFldr: true
+    SendPrivMsg: true
+FileRoot: ""
index bacffeea19726e383c861182e5d29e61395e37af..86235d1a737cb8e0354dae1ec3ab9056e37d4dc0 100644 (file)
@@ -1,4 +1,45 @@
 Login: guest
 Name: guest
-Password: $2a$04$9P/jgLn1fR9TjSoWL.rKxuN6g.1TSpf2o6Hw.aaRuBwrWIJNwsKkS
-Access: [96, 112, 12, 32, 3, 128, 0, 0]
+Password: $2a$04$6Yq/TIlgjSD.FbARwtYs9ODnkHawonu1TJ5W2jJKfhnHwBIQTk./y
+Access:
+    DownloadFile: true
+    DownloadFolder: true
+    UploadFile: true
+    UploadFolder: true
+    DeleteFile: false
+    RenameFile: false
+    MoveFile: false
+    CreateFolder: false
+    DeleteFolder: false
+    RenameFolder: false
+    MoveFolder: false
+    ReadChat: true
+    SendChat: true
+    OpenChat: true
+    CloseChat: false
+    ShowInList: false
+    CreateUser: false
+    DeleteUser: false
+    OpenUser: false
+    ModifyUser: false
+    ChangeOwnPass: false
+    NewsReadArt: true
+    NewsPostArt: true
+    DisconnectUser: false
+    CannotBeDisconnected: false
+    GetClientInfo: false
+    UploadAnywhere: false
+    AnyName: true
+    NoAgreement: false
+    SetFileComment: false
+    SetFolderComment: false
+    ViewDropBoxes: false
+    MakeAlias: false
+    Broadcast: false
+    NewsDeleteArt: false
+    NewsCreateCat: false
+    NewsDeleteCat: false
+    NewsCreateFldr: false
+    NewsDeleteFldr: false
+    SendPrivMsg: true
+FileRoot: ""
index 2d78464177e22e29c76889a873e04aedd8c51d97..42e96c4bac5407027818b84cd8b1a06f9cb34edc 100644 (file)
@@ -1,5 +1,7 @@
 package hotline
 
+import "fmt"
+
 const (
        AccessDeleteFile       = 0  // File System Maintenance: Can Delete Files
        AccessUploadFile       = 1  // File System Maintenance: Can Upload Files
@@ -38,6 +40,8 @@ const (
        AccessNewsDeleteCat    = 35 // News: Can Delete Categories
        AccessNewsCreateFldr   = 36 // News: Can Create News Bundles
        AccessNewsDeleteFldr   = 37 // News: Can Delete News Bundles
+       AccessUploadFolder     = 38 // File System Maintenance: Can Upload Folders
+       AccessDownloadFolder   = 39 // File System Maintenance: Can Download Folders
        AccessSendPrivMsg      = 40 // Messaging: Can Send Messages (Note: 1.9 protocol doc incorrectly says this is bit 19)
 )
 
@@ -50,3 +54,235 @@ func (bits *AccessBitmap) Set(i int) {
 func (bits *AccessBitmap) IsSet(i int) bool {
        return bits[i/8]&(1<<uint(7-i%8)) != 0
 }
+
+func (bits *AccessBitmap) UnmarshalYAML(unmarshal func(interface{}) error) error {
+       var flags interface{}
+       err := unmarshal(&flags)
+       if err != nil {
+               return fmt.Errorf("unmarshal access bitmap: %w", err)
+       }
+
+       switch v := flags.(type) {
+       case []interface{}:
+               // Mobius versions < v0.17.0 store the user access bitmap as an array of int values like:
+               // [96, 112, 12, 32, 3, 128, 0, 0]
+               // This case supports reading of user config files using this format.
+               for i, v := range flags.([]interface{}) {
+                       bits[i] = byte(v.(int))
+               }
+       case map[string]interface{}:
+               // Mobius versions >= v0.17.0 store the user access bitmap as map[string]bool to provide a human-readable view of
+               // the account permissions.
+               if f, ok := v["DeleteFile"].(bool); ok && f {
+                       bits.Set(AccessDeleteFile)
+               }
+               if f, ok := v["UploadFile"].(bool); ok && f {
+                       bits.Set(AccessUploadFile)
+               }
+               if f, ok := v["DownloadFile"].(bool); ok && f {
+                       bits.Set(AccessDownloadFile)
+               }
+               if f, ok := v["UploadFolder"].(bool); ok && f {
+                       bits.Set(AccessUploadFolder)
+               }
+               if f, ok := v["DownloadFolder"].(bool); ok && f {
+                       bits.Set(AccessDownloadFolder)
+               }
+               if f, ok := v["RenameFile"].(bool); ok && f {
+                       bits.Set(AccessRenameFile)
+               }
+               if f, ok := v["MoveFile"].(bool); ok && f {
+                       bits.Set(AccessMoveFile)
+               }
+               if f, ok := v["CreateFolder"].(bool); ok && f {
+                       bits.Set(AccessCreateFolder)
+               }
+               if f, ok := v["DeleteFolder"].(bool); ok && f {
+                       bits.Set(AccessDeleteFolder)
+               }
+               if f, ok := v["RenameFolder"].(bool); ok && f {
+                       bits.Set(AccessRenameFolder)
+               }
+               if f, ok := v["MoveFolder"].(bool); ok && f {
+                       bits.Set(AccessMoveFolder)
+               }
+               if f, ok := v["ReadChat"].(bool); ok && f {
+                       bits.Set(AccessReadChat)
+               }
+               if f, ok := v["SendChat"].(bool); ok && f {
+                       bits.Set(AccessSendChat)
+               }
+               if f, ok := v["OpenChat"].(bool); ok && f {
+                       bits.Set(AccessOpenChat)
+               }
+               if f, ok := v["CloseChat"].(bool); ok && f {
+                       bits.Set(AccessCloseChat)
+               }
+               if f, ok := v["ShowInList"].(bool); ok && f {
+                       bits.Set(AccessShowInList)
+               }
+               if f, ok := v["CreateUser"].(bool); ok && f {
+                       bits.Set(AccessCreateUser)
+               }
+               if f, ok := v["DeleteUser"].(bool); ok && f {
+                       bits.Set(AccessDeleteUser)
+               }
+               if f, ok := v["OpenUser"].(bool); ok && f {
+                       bits.Set(AccessOpenUser)
+               }
+               if f, ok := v["ModifyUser"].(bool); ok && f {
+                       bits.Set(AccessModifyUser)
+               }
+               if f, ok := v["ChangeOwnPass"].(bool); ok && f {
+                       bits.Set(AccessChangeOwnPass)
+               }
+               if f, ok := v["NewsReadArt"].(bool); ok && f {
+                       bits.Set(AccessNewsReadArt)
+               }
+               if f, ok := v["NewsPostArt"].(bool); ok && f {
+                       bits.Set(AccessNewsPostArt)
+               }
+               if f, ok := v["DisconnectUser"].(bool); ok && f {
+                       bits.Set(AccessDisconUser)
+               }
+               if f, ok := v["CannotBeDisconnected"].(bool); ok && f {
+                       bits.Set(AccessCannotBeDiscon)
+               }
+               if f, ok := v["GetClientInfo"].(bool); ok && f {
+                       bits.Set(AccessGetClientInfo)
+               }
+               if f, ok := v["UploadAnywhere"].(bool); ok && f {
+                       bits.Set(AccessUploadAnywhere)
+               }
+               if f, ok := v["AnyName"].(bool); ok && f {
+                       bits.Set(AccessAnyName)
+               }
+               if f, ok := v["NoAgreement"].(bool); ok && f {
+                       bits.Set(AccessNoAgreement)
+               }
+               if f, ok := v["SetFileComment"].(bool); ok && f {
+                       bits.Set(AccessSetFileComment)
+               }
+               if f, ok := v["SetFolderComment"].(bool); ok && f {
+                       bits.Set(AccessSetFolderComment)
+               }
+               if f, ok := v["ViewDropBoxes"].(bool); ok && f {
+                       bits.Set(AccessViewDropBoxes)
+               }
+               if f, ok := v["MakeAlias"].(bool); ok && f {
+                       bits.Set(AccessMakeAlias)
+               }
+               if f, ok := v["Broadcast"].(bool); ok && f {
+                       bits.Set(AccessBroadcast)
+               }
+               if f, ok := v["NewsDeleteArt"].(bool); ok && f {
+                       bits.Set(AccessNewsDeleteArt)
+               }
+               if f, ok := v["NewsCreateCat"].(bool); ok && f {
+                       bits.Set(AccessNewsCreateCat)
+               }
+               if f, ok := v["NewsDeleteCat"].(bool); ok && f {
+                       bits.Set(AccessNewsDeleteCat)
+               }
+               if f, ok := v["NewsCreateFldr"].(bool); ok && f {
+                       bits.Set(AccessNewsCreateFldr)
+               }
+               if f, ok := v["NewsDeleteFldr"].(bool); ok && f {
+                       bits.Set(AccessNewsDeleteFldr)
+               }
+               if f, ok := v["SendPrivMsg"].(bool); ok && f {
+                       bits.Set(AccessSendPrivMsg)
+               }
+       }
+
+       return nil
+}
+
+// accessFlags is used to render the access bitmap to human-readable boolean flags in the account yaml.
+type accessFlags struct {
+       DownloadFile         bool `yaml:"DownloadFile"`
+       DownloadFolder       bool `yaml:"DownloadFolder"`
+       UploadFile           bool `yaml:"UploadFile"`
+       UploadFolder         bool `yaml:"UploadFolder"`
+       DeleteFile           bool `yaml:"DeleteFile"`
+       RenameFile           bool `yaml:"RenameFile"`
+       MoveFile             bool `yaml:"MoveFile"`
+       CreateFolder         bool `yaml:"CreateFolder"`
+       DeleteFolder         bool `yaml:"DeleteFolder"`
+       RenameFolder         bool `yaml:"RenameFolder"`
+       MoveFolder           bool `yaml:"MoveFolder"`
+       ReadChat             bool `yaml:"ReadChat"`
+       SendChat             bool `yaml:"SendChat"`
+       OpenChat             bool `yaml:"OpenChat"`
+       CloseChat            bool `yaml:"CloseChat"`
+       ShowInList           bool `yaml:"ShowInList"`
+       CreateUser           bool `yaml:"CreateUser"`
+       DeleteUser           bool `yaml:"DeleteUser"`
+       OpenUser             bool `yaml:"OpenUser"`
+       ModifyUser           bool `yaml:"ModifyUser"`
+       ChangeOwnPass        bool `yaml:"ChangeOwnPass"`
+       NewsReadArt          bool `yaml:"NewsReadArt"`
+       NewsPostArt          bool `yaml:"NewsPostArt"`
+       DisconnectUser       bool `yaml:"DisconnectUser"`
+       CannotBeDisconnected bool `yaml:"CannotBeDisconnected"`
+       GetClientInfo        bool `yaml:"GetClientInfo"`
+       UploadAnywhere       bool `yaml:"UploadAnywhere"`
+       AnyName              bool `yaml:"AnyName"`
+       NoAgreement          bool `yaml:"NoAgreement"`
+       SetFileComment       bool `yaml:"SetFileComment"`
+       SetFolderComment     bool `yaml:"SetFolderComment"`
+       ViewDropBoxes        bool `yaml:"ViewDropBoxes"`
+       MakeAlias            bool `yaml:"MakeAlias"`
+       Broadcast            bool `yaml:"Broadcast"`
+       NewsDeleteArt        bool `yaml:"NewsDeleteArt"`
+       NewsCreateCat        bool `yaml:"NewsCreateCat"`
+       NewsDeleteCat        bool `yaml:"NewsDeleteCat"`
+       NewsCreateFldr       bool `yaml:"NewsCreateFldr"`
+       NewsDeleteFldr       bool `yaml:"NewsDeleteFldr"`
+       SendPrivMsg          bool `yaml:"SendPrivMsg"`
+}
+
+func (bits AccessBitmap) MarshalYAML() (interface{}, error) {
+       return accessFlags{
+               DownloadFile:         bits.IsSet(AccessDownloadFile),
+               DownloadFolder:       bits.IsSet(AccessDownloadFolder),
+               UploadFolder:         bits.IsSet(AccessUploadFolder),
+               DeleteFile:           bits.IsSet(AccessDeleteFile),
+               UploadFile:           bits.IsSet(AccessUploadFile),
+               RenameFile:           bits.IsSet(AccessRenameFile),
+               MoveFile:             bits.IsSet(AccessMoveFile),
+               CreateFolder:         bits.IsSet(AccessCreateFolder),
+               DeleteFolder:         bits.IsSet(AccessDeleteFolder),
+               RenameFolder:         bits.IsSet(AccessRenameFolder),
+               MoveFolder:           bits.IsSet(AccessMoveFolder),
+               ReadChat:             bits.IsSet(AccessReadChat),
+               SendChat:             bits.IsSet(AccessSendChat),
+               OpenChat:             bits.IsSet(AccessOpenChat),
+               CloseChat:            bits.IsSet(AccessCloseChat),
+               ShowInList:           bits.IsSet(AccessShowInList),
+               CreateUser:           bits.IsSet(AccessCreateUser),
+               DeleteUser:           bits.IsSet(AccessDeleteUser),
+               OpenUser:             bits.IsSet(AccessOpenUser),
+               ModifyUser:           bits.IsSet(AccessModifyUser),
+               ChangeOwnPass:        bits.IsSet(AccessChangeOwnPass),
+               NewsReadArt:          bits.IsSet(AccessNewsReadArt),
+               NewsPostArt:          bits.IsSet(AccessNewsPostArt),
+               DisconnectUser:       bits.IsSet(AccessDisconUser),
+               CannotBeDisconnected: bits.IsSet(AccessCannotBeDiscon),
+               GetClientInfo:        bits.IsSet(AccessGetClientInfo),
+               UploadAnywhere:       bits.IsSet(AccessUploadAnywhere),
+               AnyName:              bits.IsSet(AccessAnyName),
+               NoAgreement:          bits.IsSet(AccessNoAgreement),
+               SetFileComment:       bits.IsSet(AccessSetFileComment),
+               SetFolderComment:     bits.IsSet(AccessSetFolderComment),
+               ViewDropBoxes:        bits.IsSet(AccessViewDropBoxes),
+               MakeAlias:            bits.IsSet(AccessMakeAlias),
+               Broadcast:            bits.IsSet(AccessBroadcast),
+               NewsDeleteArt:        bits.IsSet(AccessNewsDeleteArt),
+               NewsCreateCat:        bits.IsSet(AccessNewsCreateCat),
+               NewsDeleteCat:        bits.IsSet(AccessNewsDeleteCat),
+               NewsCreateFldr:       bits.IsSet(AccessNewsCreateFldr),
+               NewsDeleteFldr:       bits.IsSet(AccessNewsDeleteFldr),
+               SendPrivMsg:          bits.IsSet(AccessSendPrivMsg),
+       }, nil
+}
index 526eb053b176a84b54d9d84b56ade12ac9e0a89f..39ea9740275c1771fa3892b1d4429661f4de0b8e 100644 (file)
@@ -14,7 +14,7 @@ type Account struct {
        Login    string       `yaml:"Login"`
        Name     string       `yaml:"Name"`
        Password string       `yaml:"Password"`
-       Access   AccessBitmap `yaml:"Access,flow"`
+       Access   AccessBitmap `yaml:"Access"`
        FileRoot string       `yaml:"FileRoot"`
 
        readOffset int // Internal offset to track read progress
index cba33b271c326c94bbecb2ffb0c31640ccdf10d8..3bb5cbc803b48c4cbdcb6a858edab703b9e04f96 100644 (file)
@@ -38,7 +38,7 @@ func NewYAMLAccountManager(accountDir string) (*YAMLAccountManager, error) {
 
        matches, err := filepath.Glob(filepath.Join(accountDir, "*.yaml"))
        if err != nil {
-               return nil, err
+               return nil, fmt.Errorf("list account files: %w", err)
        }
 
        if len(matches) == 0 {
@@ -50,7 +50,6 @@ func NewYAMLAccountManager(accountDir string) (*YAMLAccountManager, error) {
                if err = loadFromYAMLFile(file, &account); err != nil {
                        return nil, fmt.Errorf("error loading account %s: %w", file, err)
                }
-
                accountMgr.accounts[account.Login] = account
        }
 
diff --git a/internal/mobius/account_manager_test.go b/internal/mobius/account_manager_test.go
new file mode 100644 (file)
index 0000000..fe834a4
--- /dev/null
@@ -0,0 +1,89 @@
+package mobius
+
+import (
+       "fmt"
+       "github.com/jhalter/mobius/hotline"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+func TestNewYAMLAccountManager(t *testing.T) {
+       type args struct {
+               accountDir string
+       }
+       tests := []struct {
+               name    string
+               args    args
+               want    *YAMLAccountManager
+               wantErr assert.ErrorAssertionFunc
+       }{
+               {
+                       name: "loads accounts from a directory",
+                       args: args{
+                               accountDir: "test/config/Users",
+                       },
+                       want: &YAMLAccountManager{
+                               accountDir: "test/config/Users",
+                               accounts: map[string]hotline.Account{
+                                       "admin": {
+                                               Name:     "admin",
+                                               Login:    "admin",
+                                               Password: "$2a$04$2itGEYx8C1N5bsfRSoC9JuonS3I4YfnyVPZHLSwp7kEInRX0yoB.a",
+                                               Access:   hotline.AccessBitmap{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00},
+                                       },
+                                       "Test User Name": {
+                                               Name:     "test-user",
+                                               Login:    "Test User Name",
+                                               Password: "$2a$04$9P/jgLn1fR9TjSoWL.rKxuN6g.1TSpf2o6Hw.aaRuBwrWIJNwsKkS",
+                                               Access:   hotline.AccessBitmap{0x7d, 0xf0, 0x0c, 0xef, 0xab, 0x80, 0x00, 0x00},
+                                       },
+                                       "guest": {
+                                               Name:     "guest",
+                                               Login:    "guest",
+                                               Password: "$2a$04$6Yq/TIlgjSD.FbARwtYs9ODnkHawonu1TJ5W2jJKfhnHwBIQTk./y",
+                                               Access:   hotline.AccessBitmap{0x7d, 0xf0, 0x0c, 0xef, 0xab, 0x80, 0x00, 0x00},
+                                       },
+                               },
+                       },
+                       wantErr: assert.NoError,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       got, err := NewYAMLAccountManager(tt.args.accountDir)
+                       if !tt.wantErr(t, err, fmt.Sprintf("NewYAMLAccountManager(%v)", tt.args.accountDir)) {
+                               return
+                       }
+
+                       assert.Equal(t,
+                               &hotline.Account{
+                                       Name:     "admin",
+                                       Login:    "admin",
+                                       Password: "$2a$04$2itGEYx8C1N5bsfRSoC9JuonS3I4YfnyVPZHLSwp7kEInRX0yoB.a",
+                                       Access:   hotline.AccessBitmap{0xff, 0xff, 0xef, 0xff, 0xff, 0x80, 0x00, 0x00},
+                               },
+                               got.Get("admin"),
+                       )
+
+                       assert.Equal(t,
+                               &hotline.Account{
+                                       Login:    "guest",
+                                       Name:     "guest",
+                                       Password: "$2a$04$6Yq/TIlgjSD.FbARwtYs9ODnkHawonu1TJ5W2jJKfhnHwBIQTk./y",
+                                       Access:   hotline.AccessBitmap{0x60, 0x70, 0x0c, 0x20, 0x03, 0x80, 0x00, 0x00},
+                               },
+                               got.Get("guest"),
+                       )
+
+                       assert.Equal(t,
+                               &hotline.Account{
+                                       Login:    "test-user",
+                                       Name:     "Test User Name",
+                                       Password: "$2a$04$9P/jgLn1fR9TjSoWL.rKxuN6g.1TSpf2o6Hw.aaRuBwrWIJNwsKkS",
+                                       Access:   hotline.AccessBitmap{0x7d, 0xf0, 0x0c, 0xef, 0xab, 0x80, 0x00, 0x00},
+                               },
+                               got.Get("test-user"),
+                       )
+               })
+       }
+}
index 1bf656bcdbd1c41a3df45ac7d4ba458ee656cdbb..1c33eff48c22ac326e26e62bd369b6d697087edc 100644 (file)
@@ -1,13 +1,45 @@
 Login: admin
 Name: admin
 Password: $2a$04$2itGEYx8C1N5bsfRSoC9JuonS3I4YfnyVPZHLSwp7kEInRX0yoB.a
-Access: 
-- 255
-- 255
-- 255
-- 255
-- 255
-- 255
-- 0
-- 0
-
+Access:
+    DownloadFile: true
+    DownloadFolder: true
+    UploadFile: true
+    UploadFolder: true
+    DeleteFile: true
+    RenameFile: true
+    MoveFile: true
+    CreateFolder: true
+    DeleteFolder: true
+    RenameFolder: true
+    MoveFolder: true
+    ReadChat: true
+    SendChat: true
+    OpenChat: true
+    CloseChat: true
+    ShowInList: true
+    CreateUser: true
+    DeleteUser: true
+    OpenUser: true
+    ModifyUser: true
+    ChangeOwnPass: true
+    NewsReadArt: true
+    NewsPostArt: true
+    DisconnectUser: true
+    CannotBeDisconnected: true
+    GetClientInfo: true
+    UploadAnywhere: true
+    AnyName: true
+    NoAgreement: true
+    SetFileComment: true
+    SetFolderComment: true
+    ViewDropBoxes: true
+    MakeAlias: true
+    Broadcast: true
+    NewsDeleteArt: true
+    NewsCreateCat: true
+    NewsDeleteCat: true
+    NewsCreateFldr: true
+    NewsDeleteFldr: true
+    SendPrivMsg: true
+FileRoot: ""
index 57117bd8345e4f88ab2ab61a399fb429e27b875c..a0dd376fe60f023a0a5854206d67166ef7cd6d86 100644 (file)
@@ -1,12 +1,45 @@
 Login: guest
 Name: guest
-Password: $2a$04$9P/jgLn1fR9TjSoWL.rKxuN6g.1TSpf2o6Hw.aaRuBwrWIJNwsKkS
+Password: $2a$04$6Yq/TIlgjSD.FbARwtYs9ODnkHawonu1TJ5W2jJKfhnHwBIQTk./y
 Access:
-- 125
-- 240
-- 12
-- 239
-- 171
-- 128
-- 0
-- 0
+  DownloadFile: true
+  DownloadFolder: true
+  UploadFile: true
+  UploadFolder: true
+  DeleteFile: false
+  RenameFile: false
+  MoveFile: false
+  CreateFolder: false
+  DeleteFolder: false
+  RenameFolder: false
+  MoveFolder: false
+  ReadChat: true
+  SendChat: true
+  OpenChat: true
+  CloseChat: false
+  ShowInList: false
+  CreateUser: false
+  DeleteUser: false
+  OpenUser: false
+  ModifyUser: false
+  ChangeOwnPass: false
+  NewsReadArt: true
+  NewsPostArt: true
+  DisconnectUser: false
+  CannotBeDisconnected: false
+  GetClientInfo: false
+  UploadAnywhere: false
+  AnyName: true
+  NoAgreement: false
+  SetFileComment: false
+  SetFolderComment: false
+  ViewDropBoxes: false
+  MakeAlias: false
+  Broadcast: false
+  NewsDeleteArt: false
+  NewsCreateCat: false
+  NewsDeleteCat: false
+  NewsCreateFldr: false
+  NewsDeleteFldr: false
+  SendPrivMsg: true
+FileRoot: ""
diff --git a/internal/mobius/test/config/Users/user-with-old-access-format.yaml b/internal/mobius/test/config/Users/user-with-old-access-format.yaml
new file mode 100644 (file)
index 0000000..fa2f5cb
--- /dev/null
@@ -0,0 +1,12 @@
+Login: test-user
+Name: Test User Name
+Password: $2a$04$9P/jgLn1fR9TjSoWL.rKxuN6g.1TSpf2o6Hw.aaRuBwrWIJNwsKkS
+Access:
+  - 125
+  - 240
+  - 12
+  - 239
+  - 171
+  - 128
+  - 0
+  - 0
index 6f734c783a0e02c5c645328bbdf03fa1bdadd597..d18dba51f35b0793a08b3035725ce8eb95517916 100644 (file)
@@ -1329,7 +1329,7 @@ func HandleDownloadFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []h
 
 // Download all files from the specified folder and sub-folders
 func HandleDownloadFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
-       if !cc.Authorize(hotline.AccessDownloadFile) {
+       if !cc.Authorize(hotline.AccessDownloadFolder) {
                return cc.NewErrReply(t, "You are not allowed to download folders.")
        }
 
@@ -1372,6 +1372,10 @@ func HandleDownloadFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res [
 // 220 Folder item count
 // 204 File transfer options   "Optional Currently set to 1" (TODO: ??)
 func HandleUploadFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
+       if !cc.Authorize(hotline.AccessUploadFolder) {
+               return cc.NewErrReply(t, "You are not allowed to upload folders.")
+       }
+
        var fp hotline.FilePath
        if t.GetField(hotline.FieldFilePath).Data != nil {
                if _, err := fp.Write(t.GetField(hotline.FieldFilePath).Data); err != nil {
index 26ea9504fac1b4121d98f138315e5e0185f17afd..43c033f67aaf4e8984056fd8cba49961c745397d 100644 (file)
@@ -3774,3 +3774,97 @@ func TestHandlePostNewsArt(t *testing.T) {
                })
        }
 }
+
+func TestHandleUploadFolder(t *testing.T) {
+       type args struct {
+               cc *hotline.ClientConn
+               t  hotline.Transaction
+       }
+       tests := []struct {
+               name    string
+               args    args
+               wantRes []hotline.Transaction
+       }{
+               {
+                       name: "when user does not have required access",
+                       args: args{
+                               cc: &hotline.ClientConn{
+                                       Account: &hotline.Account{
+                                               Access: hotline.AccessBitmap{},
+                                       },
+                               },
+                               t: hotline.NewTransaction(
+                                       hotline.TranUploadFldr, [2]byte{0, 1},
+                                       hotline.NewField(hotline.FieldFileName, []byte("testFile")),
+                                       hotline.NewField(hotline.FieldFilePath, []byte{
+                                               0x00, 0x01,
+                                               0x00, 0x00,
+                                               0x03,
+                                               0x2e, 0x2e, 0x2f,
+                                       }),
+                               ),
+                       },
+                       wantRes: []hotline.Transaction{
+                               {
+                                       IsReply:   0x01,
+                                       ErrorCode: [4]byte{0, 0, 0, 1},
+                                       Fields: []hotline.Field{
+                                               hotline.NewField(hotline.FieldError, []byte("You are not allowed to upload folders.")),
+                                       },
+                               },
+                       },
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       TranAssertEqual(t, tt.wantRes, HandleUploadFolder(tt.args.cc, &tt.args.t))
+               })
+       }
+}
+
+func TestHandleDownloadFolder(t *testing.T) {
+       type args struct {
+               cc *hotline.ClientConn
+               t  hotline.Transaction
+       }
+       tests := []struct {
+               name    string
+               args    args
+               wantRes []hotline.Transaction
+       }{
+               {
+                       name: "when user does not have required access",
+                       args: args{
+                               cc: &hotline.ClientConn{
+                                       Account: &hotline.Account{
+                                               Access: hotline.AccessBitmap{},
+                                       },
+                               },
+                               t: hotline.NewTransaction(
+                                       hotline.TranDownloadFldr, [2]byte{0, 1},
+                                       hotline.NewField(hotline.FieldFileName, []byte("testFile")),
+                                       hotline.NewField(hotline.FieldFilePath, []byte{
+                                               0x00, 0x01,
+                                               0x00, 0x00,
+                                               0x03,
+                                               0x2e, 0x2e, 0x2f,
+                                       }),
+                               ),
+                       },
+                       wantRes: []hotline.Transaction{
+                               {
+                                       IsReply:   0x01,
+                                       ErrorCode: [4]byte{0, 0, 0, 1},
+                                       Fields: []hotline.Field{
+                                               hotline.NewField(hotline.FieldError, []byte("You are not allowed to download folders.")),
+                                       },
+                               },
+                       },
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       TranAssertEqual(t, tt.wantRes, HandleDownloadFolder(tt.args.cc, &tt.args.t))
+               })
+       }
+}