Lots of changes, I guess

This commit is contained in:
Kumi 2023-07-03 11:24:31 +02:00
parent ff17665cb0
commit a7589e604c
Signed by: kumi
GPG key ID: ECBCC9082395383F
8 changed files with 156 additions and 67 deletions

2
.gitignore vendored
View file

@ -5,7 +5,7 @@ settings.ini
completionmail.ini
venv/
output/
reports/*.py
reports/*
!reports/__init__.py
.vscode
*.old

View file

@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "reportmonster"
version = "0.9.6"
version = "0.9.7"
authors = [
{ name="Kumi Systems e.U.", email="office@kumi.systems" },
]

0
reports/__init__.py Normal file
View file

View file

@ -1,7 +1,7 @@
import configparser
from pathlib import Path
from typing import Union
from typing import Union, List
from .vessel import Vessel
from .user import User
@ -37,16 +37,17 @@ class MonsterConfig:
except KeyError:
try:
import pyadonis
self.pyadonis = Path(pyadonis.__path__[0])
except ImportError:
print(f"PyAdonis is not defined in the MONSTER section of {path}, some features may be missing.")
print(
f"PyAdonis is not defined in the MONSTER section of {path}, some features may be missing."
)
def __init__(self, path: Union[str, Path]) -> None:
"""Initialize a new (empty) MonsterConfig object
"""
self.vessels = []
self.users = []
"""Initialize a new (empty) MonsterConfig object"""
self.vessels: List[Vessel] = []
self.users: List[User] = []
self.pyadonis = None
if path:

View file

@ -10,8 +10,7 @@ import socket
class Connection:
"""Class representing an SSH/SFTP connection to a Vessel
"""
"""Class representing an SSH/SFTP connection to a Vessel"""
def __init__(self, vessel):
"""Initialize a new Connection to a Vessel
@ -24,9 +23,14 @@ class Connection:
self._client = SSHClient()
self._client.load_system_host_keys()
self._client.set_missing_host_key_policy(WarningPolicy)
self._client.connect(vessel.host, 22, vessel.ssh_username,
vessel.ssh_password, timeout=vessel.ssh_timeout,
passphrase=vessel.ssh_passphrase)
self._client.connect(
vessel.host,
22,
vessel.ssh_username,
vessel.ssh_password,
timeout=vessel.ssh_timeout,
passphrase=vessel.ssh_passphrase,
)
self._transport = self._client.get_transport()
self._transport.set_keepalive(10)
self._sftp = self._client.open_sftp()
@ -36,14 +40,14 @@ class Connection:
(self._vessel.host, 22),
ssh_username=self._vessel.ssh_username,
ssh_private_key_password=self._vessel.ssh_passphrase,
remote_bind_address=("127.0.0.1", remote))
remote_bind_address=("127.0.0.1", remote),
)
self._process.start()
return self._process.local_bind_port
def __del__(self):
"""Close SSH connection when ending Connection
"""
"""Close SSH connection when ending Connection"""
self._client.close()
if self._process:

View file

@ -9,18 +9,23 @@ from .logger import Logger
logger = Logger()
class Database:
"""Class wrapping MySQL database connection
"""
"""Class wrapping MySQL database connection"""
def __init__(self, vessel):
"""Initialize a new Database object
"""
"""Initialize a new Database object"""
self.vessel = vessel
self._con = None
self._ssh = None
def _execute(self, query: str, parameters: Optional[tuple] = None, ctype: Optional[MySQLdb.cursors.BaseCursor] = None, retry: bool = True):
def _execute(
self,
query: str,
parameters: Optional[tuple] = None,
ctype: Optional[MySQLdb.cursors.BaseCursor] = None,
retry: bool = True,
):
"""Execute a query on the database
Args:
@ -45,15 +50,20 @@ class Database:
def _connect(self) -> None:
if self.vessel.ssh:
self._ssh = Connection(self.vessel)
port = self._ssh.forward_tcp(3306)
host = "127.0.0.1"
self._ssh = Connection(self.vessel)
port = self._ssh.forward_tcp(3306)
host = "127.0.0.1"
else:
port = 3306
host = self.vessel.host
port = 3306
host = self.vessel.host
self._con = MySQLdb.connect(host=host, user=self.vessel.username,
passwd=self.vessel.password, db=self.vessel.database, port=port)
self._con = MySQLdb.connect(
host=host,
user=self.vessel.username,
passwd=self.vessel.password,
db=self.vessel.database,
port=port,
)
def commit(self) -> None:
"""Commit the current database transaction
@ -64,7 +74,9 @@ class Database:
"""
self._con.commit()
def getCursor(self, ctype: Optional[MySQLdb.cursors.BaseCursor] = None) -> MySQLdb.cursors.BaseCursor:
def getCursor(
self, ctype: Optional[MySQLdb.cursors.BaseCursor] = None
) -> MySQLdb.cursors.BaseCursor:
"""Return a cursor to operate on the MySQL database
Returns:
@ -73,7 +85,6 @@ class Database:
return self._con.cursor(ctype)
def __del__(self):
"""Close database connection on removal of the Database object
"""
"""Close database connection on removal of the Database object"""
if self._con:
self._con.close()
self._con.close()

View file

@ -10,8 +10,8 @@ from bcrypt import hashpw, gensalt
class User:
"""Class describing a User
"""
"""Class describing a User"""
@classmethod
def fromConfig(cls, config: SectionProxy):
"""Create User object from a User section in the Config file
@ -29,7 +29,6 @@ class User:
return cls(config.name.split()[1], config["Password"])
def __init__(self, username: str, password: str) -> None:
"""Initialize new Vessel object
@ -39,6 +38,5 @@ class User:
self.username = username
self.password = password
def validatePasword(self, password) -> bool:
return password == self.password
return password == self.password

View file

@ -1,4 +1,4 @@
from classes.database import Database
from .database import Database
from configparser import SectionProxy
from typing import Optional, Union
@ -8,18 +8,18 @@ from MySQLdb.cursors import DictCursor
from MySQLdb._exceptions import IntegrityError
from bcrypt import hashpw, gensalt
from const import *
from ..const import *
class Vessel:
"""Class describing a Vessel
"""
"""Class describing a Vessel"""
@classmethod
def fromConfig(cls, config: SectionProxy):
"""Create Vessel object from a Vessel section in the Config file
Args:
config (configparser.SectionProxy): Vessel section defining a
config (configparser.SectionProxy): Vessel section defining a
Vessel
Raises:
@ -50,14 +50,37 @@ class Vessel:
database = config["Database"]
if "SSH" in config.keys():
if int(config["SSH"]) == 1:
if config.getint("SSH") == 1:
ssh = True
return cls(config.name.split()[1], config["Host"], username, password, database, ssh, ssh_username, ssh_password, ssh_timeout, ssh_passphrase)
ssh_username = config.get("SSHUser")
def __init__(self, name: str, host: str, username: Optional[str] = None,
password: Optional[str] = None, database: Optional[str] = None,
ssh = False, ssh_username = None, ssh_password = None, ssh_timeout = None, ssh_passphrase = None) -> None:
return cls(
config.name.split()[1],
config["Host"],
username,
password,
database,
ssh,
ssh_username,
ssh_password,
ssh_timeout,
ssh_passphrase,
)
def __init__(
self,
name: str,
host: str,
username: Optional[str] = None,
password: Optional[str] = None,
database: Optional[str] = None,
ssh=False,
ssh_username=None,
ssh_password=None,
ssh_timeout=None,
ssh_passphrase=None,
) -> None:
"""Initialize new Vessel object
Args:
@ -76,7 +99,7 @@ class Vessel:
self.db = self.connect()
def connect(self):
def connect(self) -> Database:
return Database(self)
def reconnect(self):
@ -98,7 +121,9 @@ class Vessel:
results = self.db._execute(QUERY_USER_INFO_FIELD, ctype=DictCursor)
return results
def getUserInfoData(self, field: Optional[int] = None, user: Optional[int] = None) -> list:
def getUserInfoData(
self, field: Optional[int] = None, user: Optional[int] = None
) -> list:
query = QUERY_USER_INFO_DATA
parameters = []
@ -119,7 +144,9 @@ class Vessel:
return results
def getUsers(self, username: Optional[str] = None, id: Optional[int] = None) -> dict:
def getUsers(
self, username: Optional[str] = None, id: Optional[int] = None
) -> dict:
query = QUERY_USER
parameters = tuple()
@ -147,7 +174,9 @@ class Vessel:
for value in odata:
try:
users[value["userid"]]["custom_fields"][ofield["shortname"]] = value["data"]
users[value["userid"]]["custom_fields"][
ofield["shortname"]
] = value["data"]
except KeyError:
pass
@ -155,7 +184,10 @@ class Vessel:
def getHTMLCerts(self, after: int = 0, before: Optional[int] = None):
before = before or Vessel.getTimestamp()
results = self.db._execute(f"{QUERY_HTML_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}", ctype=DictCursor)
results = self.db._execute(
f"{QUERY_HTML_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}",
ctype=DictCursor,
)
ocerts = self.db._execute(QUERY_HTML_CERT, ctype=DictCursor)
certs = dict()
@ -175,7 +207,10 @@ class Vessel:
def getCustomCerts(self, after: int = 0, before: Optional[int] = None):
before = before or Vessel.getTimestamp()
results = self.db._execute(f"{QUERY_CUSTOM_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}", ctype=DictCursor)
results = self.db._execute(
f"{QUERY_CUSTOM_CERT_ISSUES} {QUERY_WHERE_TIMESTAMPS % {'column': 'timecreated', 'after': after, 'before': before}}",
ctype=DictCursor,
)
ocerts = self.db._execute(QUERY_CUSTOM_CERT, ctype=DictCursor)
certs = dict()
@ -195,7 +230,10 @@ class Vessel:
def getCerts(self, after: int = 0, before: Optional[int] = None):
before = before or Vessel.getTimestamp()
return sorted(self.getHTMLCerts(after, before) + self.getCustomCerts(after, before), key=lambda d: d["timecreated"])
return sorted(
self.getHTMLCerts(after, before) + self.getCustomCerts(after, before),
key=lambda d: d["timecreated"],
)
def setPassword(self, username: str, password: str):
hashed = hashpw(password.encode(), gensalt(prefix=b"2b"))
@ -218,11 +256,16 @@ class Vessel:
if not enrol:
self.createEnrol(courseid, enrol)
enrol = list(filter(lambda x: x["courseid"] == courseid , self.getEnrols(enrol)))
enrol = list(
filter(lambda x: x["courseid"] == courseid, self.getEnrols(enrol))
)
assert enrol
self.db._execute(QUERY_ENROL_USER, (enrol[0]["id"], userid, Vessel.getTimestamp(), Vessel.getTimestamp()))
self.db._execute(
QUERY_ENROL_USER,
(enrol[0]["id"], userid, Vessel.getTimestamp(), Vessel.getTimestamp()),
)
def getEnrolments(self):
results = list(self.db._execute(QUERY_ENROLMENTS, ctype=DictCursor))
@ -230,16 +273,30 @@ class Vessel:
def createUser(self, username, password, email, firstname, lastname):
email = email or f"{username}@pin.seachefsacademy.com"
self.db._execute(QUERY_USER_CREATE, (username, email, firstname, lastname, Vessel.getTimestamp(), Vessel.getTimestamp()))
self.db._execute(
QUERY_USER_CREATE,
(
username,
email,
firstname,
lastname,
Vessel.getTimestamp(),
Vessel.getTimestamp(),
),
)
self.setPassword(username, password)
def assignRole(self, userid: int, courseid: int, roleid: int = 5):
contextid = self.getCourseContext(courseid)[0]["id"]
self.db._execute(QUERY_ASSIGN_ROLE, (roleid, contextid, userid, Vessel.getTimestamp()))
self.db._execute(
QUERY_ASSIGN_ROLE, (roleid, contextid, userid, Vessel.getTimestamp())
)
def getRole(self, userid: int, courseid: int) -> Optional[int]:
contextid = self.getCourseContext(courseid)[0]["id"]
results = self.db._execute(QUERY_GET_ROLE, (contextid, userid), ctype=DictCursor)
contextid = self.getCourseContext(courseid)[0]["id"]
results = self.db._execute(
QUERY_GET_ROLE, (contextid, userid), ctype=DictCursor
)
if results:
return results[0]["roleid"]
@ -249,7 +306,10 @@ class Vessel:
return results[0]["id"]
def setEmail(self, userid: int, email: str):
email = email or f"{self.getUsers(id=userid)[userid]['username']}@pin.seachefsacademy.com"
email = (
email
or f"{self.getUsers(id=userid)[userid]['username']}@pin.seachefsacademy.com"
)
self.db._execute(QUERY_USER_SET_EMAIL, (email, userid))
def setName(self, userid: int, first: str, last: str):
@ -260,23 +320,32 @@ class Vessel:
return results
def getCourseByContext(self, contextid: int) -> Optional[int]:
results = self.db._execute(QUERY_COURSE_CONTEXT_REVERSE, (contextid,), ctype=DictCursor)
results = self.db._execute(
QUERY_COURSE_CONTEXT_REVERSE, (contextid,), ctype=DictCursor
)
if results:
return results[0]["instanceid"]
def getCourseModules(self, courseid: int):
results = list(self.db._execute(QUERY_COURSE_MODULES, (courseid,), ctype=DictCursor))
results = list(
self.db._execute(QUERY_COURSE_MODULES, (courseid,), ctype=DictCursor)
)
return results
def getCourseModuleCompletion(self, moduleid: int):
results = list(self.db._execute(QUERY_MODULE_COMPLETION, (moduleid,), ctype=DictCursor))
results = list(
self.db._execute(QUERY_MODULE_COMPLETION, (moduleid,), ctype=DictCursor)
)
return results
def setCourseModuleCompletion(self, moduleid: int, userid: int):
try:
self.db._execute(QUERY_INSERT_MODULE_COMPLETION, (moduleid, userid, Vessel.getTimestamp()))
self.db._execute(
QUERY_INSERT_MODULE_COMPLETION,
(moduleid, userid, Vessel.getTimestamp()),
)
except IntegrityError:
pass # Module completion record already exists
pass # Module completion record already exists
self.db._execute(QUERY_UPDATE_MODULE_COMPLETION, (moduleid, userid))
def setCourseCompletion(self, courseid: int, userid: int):
@ -287,3 +356,9 @@ class Vessel:
def writeLog(self, event, data):
self.db._execute(QUERY_LOG_INSERT, (event, data))
def getCourseCompletions(self, courseid: int):
results = list(
self.db._execute(QUERY_COURSE_COMPLETION, (courseid,), ctype=DictCursor)
)
return results