feat: Benutzerfreigabe - Weiterteilen-Recht + Lesezugriff wird erzwungen
Neues Berechtigungs-Modell fuer Benutzerfreigaben: * FilePermission bekommt zwei neue Spalten: - can_reshare (bool): darf dieser Nutzer die Freigabe weiterverteilen? - granted_by (user_id): wer hat diese Freigabe erstellt? * set_permission / create_share_link erlauben jetzt auch Nicht-Owner, sofern sie can_reshare haben. Dabei gilt: - Lesend + reshare -> kann nur lesend weiterteilen - Schreibend + reshare -> kann lesend ODER schreibend weiterteilen - Admin kann nur der Eigentuemer vergeben - Jeder Re-Sharer kann wiederum can_reshare weitergeben * remove_permission: Owner kann alle Freigaben entfernen; Re-Sharer nur die von ihnen selbst erstellten. * get_permissions: Owner sieht alle; Re-Sharer nur selbst-erstellte. * list_files liefert my_permission + my_can_reshare pro Eintrag - Frontend kann Rename/Delete/Share-Buttons gezielt ein- und ausblenden statt blind alle anzuzeigen. Frontend: * Rename/Delete-Buttons nur fuer Write-Zugriff * Share-Button nur fuer Owner oder Re-Sharer * "darf weiterteilen" Checkbox neben Permission-Dropdown im Dialog * Dropdown-Optionen nach eigenem Level gefiltert (Re-Sharer sieht keine hoeheren Stufen als seine eigene) * Hinweis-Text "Du hast X - du kannst maximal X weiterteilen" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
035923834b
commit
9369c851a0
|
|
@ -33,6 +33,29 @@ def _share_recipients(file_obj):
|
||||||
return list(ids)
|
return list(ids)
|
||||||
|
|
||||||
|
|
||||||
|
def _effective_permission(file_obj, user):
|
||||||
|
"""Returns (permission_level, can_reshare) for the given user on this file,
|
||||||
|
walking up the folder tree. Owner gets ('admin', True). Returns
|
||||||
|
(None, False) if no access."""
|
||||||
|
if file_obj.owner_id == user.id:
|
||||||
|
return ('admin', True)
|
||||||
|
levels = {'read': 0, 'write': 1, 'admin': 2}
|
||||||
|
best_level = -1
|
||||||
|
best_perm = None
|
||||||
|
best_reshare = False
|
||||||
|
cur = file_obj
|
||||||
|
while cur is not None:
|
||||||
|
perm = FilePermission.query.filter_by(file_id=cur.id, user_id=user.id).first()
|
||||||
|
if perm:
|
||||||
|
lvl = levels.get(perm.permission, -1)
|
||||||
|
if lvl > best_level:
|
||||||
|
best_level = lvl
|
||||||
|
best_perm = perm.permission
|
||||||
|
best_reshare = perm.can_reshare
|
||||||
|
cur = cur.parent
|
||||||
|
return (best_perm, best_reshare)
|
||||||
|
|
||||||
|
|
||||||
def _user_upload_dir(user_id):
|
def _user_upload_dir(user_id):
|
||||||
base = Path(current_app.config['UPLOAD_PATH'])
|
base = Path(current_app.config['UPLOAD_PATH'])
|
||||||
user_dir = base / str(user_id)
|
user_dir = base / str(user_id)
|
||||||
|
|
@ -120,6 +143,9 @@ def list_files():
|
||||||
d = f.to_dict()
|
d = f.to_dict()
|
||||||
d['has_shares'] = ShareLink.query.filter_by(file_id=f.id).count() > 0
|
d['has_shares'] = ShareLink.query.filter_by(file_id=f.id).count() > 0
|
||||||
d['has_permissions'] = FilePermission.query.filter_by(file_id=f.id).count() > 0
|
d['has_permissions'] = FilePermission.query.filter_by(file_id=f.id).count() > 0
|
||||||
|
my_perm, my_reshare = _effective_permission(f, user)
|
||||||
|
d['my_permission'] = my_perm
|
||||||
|
d['my_can_reshare'] = bool(my_reshare)
|
||||||
lock = FileLock.get_lock(f.id)
|
lock = FileLock.get_lock(f.id)
|
||||||
if lock:
|
if lock:
|
||||||
d['locked'] = True
|
d['locked'] = True
|
||||||
|
|
@ -129,6 +155,9 @@ def list_files():
|
||||||
for f in shared:
|
for f in shared:
|
||||||
d = f.to_dict()
|
d = f.to_dict()
|
||||||
d['shared'] = True
|
d['shared'] = True
|
||||||
|
my_perm, my_reshare = _effective_permission(f, user)
|
||||||
|
d['my_permission'] = my_perm
|
||||||
|
d['my_can_reshare'] = bool(my_reshare)
|
||||||
result.append(d)
|
result.append(d)
|
||||||
|
|
||||||
# Build breadcrumb
|
# Build breadcrumb
|
||||||
|
|
@ -543,12 +572,21 @@ def empty_trash():
|
||||||
@token_required
|
@token_required
|
||||||
def get_permissions(file_id):
|
def get_permissions(file_id):
|
||||||
user = request.current_user
|
user = request.current_user
|
||||||
f, err = _get_file_or_403(file_id, user, 'admin')
|
f = db.session.get(File, file_id)
|
||||||
if err:
|
if not f:
|
||||||
if not (f := db.session.get(File, file_id)) or f.owner_id != user.id:
|
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||||
return jsonify({'error': 'Zugriff verweigert'}), 403
|
|
||||||
|
is_owner = (f.owner_id == user.id)
|
||||||
|
my_perm, my_reshare = _effective_permission(f, user)
|
||||||
|
if not is_owner and not my_reshare:
|
||||||
|
return jsonify({'error': 'Zugriff verweigert'}), 403
|
||||||
|
|
||||||
|
# Owners see everyone; re-sharers only see perms they granted themselves.
|
||||||
|
if is_owner:
|
||||||
|
perms = FilePermission.query.filter_by(file_id=file_id).all()
|
||||||
|
else:
|
||||||
|
perms = FilePermission.query.filter_by(file_id=file_id, granted_by=user.id).all()
|
||||||
|
|
||||||
perms = FilePermission.query.filter_by(file_id=file_id).all()
|
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
result = []
|
result = []
|
||||||
for p in perms:
|
for p in perms:
|
||||||
|
|
@ -558,6 +596,8 @@ def get_permissions(file_id):
|
||||||
'user_id': p.user_id,
|
'user_id': p.user_id,
|
||||||
'username': u.username if u else None,
|
'username': u.username if u else None,
|
||||||
'permission': p.permission,
|
'permission': p.permission,
|
||||||
|
'can_reshare': bool(p.can_reshare),
|
||||||
|
'granted_by': p.granted_by,
|
||||||
})
|
})
|
||||||
return jsonify(result), 200
|
return jsonify(result), 200
|
||||||
|
|
||||||
|
|
@ -567,29 +607,60 @@ def get_permissions(file_id):
|
||||||
def set_permission(file_id):
|
def set_permission(file_id):
|
||||||
user = request.current_user
|
user = request.current_user
|
||||||
f = db.session.get(File, file_id)
|
f = db.session.get(File, file_id)
|
||||||
if not f or f.owner_id != user.id:
|
if not f:
|
||||||
return jsonify({'error': 'Nur der Eigentuemer kann Berechtigungen setzen'}), 403
|
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||||
|
|
||||||
|
is_owner = (f.owner_id == user.id)
|
||||||
|
my_perm, my_reshare = _effective_permission(f, user)
|
||||||
|
if not is_owner and not my_reshare:
|
||||||
|
return jsonify({'error': 'Keine Berechtigung zum Weiterteilen'}), 403
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
target_user_id = data.get('user_id')
|
target_user_id = data.get('user_id')
|
||||||
permission = data.get('permission', 'read')
|
permission = data.get('permission', 'read')
|
||||||
|
can_reshare_req = bool(data.get('can_reshare', False))
|
||||||
|
|
||||||
if permission not in ('read', 'write', 'admin'):
|
if permission not in ('read', 'write', 'admin'):
|
||||||
return jsonify({'error': 'Ungueltige Berechtigung'}), 400
|
return jsonify({'error': 'Ungueltige Berechtigung'}), 400
|
||||||
|
|
||||||
|
# Re-sharers can't hand out more than they have themselves.
|
||||||
|
levels = {'read': 0, 'write': 1, 'admin': 2}
|
||||||
|
if not is_owner:
|
||||||
|
max_allowed = levels.get(my_perm, -1)
|
||||||
|
if levels.get(permission, -1) > max_allowed:
|
||||||
|
return jsonify({
|
||||||
|
'error': f'Du kannst hoechstens "{my_perm}" weiterverteilen'
|
||||||
|
}), 403
|
||||||
|
if permission == 'admin':
|
||||||
|
return jsonify({'error': 'Admin-Recht kann nur der Eigentuemer vergeben'}), 403
|
||||||
|
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
target = db.session.get(User, target_user_id)
|
target = db.session.get(User, target_user_id)
|
||||||
if not target:
|
if not target:
|
||||||
return jsonify({'error': 'Benutzer nicht gefunden'}), 404
|
return jsonify({'error': 'Benutzer nicht gefunden'}), 404
|
||||||
|
if target.id == f.owner_id:
|
||||||
|
return jsonify({'error': 'Eigentuemer hat bereits Vollzugriff'}), 400
|
||||||
|
|
||||||
existing = FilePermission.query.filter_by(
|
existing = FilePermission.query.filter_by(
|
||||||
file_id=file_id, user_id=target_user_id
|
file_id=file_id, user_id=target_user_id
|
||||||
).first()
|
).first()
|
||||||
is_new = not existing
|
is_new = not existing
|
||||||
if existing:
|
if existing:
|
||||||
|
# Re-sharers may only modify perms they themselves granted
|
||||||
|
if not is_owner and existing.granted_by != user.id:
|
||||||
|
return jsonify({'error': 'Diese Freigabe wurde von jemand anderem erstellt'}), 403
|
||||||
existing.permission = permission
|
existing.permission = permission
|
||||||
|
existing.can_reshare = can_reshare_req
|
||||||
|
if is_new or existing.granted_by is None:
|
||||||
|
existing.granted_by = user.id
|
||||||
else:
|
else:
|
||||||
perm = FilePermission(file_id=file_id, user_id=target_user_id, permission=permission)
|
perm = FilePermission(
|
||||||
|
file_id=file_id,
|
||||||
|
user_id=target_user_id,
|
||||||
|
permission=permission,
|
||||||
|
can_reshare=can_reshare_req,
|
||||||
|
granted_by=user.id,
|
||||||
|
)
|
||||||
db.session.add(perm)
|
db.session.add(perm)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
@ -610,13 +681,17 @@ def set_permission(file_id):
|
||||||
def remove_permission(file_id, perm_id):
|
def remove_permission(file_id, perm_id):
|
||||||
user = request.current_user
|
user = request.current_user
|
||||||
f = db.session.get(File, file_id)
|
f = db.session.get(File, file_id)
|
||||||
if not f or f.owner_id != user.id:
|
if not f:
|
||||||
return jsonify({'error': 'Nur der Eigentuemer kann Berechtigungen entfernen'}), 403
|
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||||
|
|
||||||
perm = db.session.get(FilePermission, perm_id)
|
perm = db.session.get(FilePermission, perm_id)
|
||||||
if not perm or perm.file_id != file_id:
|
if not perm or perm.file_id != file_id:
|
||||||
return jsonify({'error': 'Berechtigung nicht gefunden'}), 404
|
return jsonify({'error': 'Berechtigung nicht gefunden'}), 404
|
||||||
|
|
||||||
|
is_owner = (f.owner_id == user.id)
|
||||||
|
if not is_owner and perm.granted_by != user.id:
|
||||||
|
return jsonify({'error': 'Du kannst nur selbst erstellte Freigaben entfernen'}), 403
|
||||||
|
|
||||||
db.session.delete(perm)
|
db.session.delete(perm)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return jsonify({'message': 'Berechtigung entfernt'}), 200
|
return jsonify({'message': 'Berechtigung entfernt'}), 200
|
||||||
|
|
@ -628,9 +703,14 @@ def remove_permission(file_id, perm_id):
|
||||||
@token_required
|
@token_required
|
||||||
def create_share_link(file_id):
|
def create_share_link(file_id):
|
||||||
user = request.current_user
|
user = request.current_user
|
||||||
f, err = _get_file_or_403(file_id, user, 'read')
|
f = db.session.get(File, file_id)
|
||||||
if err:
|
if not f:
|
||||||
return err
|
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
||||||
|
|
||||||
|
is_owner = (f.owner_id == user.id)
|
||||||
|
my_perm, my_reshare = _effective_permission(f, user)
|
||||||
|
if not is_owner and not my_reshare:
|
||||||
|
return jsonify({'error': 'Keine Berechtigung zum Weiterteilen'}), 403
|
||||||
|
|
||||||
data = request.get_json() or {}
|
data = request.get_json() or {}
|
||||||
password = data.get('password')
|
password = data.get('password')
|
||||||
|
|
@ -641,6 +721,18 @@ def create_share_link(file_id):
|
||||||
if permission not in ('read', 'write', 'upload_only'):
|
if permission not in ('read', 'write', 'upload_only'):
|
||||||
return jsonify({'error': 'Berechtigung muss "read", "write" oder "upload_only" sein'}), 400
|
return jsonify({'error': 'Berechtigung muss "read", "write" oder "upload_only" sein'}), 400
|
||||||
|
|
||||||
|
# Re-sharers can only hand out what they have themselves.
|
||||||
|
if not is_owner:
|
||||||
|
levels = {'read': 0, 'write': 1}
|
||||||
|
max_allowed = levels.get(my_perm, -1)
|
||||||
|
requested = levels.get(permission, 99)
|
||||||
|
if requested > max_allowed:
|
||||||
|
return jsonify({
|
||||||
|
'error': f'Du hast selbst nur "{my_perm}" - kannst nicht schreibend weiterteilen'
|
||||||
|
}), 403
|
||||||
|
if permission == 'upload_only' and my_perm not in ('write', 'admin'):
|
||||||
|
return jsonify({'error': 'Upload-Links nur mit Schreibrecht moeglich'}), 403
|
||||||
|
|
||||||
token = secrets.token_urlsafe(32)
|
token = secrets.token_urlsafe(32)
|
||||||
password_hash = None
|
password_hash = None
|
||||||
if password:
|
if password:
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,11 @@ class FilePermission(db.Model):
|
||||||
file_id = db.Column(db.Integer, db.ForeignKey('files.id'), nullable=False, index=True)
|
file_id = db.Column(db.Integer, db.ForeignKey('files.id'), nullable=False, index=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, index=True)
|
||||||
permission = db.Column(db.String(20), nullable=False) # 'read', 'write', 'admin'
|
permission = db.Column(db.String(20), nullable=False) # 'read', 'write', 'admin'
|
||||||
|
can_reshare = db.Column(db.Boolean, default=False, nullable=False)
|
||||||
|
granted_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||||
|
|
||||||
user = db.relationship('User', backref='file_permissions')
|
user = db.relationship('User', foreign_keys=[user_id], backref='file_permissions')
|
||||||
|
grantor = db.relationship('User', foreign_keys=[granted_by])
|
||||||
|
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
db.UniqueConstraint('file_id', 'user_id', name='uq_file_user_permission'),
|
db.UniqueConstraint('file_id', 'user_id', name='uq_file_user_permission'),
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@
|
||||||
@click.stop="downloadFile(data)"
|
@click.stop="downloadFile(data)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
v-if="canShare(data)"
|
||||||
:icon="(data.has_shares || data.has_permissions) ? 'pi pi-users' : 'pi pi-share-alt'"
|
:icon="(data.has_shares || data.has_permissions) ? 'pi pi-users' : 'pi pi-share-alt'"
|
||||||
text rounded size="small"
|
text rounded size="small"
|
||||||
:severity="(data.has_shares || data.has_permissions) ? 'success' : undefined"
|
:severity="(data.has_shares || data.has_permissions) ? 'success' : undefined"
|
||||||
|
|
@ -116,12 +117,14 @@
|
||||||
@click.stop="unlockFile(data)"
|
@click.stop="unlockFile(data)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
v-if="canWrite(data)"
|
||||||
icon="pi pi-pencil"
|
icon="pi pi-pencil"
|
||||||
text rounded size="small"
|
text rounded size="small"
|
||||||
:disabled="data.locked && data.locked_by !== auth.user?.username"
|
:disabled="data.locked && data.locked_by !== auth.user?.username"
|
||||||
@click.stop="openRename(data)"
|
@click.stop="openRename(data)"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
v-if="canWrite(data)"
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
text rounded size="small"
|
text rounded size="small"
|
||||||
severity="danger"
|
severity="danger"
|
||||||
|
|
@ -167,9 +170,15 @@
|
||||||
<h5>Mit Benutzer teilen</h5>
|
<h5>Mit Benutzer teilen</h5>
|
||||||
<div class="user-share-row">
|
<div class="user-share-row">
|
||||||
<InputText v-model="shareUserQuery" placeholder="Benutzername suchen..." fluid @input="searchUsers" />
|
<InputText v-model="shareUserQuery" placeholder="Benutzername suchen..." fluid @input="searchUsers" />
|
||||||
<Select v-model="shareUserPermission" :options="userPermOptions" optionLabel="label" optionValue="value" />
|
<Select v-model="shareUserPermission" :options="availableUserPermOptions" optionLabel="label" optionValue="value" />
|
||||||
|
<label class="reshare-check">
|
||||||
|
<input type="checkbox" v-model="shareUserReshare" /> darf weiterteilen
|
||||||
|
</label>
|
||||||
<Button label="Teilen" size="small" @click="shareWithUser" :disabled="!selectedShareUser" />
|
<Button label="Teilen" size="small" @click="shareWithUser" :disabled="!selectedShareUser" />
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!isOwner(shareFile) && shareFile" class="share-hint">
|
||||||
|
Du hast {{ myPermLabel(shareFile) }} - du kannst maximal {{ myPermLabel(shareFile) }} weiterteilen.
|
||||||
|
</div>
|
||||||
<div v-if="userSearchResults.length" class="user-search-results">
|
<div v-if="userSearchResults.length" class="user-search-results">
|
||||||
<div v-for="u in userSearchResults" :key="u.id"
|
<div v-for="u in userSearchResults" :key="u.id"
|
||||||
class="user-result" :class="{ selected: selectedShareUser?.id === u.id }"
|
class="user-result" :class="{ selected: selectedShareUser?.id === u.id }"
|
||||||
|
|
@ -182,6 +191,7 @@
|
||||||
<i class="pi pi-user"></i>
|
<i class="pi pi-user"></i>
|
||||||
<span>{{ perm.username }}</span>
|
<span>{{ perm.username }}</span>
|
||||||
<Tag :value="permLabel(perm.permission)" size="small" />
|
<Tag :value="permLabel(perm.permission)" size="small" />
|
||||||
|
<Tag v-if="perm.can_reshare" value="darf weiterteilen" severity="info" size="small" />
|
||||||
<Button icon="pi pi-trash" text size="small" severity="danger" @click="removeUserShare(perm.id)" />
|
<Button icon="pi pi-trash" text size="small" severity="danger" @click="removeUserShare(perm.id)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -193,7 +203,7 @@
|
||||||
<div class="share-form">
|
<div class="share-form">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Berechtigung</label>
|
<label>Berechtigung</label>
|
||||||
<Select v-model="shareLinkPermission" :options="linkPermOptions" optionLabel="label" optionValue="value" fluid />
|
<Select v-model="shareLinkPermission" :options="availableLinkPermOptions" optionLabel="label" optionValue="value" fluid />
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Passwort (optional)</label>
|
<label>Passwort (optional)</label>
|
||||||
|
|
@ -241,7 +251,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useAuthStore } from '../stores/auth'
|
import { useAuthStore } from '../stores/auth'
|
||||||
import { useFilesStore } from '../stores/files'
|
import { useFilesStore } from '../stores/files'
|
||||||
|
|
@ -284,6 +294,7 @@ const filePermissions = ref([])
|
||||||
const shareUserQuery = ref('')
|
const shareUserQuery = ref('')
|
||||||
const selectedShareUser = ref(null)
|
const selectedShareUser = ref(null)
|
||||||
const shareUserPermission = ref('read')
|
const shareUserPermission = ref('read')
|
||||||
|
const shareUserReshare = ref(false)
|
||||||
const userSearchResults = ref([])
|
const userSearchResults = ref([])
|
||||||
const userPermOptions = [{ label: 'Lesen', value: 'read' }, { label: 'Schreiben', value: 'write' }, { label: 'Admin', value: 'admin' }]
|
const userPermOptions = [{ label: 'Lesen', value: 'read' }, { label: 'Schreiben', value: 'write' }, { label: 'Admin', value: 'admin' }]
|
||||||
const linkPermOptions = [
|
const linkPermOptions = [
|
||||||
|
|
@ -291,6 +302,12 @@ const linkPermOptions = [
|
||||||
{ label: 'Lesen + Hochladen (nur Ordner)', value: 'write' },
|
{ label: 'Lesen + Hochladen (nur Ordner)', value: 'write' },
|
||||||
{ label: 'Nur Upload (Ordner, kein Einblick)', value: 'upload_only' },
|
{ label: 'Nur Upload (Ordner, kein Einblick)', value: 'upload_only' },
|
||||||
]
|
]
|
||||||
|
const availableLinkPermOptions = computed(() => {
|
||||||
|
const f = shareFile.value
|
||||||
|
if (!f || isOwner(f)) return linkPermOptions
|
||||||
|
if (f.my_permission === 'read') return linkPermOptions.filter(o => o.value === 'read')
|
||||||
|
return linkPermOptions
|
||||||
|
})
|
||||||
const shareLinkPermission = ref('read')
|
const shareLinkPermission = ref('read')
|
||||||
const currentOrigin = window.location.origin
|
const currentOrigin = window.location.origin
|
||||||
const shareLoading = ref(false)
|
const shareLoading = ref(false)
|
||||||
|
|
@ -570,6 +587,37 @@ function permLabel(perm) {
|
||||||
return { read: 'Lesen', write: 'Schreiben', admin: 'Admin' }[perm] || perm
|
return { read: 'Lesen', write: 'Schreiben', admin: 'Admin' }[perm] || perm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isOwner(data) {
|
||||||
|
return data && data.owner_id === auth.user?.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function canWrite(data) {
|
||||||
|
if (!data) return false
|
||||||
|
if (isOwner(data)) return true
|
||||||
|
return data.my_permission === 'write' || data.my_permission === 'admin'
|
||||||
|
}
|
||||||
|
|
||||||
|
function canShare(data) {
|
||||||
|
if (!data) return false
|
||||||
|
if (isOwner(data)) return true
|
||||||
|
return !!data.my_can_reshare
|
||||||
|
}
|
||||||
|
|
||||||
|
function myPermLabel(data) {
|
||||||
|
if (!data || !data.my_permission) return ''
|
||||||
|
return permLabel(data.my_permission)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option list for the "Mit Benutzer teilen" dropdown - re-sharers can only
|
||||||
|
// hand out permissions up to their own level. Admin is owner-only.
|
||||||
|
const availableUserPermOptions = computed(() => {
|
||||||
|
const f = shareFile.value
|
||||||
|
const levels = { read: 0, write: 1, admin: 2 }
|
||||||
|
if (!f || isOwner(f)) return userPermOptions
|
||||||
|
const myLevel = levels[f.my_permission] ?? -1
|
||||||
|
return userPermOptions.filter(o => levels[o.value] <= myLevel && o.value !== 'admin')
|
||||||
|
})
|
||||||
|
|
||||||
async function openShare(data) {
|
async function openShare(data) {
|
||||||
shareFile.value = data
|
shareFile.value = data
|
||||||
sharePassword.value = ''
|
sharePassword.value = ''
|
||||||
|
|
@ -611,10 +659,12 @@ async function shareWithUser() {
|
||||||
await apiClient.post(`/files/${shareFile.value.id}/permissions`, {
|
await apiClient.post(`/files/${shareFile.value.id}/permissions`, {
|
||||||
user_id: selectedShareUser.value.id,
|
user_id: selectedShareUser.value.id,
|
||||||
permission: shareUserPermission.value,
|
permission: shareUserPermission.value,
|
||||||
|
can_reshare: shareUserReshare.value,
|
||||||
})
|
})
|
||||||
toast.add({ severity: 'success', summary: `Mit ${selectedShareUser.value.username} geteilt`, life: 3000 })
|
toast.add({ severity: 'success', summary: `Mit ${selectedShareUser.value.username} geteilt`, life: 3000 })
|
||||||
shareUserQuery.value = ''
|
shareUserQuery.value = ''
|
||||||
selectedShareUser.value = null
|
selectedShareUser.value = null
|
||||||
|
shareUserReshare.value = false
|
||||||
const res = await apiClient.get(`/files/${shareFile.value.id}/permissions`)
|
const res = await apiClient.get(`/files/${shareFile.value.id}/permissions`)
|
||||||
filePermissions.value = res.data
|
filePermissions.value = res.data
|
||||||
await filesStore.loadFiles(currentParentId())
|
await filesStore.loadFiles(currentParentId())
|
||||||
|
|
@ -811,7 +861,9 @@ onUnmounted(() => {
|
||||||
.share-section:last-child { border-bottom: none; }
|
.share-section:last-child { border-bottom: none; }
|
||||||
.share-section h5 { margin: 0 0 0.75rem; font-size: 0.9rem; }
|
.share-section h5 { margin: 0 0 0.75rem; font-size: 0.9rem; }
|
||||||
.share-form { }
|
.share-form { }
|
||||||
.user-share-row { display: flex; gap: 0.5rem; align-items: flex-start; }
|
.user-share-row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
|
||||||
|
.reshare-check { display: flex; align-items: center; gap: 0.25rem; font-size: 0.8rem; white-space: nowrap; }
|
||||||
|
.share-hint { font-size: 0.75rem; color: var(--p-surface-500); margin-top: 0.35rem; font-style: italic; }
|
||||||
.user-search-results { border: 1px solid var(--p-surface-200); border-radius: 6px; margin-top: 0.25rem; max-height: 150px; overflow-y: auto; }
|
.user-search-results { border: 1px solid var(--p-surface-200); border-radius: 6px; margin-top: 0.25rem; max-height: 150px; overflow-y: auto; }
|
||||||
.user-result { padding: 0.5rem 0.75rem; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; }
|
.user-result { padding: 0.5rem 0.75rem; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; }
|
||||||
.user-result:hover, .user-result.selected { background: var(--p-primary-50); }
|
.user-result:hover, .user-result.selected { background: var(--p-primary-50); }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue