Archived
2
0

file transfer permission state management

This commit is contained in:
William Harrell 2022-11-15 20:39:06 -05:00
parent 19af921913
commit 57e89bb1cc
14 changed files with 208 additions and 41 deletions

View File

@ -131,6 +131,26 @@
max-height: 50vh; max-height: 50vh;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: thin;
scrollbar-color: $background-tertiary transparent;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $background-tertiary;
border: 2px solid $background-primary;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $background-floating;
}
} }
.transfers > p { .transfers > p {

View File

@ -44,6 +44,20 @@
<span /> <span />
</label> </label>
</li> </li>
<li v-if="admin">
<span>File transfer</span>
<label class="switch">
<input type="checkbox" v-model="file_transfer" />
<span />
</label>
</li>
<li v-if="admin && file_transfer">
<span>Non-admin file transfer</span>
<label class="switch">
<input type="checkbox" v-model="unpriv_file_transfer" />
<span />
</label>
</li>
<li class="broadcast" v-if="admin"> <li class="broadcast" v-if="admin">
<div> <div>
<span>{{ $t('setting.broadcast_title') }}</span> <span>{{ $t('setting.broadcast_title') }}</span>
@ -366,6 +380,22 @@
return this.$accessor.settings.keyboard_layout return this.$accessor.settings.keyboard_layout
} }
get file_transfer() {
return this.$accessor.settings.file_transfer
}
set file_transfer(value: boolean) {
this.$accessor.settings.setGlobalFileTransferStatus({ admin: value, unpriv: false })
}
get unpriv_file_transfer() {
return this.$accessor.settings.unpriv_file_transfer
}
set unpriv_file_transfer(value: boolean) {
this.$accessor.settings.setGlobalFileTransferStatus({ admin: this.file_transfer, unpriv: value })
}
get broadcast_is_active() { get broadcast_is_active() {
return this.$accessor.settings.broadcast_is_active return this.$accessor.settings.broadcast_is_active
} }

View File

