Merge branch 'master' into share_subdirectory
This commit is contained in:
commit
0c50a39ba2
@ -9,6 +9,25 @@ import (
|
|||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MoveFile moves file from src to dst.
|
||||||
|
// By default the rename filesystem system call is used. If src and dst point to different volumes
|
||||||
|
// the file copy is used as a fallback
|
||||||
|
func MoveFile(fs afero.Fs, src, dst string) error {
|
||||||
|
if fs.Rename(src, dst) == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// fallback
|
||||||
|
err := CopyFile(fs, src, dst)
|
||||||
|
if err != nil {
|
||||||
|
_ = fs.Remove(dst)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fs.Remove(src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CopyFile copies a file from source to dest and returns
|
// CopyFile copies a file from source to dest and returns
|
||||||
// an error if any.
|
// an error if any.
|
||||||
func CopyFile(fs afero.Fs, source, dest string) error {
|
func CopyFile(fs afero.Fs, source, dest string) error {
|
||||||
@ -39,14 +58,14 @@ func CopyFile(fs afero.Fs, source, dest string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the mode if the user can't
|
// Copy the mode
|
||||||
// open the file.
|
|
||||||
info, err := fs.Stat(source)
|
info, err := fs.Stat(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fs.Chmod(dest, info.Mode())
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return err
|
err = fs.Chmod(dest, info.Mode())
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -121,7 +121,7 @@ function moveCopy (items, copy = false, overwrite = false, rename = false) {
|
|||||||
let promises = []
|
let promises = []
|
||||||
|
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
const from = removePrefix(item.from)
|
const from = item.from
|
||||||
const to = encodeURIComponent(removePrefix(item.to))
|
const to = encodeURIComponent(removePrefix(item.to))
|
||||||
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
|
const url = `${from}?action=${copy ? 'copy' : 'rename'}&destination=${to}&override=${overwrite}&rename=${rename}`
|
||||||
promises.push(resourceAction(url, 'PATCH'))
|
promises.push(resourceAction(url, 'PATCH'))
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
import { fetchURL, fetchJSON, removePrefix } from './utils'
|
import { fetchURL, fetchJSON, removePrefix } from './utils'
|
||||||
|
|
||||||
|
export async function list() {
|
||||||
|
return fetchJSON('/api/shares')
|
||||||
|
}
|
||||||
|
|
||||||
export async function getHash(hash) {
|
export async function getHash(hash) {
|
||||||
return fetchJSON(`/api/public/share/${hash}`)
|
return fetchJSON(`/api/public/share/${hash}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,7 +206,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.singleClick && !event.ctrlKey && !this.$store.state.multiple) this.resetSelected()
|
if (!this.singleClick && !event.ctrlKey && !event.metaKey && !this.$store.state.multiple) this.resetSelected()
|
||||||
this.addSelected(this.index)
|
this.addSelected(this.index)
|
||||||
},
|
},
|
||||||
dblclick: function () {
|
dblclick: function () {
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "تنفيذ الأوامر",
|
"allowCommands": "تنفيذ الأوامر",
|
||||||
|
|||||||
@ -132,7 +132,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Execute commands",
|
"allowCommands": "Execute commands",
|
||||||
@ -157,6 +157,9 @@
|
|||||||
"permissions": "Permissions",
|
"permissions": "Permissions",
|
||||||
"permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n",
|
"permissionsHelp": "You can set the user to be an administrator or choose the permissions individually. If you select \"Administrator\", all of the other options will be automatically checked. The management of users remains a privilege of an administrator.\n",
|
||||||
"profileSettings": "Profile Settings",
|
"profileSettings": "Profile Settings",
|
||||||
|
"shareManagement": "Share Management",
|
||||||
|
"path": "Path",
|
||||||
|
"shareDuration": "Share Duration",
|
||||||
"ruleExample1": "prevents the access to any dot file (such as .git, .gitignore) in every folder.\n",
|
"ruleExample1": "prevents the access to any dot file (such as .git, .gitignore) in every folder.\n",
|
||||||
"ruleExample2": "blocks the access to the file named Caddyfile on the root of the scope.",
|
"ruleExample2": "blocks the access to the file named Caddyfile on the root of the scope.",
|
||||||
"rules": "Rules",
|
"rules": "Rules",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrateur",
|
"administrator": "Administrateur",
|
||||||
"allowCommands": "Exécuter des commandes",
|
"allowCommands": "Exécuter des commandes",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Amministratore",
|
"administrator": "Amministratore",
|
||||||
"allowCommands": "Esegui comandi",
|
"allowCommands": "Esegui comandi",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "管理者",
|
"admin": "管理者",
|
||||||
"administrator": "管理者",
|
"administrator": "管理者",
|
||||||
"allowCommands": "コマンドの実行",
|
"allowCommands": "コマンドの実行",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrator",
|
"administrator": "Administrator",
|
||||||
"allowCommands": "Wykonaj polecenie",
|
"allowCommands": "Wykonaj polecenie",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentação",
|
"documentation": "documentação",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"administrator": "Administrador",
|
"administrator": "Administrador",
|
||||||
"allowCommands": "Executar comandos",
|
"allowCommands": "Executar comandos",
|
||||||
|
|||||||
@ -124,7 +124,7 @@
|
|||||||
"documentation": "documentation",
|
"documentation": "documentation",
|
||||||
"branding": "Branding",
|
"branding": "Branding",
|
||||||
"disableExternalLinks": "Disable external links (except documentation)",
|
"disableExternalLinks": "Disable external links (except documentation)",
|
||||||
"brandingHelp": "You can costumize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
"brandingHelp": "You can customize how your File Browser instance looks and feels by changing its name, replacing the logo, adding custom styles and even disable external links to GitHub.\nFor more information about custom branding, please check out the {0}.",
|
||||||
"admin": "Админ",
|
"admin": "Админ",
|
||||||
"administrator": "Администратор",
|
"administrator": "Администратор",
|
||||||
"allowCommands": "Запуск команд",
|
"allowCommands": "Запуск команд",
|
||||||
|
|||||||
@ -156,6 +156,9 @@
|
|||||||
"permissions": "权限",
|
"permissions": "权限",
|
||||||
"permissionsHelp": "您可以将该用户设置为管理员,也可以单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动勾上,同时该用户可以管理其他用户。",
|
"permissionsHelp": "您可以将该用户设置为管理员,也可以单独选择各项权限。如果选择了“管理员”,则其他的选项会被自动勾上,同时该用户可以管理其他用户。",
|
||||||
"profileSettings": "个人设置",
|
"profileSettings": "个人设置",
|
||||||
|
"shareManagement": "分享管理",
|
||||||
|
"path": "路径",
|
||||||
|
"shareDuration": "分享期限",
|
||||||
"ruleExample1": "阻止用户访问所有文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore)。",
|
"ruleExample1": "阻止用户访问所有文件夹下任何以 . 开头的文件(隐藏文件, 例如: .git, .gitignore)。",
|
||||||
"ruleExample2": "阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。",
|
"ruleExample2": "阻止用户访问其目录范围的根目录下名为 Caddyfile 的文件。",
|
||||||
"rules": "规则",
|
"rules": "规则",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import User from '@/views/settings/User'
|
|||||||
import Settings from '@/views/Settings'
|
import Settings from '@/views/Settings'
|
||||||
import GlobalSettings from '@/views/settings/Global'
|
import GlobalSettings from '@/views/settings/Global'
|
||||||
import ProfileSettings from '@/views/settings/Profile'
|
import ProfileSettings from '@/views/settings/Profile'
|
||||||
|
import Shares from '@/views/settings/Shares'
|
||||||
import Error403 from '@/views/errors/403'
|
import Error403 from '@/views/errors/403'
|
||||||
import Error404 from '@/views/errors/404'
|
import Error404 from '@/views/errors/404'
|
||||||
import Error500 from '@/views/errors/500'
|
import Error500 from '@/views/errors/500'
|
||||||
@ -67,6 +68,11 @@ const router = new Router({
|
|||||||
name: 'Profile Settings',
|
name: 'Profile Settings',
|
||||||
component: ProfileSettings
|
component: ProfileSettings
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/shares',
|
||||||
|
name: 'Shares',
|
||||||
|
component: Shares
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/settings/global',
|
path: '/settings/global',
|
||||||
name: 'Global Settings',
|
name: 'Global Settings',
|
||||||
|
|||||||
@ -41,8 +41,10 @@ export default {
|
|||||||
mounted () {
|
mounted () {
|
||||||
if (!recaptcha) return
|
if (!recaptcha) return
|
||||||
|
|
||||||
window.grecaptcha.render('recaptcha', {
|
window.grecaptcha.ready(function () {
|
||||||
sitekey: recaptchaKey
|
window.grecaptcha.render('recaptcha', {
|
||||||
|
sitekey: recaptchaKey
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<ul id="nav" v-if="user.perm.admin">
|
<ul id="nav">
|
||||||
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
|
<li :class="{ active: $route.path === '/settings/profile' }"><router-link to="/settings/profile">{{ $t('settings.profileSettings') }}</router-link></li>
|
||||||
<li :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
|
<li :class="{ active: $route.path === '/settings/shares' }"><router-link to="/settings/shares">{{ $t('settings.shareManagement') }}</router-link></li>
|
||||||
<li :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
|
<li v-if="user.perm.admin" :class="{ active: $route.path === '/settings/global' }"><router-link to="/settings/global">{{ $t('settings.globalSettings') }}</router-link></li>
|
||||||
|
<li v-if="user.perm.admin" :class="{ active: $route.path === '/settings/users' }"><router-link to="/settings/users">{{ $t('settings.userManagement') }}</router-link></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
|
|||||||
98
frontend/src/views/settings/Shares.vue
Normal file
98
frontend/src/views/settings/Shares.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ $t('settings.shareManagement') }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content full">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>{{ $t('settings.path') }}</th>
|
||||||
|
<th>{{ $t('settings.shareDuration') }}</th>
|
||||||
|
<th v-if="user.perm.admin">{{ $t('settings.username') }}</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-for="link in links" :key="link.hash">
|
||||||
|
<td><a :href="buildLink(link.hash)" target="_blank">{{ link.path }}</a></td>
|
||||||
|
<td>
|
||||||
|
<template v-if="link.expire !== 0">{{ humanTime(link.expire) }}</template>
|
||||||
|
<template v-else>{{ $t('permanent') }}</template>
|
||||||
|
</td>
|
||||||
|
<td v-if="user.perm.admin">{{ link.username }}</td>
|
||||||
|
<td class="small">
|
||||||
|
<button class="action"
|
||||||
|
@click="deleteLink($event, link)"
|
||||||
|
:aria-label="$t('buttons.delete')"
|
||||||
|
:title="$t('buttons.delete')"><i class="material-icons">delete</i></button>
|
||||||
|
</td>
|
||||||
|
<td class="small">
|
||||||
|
<button class="action copy-clipboard"
|
||||||
|
:data-clipboard-text="buildLink(link.hash)"
|
||||||
|
:aria-label="$t('buttons.copyToClipboard')"
|
||||||
|
:title="$t('buttons.copyToClipboard')"><i class="material-icons">content_paste</i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { share as api, users } from '@/api'
|
||||||
|
import moment from 'moment'
|
||||||
|
import {baseURL} from "@/utils/constants"
|
||||||
|
import Clipboard from 'clipboard'
|
||||||
|
import {mapState} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'shares',
|
||||||
|
computed: mapState([ 'user' ]),
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
links: [],
|
||||||
|
clip: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
try {
|
||||||
|
let links = await api.list()
|
||||||
|
if (this.user.perm.admin) {
|
||||||
|
let userMap = new Map()
|
||||||
|
for (let user of await users.getAll()) userMap.set(user.id, user.username)
|
||||||
|
for (let link of links) link.username = userMap.has(link.userID) ? userMap.get(link.userID) : ''
|
||||||
|
}
|
||||||
|
this.links = links
|
||||||
|
} catch (e) {
|
||||||
|
this.$showError(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.clip = new Clipboard('.copy-clipboard')
|
||||||
|
this.clip.on('success', () => {
|
||||||
|
this.$showSuccess(this.$t('success.linkCopied'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.clip.destroy()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteLink: async function (event, link) {
|
||||||
|
event.preventDefault()
|
||||||
|
try {
|
||||||
|
await api.remove(link.hash)
|
||||||
|
this.links = this.links.filter(item => item.hash !== link.hash)
|
||||||
|
} catch (e) {
|
||||||
|
this.$showError(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
humanTime (time) {
|
||||||
|
return moment(time * 1000).fromNow()
|
||||||
|
},
|
||||||
|
buildLink (hash) {
|
||||||
|
return `${window.location.origin}${baseURL}/share/${hash}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -51,6 +51,7 @@ func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage,
|
|||||||
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
|
api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("PUT")
|
||||||
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")
|
api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH")
|
||||||
|
|
||||||
|
api.Path("/shares").Handler(monkey(shareListHandler, "/api/shares")).Methods("GET")
|
||||||
api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")
|
api.PathPrefix("/share").Handler(monkey(shareGetsHandler, "/api/share")).Methods("GET")
|
||||||
api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
|
api.PathPrefix("/share").Handler(monkey(sharePostHandler, "/api/share")).Methods("POST")
|
||||||
api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE")
|
api.PathPrefix("/share").Handler(monkey(shareDeleteHandler, "/api/share")).Methods("DELETE")
|
||||||
|
|||||||
@ -200,7 +200,7 @@ var resourcePatchHandler = withUser(func(w http.ResponseWriter, r *http.Request,
|
|||||||
src = path.Clean("/" + src)
|
src = path.Clean("/" + src)
|
||||||
dst = path.Clean("/" + dst)
|
dst = path.Clean("/" + dst)
|
||||||
|
|
||||||
return d.user.Fs.Rename(src, dst)
|
return fileutils.MoveFile(d.user.Fs, src, dst)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
return fmt.Errorf("unsupported action %s: %w", action, errors.ErrInvalidRequestParams)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -23,6 +24,34 @@ func withPermShare(fn handleFunc) handleFunc {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shareListHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
|
var (
|
||||||
|
s []*share.Link
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if d.user.Perm.Admin {
|
||||||
|
s, err = d.store.Share.All()
|
||||||
|
} else {
|
||||||
|
s, err = d.store.Share.FindByUserID(d.user.ID)
|
||||||
|
}
|
||||||
|
if err == errors.ErrNotExist {
|
||||||
|
return renderJSON(w, r, []*share.Link{})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return http.StatusInternalServerError, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(s, func(i, j int) bool {
|
||||||
|
if s[i].UserID != s[j].UserID {
|
||||||
|
return s[i].UserID < s[j].UserID
|
||||||
|
}
|
||||||
|
return s[i].Expire < s[j].Expire
|
||||||
|
})
|
||||||
|
|
||||||
|
return renderJSON(w, r, s)
|
||||||
|
})
|
||||||
|
|
||||||
var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
var shareGetsHandler = withPermShare(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
s, err := d.store.Share.Gets(r.URL.Path, d.user.ID)
|
s, err := d.store.Share.Gets(r.URL.Path, d.user.ID)
|
||||||
if err == errors.ErrNotExist {
|
if err == errors.ErrNotExist {
|
||||||
|
|||||||
@ -14,6 +14,10 @@ import (
|
|||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
NonModifiableFieldsForNonAdmin = []string{"Username", "Scope", "LockPassword", "Perm", "Commands", "Rules"}
|
||||||
|
)
|
||||||
|
|
||||||
type modifyUserRequest struct {
|
type modifyUserRequest struct {
|
||||||
modifyRequest
|
modifyRequest
|
||||||
Data *users.User `json:"data"`
|
Data *users.User `json:"data"`
|
||||||
@ -148,9 +152,9 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
|||||||
return http.StatusBadRequest, nil
|
return http.StatusBadRequest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(req.Which) == 1 && req.Which[0] == "all" {
|
if len(req.Which) == 0 || (len(req.Which) == 1 && req.Which[0] == "all") {
|
||||||
if !d.user.Perm.Admin {
|
if !d.user.Perm.Admin {
|
||||||
return http.StatusForbidden, err
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Data.Password != "" {
|
if req.Data.Password != "" {
|
||||||
@ -169,7 +173,10 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range req.Which {
|
for k, v := range req.Which {
|
||||||
if v == "password" {
|
v = strings.Title(v)
|
||||||
|
req.Which[k] = v
|
||||||
|
|
||||||
|
if v == "Password" {
|
||||||
if !d.user.Perm.Admin && d.user.LockPassword {
|
if !d.user.Perm.Admin && d.user.LockPassword {
|
||||||
return http.StatusForbidden, nil
|
return http.StatusForbidden, nil
|
||||||
}
|
}
|
||||||
@ -180,11 +187,11 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.user.Perm.Admin && (v == "scope" || v == "perm" || v == "username") {
|
for _, f := range NonModifiableFieldsForNonAdmin {
|
||||||
return http.StatusForbidden, nil
|
if !d.user.Perm.Admin && v == f {
|
||||||
|
return http.StatusForbidden, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Which[k] = strings.Title(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.store.Users.Update(req.Data, req.Which...)
|
err = d.store.Users.Update(req.Data, req.Which...)
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import (
|
|||||||
|
|
||||||
// StorageBackend is the interface to implement for a share storage.
|
// StorageBackend is the interface to implement for a share storage.
|
||||||
type StorageBackend interface {
|
type StorageBackend interface {
|
||||||
|
All() ([]*Link, error)
|
||||||
|
FindByUserID(id uint) ([]*Link, error)
|
||||||
GetByHash(hash string) (*Link, error)
|
GetByHash(hash string) (*Link, error)
|
||||||
GetPermanent(path string, id uint) (*Link, error)
|
GetPermanent(path string, id uint) (*Link, error)
|
||||||
Gets(path string, id uint) ([]*Link, error)
|
Gets(path string, id uint) ([]*Link, error)
|
||||||
@ -25,6 +27,46 @@ func NewStorage(back StorageBackend) *Storage {
|
|||||||
return &Storage{back: back}
|
return &Storage{back: back}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All wraps a StorageBackend.All.
|
||||||
|
func (s *Storage) All() ([]*Link, error) {
|
||||||
|
links, err := s.back.All()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, link := range links {
|
||||||
|
if link.Expire != 0 && link.Expire <= time.Now().Unix() {
|
||||||
|
if err := s.Delete(link.Hash); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
links = append(links[:i], links[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindByUserID wraps a StorageBackend.FindByUserID.
|
||||||
|
func (s *Storage) FindByUserID(id uint) ([]*Link, error) {
|
||||||
|
links, err := s.back.FindByUserID(id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, link := range links {
|
||||||
|
if link.Expire != 0 && link.Expire <= time.Now().Unix() {
|
||||||
|
if err := s.Delete(link.Hash); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
links = append(links[:i], links[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return links, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetByHash wraps a StorageBackend.GetByHash.
|
// GetByHash wraps a StorageBackend.GetByHash.
|
||||||
func (s *Storage) GetByHash(hash string) (*Link, error) {
|
func (s *Storage) GetByHash(hash string) (*Link, error) {
|
||||||
link, err := s.back.GetByHash(hash)
|
link, err := s.back.GetByHash(hash)
|
||||||
|
|||||||
@ -12,6 +12,26 @@ type shareBackend struct {
|
|||||||
db *storm.DB
|
db *storm.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s shareBackend) All() ([]*share.Link, error) {
|
||||||
|
var v []*share.Link
|
||||||
|
err := s.db.All(&v)
|
||||||
|
if err == storm.ErrNotFound {
|
||||||
|
return v, errors.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s shareBackend) FindByUserID(id uint) ([]*share.Link, error) {
|
||||||
|
var v []*share.Link
|
||||||
|
err := s.db.Select(q.Eq("UserID", id)).Find(&v)
|
||||||
|
if err == storm.ErrNotFound {
|
||||||
|
return v, errors.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s shareBackend) GetByHash(hash string) (*share.Link, error) {
|
func (s shareBackend) GetByHash(hash string) (*share.Link, error) {
|
||||||
var v share.Link
|
var v share.Link
|
||||||
err := s.db.One("Hash", hash, &v)
|
err := s.db.One("Hash", hash, &v)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user