Skeleton user manager added

This commit is contained in:
computergeek125 2020-12-24 17:55:15 -05:00
parent 7ddcdd4e0e
commit cf61c3c931
5 changed files with 547 additions and 52 deletions

View File

@ -34,28 +34,46 @@ class Users(BaseModel):
created = DateTimeField(default=datetime.datetime.now)
last_login = DateTimeField(default=datetime.datetime.now)
last_ip = CharField(default="")
username = CharField(default="")
username = CharField(default="", unique=True, index=True)
password = CharField(default="")
enabled = BooleanField(default=True)
api_token = CharField(default="")
allowed_servers = CharField(default="[]")
superuser = BooleanField(default=False)
api_token = CharField(default="", unique=True, index=True) # we may need to revisit this
class Meta:
table_name = "users"
class Roles(BaseModel):
role_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
role_name = CharField(default="", unique=True, index=True)
class Meta:
table_name = "roles"
class User_Roles(BaseModel):
user_id = ForeignKeyField(Users, backref='user_role')
role_id = ForeignKeyField(Roles, backref='user_role')
class Meta:
table_name = 'user_roles'
primary_key = CompositeKey('user_id', 'role_id')
class Audit_Log(BaseModel):
audit_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
user_name = CharField(default="")
user_id = IntegerField(default=0)
user_id = IntegerField(default=0, index=True)
source_ip = CharField(default='127.0.0.1')
server_id = IntegerField(default=None)
server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0
log_msg = TextField(default='')
class Host_Stats(BaseModel):
time = DateTimeField(default=datetime.datetime.now)
time = DateTimeField(default=datetime.datetime.now, index=True)
boot_time = CharField(default="")
cpu_usage = FloatField(default=0)
cpu_cores = IntegerField(default=0)
@ -73,8 +91,8 @@ class Host_Stats(BaseModel):
class Servers(BaseModel):
server_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_uuid = CharField(default="")
server_name = CharField(default="Server")
server_uuid = CharField(default="", index=True)
server_name = CharField(default="Server", index=True)
path = CharField(default="")
executable = CharField(default="")
log_path = CharField(default="")
@ -90,10 +108,28 @@ class Servers(BaseModel):
table_name = "servers"
class User_Servers(BaseModel):
user_id = ForeignKeyField(Users, backref='user_server')
server_id = ForeignKeyField(Servers, backref='user_server')
class Meta:
table_name = 'user_servers'
primary_key = CompositeKey('user_id', 'server_id')
class Role_Servers(BaseModel):
user_id = ForeignKeyField(Roles, backref='role_server')
server_id = ForeignKeyField(Servers, backref='role_server')
class Meta:
table_name = 'role_servers'
primary_key = CompositeKey('role_id', 'server_id')
class Server_Stats(BaseModel):
stats_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server')
server_id = ForeignKeyField(Servers, backref='server', index=True)
started = CharField(default="")
running = BooleanField(default=False)
cpu = FloatField(default=0)
@ -117,8 +153,8 @@ class Server_Stats(BaseModel):
class Commands(BaseModel):
command_id = AutoField()
created = DateTimeField(default=datetime.datetime.now)
server_id = ForeignKeyField(Servers, backref='server')
user = ForeignKeyField(Users, backref='user')
server_id = ForeignKeyField(Servers, backref='server', index=True)
user = ForeignKeyField(Users, backref='user', index=True)
source_ip = CharField(default='127.0.0.1')
command = CharField(default='')
executed = BooleanField(default=False)
@ -129,7 +165,7 @@ class Commands(BaseModel):
class Webhooks(BaseModel):
id = AutoField()
name = CharField(max_length=64, unique=True)
name = CharField(max_length=64, unique=True, index=True)
method = CharField(default="POST")
url = CharField(unique=True)
event = CharField(default="")
@ -143,7 +179,7 @@ class Backups(BaseModel):
directories = CharField()
storage_location = CharField()
max_backups = IntegerField()
server_id = IntegerField()
server_id = IntegerField(index=True)
class Meta:
table_name = 'backups'
@ -157,6 +193,8 @@ class db_builder:
database.create_tables([
Backups,
Users,
Roles,
User_Roles,
Host_Stats,
Webhooks,
Servers,
@ -173,16 +211,18 @@ class db_builder:
username = default_data.get("username", 'admin')
password = default_data.get("password", 'crafty')
api_token = helper.random_string_generator(32)
Users.insert({
Users.username: username.lower(),
Users.password: helper.encode_pass(password),
Users.api_token: api_token,
Users.enabled: True
}).execute()
#api_token = helper.random_string_generator(32)
#
#Users.insert({
# Users.username: username.lower(),
# Users.password: helper.encode_pass(password),
# Users.api_token: api_token,
# Users.enabled: True,
# Users.superuser: True
#}).execute()
db_shortcuts.add_user(username, password=password, superuser=True)
console.info("API token is {}".format(api_token))
#console.info("API token is {}".format(api_token))
@staticmethod
def is_fresh_install():
@ -196,7 +236,8 @@ class db_builder:
class db_shortcuts:
def return_rows(self, query):
@staticmethod
def return_rows(query):
rows = []
try:
@ -209,7 +250,8 @@ class db_shortcuts:
return rows
def get_server_data_by_id(self, server_id):
@staticmethod
def get_server_data_by_id(server_id):
try:
query = Servers.get_by_id(server_id)
except DoesNotExist:
@ -217,25 +259,29 @@ class db_shortcuts:
return model_to_dict(query)
def get_all_defined_servers(self):
@staticmethod
def get_all_defined_servers():
query = Servers.select()
return self.return_rows(query)
return db_helper.return_rows(query)
def get_all_servers_stats(self):
servers = self.get_all_defined_servers()
@staticmethod
def get_all_servers_stats():
servers = db_helper.get_all_defined_servers()
server_data = []
for s in servers:
latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1)
server_data.append({'server_data': s, "stats": self.return_rows(latest)})
server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)})
return server_data
def get_server_stats_by_id(self, server_id):
@staticmethod
def get_server_stats_by_id(server_id):
stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1)
return self.return_rows(stats)
return db_helper.return_rows(stats)
def server_id_exists(self, server_id):
if not self.get_server_data_by_id(server_id):
@staticmethod
def server_id_exists(server_id):
if not db_helper.get_server_data_by_id(server_id):
return False
return True
@ -244,24 +290,118 @@ class db_shortcuts:
query = Host_Stats.select().order_by(Host_Stats.id.desc()).get()
return model_to_dict(query)
def get_all_users(self):
@staticmethod
def new_api_token():
while True:
token = helper.random_string_generator(32)
test = list(Users.select(Users.user_id).where(Users.api_token == token))
if len(test) == 0:
return token
@staticmethod
def get_all_users():
query = Users.select()
return query
def get_unactioned_commands(self):
query = Commands.select().where(Commands.executed == 0)
return self.return_rows(query)
@staticmethod
def get_all_roles():
query = Roles.select()
return query
def get_server_friendly_name(self, server_id):
server_data = self.get_server_data_by_id(server_id)
@staticmethod
def get_userid_by_name(username):
try:
return (Users.get(Users.username == username)).user_id
except DoesNotExist:
return None
@staticmethod
def get_user(uid):
query = Users.select(Users, Roles).join(User_Roles, JOIN.LEFT_OUTER).join(Roles, JOIN.LEFT_OUTER).where(Users.user_id == uid)
query = [model_to_dict(r) for r in query]
if len(query) > 0:
user = query[0].copy()
return user
else:
return {}
@staticmethod
def update_user(user_id, user_data={}):
base_data = db_helper.get_user(user_id)
up_data = {}
for key in user_data:
if key == "user_id":
continue
elif key == "roles":
continue
elif key == "regen_api":
up_data['api_token'] = db_shortcuts.new_api_token()
elif key == "password":
up_data['password'] = helper.encode_pass(user_data['password'])
elif base_data[key] != user_data[key]:
up_data[key] = user_data[key]
Users.update(up_data).where(Users.user_id == user_id).execute()
@staticmethod
def add_user(username, password=None, api_token=None, enabled=True, superuser=False):
if password is not None:
pw_enc = helper.encode_pass(password)
else:
pw_enc = None
if api_token is None:
api_token = db_shortcuts.new_api_token()
else:
if type(api_token) is not str and len(api_token) != 32:
raise ValueError("API token must be a 32 character string")
user_id = Users.insert({
Users.username: username.lower(),
Users.password: pw_enc,
Users.api_token: api_token,
Users.enabled: enabled,
Users.superuser: superuser
}).execute()
return user_id
@staticmethod
def remove_user(user_id):
user = Users.get(Users.user_id == user_id)
return user.delete_instance()
@staticmethod
def user_id_exists(user_id):
if not db_shortcuts.get_user(user_id):
return False
return True
@staticmethod
def get_roleid_by_name(role_name):
return (Roles.get(Roles.role_name == role_name)).role_id
@staticmethod
def get_role(role_id):
query = model_to_dict(Roles.get(Roles.role_id == role_id))
Roles['roles_flat'] = []
Roles['allowed_servers_flat'] = []
return query
@staticmethod
def get_unactioned_commands():
query = Commands.select().where(Commands.executed == 0)
return db_helper.return_rows(query)
@staticmethod
def get_server_friendly_name(server_id):
server_data = db_helper.get_server_data_by_id(server_id)
friendly_name = "{}-{}".format(server_data.get('server_id', 0), server_data.get('server_name', None))
return friendly_name
def send_command(self, user_id, server_id, remote_ip, command):
@staticmethod
def send_command(user_id, server_id, remote_ip, command):
server_name = self.get_server_friendly_name(server_id)
server_name = db_helper.get_server_friendly_name(server_id)
self.add_to_audit_log(user_id, "Issued Command {} for Server: {}".format(command, server_name),
db_helper.add_to_audit_log(user_id, "Issued Command {} for Server: {}".format(command, server_name),
server_id, remote_ip)
Commands.insert({
@ -271,11 +411,13 @@ class db_shortcuts:
Commands.command: command
}).execute()
def get_actity_log(self):
@staticmethod
def get_actity_log():
q = Audit_Log.select()
return self.return_db_rows(q)
return db_helper.return_db_rows(q)
def return_db_rows(self, model):
@staticmethod
def return_db_rows(model):
data = [model_to_dict(row) for row in model]
return data

View File

@ -120,9 +120,57 @@ class PanelHandler(BaseHandler):
elif page == 'panel_config':
page_data['users'] = db_helper.get_all_users()
# print(page_data['users'])
template = "panel/panel_config.html"
elif page == "add_user":
page_data['new_user'] = True
page_data['user'] = {}
page_data['user']['username'] = ""
page_data['user']['user_id'] = -1
page_data['user']['enabled'] = True
page_data['user']['superuser'] = False
page_data['user']['roles'] = []
page_data['roles_all'] = db_helper.get_all_roles()
template = "panel/panel_edit_user.html"
elif page == "edit_user":
page_data['new_user'] = False
uid = self.get_argument('id', None)
page_data['user'] = db_helper.get_user(uid)
page_data['roles_all'] = db_helper.get_all_roles()
template = "panel/panel_edit_user.html"
elif page == "remove_user":
user_id = bleach.clean(self.get_argument('id', None))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
else:
# does this user id exist?
target_user = db_helper.get_user(user_id)
if not target_user:
self.redirect("/panel/error?error=Invalid User ID")
return False
elif target_user['superuser']:
self.redirect("/panel/error?error=Cannot remove a superuser")
return False
db_helper.remove_user(user_id)
db_helper.add_to_audit_log(exec_user['user_id'],
"Removed user {} (UID:{})".format(target_user['username'], user_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "activity_logs":
page_data['audit_logs'] = db_helper.get_actity_log()
@ -151,7 +199,13 @@ class PanelHandler(BaseHandler):
crash_detection = int(float(self.get_argument('crash_detection', '0')))
subpage = self.get_argument('subpage', None)
if server_id is None:
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user.superuser:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif server_id is None:
self.redirect("/panel/error?error=Invalid Server ID")
return False
else:
@ -178,11 +232,91 @@ class PanelHandler(BaseHandler):
controller.refresh_server_settings(server_id)
user_data = json.loads(self.get_secure_cookie("user_data"))
db_helper.add_to_audit_log(user_data['user_id'],
"Edited server {} named {}".format(server_id, server_name),
server_id,
self.get_remote_ip())
self.redirect("/panel/server_detail?id={}&subpage=config".format(server_id))
elif page == "edit_user":
user_id = bleach.clean(self.get_argument('id', None))
username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None))
enabled = int(float(bleach.clean(self.get_argument('enabled'), '0')))
regen_api = int(float(bleach.clean(self.get_argument('regen_api', '0'))))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
elif user_id is None:
self.redirect("/panel/error?error=Invalid User ID")
return False
else:
# does this user id exist?
if not db_helper.user_id_exists(user_id):
self.redirect("/panel/error?error=Invalid User ID")
return False
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
user_data = {
"username": username,
"password": password0,
"enabled": enabled,
"regen_api": regen_api,
"roles": []
}
db_helper.update_user(user_id, user_data=user_data)
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{})".format(username, user_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")
elif page == "add_user":
username = bleach.clean(self.get_argument('username', None))
password0 = bleach.clean(self.get_argument('password0', None))
password1 = bleach.clean(self.get_argument('password1', None))
enabled = int(float(bleach.clean(self.get_argument('enabled'), '0')))
user_data = json.loads(self.get_secure_cookie("user_data"))
exec_user = db_helper.get_user(user_data['user_id'])
if not exec_user['superuser']:
self.redirect("/panel/error?error=Unauthorized access: not superuser")
return False
elif username is None or username == "":
self.redirect("/panel/error?error=Invalid username")
return False
else:
# does this user id exist?
if db_helper.get_userid_by_name(username) is not None:
self.redirect("/panel/error?error=User exists")
return False
if password0 != password1:
self.redirect("/panel/error?error=Passwords must match")
return False
user_id = db_helper.add_user(username, password=password0, enabled=enabled)
db_helper.add_to_audit_log(exec_user['user_id'],
"Added user {} (UID:{})".format(username, user_id),
server_id=0,
source_ip=self.get_remote_ip())
db_helper.add_to_audit_log(exec_user['user_id'],
"Edited user {} (UID:{})".format(username, user_id),
server_id=0,
source_ip=self.get_remote_ip())
self.redirect("/panel/panel_config")

View File

@ -106,7 +106,7 @@ class PublicHandler(BaseHandler):
cookie_data = {
"username": user_data.username,
"user_id": user_data.user_id,
"account_type": user_data.allowed_servers,
"account_type": user_data.superuser,
}
self.set_secure_cookie('user_data', json.dumps(cookie_data))

View File

@ -66,8 +66,8 @@
</td>
<td>{{ user.api_token }}</td>
<td>{{ user.allowed_servers}}</td>
<td><a href="/panel/edit_user?id={{user.id}}"><i class="fas fa-pencil-alt"></i></a></td>
<td>{{ [] }}</td>
<td><a href="/panel/edit_user?id={{user.user_id}}"><i class="fas fa-pencil-alt"></i></a></td>
</tr>
{% end %}

View File

@ -0,0 +1,219 @@
{% extends ../base.html %}
{% block meta %}
<!-- <meta http-equiv="refresh" content="60">-->
{% end %}
{% block title %}Crafty Controller - Edit User{% end %}
{% block content %}
<div class="content-wrapper">
<!-- Page Title Header Starts-->
<div class="row page-title-header">
<div class="col-12">
<div class="page-header">
{% if data['new_user'] %}
<h4 class="page-title">
New User
<br />
<small>UID: N/A</small>
</h4>
{% else %}
<h4 class="page-title">
Edit User - {{ data['user']['user_id'] }}
<br />
<small>UID: {{ data['user']['user_id'] }}</small>
</h4>
{% end %}
</div>
</div>
</div>
<!-- Page Title Header Ends-->
<div class="row">
<div class="col-sm-12 grid-margin">
<div class="card">
<div class="card-body pt-0">
<ul class="nav nav-tabs col-md-12 tab-simple-styled " role="tablist">
<li class="nav-item">
<a class="nav-link active" href="/panel/edit_user?id={{ data['user']['username'] }}&subpage=config" role="tab" aria-selected="true">
<i class="fas fa-cogs"></i>Config</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/panel/edit_user?id={{ data['user']['username'] }}&subpage=other" role="tab" aria-selected="false">
<i class="fas fa-folder-tree"></i>Other</a>
</li>
</ul>
<div class="row">
<div class="col-md-6 col-sm-12">
{% if data['new_user'] %}
<form class="forms-sample" method="post" action="/panel/add_user">
{% else %}
<form class="forms-sample" method="post" action="/panel/edit_user">
{% end %}
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['user']['user_id'] }}">
<input type="hidden" name="subpage" value="config">
<div class="form-group">
<label for="username">User Name <small class="text-muted ml-1"> - What you wish to call this user</small> </label>
<input type="text" class="form-control" name="username" id="username" value="{{ data['user']['username'] }}" placeholder="User Name" >
</div>
<div class="form-group">
<label for="password0">Password <small class="text-muted ml-1"></small> </label>
<input type="password" class="form-control" name="password0" id="password0" value="" placeholder="Password" >
</div>
<div class="form-group">
<label for="password1">Repeat Password <small class="text-muted ml-1"></small> </label>
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="Repeat Password" >
</div>
<div class="form-group">
<label for="auto_start_delay">Server Autostart Delay <small class="text-muted ml-1"> - Delay before auto starting (if enabled below)</small> </label>
<input type="number" class="form-control" name="auto_start_delay" id="auto_start_delay" value="{{ 10 }}" step="1" max="999" min="10" >
</div>
<div class="form-group">
<label for="role_membership">Roles <small class="text-muted ml-1"> - the roles this user is a member of</small> </label>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="rounded">
<th>Role Name</th>
<th>Member?</th>
</tr>
</thead>
<tbody>
{% for role in data['roles_all'] %}
<tr>
<td>{{ role.role_name }}</td>
<td>
{% if role.role_name in data['user']['roles'] %}
<span class="text-success">
<i class="fas fa-check-square"></i> Yes
</span>
{% else %}
<span class="text-danger">
<i class="far fa-times-square"></i> No
</span>
{% end %}
</td>
</tr>
{% end %}
</tbody>
</table>
</div>
</div>
<div class="form-check-flat">
<label for="enabled" class="form-check-label ml-4 mb-4">
{% if data['user']['enabled'] %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" checked="" value="1">Enabled
{% else %}
<input type="checkbox" class="form-check-input" id="enabled" name="enabled" value="1">Enabled
{% end %}
</label>
<label for="regen_api" class="form-check-label ml-4 mb-4">
{% if data['new_user'] %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" checked="" value="1" disabled >Regenerate API Key
{% else %}
<input type="checkbox" class="form-check-input" id="regen_api" name="regen_api" value="1">Regenerate API Key
{% end %}
</label>
<label for="superuser" class="form-check-label ml-4 mb-4">
{% if data['user']['superuser'] %}
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" checked="" value="1" disabled >Super User
{% else %}
<input type="checkbox" class="form-check-input" id="superuser" name="superuser" value="1" disabled >Super User
{% end %}
</label>
</div>
<button type="submit" class="btn btn-success mr-2">Save</button>
<button type="reset" class="btn btn-light">Cancel</button>
</form>
</div>
<div class="col-md-6 col-sm-12">
<div class="card">
<div class="card-body">
<h4 class="card-title">User Config Area</h4>
<p class="card-description"> Here is where you can change the configuration of your user</p>
<blockquote class="blockquote">
<p class="mb-0">
It is recommended to <code>NOT</code> change the paths of a server managed by Crafty.
Changing paths <code>CAN</code> break things, especially on Linux type operating systems where
file permissions are more locked down.
<br /><br/>
If you feel you have to change a where a server is located
you may do so as long as you give the "Crafty" user permission to read / write to the server path.
<br />
<br />
On Linux this is best done by executing the following:<br />
<code>
sudo chown crafty:crafty /path/to/your/server -R<br />
sudo chmod 2775 /path/to/your/server -R<br />
</code>
</p>
</blockquote>
</div>
</div>
<div class="text-center">
{% if data['new_user'] %}
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
<small>You cannot delete something that does not yet exist</small>
{% elif data['user']['superuser'] %}
<a class="btn btn-sm btn-danger disabled">Delete User</a><br />
<small>You cannot delete a superuser</small>
{% else %}
<a href="/panel/remove_user?id={{ data['user']['user_id'] }}" class="btn btn-sm btn-danger">Delete User</a>
{% end %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- content-wrapper ends -->
{% end %}
{% block js %}
<script>
//used to get cookies from browser - this is part of tornados xsrf protection - it's for extra security
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
$( document ).ready(function() {
console.log( "ready!" );
});
</script>
{% end %}