Add group management and owner/group dropdowns in permission editor
Backend: - _list_groups(): reads /etc/group, returns groups with gid >= 1000 plus a small whitelist (root, staff, sambashare, www-data) - GET/POST /api/groups: list and create groups (groupadd) - DELETE /api/groups/<name>: delete group (groupdel) - POST /api/groups/<name>/members: add user to group (gpasswd -a) - DELETE /api/groups/<name>/members/<user>: remove user (gpasswd -d) Frontend: - Users tab now has a Groups section: table with GID, member badges with × remove buttons, per-row "add member" select populated with users not already in the group, delete group button - loadUsers() caches result in _cachedUsers for groups table to use - openEditModal is now async; fetches /api/users + /api/groups before rendering so Owner and Group inputs have datalist autocomplete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
302a5cb023
commit
596365b805
5 files changed
+227
-11
No files matched your search
@@ -475,6 +475,83 @@ def set_permissions():
|
||||
return jsonify({'status': 'ok', 'stat': _path_stat(safe_path)})
|
||||
|
||||
|
||||
# ===== GROUPS =====
|
||||
|
||||
_GROUP_WHITELIST = {'root', 'staff', 'sambashare', 'www-data'}
|
||||
|
||||
|
||||
def _list_groups():
|
||||
groups = []
|
||||
with open('/etc/group', 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split(':')
|
||||
if len(parts) < 4:
|
||||
continue
|
||||
name, gid_str, members_str = parts[0], parts[2], parts[3]
|
||||
try:
|
||||
gid = int(gid_str)
|
||||
except ValueError:
|
||||
continue
|
||||
members = [m for m in members_str.split(',') if m]
|
||||
if gid >= 1000 or name in _GROUP_WHITELIST:
|
||||
groups.append({'name': name, 'gid': gid, 'members': members})
|
||||
return sorted(groups, key=lambda g: g['name'])
|
||||
|
||||
|
||||
@app.route('/api/groups', methods=['GET'])
|
||||
@require_auth
|
||||
def list_groups():
|
||||
return jsonify(_list_groups())
|
||||
|
||||
|
||||
@app.route('/api/groups', methods=['POST'])
|
||||
@require_auth
|
||||
def create_group():
|
||||
name = (request.json or {}).get('name', '')
|
||||
if not VALID_NAME.match(name):
|
||||
return jsonify({'error': 'invalid group name'}), 400
|
||||
r = subprocess.run(['groupadd', name], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return jsonify({'error': r.stderr.strip() or 'groupadd failed'}), 400
|
||||
return jsonify({'status': 'created'})
|
||||
|
||||
|
||||
@app.route('/api/groups/<name>', methods=['DELETE'])
|
||||
@require_auth
|
||||
def delete_group(name):
|
||||
if not VALID_NAME.match(name):
|
||||
return jsonify({'error': 'invalid group name'}), 400
|
||||
r = subprocess.run(['groupdel', name], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return jsonify({'error': r.stderr.strip() or 'groupdel failed'}), 500
|
||||
return jsonify({'status': 'deleted'})
|
||||
|
||||
|
||||
@app.route('/api/groups/<name>/members', methods=['POST'])
|
||||
@require_auth
|
||||
def add_group_member(name):
|
||||
if not VALID_NAME.match(name):
|
||||
return jsonify({'error': 'invalid group name'}), 400
|
||||
user = (request.json or {}).get('user', '')
|
||||
if not VALID_NAME.match(user):
|
||||
return jsonify({'error': 'invalid username'}), 400
|
||||
r = subprocess.run(['gpasswd', '-a', user, name], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return jsonify({'error': r.stderr.strip() or 'gpasswd failed'}), 500
|
||||
return jsonify({'status': 'added'})
|
||||
|
||||
|
||||
@app.route('/api/groups/<name>/members/<user>', methods=['DELETE'])
|
||||
@require_auth
|
||||
def remove_group_member(name, user):
|
||||
if not VALID_NAME.match(name) or not VALID_NAME.match(user):
|
||||
return jsonify({'error': 'invalid name'}), 400
|
||||
r = subprocess.run(['gpasswd', '-d', user, name], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
return jsonify({'error': r.stderr.strip() or 'gpasswd failed'}), 500
|
||||
return jsonify({'status': 'removed'})
|
||||
|
||||
|
||||
# ===== USERS =====
|
||||
|
||||
def _smb_users():
|
||||
|
||||
Reference in new issue
Block a user