Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
CFMS WebSocket Server - Core Include Package

This package contains the core functionality for the CFMS WebSocket server,
including connection handling, database models, request handlers, and utilities.
"""
5 changes: 5 additions & 0 deletions include/classes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
CFMS Classes Module

Core classes for handling connections, requests, authentication, and access rules.
"""
4 changes: 3 additions & 1 deletion include/classes/auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import time
from typing import Optional
import jwt, time

import jwt

__all__ = ["Token"]

Expand Down
51 changes: 19 additions & 32 deletions include/classes/connection.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
import base64
import hashlib
import json
import mmap
import os
import sys
import time
import os
import base64
import hashlib
import traceback
import jsonschema
from typing import Iterable
from typing import Optional

import jsonschema
import websockets
from websockets.sync.server import ServerConnection
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from websockets.asyncio.server import broadcast
from websockets.sync.server import ServerConnection
from websockets.typing import Data

from include.conf_loader import global_config
from include.constants import FILE_TRANSFER_CHUNK_SIZE
from include.database.handler import Session
from include.database.models.classic import User
from include.database.models.file import File, FileTask
from include.util.log import getCustomLogger

from include.shared import connected_listeners

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import mmap
from include.util.log import getCustomLogger

logger = getCustomLogger(
"connection",
Expand Down Expand Up @@ -53,16 +53,20 @@ def __init__(self, websocket: ServerConnection, message: Data) -> None:
self.username: str = self.request.get("username", "")
self.token: str = self.request.get("token", "")

def conclude_request(self, code: int, data: dict = {}, message: str = "") -> None:
def conclude_request(
self, code: int, data: Optional[dict] = None, message: str = ""
) -> None:
"""
Conclude the request by sending a response back to the client.

Args:
message: The data/message received from the client.
code: HTTP status code for the response.
data: Data dictionary to include in the response.
message: Message string to include in the response.
"""
response = {
"code": code,
"data": data,
"data": data if data is not None else {},
"message": message,
"timestamp": time.time(),
}
Expand All @@ -72,23 +76,6 @@ def conclude_request(self, code: int, data: dict = {}, message: str = "") -> Non

self.websocket.send(response_json)

# def authenticate_user(self, user: User|None) -> bool:
# """
# Authenticates the user by checking the user authentication status.
# Returns:
# bool: True if the user is authenticated, False otherwise. If the user is not authenticated,
# it concludes the request with a 403 status code and an error message indicating
# an invalid user or token.
# """

# if not user or not user.is_token_valid(self.token):
# self.conclude_request(
# **{"code": 403, "message": "Invalid user or token", "data": {}}
# )
# return False

# return True

def send_file(self, task_id: str) -> None:
"""
Sends a file associated with the given task ID to the client over a websocket connection using AES encryption.
Expand Down Expand Up @@ -318,7 +305,7 @@ def receive_file(self, task_id: str) -> None:
data = self.websocket.recv()
f.write(data) # type: ignore

if not data or len(data) < 8192:
if not data or len(data) < FILE_TRANSFER_CHUNK_SIZE:
break
except (
websockets.ConnectionClosed,
Expand Down
7 changes: 5 additions & 2 deletions include/conf_loader.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import os, tomllib
import os
import secrets
from tomlkit import parse, dumps
import tomllib

from tomlkit import dumps
from tomlkit import parse

__all__ = ["global_config"]

Expand Down
23 changes: 21 additions & 2 deletions include/constants.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
from include.classes.version import Version

# __all__ = ["PROTOCOL_VERSION"]
__all__ = [
"CORE_VERSION",
"PROTOCOL_VERSION",
"AVAILABLE_ACCESS_TYPES",
"AVAILABLE_BLOCK_TYPES",
"DEFAULT_TOKEN_EXPIRY_SECONDS",
"FAILED_LOGIN_DELAY_SECONDS",
"DEFAULT_SSL_CERT_VALIDITY_DAYS",
"FILE_TRANSFER_CHUNK_SIZE",
"FILE_TASK_DEFAULT_DURATION_SECONDS",
]

CORE_VERSION = Version("0.1.0.250919_alpha")
PROTOCOL_VERSION = 3

AVAILABLE_ACCESS_TYPES = ["read", "write", "move", "manage"]
AVAILABLE_BLOCK_TYPES: set = {"read", "write", "move"}
AVAILABLE_BLOCK_TYPES: set = {"read", "write", "move"}

# Authentication and Security Constants
DEFAULT_TOKEN_EXPIRY_SECONDS = 3600 # 1 hour
FAILED_LOGIN_DELAY_SECONDS = 3 # Delay after failed login attempt
DEFAULT_SSL_CERT_VALIDITY_DAYS = 365 # 1 year

# File Transfer Constants
FILE_TRANSFER_CHUNK_SIZE = 8192 # 8KB - size threshold for determining end of transfer
FILE_TASK_DEFAULT_DURATION_SECONDS = 3600 # 1 hour
5 changes: 5 additions & 0 deletions include/database/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
CFMS Database Module

Database connection handler and ORM models for the CFMS system.
"""
12 changes: 6 additions & 6 deletions include/database/handler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from sqlalchemy import create_engine, URL, MetaData
from sqlalchemy import URL, create_engine
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import sessionmaker
from include.conf_loader import global_config

from sqlalchemy.orm import DeclarativeBase
from include.conf_loader import global_config
from include.constants import DEFAULT_TOKEN_EXPIRY_SECONDS

__all__ = ["engine", "Session", "Base"]

Expand Down Expand Up @@ -39,13 +40,12 @@
)
engine = create_engine(
url,
pool_recycle=3600,
pool_recycle=DEFAULT_TOKEN_EXPIRY_SECONDS,
echo=debug_enabled,
)

