Skip to content

Commit

Permalink
web - added user management
Browse files Browse the repository at this point in the history
  • Loading branch information
superstes committed Jan 5, 2022
1 parent 7a8f410 commit 8d62efa
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 2 deletions.
1 change: 1 addition & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Version: 1.0


- webUI improvements
- user management (_password change, user settings, add/delete users_)
- actors in dashboard elements => when was an action taken; when was it reversed
- using AJAX for dynamic data refreshment and workflow optimizations

Expand Down
1 change: 1 addition & 0 deletions code/web/base/ga/config/nav.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
'Service': '/system/service/', # status and restart of service(s)
'Logs': '/system/log/', # read various log files
'Scripts': '/system/script/', # upload or delete scripts
'Users': '/system/user/', # add, modify or delete users
'Update': '/system/update/',
'Backup': {
'Config': '/system/export/config/', # download db dump of config
Expand Down
3 changes: 2 additions & 1 deletion code/web/base/ga/config/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
GA_USER_GROUP = 'ga_user'
GA_READ_GROUP = 'ga_read'
GA_WRITE_GROUP = 'ga_write'
GA_ADMIN_GROUP = 'ga_admin'
# GA_ADMIN_GROUP = 'ga_admin'
GA_ADMIN_USER = 'admin'

# log settings
LOG_MAX_TRACEBACK_LENGTH = 5000
Expand Down
136 changes: 136 additions & 0 deletions code/web/base/ga/subviews/system/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from django.shortcuts import render
from ...utils.auth import method_user_passes_test
from django.contrib.auth.models import User, Group
from django.db.utils import IntegrityError

from ...user import authorized_to_read, authorized_to_write
from ..handlers import Pseudo404
from ...config import shared as config
from ...utils.helper import develop_log


TITLE = 'System users'


class UserMgmt:
GA_GROUPS = {
# config.GA_ADMIN_GROUP: {
# 'privs': 'ADMIN',
# 'pretty': 'ALL',
# },
config.GA_WRITE_GROUP: {
'privs': 'WRITE',
'pretty': 'READ+WRITE',
},
config.GA_READ_GROUP: {
'privs': 'READ',
'pretty': 'READ',
},
}

def __init__(self, request):
self.request = request

@method_user_passes_test(authorized_to_read, login_url=config.DENIED_URL)
def go(self):
if self.request.method == 'GET':
return self._list()

else:
action = self.request.POST['do']

if action == 'create':
return self._create()

elif action == 'delete':
return self._delete()

elif action == 'update':
return self._update()

else:
raise Pseudo404(ga={'request': self.request, 'msg': f"Got unsupported user action: '{action}'"})

@method_user_passes_test(authorized_to_read, login_url=config.DENIED_URL)
def _list(self, msg: str = None, msg_style: str = 'success'):
users = User.objects.all().exclude(username=config.GA_ADMIN_USER)

update_user = None

for user in users:
if 'do' in self.request.GET and self.request.GET['do'] == 'update' \
and 'name' in self.request.GET and self.request.GET['name'] == user.username:
update_user = user.username
break

return render(self.request, 'system/users.html', context={
'request': self.request, 'title': TITLE, 'users': users, 'GA_GROUPS': self.GA_GROUPS, 'action_msg': msg, 'action_msg_style': msg_style,
'update_user': update_user,
})

@method_user_passes_test(authorized_to_write, login_url=config.DENIED_URL)
def _create(self):
try:
user = User.objects.create_user(
username=self.request.POST['name'],
email=self.request.POST['email'],
password=self.request.POST['password'],
)

privs = self.request.POST['privileges']
user.groups.add(Group.objects.get(name=config.GA_USER_GROUP))

for grp, value in self.GA_GROUPS.items():
if privs.find(value['pretty']) != -1:
user.groups.add(Group.objects.get(name=grp))

return self._list(msg=f"User '{self.request.POST['name']}' successfully created!")

except IntegrityError:
return self._list(msg=f"User '{self.request.POST['name']}' already exists!", msg_style='danger')

@method_user_passes_test(authorized_to_write, login_url=config.DENIED_URL)
def _update(self):
user = User.objects.get(username=self.request.POST['current_name'])
user.username = self.request.POST['name']
user.email = self.request.POST['email']
privs = self.request.POST['privileges']
pwd = self.request.POST['password']

if pwd != ' ' and pwd != config.CENSOR_STRING:
user.set_password(password=pwd)

user.groups.add(Group.objects.get(name=config.GA_USER_GROUP))

try:
for grp, value in self.GA_GROUPS.items():
grp_obj = Group.objects.get(name=grp)
if privs.find(value['pretty']) != -1:
user.groups.add(grp_obj)

else:
user.groups.remove(grp_obj)

msg = f"User '{self.request.POST['name']}' successfully updated!"
msg_style = 'success'

except Exception as error:
develop_log(
request=self.request,
output=f"User '{self.request.POST['name']}' could not be added to a group! It could be that the group does not exist! Error: {error}",
level=3
)
msg = f"User '{self.request.POST['name']}' could not be added to all groups!"
msg_style = 'warning'

user.save()
return self._list(msg=msg, msg_style=msg_style)

@method_user_passes_test(authorized_to_write, login_url=config.DENIED_URL)
def _delete(self):
if len(User.objects.all()) > 2:
User.objects.get(username=self.request.POST['name']).delete()
return self._list(msg=f"User '{self.request.POST['name']}' successfully deleted!")

else:
return self._list(msg=f"It seems only this user exists => you cannot delete this one!", msg_style='warning')
152 changes: 152 additions & 0 deletions code/web/base/ga/templates/system/users.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{% extends "../body.html" %}
{% load util %}
{% block content %}
<br>
{% if action_msg is not None %}
<div class="alert alert-{{ action_msg_style }}">
<strong>{{ action_msg }}</strong>
</div>
<br>
{% endif %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>
Username
</th>
<th>
E-Mail
</th>
<th>
Privileges
</th>
<th>
Action
</th>
</tr>
{% for user in users %}
{% if user.username != update_user %}
<tr>
<td>
{{ user.username }}
</td>
<td>
{{ user.email }}
</td>
<td>
{% if user.groups|all_groups|intersects:GA_GROUPS.keys %}{% for grp, config in GA_GROUPS.items %}{% if grp in user.groups|all_groups %}{{ config.privs }} {% endif %}{% endfor %}{% else %}NONE{% endif %}
</td>
<td>
<form class="ga-form-inline" method="get" action="/system/user/">
<input type="hidden" name="do" value="update" />
<input type="hidden" name="name" value="{{ user.username }}" />
{% include "../btn/update.html" %}
</form>
<form class="ga-form-inline" method="post" action="/system/user/">
{% csrf_token %}
<input type="hidden" name="do" value="delete" />
<input type="hidden" name="name" value="{{ user.username }}" />
{% include "../btn/delete.html" %}
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="100%">
<form class="ga-form-inline" method="post" action="/system/user/">
{% csrf_token %}
<div class="row">
<div class="col">
<label>Username:
<input class="form-control" type="text" name="name" autocapitalize="none" autocomplete="username" maxlength="150" required value="{{ user.username }}">
</label>
</div>
<div class="col">
<label>E-Mail:
<input class="form-control" type="text" name="email" autocapitalize="none" autocomplete="username" maxlength="75" required value="{{ user.email }}">
</label>
</div>
<div class="col">
<label>Password:
<input class="form-control" type="password" name="password" autocomplete="current-password" value='{% censored %}' maxlength="128">
</label>
</div>
<div class="col">
<label>Privileges:
<select class="form-control" name="privileges" required>
{% for grp, value in GA_GROUPS.items reversed %}
{% if grp in user.groups|all_groups %}
<option value="{{ value.pretty }}" selected>{{ value.pretty }}</option>
{% else %}
<option value="{{ value.pretty }}">{{ value.pretty }}</option>
{% endif %}
{% endfor %}
{% if not user.groups|all_groups|intersects:GA_GROUPS.keys %}
<option selected>{% get_empty %}</option>
{% else %}
<option>NONE</option>
{% endif %}
</select>
</label>
</div>
<input type="hidden" name="do" value="update" />
<input type="hidden" name="current_name" value="{{ user.username }}" />
{% include "../btn/save.html" %}
</div>
</form>
</td>
</tr>
{% endif %}
{% if user_create %}
<tr>
<td colspan="100%">
<form class="ga-form-inline" method="post" action="/system/user/">
{% csrf_token %}
<div class="row">
<div class="col">
<label>Username:
<input class="form-control" type="text" name="name" autocapitalize="none" autocomplete="username" maxlength="150" required>
</label>
</div>
<div class="col">
<label>E-Mail:
<input class="form-control" type="text" name="email" autocapitalize="none" autocomplete="username" maxlength="75" required>
</label>
</div>
<div class="col">
<label>Password:
<input class="form-control" type="password" name="password" autocomplete="current-password" maxlength="128" required>
</label>
</div>
<div class="col">
<label>Privileges:
<select class="form-control" name="privileges" required>
<option selected>{% get_empty %}</option>
{% for group, value in GA_GROUPS.items %}
<option value="{{ group }}">{{ value.pretty }}</option>
{% endfor %}
</select>
</label>
</div>
<input type="hidden" name="do" value="update" />
<input type="hidden" name="current_name" value="{{ user.username }}" />
{% include "../btn/save.html" %}
</div>
</form>
</td>
</tr>
<tr>
<td>
<form class="ga-form-inline" method="get" action="/system/user/">
<label>Create user:
<input type="hidden" name="do" value="create" />
{% include "../btn/create.html" %}
</label>
</form>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</div>
{% endblock %}
16 changes: 16 additions & 0 deletions code/web/base/ga/templatetags/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,19 @@ def form_help(key: str) -> str:
@register.filter
def form_label(key: str) -> str:
return LABEL_DICT[key]


@register.filter
def intersects(l1: list, l2: list) -> bool:
intersect = [value for value in l1 if value in l2]
return True if len(intersect) > 0 else False


@register.filter
def all_groups(obj) -> list:
return [g.name for g in obj.all()]


@register.simple_tag
def censored() -> str:
return config.CENSOR_STRING
7 changes: 7 additions & 0 deletions code/web/base/ga/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from .subviews.system.export import export_view
from .subviews.system.config import system_config_view
from .subviews.system.update import update_view, update_status_view
from .subviews.system.users import UserMgmt
from .config.shared import LOGIN_URL


Expand All @@ -27,6 +28,9 @@ def view(request, **kwargs):

class GaView:
def start(self, request, a: str = None, b: str = None, c: str = None, d: str = None, e: str = None):
if request.method not in ['GET', 'POST']:
return handler404(f"Got unsupported method: '{request.method}'!")

try:
if a == 'denied':
return self.denied(request=request)
Expand Down Expand Up @@ -113,6 +117,9 @@ def system(request, typ: str, sub_type: str = None):
elif typ == 'config':
return logout_check(request=request, default=system_config_view(request=request))

elif typ == 'user':
return logout_check(request=request, default=UserMgmt(request=request).go())

elif typ == 'update':
if sub_type is not None and sub_type == 'status':
return logout_check(request=request, default=update_status_view(request=request))
Expand Down
1 change: 1 addition & 0 deletions setup/roles/web/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ ga_django_groups:
- 'ga_read'
- 'ga_user'
- 'ga_write'
- 'ga_admin'
2 changes: 1 addition & 1 deletion setup/roles/web/tasks/init_db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
- name: GA | Web | Init | Creating django admin
ansible.builtin.shell: "source {{ ga_web_path_venv }}/bin/activate &&
{{ ga_web_path_venv }}/bin/python3 /tmp/create_user.py {{ ga_django_superuser | quote }} {{ ga_sql_pwd_web | quote }} {{ ga_web_dns | quote }}
\"ga_user,ga_read,ga_write\" 1"
\"ga_user,ga_read,ga_write,ga_admin\" 1"
args:
executable: '/bin/bash'
chdir: "{{ ga_web_path }}"
Expand Down
11 changes: 11 additions & 0 deletions setup/roles/web/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@
recursive: yes
rsync_path: 'sudo rsync'

- name: GA | Web | Setting privileges
ansible.builtin.file:
path: "{{ item.path }}"
owner: "{{ ga_web_service_user }}"
group: "{{ ga_service_group }}"
recurse: true
mode: "{{ item.mode }}"
loop:
- { path: "{{ ga_web_path }}", mode: '0750' }
- { path: "{{ ga_web_path_static }}", mode: '0755' }

- name: GA | Web | Configuring django settings
ansible.builtin.lineinfile:
path: "{{ ga_web_path }}/{{ ga_web_django_project }}/config.py"
Expand Down

0 comments on commit 8d62efa

Please sign in to comment.