Lots of changes, I guess
This commit is contained in:
parent
ff17665cb0
commit
a7589e604c
8 changed files with 156 additions and 67 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,7 +5,7 @@ settings.ini
|
|||
completionmail.ini
|
||||
venv/
|
||||
output/
|
||||
reports/*.py
|
||||
reports/*
|
||||
!reports/__init__.py
|
||||
.vscode
|
||||
*.old
|
||||
|
|
|
@ -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
0
reports/__init__.py
Normal 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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue