mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
fix(filetransfer): allow non-admin file uploads and downloads (#679)
* fix(filetransfer): remove auth.AdminsOnly middleware wrapper from routes * fix(filetransfer): add config option to allow non-admin users to download and upload files. * fix(filetransfer): implement user permissions for file download and upload in client. --------- Co-authored-by: Miroslav Šedivý <sedivy.miro@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e843961c37
commit
613647c72c
@@ -9,7 +9,7 @@
|
|||||||
<i :class="fileIcon(item)" />
|
<i :class="fileIcon(item)" />
|
||||||
<p class="file-name" :title="item.name">{{ item.name }}</p>
|
<p class="file-name" :title="item.name">{{ item.name }}</p>
|
||||||
<p class="file-size">{{ fileSize(item.size) }}</p>
|
<p class="file-size">{{ fileSize(item.size) }}</p>
|
||||||
<i v-if="item.type !== 'dir'" class="fas fa-download download" @click="download(item)" />
|
<i v-if="item.type !== 'dir' && canDownload" class="fas fa-download download" @click="download(item)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="transfer-area">
|
<div class="transfer-area">
|
||||||
@@ -73,6 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
v-if="canUpload"
|
||||||
class="upload-area"
|
class="upload-area"
|
||||||
:class="{ 'upload-area-drag': uploadAreaDrag }"
|
:class="{ 'upload-area-drag': uploadAreaDrag }"
|
||||||
@dragover.prevent="uploadAreaDrag = true"
|
@dragover.prevent="uploadAreaDrag = true"
|
||||||
@@ -281,6 +282,14 @@
|
|||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public uploadAreaDrag: boolean = false
|
public uploadAreaDrag: boolean = false
|
||||||
|
|
||||||
|
get canDownload() {
|
||||||
|
return this.$accessor.user.admin || this.$accessor.files.userDownload
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUpload() {
|
||||||
|
return this.$accessor.user.admin || this.$accessor.files.userUpload
|
||||||
|
}
|
||||||
|
|
||||||
get cwd() {
|
get cwd() {
|
||||||
return this.$accessor.files.cwd
|
return this.$accessor.files.cwd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,9 +94,19 @@
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
|
get canDownload() {
|
||||||
|
return this.$accessor.user.admin || this.$accessor.files.userDownload
|
||||||
|
}
|
||||||
|
|
||||||
|
get canUpload() {
|
||||||
|
return this.$accessor.user.admin || this.$accessor.files.userUpload
|
||||||
|
}
|
||||||
|
|
||||||
get filetransferAllowed() {
|
get filetransferAllowed() {
|
||||||
return (
|
return (
|
||||||
this.$accessor.remote.fileTransfer && (this.$accessor.user.admin || !this.$accessor.isLocked('file_transfer'))
|
this.$accessor.remote.fileTransfer &&
|
||||||
|
(this.$accessor.user.admin || !this.$accessor.isLocked('file_transfer')) &&
|
||||||
|
(this.canDownload || this.canUpload)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -361,9 +361,11 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
|
|||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// File Transfer Events
|
// File Transfer Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) {
|
protected [EVENT.FILETRANSFER.LIST]({ cwd, user_download, user_upload, files }: FileTransferListPayload) {
|
||||||
this.$accessor.files.setCwd(cwd)
|
this.$accessor.files.setCwd(cwd)
|
||||||
this.$accessor.files.setFileList(files)
|
this.$accessor.files.setFileList(files)
|
||||||
|
this.$accessor.files.setUserDownload(user_download)
|
||||||
|
this.$accessor.files.setUserUpload(user_upload)
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|||||||
@@ -204,6 +204,8 @@ export interface FileTransferListMessage extends WebSocketMessage, FileTransferL
|
|||||||
|
|
||||||
export interface FileTransferListPayload {
|
export interface FileTransferListPayload {
|
||||||
cwd: string
|
cwd: string
|
||||||
|
user_download: boolean
|
||||||
|
user_upload: boolean
|
||||||
files: FileListItem[]
|
files: FileListItem[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export const state = () => ({
|
|||||||
cwd: '',
|
cwd: '',
|
||||||
files: [] as FileListItem[],
|
files: [] as FileListItem[],
|
||||||
transfers: [] as FileTransfer[],
|
transfers: [] as FileTransfer[],
|
||||||
|
userDownload: false,
|
||||||
|
userUpload: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getters = getterTree(state, {
|
export const getters = getterTree(state, {
|
||||||
@@ -22,6 +24,14 @@ export const mutations = mutationTree(state, {
|
|||||||
state.files = files
|
state.files = files
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_setUserDownload(state, val: boolean) {
|
||||||
|
state.userDownload = val
|
||||||
|
},
|
||||||
|
|
||||||
|
_setUserUpload(state, val: boolean) {
|
||||||
|
state.userUpload = val
|
||||||
|
},
|
||||||
|
|
||||||
_addTransfer(state, transfer: FileTransfer) {
|
_addTransfer(state, transfer: FileTransfer) {
|
||||||
state.transfers = [...state.transfers, transfer]
|
state.transfers = [...state.transfers, transfer]
|
||||||
},
|
},
|
||||||
@@ -42,6 +52,14 @@ export const actions = actionTree(
|
|||||||
accessor.files._setFileList(files)
|
accessor.files._setFileList(files)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setUserDownload(store, val: boolean) {
|
||||||
|
accessor.files._setUserDownload(val)
|
||||||
|
},
|
||||||
|
|
||||||
|
setUserUpload(store, val: boolean) {
|
||||||
|
accessor.files._setUserUpload(val)
|
||||||
|
},
|
||||||
|
|
||||||
addTransfer(store, transfer: FileTransfer) {
|
addTransfer(store, transfer: FileTransfer) {
|
||||||
if (transfer.status !== 'pending') {
|
if (transfer.status !== 'pending') {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -109,9 +109,11 @@ type EmoteSend struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FileTransferList struct {
|
type FileTransferList struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
Cwd string `json:"cwd"`
|
Cwd string `json:"cwd"`
|
||||||
Files []types.FileListItem `json:"files"`
|
UserDownload bool `json:"user_download"`
|
||||||
|
UserUpload bool `json:"user_upload"`
|
||||||
|
Files []types.FileListItem `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Admin struct {
|
type Admin struct {
|
||||||
|
|||||||
@@ -607,9 +607,11 @@ func (s *session) wsToClient(msg []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return s.toClient(&oldMessage.FileTransferList{
|
return s.toClient(&oldMessage.FileTransferList{
|
||||||
Event: oldEvent.FILETRANSFER_LIST,
|
Event: oldEvent.FILETRANSFER_LIST,
|
||||||
Cwd: request.RootDir,
|
Cwd: request.RootDir,
|
||||||
Files: files,
|
UserDownload: request.UserDownload,
|
||||||
|
UserUpload: request.UserUpload,
|
||||||
|
Files: files,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Screen Events
|
// Screen Events
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ type Config struct {
|
|||||||
Enabled bool
|
Enabled bool
|
||||||
RootDir string
|
RootDir string
|
||||||
RefreshInterval time.Duration
|
RefreshInterval time.Duration
|
||||||
|
UserDownload bool
|
||||||
|
UserUpload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Config) Init(cmd *cobra.Command) error {
|
func (Config) Init(cmd *cobra.Command) error {
|
||||||
@@ -30,6 +32,16 @@ func (Config) Init(cmd *cobra.Command) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Bool("filetransfer.user_download", false, "allow non-admin users to download files")
|
||||||
|
if err := viper.BindPFlag("filetransfer.user_download", cmd.PersistentFlags().Lookup("filetransfer.user_download")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().Bool("filetransfer.user_upload", false, "allow non-admin users to upload files")
|
||||||
|
if err := viper.BindPFlag("filetransfer.user_upload", cmd.PersistentFlags().Lookup("filetransfer.user_upload")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// v2 config
|
// v2 config
|
||||||
|
|
||||||
cmd.PersistentFlags().Bool("file_transfer_enabled", false, "enable file transfer feature")
|
cmd.PersistentFlags().Bool("file_transfer_enabled", false, "enable file transfer feature")
|
||||||
@@ -50,6 +62,8 @@ func (s *Config) Set() {
|
|||||||
rootDir := viper.GetString("filetransfer.dir")
|
rootDir := viper.GetString("filetransfer.dir")
|
||||||
s.RootDir = filepath.Clean(rootDir)
|
s.RootDir = filepath.Clean(rootDir)
|
||||||
s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval")
|
s.RefreshInterval = viper.GetDuration("filetransfer.refresh_interval")
|
||||||
|
s.UserDownload = viper.GetBool("filetransfer.user_download")
|
||||||
|
s.UserUpload = viper.GetBool("filetransfer.user_upload")
|
||||||
|
|
||||||
// v2 config
|
// v2 config
|
||||||
|
|
||||||
|
|||||||
@@ -103,9 +103,11 @@ func (m *Manager) broadcastUpdate() {
|
|||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
|
|
||||||
m.sessions.Broadcast(FILETRANSFER_UPDATE, Message{
|
m.sessions.Broadcast(FILETRANSFER_UPDATE, Message{
|
||||||
Enabled: m.config.Enabled,
|
Enabled: m.config.Enabled,
|
||||||
RootDir: m.config.RootDir,
|
RootDir: m.config.RootDir,
|
||||||
Files: fileList,
|
UserDownload: m.config.UserDownload,
|
||||||
|
UserUpload: m.config.UserUpload,
|
||||||
|
Files: fileList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,9 +117,11 @@ func (m *Manager) sendUpdate(session types.Session) {
|
|||||||
m.mu.RUnlock()
|
m.mu.RUnlock()
|
||||||
|
|
||||||
session.Send(FILETRANSFER_UPDATE, Message{
|
session.Send(FILETRANSFER_UPDATE, Message{
|
||||||
Enabled: m.config.Enabled,
|
Enabled: m.config.Enabled,
|
||||||
RootDir: m.config.RootDir,
|
RootDir: m.config.RootDir,
|
||||||
Files: fileList,
|
UserDownload: m.config.UserDownload,
|
||||||
|
UserUpload: m.config.UserUpload,
|
||||||
|
Files: fileList,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +212,8 @@ func (m *Manager) Shutdown() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Route(r types.Router) {
|
func (m *Manager) Route(r types.Router) {
|
||||||
r.With(auth.AdminsOnly).Get("/", m.downloadFileHandler)
|
r.Get("/", m.downloadFileHandler)
|
||||||
r.With(auth.AdminsOnly).Post("/", m.uploadFileHandler)
|
r.Post("/", m.uploadFileHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) WebSocketHandler(session types.Session, msg types.WebSocketMessage) bool {
|
func (m *Manager) WebSocketHandler(session types.Session, msg types.WebSocketMessage) bool {
|
||||||
@@ -251,6 +255,10 @@ func (m *Manager) downloadFileHandler(w http.ResponseWriter, r *http.Request) er
|
|||||||
return utils.HttpForbidden("file transfer is disabled")
|
return utils.HttpForbidden("file transfer is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !session.Profile().IsAdmin && !m.config.UserDownload {
|
||||||
|
return utils.HttpForbidden("file download is not allowed for non-admin users")
|
||||||
|
}
|
||||||
|
|
||||||
filename := r.URL.Query().Get("filename")
|
filename := r.URL.Query().Get("filename")
|
||||||
badChars, err := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
|
badChars, err := regexp.MatchString(`(?m)\.\.(?:\/|$)`, filename)
|
||||||
if filename == "" || badChars || err != nil {
|
if filename == "" || badChars || err != nil {
|
||||||
@@ -285,6 +293,10 @@ func (m *Manager) uploadFileHandler(w http.ResponseWriter, r *http.Request) erro
|
|||||||
return utils.HttpForbidden("file transfer is disabled")
|
return utils.HttpForbidden("file transfer is disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !session.Profile().IsAdmin && !m.config.UserUpload {
|
||||||
|
return utils.HttpForbidden("file upload is not allowed for non-admin users")
|
||||||
|
}
|
||||||
|
|
||||||
err = r.ParseMultipartForm(multipartFormMaxMemory)
|
err = r.ParseMultipartForm(multipartFormMaxMemory)
|
||||||
if err != nil || r.MultipartForm == nil {
|
if err != nil || r.MultipartForm == nil {
|
||||||
return utils.HttpBadRequest().
|
return utils.HttpBadRequest().
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
RootDir string `json:"root_dir"`
|
RootDir string `json:"root_dir"`
|
||||||
Files []Item `json:"files"`
|
UserDownload bool `json:"user_download"`
|
||||||
|
UserUpload bool `json:"user_upload"`
|
||||||
|
Files []Item `json:"files"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ItemType string
|
type ItemType string
|
||||||
|
|||||||
@@ -59,11 +59,15 @@ The file transfer plugin is a simple pre-loaded internal plugin that allows you
|
|||||||
type: 'duration',
|
type: 'duration',
|
||||||
defaultValue: '30s',
|
defaultValue: '30s',
|
||||||
},
|
},
|
||||||
|
'filetransfer.user_download': false,
|
||||||
|
'filetransfer.user_upload': false,
|
||||||
}} />
|
}} />
|
||||||
|
|
||||||
- <Def id="filetransfer.enabled" /> enables the file transfer support. If set to `false`, the file transfer is disabled.
|
- <Def id="filetransfer.enabled" /> enables the file transfer support. If set to `false`, the file transfer is disabled.
|
||||||
- <Def id="filetransfer.dir" /> refers to the directory where the files are stored.
|
- <Def id="filetransfer.dir" /> refers to the directory where the files are stored.
|
||||||
- <Def id="filetransfer.refresh_interval" /> refers to the interval at which the file list is refreshed.
|
- <Def id="filetransfer.refresh_interval" /> refers to the interval at which the file list is refreshed.
|
||||||
|
- <Def id="filetransfer.user_download" /> allows non-admin users to download files. If set to `false` (default), only admins can download files.
|
||||||
|
- <Def id="filetransfer.user_upload" /> allows non-admin users to upload files. If set to `false` (default), only admins can upload files.
|
||||||
|
|
||||||
The file transfer plugin extends user profile and room settings by adding the following fields:
|
The file transfer plugin extends user profile and room settings by adding the following fields:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user