mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 00:52:16 -06:00
vfs construction ok
This commit is contained in:
parent
500daacca2
commit
250d0bdf57
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
|
@ -10,7 +10,7 @@
|
|||
"cwd": "${workspaceFolder}",
|
||||
"args": [
|
||||
"-j",
|
||||
"2",
|
||||
"0",
|
||||
"-nc",
|
||||
"4",
|
||||
"-nw",
|
||||
|
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"workbench.colorCustomizations": {
|
||||
// https://ocv.me/dot/bifrost.html
|
||||
"terminal.background": "#1e1e1e",
|
||||
"terminal.foreground": "#d2d2d2",
|
||||
"terminalCursor.background": "#93A1A1",
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
turn your phone or raspi into a portable file server with resumable uploads/downloads using IE6 or any other browser
|
||||
|
||||
* resumable uploads need `firefox 12+` / `chrome 6+` / `safari 6+` / `IE 10+`
|
||||
* server runs on everything with `py2.7` or `py3.3+`
|
||||
* server runs on anything with `py2.7` or `py3.3+`
|
||||
* *resumable* uploads need `firefox 12+` / `chrome 6+` / `safari 6+` / `IE 10+`
|
||||
* code standard: `black`
|
||||
|
||||
## status
|
||||
|
|
|
@ -8,6 +8,48 @@ import threading
|
|||
from .__init__ import *
|
||||
|
||||
|
||||
class VFS(object):
|
||||
"""single level in the virtual fs"""
|
||||
|
||||
def __init__(self, realpath, vpath, uread=[], uwrite=[]):
|
||||
self.realpath = realpath # absolute path on host filesystem
|
||||
self.vpath = vpath # absolute path in the virtual filesystem
|
||||
self.uread = uread # users who can read this
|
||||
self.uwrite = uwrite # users who can write this
|
||||
self.nodes = {} # child nodes
|
||||
|
||||
def add(self, src, dst):
|
||||
"""get existing, or add new path to the vfs"""
|
||||
assert not src.endswith("/")
|
||||
assert not dst.endswith("/")
|
||||
|
||||
if "/" in dst:
|
||||
# requires breadth-first population (permissions trickle down)
|
||||
name, dst = dst.split("/", 1)
|
||||
if name in self.nodes:
|
||||
# exists; do not manipulate permissions
|
||||
return self.nodes[name].add(src, dst)
|
||||
|
||||
n = VFS(
|
||||
"{}/{}".format(self.realpath, name),
|
||||
"{}/{}".format(self.vpath, name).lstrip("/"),
|
||||
self.uread,
|
||||
self.uwrite,
|
||||
)
|
||||
self.nodes[name] = n
|
||||
return n.add(src, dst)
|
||||
|
||||
if dst in self.nodes:
|
||||
# leaf exists; return as-is
|
||||
return self.nodes[dst]
|
||||
|
||||
# leaf does not exist; create and keep permissions blank
|
||||
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
||||
n = VFS(src, vp)
|
||||
self.nodes[dst] = n
|
||||
return n
|
||||
|
||||
|
||||
class AuthSrv(object):
|
||||
"""verifies users against given paths"""
|
||||
|
||||
|
@ -28,9 +70,16 @@ class AuthSrv(object):
|
|||
return {v: k for k, v in orig.items()}
|
||||
|
||||
def reload(self):
|
||||
"""
|
||||
construct a flat list of mountpoints and usernames
|
||||
first from the commandline arguments
|
||||
then supplementing with config files
|
||||
before finally building the VFS
|
||||
"""
|
||||
|
||||
user = {} # username:password
|
||||
uread = {} # username:readable-mp
|
||||
uwrite = {} # username:writable-mp
|
||||
mread = {} # mountpoint:[username]
|
||||
mwrite = {} # mountpoint:[username]
|
||||
mount = {} # dst:src (mountpoint:realpath)
|
||||
|
||||
if self.args.a:
|
||||
|
@ -43,16 +92,19 @@ class AuthSrv(object):
|
|||
# permset is [rwa]username
|
||||
for src, dst, perms in [x.split(":", 2) for x in self.args.v]:
|
||||
src = os.path.abspath(src)
|
||||
dst = ("/" + dst.strip("/") + "/").replace("//", "/")
|
||||
dst = dst.strip("/")
|
||||
mount[dst] = src
|
||||
mread[dst] = []
|
||||
mwrite[dst] = []
|
||||
|
||||
perms = perms.split(":")
|
||||
for (lvl, uname) in [[x[0], x[1:]] for x in perms]:
|
||||
if uname == "":
|
||||
uname = "*"
|
||||
if lvl in "ra":
|
||||
uread[uname] = dst
|
||||
mread[dst].append(uname)
|
||||
if lvl in "wa":
|
||||
uwrite[uname] = dst
|
||||
mwrite[dst].append(uname)
|
||||
|
||||
if self.args.c:
|
||||
for logfile in self.args.c:
|
||||
|
@ -61,21 +113,27 @@ class AuthSrv(object):
|
|||
# self.log(ln)
|
||||
pass
|
||||
|
||||
with self.mutex:
|
||||
self.user = user
|
||||
self.uread = uread
|
||||
self.uwrite = uwrite
|
||||
self.mount = mount
|
||||
self.iuser = self.invert(user)
|
||||
self.iuread = self.invert(uread)
|
||||
self.iuwrite = self.invert(uwrite)
|
||||
self.imount = self.invert(mount)
|
||||
# -h says our defaults are CWD at root and read/write for everyone
|
||||
vfs = VFS(os.path.abspath("."), "", ["*"], ["*"])
|
||||
|
||||
pprint.pprint(
|
||||
{
|
||||
"user": self.user,
|
||||
"uread": self.uread,
|
||||
"uwrite": self.uwrite,
|
||||
"mount": self.mount,
|
||||
}
|
||||
)
|
||||
maxdepth = 0
|
||||
for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
|
||||
depth = dst.count("/")
|
||||
assert maxdepth <= depth
|
||||
maxdepth = depth
|
||||
|
||||
if dst == "":
|
||||
# rootfs was mapped; fully replaces the default CWD vfs
|
||||
vfs = VFS(mount[dst], dst, mread[dst], mwrite[dst])
|
||||
continue
|
||||
|
||||
v = vfs.add(mount[dst], dst)
|
||||
v.uread = mread[dst]
|
||||
v.uwrite = mwrite[dst]
|
||||
|
||||
with self.mutex:
|
||||
self.vfs = vfs
|
||||
self.user = user
|
||||
self.iuser = self.invert(user)
|
||||
|
||||
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
||||
|
|
|
@ -1,38 +1,41 @@
|
|||
# any line with a : creates a user,
|
||||
# username:password
|
||||
# so you can create users anywhere really
|
||||
# but keeping them here is prob a good idea
|
||||
ed:123
|
||||
k:k
|
||||
# create users:
|
||||
# u username:password
|
||||
u ed:123
|
||||
u k:k
|
||||
|
||||
# leave a blank line before each volume
|
||||
# leave a blank line between volumes
|
||||
# (and also between users and volumes)
|
||||
|
||||
# this is a volume,
|
||||
# it shares the contents of /home/...
|
||||
# and appears at "/dj" in the web-ui
|
||||
# create a volume:
|
||||
# share "." (the current directory)
|
||||
# as "/" (the webroot) for the following users:
|
||||
# "r" grants read-access for anyone
|
||||
# "a ed" grants read-write to ed
|
||||
/home/ed/Music/dj
|
||||
/dj
|
||||
.
|
||||
/
|
||||
r
|
||||
a ed
|
||||
|
||||
# display /home/ed/ocv.me as the webroot
|
||||
# and allow user "k" to see/read it
|
||||
/home/ed/ocv.me
|
||||
/
|
||||
# custom permissions for the "priv" folder:
|
||||
# user "k" can see/read the contents
|
||||
# and "ed" gets read-write access
|
||||
./priv
|
||||
/priv
|
||||
r k
|
||||
a ed
|
||||
|
||||
# this shares the current directory as "/pwd"
|
||||
# but does nothing since there's no permissions
|
||||
.
|
||||
/pwd
|
||||
# share /home/ed/Music/ as /music and let anyone read it
|
||||
# (this will replace any folder called "music" in the webroot)
|
||||
/home/ed/Music
|
||||
/music
|
||||
r
|
||||
|
||||
# and a folder where anyone can upload
|
||||
# but nobody can see the contents
|
||||
/home/ed/inc
|
||||
/incoming
|
||||
/dump
|
||||
w
|
||||
|
||||
# you can use relative paths too btw
|
||||
# but they're a pain for testing purpose so I didn't
|
||||
# this entire config file can be replaced with these arguments:
|
||||
# -u ed:123 -u k:k -v .::r:aed -v priv:priv:rk:aed -v /home/ed/Music:music:r -v /home/ed/inc:dump:w
|
||||
# but note that the config file always wins in case of conflicts
|
||||
|
|
112
tests/test_vfs.py
Normal file
112
tests/test_vfs.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import json
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from argparse import Namespace
|
||||
from copyparty.authsrv import *
|
||||
|
||||
|
||||
class TestVFS(unittest.TestCase):
|
||||
def dump(self, vfs):
|
||||
print(json.dumps(vfs, indent=4, sort_keys=True, default=lambda o: o.__dict__))
|
||||
|
||||
def test(self):
|
||||
td = "/dev/shm/vfs"
|
||||
try:
|
||||
shutil.rmtree(td)
|
||||
except:
|
||||
pass
|
||||
|
||||
os.mkdir(td)
|
||||
os.chdir(td)
|
||||
|
||||
for a in "abc":
|
||||
for b in "abc":
|
||||
for c in "abc":
|
||||
folder = "{0}/{0}{1}/{0}{1}{2}".format(a, b, c)
|
||||
os.makedirs(folder)
|
||||
for d in "abc":
|
||||
fn = "{}/{}{}{}{}".format(folder, a, b, c, d)
|
||||
with open(fn, "w") as f:
|
||||
f.write(fn)
|
||||
|
||||
# defaults
|
||||
vfs = AuthSrv(Namespace(c=None, a=[], v=[]), None).vfs
|
||||
self.assertEqual(vfs.nodes, {})
|
||||
self.assertEqual(vfs.vpath, "")
|
||||
self.assertEqual(vfs.realpath, td)
|
||||
self.assertEqual(vfs.uread, ["*"])
|
||||
self.assertEqual(vfs.uwrite, ["*"])
|
||||
|
||||
# single read-only rootfs (relative path)
|
||||
vfs = AuthSrv(Namespace(c=None, a=[], v=["a/ab/::r"]), None).vfs
|
||||
self.assertEqual(vfs.nodes, {})
|
||||
self.assertEqual(vfs.vpath, "")
|
||||
self.assertEqual(vfs.realpath, td + "/a/ab")
|
||||
self.assertEqual(vfs.uread, ["*"])
|
||||
self.assertEqual(vfs.uwrite, [])
|
||||
|
||||
# single read-only rootfs (absolute path)
|
||||
vfs = AuthSrv(Namespace(c=None, a=[], v=[td + "//a/ac/../aa//::r"]), None).vfs
|
||||
self.assertEqual(vfs.nodes, {})
|
||||
self.assertEqual(vfs.vpath, "")
|
||||
self.assertEqual(vfs.realpath, td + "/a/aa")
|
||||
self.assertEqual(vfs.uread, ["*"])
|
||||
self.assertEqual(vfs.uwrite, [])
|
||||
|
||||
# read-only rootfs with write-only subdirectory
|
||||
vfs = AuthSrv(
|
||||
Namespace(c=None, a=[], v=[".::r", "a/ac/acb:a/ac/acb:w"]), None
|
||||
).vfs
|
||||
self.assertEqual(len(vfs.nodes), 1)
|
||||
self.assertEqual(vfs.vpath, "")
|
||||
self.assertEqual(vfs.realpath, td)
|
||||
self.assertEqual(vfs.uread, ["*"])
|
||||
self.assertEqual(vfs.uwrite, [])
|
||||
n = vfs.nodes["a"]
|
||||
self.assertEqual(len(vfs.nodes), 1)
|
||||
self.assertEqual(n.vpath, "a")
|
||||
self.assertEqual(n.realpath, td + "/a")
|
||||
self.assertEqual(n.uread, ["*"])
|
||||
self.assertEqual(n.uwrite, [])
|
||||
n = n.nodes["ac"]
|
||||
self.assertEqual(len(vfs.nodes), 1)
|
||||
self.assertEqual(n.vpath, "a/ac")
|
||||
self.assertEqual(n.realpath, td + "/a/ac")
|
||||
self.assertEqual(n.uread, ["*"])
|
||||
self.assertEqual(n.uwrite, [])
|
||||
n = n.nodes["acb"]
|
||||
self.assertEqual(n.nodes, {})
|
||||
self.assertEqual(n.vpath, "a/ac/acb")
|
||||
self.assertEqual(n.realpath, td + "/a/ac/acb")
|
||||
self.assertEqual(n.uread, [])
|
||||
self.assertEqual(n.uwrite, ["*"])
|
||||
|
||||
# breadth-first construction
|
||||
vfs = AuthSrv(
|
||||
Namespace(
|
||||
c=None,
|
||||
a=[],
|
||||
v=[
|
||||
"a/ac/acb:a/ac/acb:w",
|
||||
"a:a:w",
|
||||
".::r",
|
||||
"abacdfasdq:abacdfasdq:w",
|
||||
"a/ac:a/ac:w",
|
||||
],
|
||||
),
|
||||
None,
|
||||
).vfs
|
||||
|
||||
# shadowing
|
||||
# crossreferences
|
||||
# loops
|
||||
# listdir mapping
|
||||
# access reduction
|
||||
|
||||
shutil.rmtree(td)
|
Loading…
Reference in a new issue