@ -6,7 +6,7 @@
<i class="fas fa-comment-alt" /> <i class="fas fa-comment-alt" />
<span>{{ $t('side.chat') }}</span> <span>{{ $t('side.chat') }}</span>
</li> </li>
<li :class="{ active: tab === 'files' }" @click.stop.prevent="change('files')"> <li v-if="filetransferAllowed" :class="{ active: tab === 'files' }" @click.stop.prevent="change('files')">
<i class="fas fa-file" /> <i class="fas fa-file" />
<span>{{ $t('side.files') }}</span> <span>{{ $t('side.files') }}</span>
</li> </li>
@ -94,6 +94,20 @@
}, },
}) })
export default class extends Vue { export default class extends Vue {
constructor() {
super()
if (this.tab === 'files' && (!this.$accessor.settings.file_transfer ||
!this.$accessor.user.admin && this.$accessor.settings.unpriv_file_transfer)) {
this.change('chat')
}
}
get filetransferAllowed() {
return this.$accessor.user.admin && this.$accessor.settings.file_transfer ||
this.$accessor.settings.unpriv_file_transfer
}
get tab() { get tab() {
return this.$accessor.client.tab return this.$accessor.client.tab
} }

View File

@ -39,10 +39,7 @@ export const EVENT = {
EMOTE: 'chat/emote', EMOTE: 'chat/emote',
}, },
FILETRANSFER: { FILETRANSFER: {
ENABLE: 'filetransfer/enable', STATUS: 'filetransfer/status',
DISABLE: 'filetransfer/disable',
UNPRIVENABLE: 'filetransfer/unprivenable',
UNPRIVDISABLE: 'filetransfer/unprivdisable',
LIST: 'filetransfer/list', LIST: 'filetransfer/list',
REFRESH: 'filetransfer/refresh' REFRESH: 'filetransfer/refresh'
}, },
@ -102,10 +99,7 @@ export type SignalEvents =
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
export type FileTransferEvents = export type FileTransferEvents =
| typeof EVENT.FILETRANSFER.ENABLE | typeof EVENT.FILETRANSFER.STATUS
| typeof EVENT.FILETRANSFER.DISABLE
| typeof EVENT.FILETRANSFER.UNPRIVENABLE
| typeof EVENT.FILETRANSFER.UNPRIVDISABLE
| typeof EVENT.FILETRANSFER.LIST | typeof EVENT.FILETRANSFER.LIST
| typeof EVENT.FILETRANSFER.REFRESH | typeof EVENT.FILETRANSFER.REFRESH

View File

@ -25,6 +25,7 @@ import {
SystemInitPayload, SystemInitPayload,
AdminLockResource, AdminLockResource,
FileTransferListPayload, FileTransferListPayload,
FileTransferStatusPayload,
} from './messages' } from './messages'
interface NekoEvents extends BaseEvents {} interface NekoEvents extends BaseEvents {}
@ -361,8 +362,12 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
} }
///////////////////////////// /////////////////////////////
// Chat Events // Filetransfer Events
///////////////////////////// /////////////////////////////
protected [EVENT.FILETRANSFER.STATUS]({ admin, unpriv }: FileTransferStatusPayload) {
this.$accessor.settings.setLocalFileTransferStatus({ admin, unpriv })
}
protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) { protected [EVENT.FILETRANSFER.LIST]({ cwd, files }: FileTransferListPayload) {
this.$accessor.files.setCwd(cwd) this.$accessor.files.setCwd(cwd)
this.$accessor.files.setFileList(files) this.$accessor.files.setFileList(files)

View File

@ -44,6 +44,7 @@ export type WebSocketPayloads =
| ChatPayload | ChatPayload
| ChatSendPayload | ChatSendPayload
| EmojiSendPayload | EmojiSendPayload
| FileTransferStatusPayload
| ScreenResolutionPayload | ScreenResolutionPayload
| ScreenConfigurationsPayload | ScreenConfigurationsPayload
| AdminPayload | AdminPayload
@ -198,8 +199,17 @@ export interface EmojiSendPayload {
emote: string emote: string
} }
// file transfer // file transfer enabled
export interface FileTransferMessage extends WebSocketMessage, FileTransferListPayload { export interface FileTransferStatusMessage extends WebSocketMessage, FileTransferStatusPayload {
event: typeof EVENT.FILETRANSFER.STATUS
}
export interface FileTransferStatusPayload {
admin: boolean,
unpriv: boolean
}
// file transfer list
export interface FileTransferListMessage extends WebSocketMessage, FileTransferListPayload {
event: FileTransferEvents event: FileTransferEvents
} }
export interface FileTransferListPayload { export interface FileTransferListPayload {

View File

@ -52,6 +52,15 @@ export const actions = actionTree(
accessor.files._removeTransfer(transfer) accessor.files._removeTransfer(transfer)
}, },
cancelAllTransfers(store) {
for (const t of accessor.files.transfers) {
if (t.status !== 'completed') {
t.abortController?.abort()
}
accessor.files.removeTransfer(t)
}
},
refresh(store) { refresh(store) {
if (!accessor.connected) { if (!accessor.connected) {
return return

View File

@ -20,6 +20,9 @@ export const state = () => {
keyboard_layouts_list: {} as KeyboardLayouts, keyboard_layouts_list: {} as KeyboardLayouts,
file_transfer: false,
unpriv_file_transfer: false,
broadcast_is_active: false, broadcast_is_active: false,
broadcast_url: '', broadcast_url: '',
} }
@ -58,6 +61,14 @@ export const mutations = mutationTree(state, {
set('keyboard_layout', value) set('keyboard_layout', value)
}, },
setFileTransfer(state, value: boolean) {
state.file_transfer = value
},
setUnprivFileTransfer(state, value: boolean) {
state.unpriv_file_transfer = value
},
setKeyboardLayoutsList(state, value: KeyboardLayouts) { setKeyboardLayoutsList(state, value: KeyboardLayouts) {
state.keyboard_layouts_list = value state.keyboard_layouts_list = value
}, },
@ -79,6 +90,23 @@ export const actions = actionTree(
} }
}, },
setLocalFileTransferStatus({ getters }, { admin, unpriv }) {
accessor.settings.setFileTransfer(admin)
accessor.settings.setUnprivFileTransfer(unpriv)
if (!admin || !accessor.user.admin && !unpriv) {
accessor.files.cancelAllTransfers()
}
if (accessor.client.tab === 'files' && !unpriv) {
accessor.client.setTab('chat')
}
},
setGlobalFileTransferStatus({ getters}, { admin, unpriv }) {
$client.sendMessage(EVENT.FILETRANSFER.STATUS, { admin, unpriv })
},
broadcastStatus({ getters }, { url, isActive }) { broadcastStatus({ getters }, { url, isActive }) {
accessor.settings.setBroadcastStatus({ url, isActive }) accessor.settings.setBroadcastStatus({ url, isActive })
}, },

View File

@ -35,10 +35,7 @@ const (
) )
const ( const (
FILETRANSFER_ENABLE = "filetransfer/enable" FILETRANSFER_STATUS = "filetransfer/status"
FILETRANSFER_DISABLE = "filetransfer/disable"
FILETRANSFER_UNPRIVENABLE = "filetransfer/unprivenable"
FILETRANSFER_UNPRIVDISABLE = "filetransfer/unprivdisable"
FILETRANSFER_LIST = "filetransfer/list" FILETRANSFER_LIST = "filetransfer/list"
FILETRANSFER_REFRESH = "filetransfer/refresh" FILETRANSFER_REFRESH = "filetransfer/refresh"
) )

View File

@ -108,7 +108,12 @@ type EmoteSend struct {
type FileTransferTarget struct { type FileTransferTarget struct {
Event string `json:"event"` Event string `json:"event"`
ID string `json:"id"` }
type FileTransferStatus struct {
Event string `json:"event"`
Admin bool `json:"admin"`
Unpriv bool `json:"unpriv"`
} }
type FileList struct { type FileList struct {

View File

@ -8,6 +8,36 @@ import (
"m1k1o/neko/internal/utils" "m1k1o/neko/internal/utils"
) )
func (h *MessageHandler) setFileTransferStatus(session types.Session, payload *message.FileTransferStatus) error {
if !session.Admin() {
return errors.New(session.Member().Name + " tried to toggle file transfer but they're not admin")
}
h.state.SetFileTransferState(payload.Admin, payload.Unpriv)
err := h.sessions.Broadcast(message.FileTransferStatus{
Event: event.FILETRANSFER_STATUS,
Admin: payload.Admin,
Unpriv: payload.Admin && payload.Unpriv,
}, nil)
if err != nil {
return err
}
files, err := utils.ListFiles(h.state.FileTransferPath())
if err != nil {
return err
}
msg := message.FileList{
Event: event.FILETRANSFER_LIST,
Cwd: h.state.FileTransferPath(),
Files: *files,
}
if payload.Unpriv {
return h.sessions.Broadcast(msg, nil)
} else {
return h.sessions.AdminBroadcast(msg, nil)
}
}
func (h *MessageHandler) refresh(session types.Session) error { func (h *MessageHandler) refresh(session types.Session) error {
if !(h.state.FileTransferEnabled() && session.Admin() || h.state.UnprivFileTransferEnabled()) { if !(h.state.FileTransferEnabled() && session.Admin() || h.state.UnprivFileTransferEnabled()) {
return errors.New(session.Member().Name + " tried to refresh file list when they can't") return errors.New(session.Member().Name + " tried to refresh file list when they can't")

View File

@ -127,6 +127,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
}), "%s failed", header.Event) }), "%s failed", header.Event)
// File Transfer Events // File Transfer Events
case event.FILETRANSFER_STATUS:
payload := &message.FileTransferStatus{}
return errors.Wrapf(
utils.Unmarshal(payload, raw, func() error {
return h.setFileTransferStatus(session, payload)
}), "%s failed", header.Event)
case event.FILETRANSFER_REFRESH: case event.FILETRANSFER_REFRESH:
return errors.Wrapf(h.refresh(session), "%s failed", header.Event) return errors.Wrapf(h.refresh(session), "%s failed", header.Event)

View File

@ -78,6 +78,11 @@ func (s *State) UnprivFileTransferEnabled() bool {
return s.fileTransferUnprivEnabled return s.fileTransferUnprivEnabled
} }
func (s *State) SetFileTransferState(admin bool, unpriv bool) {
s.fileTransferEnabled = admin
s.fileTransferUnprivEnabled = unpriv
}
func (s *State) FileTransferPath() string { func (s *State) FileTransferPath() string {
return s.fileTransferPath return s.fileTransferPath
} }

