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;
overflow-y: scroll;
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 {

View File

@ -44,6 +44,20 @@
<span />
</label>
</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">
<div>
<span>{{ $t('setting.broadcast_title') }}</span>
@ -366,6 +380,22 @@
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() {
return this.$accessor.settings.broadcast_is_active
}

View File

@ -6,7 +6,7 @@
<i class="fas fa-comment-alt" />
<span>{{ $t('side.chat') }}</span>
</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" />
<span>{{ $t('side.files') }}</span>
</li>
@ -94,6 +94,20 @@
},
})
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() {
return this.$accessor.client.tab
}

View File

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

View File

@ -25,6 +25,7 @@ import {
SystemInitPayload,
AdminLockResource,
FileTransferListPayload,
FileTransferStatusPayload,
} from './messages'
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) {
this.$accessor.files.setCwd(cwd)
this.$accessor.files.setFileList(files)

View File

@ -44,6 +44,7 @@ export type WebSocketPayloads =
| ChatPayload
| ChatSendPayload
| EmojiSendPayload
| FileTransferStatusPayload
| ScreenResolutionPayload
| ScreenConfigurationsPayload
| AdminPayload
@ -198,8 +199,17 @@ export interface EmojiSendPayload {
emote: string
}
// file transfer
export interface FileTransferMessage extends WebSocketMessage, FileTransferListPayload {
// file transfer enabled
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
}
export interface FileTransferListPayload {

View File

@ -52,6 +52,15 @@ export const actions = actionTree(
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) {
if (!accessor.connected) {
return

View File

@ -20,6 +20,9 @@ export const state = () => {
keyboard_layouts_list: {} as KeyboardLayouts,
file_transfer: false,
unpriv_file_transfer: false,
broadcast_is_active: false,
broadcast_url: '',
}
@ -58,6 +61,14 @@ export const mutations = mutationTree(state, {
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) {
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 }) {
accessor.settings.setBroadcastStatus({ url, isActive })
},

View File

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

View File

@ -108,7 +108,12 @@ type EmoteSend struct {
type FileTransferTarget struct {
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 {

View File

@ -8,6 +8,36 @@ import (
"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 {
if !(h.state.FileTransferEnabled() && session.Admin() || h.state.UnprivFileTransferEnabled()) {
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)
// 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:
return errors.Wrapf(h.refresh(session), "%s failed", header.Event)

View File

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

View File

@ -135,7 +135,19 @@ func (ws *WebSocketHandler) Start() {
}
// 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)
if err == nil {
if err := session.Send(
@ -214,27 +226,25 @@ func (ws *WebSocketHandler) Start() {
})
// watch for file changes
if ws.conf.FileTransfer {
watcher, err := fsnotify.NewWatcher()
if err != nil {
ws.logger.Err(err).Msg("unable to start file transfer dir watcher")
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
ws.logger.Err(err).Msg("unable to start file transfer dir watcher")
return
}
go func() {
for {
select {
case <-watcher.Events:
ws.sendFileTransferUpdate()
case err := <-watcher.Errors:
ws.logger.Err(err).Msg("error in file transfer dir watcher")
}
go func() {
for {
select {
case <-watcher.Events:
ws.sendFileTransferUpdate()
case err := <-watcher.Errors:
ws.logger.Err(err).Msg("error in file transfer dir watcher")
}
}()
if err := watcher.Add(ws.conf.FileTransferPath); err != nil {
ws.logger.Err(err).Msg("unable to add file transfer path to watcher")
}
}()
if err := watcher.Add(ws.conf.FileTransferPath); err != nil {
ws.logger.Err(err).Msg("unable to add file transfer path to watcher")
}
}
@ -364,11 +374,11 @@ func (ws *WebSocketHandler) IsAdmin(password string) (bool, error) {
}
func (ws *WebSocketHandler) CanTransferFiles(password string) (bool, error) {
if !ws.conf.FileTransfer {
if !ws.state.FileTransferEnabled() {
return false, nil
}
if !ws.conf.UnprivFileTransfer {
if !ws.state.UnprivFileTransferEnabled() {
return ws.IsAdmin(password)
}
@ -380,6 +390,10 @@ func (ws *WebSocketHandler) MakeFilePath(filename string) string {
}
func (ws *WebSocketHandler) sendFileTransferUpdate() {
if !ws.state.FileTransferEnabled() {
return
}
files, err := utils.ListFiles(ws.conf.FileTransferPath)
if err != nil {
ws.logger.Err(err).Msg("unable to ls file transfer path")
@ -393,7 +407,7 @@ func (ws *WebSocketHandler) sendFileTransferUpdate() {
}
var broadcastErr error
if ws.conf.UnprivFileTransfer {
if ws.state.UnprivFileTransferEnabled() {
broadcastErr = ws.sessions.Broadcast(message, nil)
} else {
broadcastErr = ws.sessions.AdminBroadcast(message, nil)