Session = sessionmaker(bind=engine)
# metadata_obj = MetaData()


class Base(DeclarativeBase):
# metadata = metadata_obj
pass
5 changes: 5 additions & 0 deletions include/database/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
CFMS Database Models

ORM models for users, groups, documents, files, and access control.
"""
28 changes: 14 additions & 14 deletions include/database/models/classic.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import hashlib
import jwt
import os
import secrets
import time
from typing import TYPE_CHECKING
from typing import List
from typing import Optional
from typing import Set

import secrets
from sqlalchemy import VARCHAR, Float, ForeignKey, Integer, Text
from include.database.handler import Base, Session
from include.conf_loader import global_config
from include.classes.auth import Token
from sqlalchemy import VARCHAR, Boolean, Float, ForeignKey, Integer, JSON, Text
from sqlalchemy import event
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy import Boolean
from sqlalchemy import event
import time
from sqlalchemy.orm.session import object_session
from sqlalchemy import JSON
import jwt
import hashlib
import os

from include.classes.auth import Token
from include.conf_loader import global_config
from include.constants import DEFAULT_TOKEN_EXPIRY_SECONDS
from include.database.handler import Base, Session


if TYPE_CHECKING:
Expand Down Expand Up @@ -73,7 +73,7 @@ def authenticate_and_create_token(self, plain_password: str) -> Optional[Token]:
else self.secret_key
)
token = Token(secret, self.username)
token.new(3600)
token.new(DEFAULT_TOKEN_EXPIRY_SECONDS)

session = object_session(self)
if session is not None:
Expand Down Expand Up @@ -113,7 +113,7 @@ def renew_token(self) -> Token:
else self.secret_key
)
new_token = Token(secret, self.username)
new_token.new(3600)
new_token.new(DEFAULT_TOKEN_EXPIRY_SECONDS)

return new_token

Expand Down
11 changes: 7 additions & 4 deletions include/database/models/file.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import os
import secrets
from sqlalchemy import VARCHAR, Float, ForeignKey, Integer, Text, Boolean
from include.database.handler import Base
import sys
import time
from typing import List
from typing import Optional

from sqlalchemy import VARCHAR, Float, ForeignKey, Integer, Text, Boolean
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
import time
from sqlalchemy.orm.session import object_session
import os, sys

from include.database.handler import Base


class File(Base):
Expand Down
6 changes: 6 additions & 0 deletions include/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
CFMS Request Handlers

Request handlers for authentication, document management, directory operations,
and system management.
"""
7 changes: 4 additions & 3 deletions include/handlers/auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import time

from include.classes.connection import ConnectionHandler
from include.classes.request import RequestHandler
from include.conf_loader import global_config
from include.constants import FAILED_LOGIN_DELAY_SECONDS
from include.database.handler import Session
from include.database.models.classic import User
from include.util.audit import log_audit
import time

from include.util.pwd import check_passwd_requirements


Expand Down Expand Up @@ -99,7 +100,7 @@ def handle(self, handler: ConnectionHandler):
response = response_invalid

if response == response_invalid:
time.sleep(3)
time.sleep(FAILED_LOGIN_DELAY_SECONDS)

# Send the response back to the client
handler.conclude_request(**response)
Expand Down
21 changes: 10 additions & 11 deletions include/handlers/document.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import datetime
import secrets
import time

import jsonschema

from include.classes.connection import ConnectionHandler
from include.classes.request import RequestHandler
from include.conf_loader import global_config
from include.constants import AVAILABLE_ACCESS_TYPES
from include.constants import FILE_TASK_DEFAULT_DURATION_SECONDS
from include.database.handler import Session
from include.database.models.classic import (
User,
)
from include.database.models.classic import User
from include.database.models.entity import (
Document,
Folder,
NoActiveRevisionsError,
DocumentAccessRule,
DocumentRevision,
Folder,
NoActiveRevisionsError,
)
from include.database.models.file import File, FileTask
from include.conf_loader import global_config
from include.util.rule.applying import apply_access_rules
import include.system.messages as smsg
import time

__all__ = [
"RequestGetDocumentInfoHandler",
Expand All @@ -36,8 +36,7 @@
]


# def create_file_task(file_id: str, transfer_mode=0):
def create_file_task(file: File, transfer_mode=0):
def create_file_task(file: File, transfer_mode: int = 0):
"""
Creates a new file processing task for the specified file.
Args:
Expand All @@ -52,17 +51,17 @@ def create_file_task(file: File, transfer_mode=0):
"""

with Session() as session:
# file = session.get(File, file_id)
if not file:
return None


now = time.time()
task = FileTask(
file_id=file.id,
status=0,
mode=transfer_mode,
start_time=now,
end_time=now + 3600,
end_time=now + FILE_TASK_DEFAULT_DURATION_SECONDS,
)
session.add(task)
session.commit()
Expand Down
6 changes: 6 additions & 0 deletions include/handlers/management/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
CFMS Management Handlers

Request handlers for user management, group management, access control,
and system administration.
"""
5 changes: 5 additions & 0 deletions include/system/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
CFMS System Module

System-level messages and utilities for server operations.
"""
6 changes: 6 additions & 0 deletions include/util/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""
CFMS Utilities

Utility functions for logging, user management, group management,
password validation, and audit logging.
"""
Loading