attempt number 2

This commit is contained in:
Keagan McClelland 2020-09-22 17:52:46 -06:00
parent 1529e796df
commit 8fc8f72103
13 changed files with 97 additions and 75 deletions

View File

@ -140,6 +140,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address) fmt.Fprintf(w, "\tAddress:\t%s\n", ser.Address)
fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert) fmt.Fprintf(w, "\tTLS Cert:\t%s\n", ser.TLSCert)
fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey) fmt.Fprintf(w, "\tTLS Key:\t%s\n", ser.TLSKey)
fmt.Fprintf(w, "\tExec Enabled:\t%t\n", ser.EnableExec)
fmt.Fprintln(w, "\nDefaults:") fmt.Fprintln(w, "\nDefaults:")
fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope) fmt.Fprintf(w, "\tScope:\t%s\n", set.Defaults.Scope)
fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale) fmt.Fprintf(w, "\tLocale:\t%s\n", set.Defaults.Locale)

View File

@ -64,6 +64,7 @@ func addServerFlags(flags *pflag.FlagSet) {
flags.Int("img-processors", 4, "image processors count") flags.Int("img-processors", 4, "image processors count")
flags.Bool("disable-thumbnails", false, "disable image thumbnails") flags.Bool("disable-thumbnails", false, "disable image thumbnails")
flags.Bool("disable-preview-resize", false, "disable resize of image previews") flags.Bool("disable-preview-resize", false, "disable resize of image previews")
flags.Bool("disable-exec", false, "disables Command Runner feature")
} }
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
@ -241,6 +242,9 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server {
_, disablePreviewResize := getParamB(flags, "disable-preview-resize") _, disablePreviewResize := getParamB(flags, "disable-preview-resize")
server.ResizePreview = !disablePreviewResize server.ResizePreview = !disablePreviewResize
_, disableExec := getParamB(flags, "disable-exec")
server.EnableExec = !disableExec
return server return server
} }

View File

@ -13,7 +13,7 @@
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<!-- Add to home screen for Android and modern mobile browsers --> <!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials"> <link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="#2979ff"> <meta name="theme-color" content="#2979ff">
@ -31,7 +31,7 @@
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
window.FileBrowser = JSON.parse(`[{[ .Json ]}]`); window.FileBrowser = JSON.parse(`[{[ .Json ]}]`);
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL; var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = { var dynamicManifest = {
"name": window.FileBrowser.Name || 'File Browser', "name": window.FileBrowser.Name || 'File Browser',

View File

@ -37,7 +37,7 @@
<delete-button v-show="showDeleteButton"></delete-button> <delete-button v-show="showDeleteButton"></delete-button>
</div> </div>
<shell-button v-show="user.perm.execute" /> <shell-button v-if="isExecEnabled && user.perm.execute" />
<switch-button v-show="isListing"></switch-button> <switch-button v-show="isListing"></switch-button>
<download-button v-show="showDownloadButton"></download-button> <download-button v-show="showDownloadButton"></download-button>
<upload-button v-show="showUpload"></upload-button> <upload-button v-show="showUpload"></upload-button>
@ -68,7 +68,7 @@ import CopyButton from './buttons/Copy'
import ShareButton from './buttons/Share' import ShareButton from './buttons/Share'
import ShellButton from './buttons/Shell' import ShellButton from './buttons/Shell'
import {mapGetters, mapState} from 'vuex' import {mapGetters, mapState} from 'vuex'
import { logoURL } from '@/utils/constants' import { logoURL, enableExec } from '@/utils/constants'
import * as api from '@/api' import * as api from '@/api'
import buttons from '@/utils/buttons' import buttons from '@/utils/buttons'
@ -120,6 +120,7 @@ export default {
'multiple' 'multiple'
]), ]),
logoURL: () => logoURL, logoURL: () => logoURL,
isExecEnabled: () => enableExec,
isMobile () { isMobile () {
return this.width <= 736 return this.width <= 736
}, },

View File

@ -9,13 +9,14 @@
<p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.delete"> {{ $t('settings.perm.delete') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.download"> {{ $t('settings.perm.download') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.modify"> {{ $t('settings.perm.modify') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p> <p v-if="isExecEnabled"><input type="checkbox" :disabled="admin" v-model="perm.execute"> {{ $t('settings.perm.execute') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.rename"> {{ $t('settings.perm.rename') }}</p>
<p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p> <p><input type="checkbox" :disabled="admin" v-model="perm.share"> {{ $t('settings.perm.share') }}</p>
</div> </div>
</template> </template>
<script> <script>
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'permissions', name: 'permissions',
props: ['perm'], props: ['perm'],
@ -33,7 +34,8 @@ export default {
this.perm.admin = value this.perm.admin = value
} }
} },
isExecEnabled: () => enableExec
} }
} }
</script> </script>

View File

@ -25,7 +25,7 @@
</p> </p>
<permissions :perm.sync="user.perm" /> <permissions :perm.sync="user.perm" />
<commands :commands.sync="user.commands" /> <commands v-if="isExecEnabled" :commands.sync="user.commands" />
<div v-if="!isDefault"> <div v-if="!isDefault">
<h3>{{ $t('settings.rules') }}</h3> <h3>{{ $t('settings.rules') }}</h3>
@ -40,6 +40,7 @@ import Languages from './Languages'
import Rules from './Rules' import Rules from './Rules'
import Permissions from './Permissions' import Permissions from './Permissions'
import Commands from './Commands' import Commands from './Commands'
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'user', name: 'user',
@ -53,7 +54,8 @@ export default {
computed: { computed: {
passwordPlaceholder () { passwordPlaceholder () {
return this.isNew ? '' : this.$t('settings.avoidChanges') return this.isNew ? '' : this.$t('settings.avoidChanges')
} },
isExecEnabled: () => enableExec
}, },
watch: { watch: {
'user.perm.admin': function () { 'user.perm.admin': function () {

View File

@ -13,6 +13,7 @@ const loginPage = window.FileBrowser.LoginPage
const theme = window.FileBrowser.Theme const theme = window.FileBrowser.Theme
const enableThumbs = window.FileBrowser.EnableThumbs const enableThumbs = window.FileBrowser.EnableThumbs
const resizePreview = window.FileBrowser.ResizePreview const resizePreview = window.FileBrowser.ResizePreview
const enableExec = window.FileBrowser.EnableExec
export { export {
name, name,
@ -28,5 +29,6 @@ export {
loginPage, loginPage,
theme, theme,
enableThumbs, enableThumbs,
resizePreview resizePreview,
enableExec
} }

View File

@ -7,7 +7,7 @@
<sidebar></sidebar> <sidebar></sidebar>
<main> <main>
<router-view></router-view> <router-view></router-view>
<shell v-if="isLogged && user.perm.execute" /> <shell v-if="isExecEnabled && isLogged && user.perm.execute" />
</main> </main>
<prompts></prompts> <prompts></prompts>
</div> </div>
@ -19,6 +19,7 @@ import Sidebar from '@/components/Sidebar'
import Prompts from '@/components/prompts/Prompts' import Prompts from '@/components/prompts/Prompts'
import SiteHeader from '@/components/Header' import SiteHeader from '@/components/Header'
import Shell from '@/components/Shell' import Shell from '@/components/Shell'
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'layout', name: 'layout',
@ -30,7 +31,8 @@ export default {
}, },
computed: { computed: {
...mapGetters([ 'isLogged', 'progress' ]), ...mapGetters([ 'isLogged', 'progress' ]),
...mapState([ 'user' ]) ...mapState([ 'user' ]),
isExecEnabled: () => enableExec
}, },
watch: { watch: {
'$route': function () { '$route': function () {

View File

@ -14,9 +14,11 @@
<p class="small">{{ $t('settings.globalRules') }}</p> <p class="small">{{ $t('settings.globalRules') }}</p>
<rules :rules.sync="settings.rules" /> <rules :rules.sync="settings.rules" />
<h3>{{ $t('settings.executeOnShell') }}</h3> <div v-if="isExecEnabled">
<p class="small">{{ $t('settings.executeOnShellDescription') }}</p> <h3>{{ $t('settings.executeOnShell') }}</h3>
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" /> <p class="small">{{ $t('settings.executeOnShellDescription') }}</p>
<input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." v-model="settings.shell" />
</div>
<h3>{{ $t('settings.branding') }}</h3> <h3>{{ $t('settings.branding') }}</h3>
@ -67,7 +69,7 @@
</div> </div>
</form> </form>
<form class="card" @submit.prevent="save"> <form v-if="isExecEnabled" class="card" @submit.prevent="save">
<div class="card-title"> <div class="card-title">
<h2>{{ $t('settings.commandRunner') }}</h2> <h2>{{ $t('settings.commandRunner') }}</h2>
</div> </div>
@ -104,6 +106,7 @@ import { settings as api } from '@/api'
import UserForm from '@/components/settings/UserForm' import UserForm from '@/components/settings/UserForm'
import Rules from '@/components/settings/Rules' import Rules from '@/components/settings/Rules'
import Themes from '@/components/settings/Themes' import Themes from '@/components/settings/Themes'
import { enableExec } from '@/utils/constants'
export default { export default {
name: 'settings', name: 'settings',
@ -119,7 +122,8 @@ export default {
} }
}, },
computed: { computed: {
...mapState([ 'user' ]) ...mapState([ 'user' ]),
isExecEnabled: () => enableExec
}, },
async created () { async created () {
try { try {

View File

@ -37,74 +37,76 @@ func wsErr(ws *websocket.Conn, r *http.Request, status int, err error) { //nolin
} }
} }
var commandsHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { func commandsHandler (enableExec bool) handleFunc {
conn, err := upgrader.Upgrade(w, r, nil) return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
if err != nil { conn, err := upgrader.Upgrade(w, r, nil)
return http.StatusInternalServerError, err if err != nil {
} return http.StatusInternalServerError, err
defer conn.Close() }
defer conn.Close()
var raw string var raw string
for { for {
_, msg, err := conn.ReadMessage() //nolint:shadow _, msg, err := conn.ReadMessage() //nolint:shadow
if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
}
raw = strings.TrimSpace(string(msg))
if raw != "" {
break
}
}
if !enableExec || !d.user.CanExecute(strings.Split(raw, " ")[0]) {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
command, err := runner.ParseCommand(d.settings, raw)
if err != nil {
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:shadow
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
cmd.Dir = d.user.FullPath(r.URL.Path)
stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil return 0, nil
} }
raw = strings.TrimSpace(string(msg)) stderr, err := cmd.StderrPipe()
if raw != "" { if err != nil {
break wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
} }
}
if !d.user.CanExecute(strings.Split(raw, " ")[0]) { if err := cmd.Start(); err != nil {
if err := conn.WriteMessage(websocket.TextMessage, cmdNotAllowed); err != nil { //nolint:shadow wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
}
s := bufio.NewScanner(io.MultiReader(stdout, stderr))
for s.Scan() {
if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
log.Print(err)
}
}
if err := cmd.Wait(); err != nil {
wsErr(conn, r, http.StatusInternalServerError, err) wsErr(conn, r, http.StatusInternalServerError, err)
} }
return 0, nil return 0, nil
} })
}
command, err := runner.ParseCommand(d.settings, raw)
if err != nil {
if err := conn.WriteMessage(websocket.TextMessage, []byte(err.Error())); err != nil { //nolint:shadow
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
}
cmd := exec.Command(command[0], command[1:]...) //nolint:gosec
cmd.Dir = d.user.FullPath(r.URL.Path)
stdout, err := cmd.StdoutPipe()
if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
}
stderr, err := cmd.StderrPipe()
if err != nil {
wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
}
if err := cmd.Start(); err != nil {
wsErr(conn, r, http.StatusInternalServerError, err)
return 0, nil
}
s := bufio.NewScanner(io.MultiReader(stdout, stderr))
for s.Scan() {
if err := conn.WriteMessage(websocket.TextMessage, s.Bytes()); err != nil {
log.Print(err)
}
}
if err := cmd.Wait(); err != nil {
wsErr(conn, r, http.StatusInternalServerError, err)
}
return 0, nil
})

View File

@ -61,7 +61,7 @@ func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage,
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET") api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
api.PathPrefix("/preview/{size}/{path:.*}"). api.PathPrefix("/preview/{size}/{path:.*}").
Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET") Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET")
api.PathPrefix("/command").Handler(monkey(commandsHandler, "/api/command")).Methods("GET") api.PathPrefix("/command").Handler(monkey(commandsHandler(server.EnableExec), "/api/command")).Methods("GET")
api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET") api.PathPrefix("/search").Handler(monkey(searchHandler, "/api/search")).Methods("GET")
public := api.PathPrefix("/public").Subrouter() public := api.PathPrefix("/public").Subrouter()

View File

@ -41,6 +41,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, box *
"Theme": d.settings.Branding.Theme, "Theme": d.settings.Branding.Theme,
"EnableThumbs": d.server.EnableThumbnails, "EnableThumbs": d.server.EnableThumbnails,
"ResizePreview": d.server.ResizePreview, "ResizePreview": d.server.ResizePreview,
"EnableExec": d.server.EnableExec,
} }
if d.settings.Branding.Files != "" { if d.settings.Branding.Files != "" {

View File

@ -40,6 +40,7 @@ type Server struct {
Log string `json:"log"` Log string `json:"log"`
EnableThumbnails bool `json:"enableThumbnails"` EnableThumbnails bool `json:"enableThumbnails"`
ResizePreview bool `json:"resizePreview"` ResizePreview bool `json:"resizePreview"`
EnableExec bool `json:"enableExec"`
} }
// Clean cleans any variables that might need cleaning. // Clean cleans any variables that might need cleaning.