View File

@ -135,7 +135,19 @@ func (ws *WebSocketHandler) Start() {
} }
// send file list if necessary // send file list if necessary
if session.Admin() && ws.conf.FileTransfer || ws.conf.FileTransfer && ws.conf.UnprivFileTransfer { if session.Admin() && ws.state.FileTransferEnabled() ||
ws.state.FileTransferEnabled() && ws.state.UnprivFileTransferEnabled() {
err := session.Send(
message.FileTransferStatus{
Event: event.FILETRANSFER_STATUS,
Admin: ws.state.FileTransferEnabled(),
Unpriv: ws.state.UnprivFileTransferEnabled(),
})
if err != nil {
ws.logger.Warn().Err(err).Msgf("file transfer status event has failed")
return
}
files, err := utils.ListFiles(ws.conf.FileTransferPath) files, err := utils.ListFiles(ws.conf.FileTransferPath)
if err == nil { if err == nil {
if err := session.Send( if err := session.Send(
@ -214,7 +226,6 @@ func (ws *WebSocketHandler) Start() {
}) })
// watch for file changes // watch for file changes
if ws.conf.FileTransfer {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
ws.logger.Err(err).Msg("unable to start file transfer dir watcher") ws.logger.Err(err).Msg("unable to start file transfer dir watcher")
@ -235,7 +246,6 @@ func (ws *WebSocketHandler) Start() {
if err := watcher.Add(ws.conf.FileTransferPath); err != nil { if err := watcher.Add(ws.conf.FileTransferPath); err != nil {
ws.logger.Err(err).Msg("unable to add file transfer path to watcher") ws.logger.Err(err).Msg("unable to add file transfer path to watcher")
} }
}
} }
func (ws *WebSocketHandler) Shutdown() error { func (ws *WebSocketHandler) Shutdown() error {
@ -364,11 +374,11 @@ func (ws *WebSocketHandler) IsAdmin(password string) (bool, error) {
} }
func (ws *WebSocketHandler) CanTransferFiles(password string) (bool, error) { func (ws *WebSocketHandler) CanTransferFiles(password string) (bool, error) {
if !ws.conf.FileTransfer { if !ws.state.FileTransferEnabled() {
return false, nil return false, nil
} }
if !ws.conf.UnprivFileTransfer { if !ws.state.UnprivFileTransferEnabled() {
return ws.IsAdmin(password) return ws.IsAdmin(password)
} }
@ -380,6 +390,10 @@ func (ws *WebSocketHandler) MakeFilePath(filename string) string {
} }
func (ws *WebSocketHandler) sendFileTransferUpdate() { func (ws *WebSocketHandler) sendFileTransferUpdate() {
if !ws.state.FileTransferEnabled() {
return
}
files, err := utils.ListFiles(ws.conf.FileTransferPath) files, err := utils.ListFiles(ws.conf.FileTransferPath)
if err != nil { if err != nil {
ws.logger.Err(err).Msg("unable to ls file transfer path") ws.logger.Err(err).Msg("unable to ls file transfer path")
@ -393,7 +407,7 @@ func (ws *WebSocketHandler) sendFileTransferUpdate() {
} }
var broadcastErr error var broadcastErr error
if ws.conf.UnprivFileTransfer { if ws.state.UnprivFileTransferEnabled() {
broadcastErr = ws.sessions.Broadcast(message, nil) broadcastErr = ws.sessions.Broadcast(message, nil)
} else { } else {
broadcastErr = ws.sessions.AdminBroadcast(message, nil) broadcastErr = ws.sessions.AdminBroadcast(message, nil)