prod azure 1.1 ready internal
This commit is contained in:
parent
25234cff33
commit
195bc64b46
15 changed files with 906 additions and 3 deletions
|
|
@ -489,7 +489,7 @@ class AgentEmail(AgentBase):
|
|||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
# Prepare email data
|
||||
# Prepare email data with proper structure
|
||||
email_data = {
|
||||
'subject': subject,
|
||||
'body': {
|
||||
|
|
@ -541,15 +541,22 @@ class AgentEmail(AgentBase):
|
|||
logger.info("Encoding bytes content to base64")
|
||||
file_content = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
# Calculate actual size from base64 content
|
||||
try:
|
||||
decoded_size = len(base64.b64decode(file_content))
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating size for {file_name}: {str(e)}")
|
||||
decoded_size = 0
|
||||
|
||||
# Add attachment to email data
|
||||
logger.info(f"Adding attachment: {file_name} ({content_type})")
|
||||
logger.info(f"Adding attachment: {file_name} ({content_type}, size: {decoded_size} bytes)")
|
||||
attachment_data = {
|
||||
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||
'name': file_name,
|
||||
'contentType': content_type,
|
||||
'contentBytes': file_content,
|
||||
'isInline': False,
|
||||
'size': len(base64.b64decode(file_content)) if file_content else 0
|
||||
'size': decoded_size
|
||||
}
|
||||
email_data['attachments'].append(attachment_data)
|
||||
logger.info(f"Successfully added attachment: {file_name}")
|
||||
|
|
@ -561,6 +568,9 @@ class AgentEmail(AgentBase):
|
|||
f"has_attachments={bool(email_data.get('attachments'))}, " +
|
||||
f"attachment_count={len(email_data.get('attachments', []))}")
|
||||
|
||||
# Log the full email data structure for debugging
|
||||
logger.debug(f"Full email data structure: {json.dumps(email_data, indent=2)}")
|
||||
|
||||
# First create the draft message
|
||||
response = requests.post(
|
||||
'https://graph.microsoft.com/v1.0/me/mailFolders/drafts/messages',
|
||||
|
|
@ -573,6 +583,8 @@ class AgentEmail(AgentBase):
|
|||
return response.json()
|
||||
else:
|
||||
logger.error(f"Drafts folder method failed: {response.status_code} - {response.text}")
|
||||
logger.error(f"Request headers: {headers}")
|
||||
logger.error(f"Request body: {json.dumps(email_data, indent=2)}")
|
||||
|
||||
# Try fallback method with messages endpoint
|
||||
logger.info("Trying fallback with messages endpoint")
|
||||
|
|
@ -587,6 +599,8 @@ class AgentEmail(AgentBase):
|
|||
return response.json()
|
||||
else:
|
||||
logger.error(f"Messages endpoint method also failed: {response.status_code} - {response.text}")
|
||||
logger.error(f"Request headers: {headers}")
|
||||
logger.error(f"Request body: {json.dumps(email_data, indent=2)}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
42
static/40_email_preview.html
Normal file
42
static/40_email_preview.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Preview: Terminverschiebung: Neuer Termin am Freitag</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }
|
||||
.email-container { max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
|
||||
.email-header { background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }
|
||||
.email-content { padding: 20px; }
|
||||
.email-footer { background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
||||
.field { margin-bottom: 10px; }
|
||||
.field-label { font-weight: bold; color: #555; }
|
||||
.email-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<h2>Email Template Preview</h2>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
<div class="field">
|
||||
<div class="field-label">To:</div>
|
||||
<div>peter.muster@domain.com</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">Subject:</div>
|
||||
<div>Terminverschiebung: Neuer Termin am Freitag</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. Könnten wir den Termin stattdessen auf Freitag verlegen?</p><p>Bitte lassen Sie mich wissen, ob dieser neue Termin für Sie passt.</p><p>Vielen Dank für Ihr Verständnis.</p><p>Mit freundlichen Grüßen,<br>[Ihr Name]</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
static/41_email_template.json
Normal file
6
static/41_email_template.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"recipient": "peter.muster@domain.com",
|
||||
"subject": "Terminverschiebung: Neuer Termin am Freitag",
|
||||
"plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. K\u00f6nnten wir den Termin stattdessen auf Freitag verlegen?\n\nBitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.\n\nVielen Dank f\u00fcr Ihr Verst\u00e4ndnis.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]",
|
||||
"htmlBody": "<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, es geht Ihnen gut. Ich schreibe Ihnen, um unseren geplanten Termin um 10 Uhr zu verschieben. K\u00f6nnten wir den Termin stattdessen auf Freitag verlegen?</p><p>Bitte lassen Sie mich wissen, ob dieser neue Termin f\u00fcr Sie passt.</p><p>Vielen Dank f\u00fcr Ihr Verst\u00e4ndnis.</p><p>Mit freundlichen Gr\u00fc\u00dfen,<br>[Ihr Name]</p>"
|
||||
}
|
||||
526
static/42_gatewayInterface.py
Normal file
526
static/42_gatewayInterface.py
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
"""
|
||||
Interface to the Gateway system.
|
||||
Manages users and mandates for authentication.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, List, Optional, Union
|
||||
import importlib
|
||||
from passlib.context import CryptContext
|
||||
|
||||
from connectors.connectorDbJson import DatabaseConnector
|
||||
from modules.configuration import APP_CONFIG
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Password-Hashing
|
||||
pwdContext = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||
|
||||
|
||||
class GatewayInterface:
|
||||
"""
|
||||
Interface to the Gateway system.
|
||||
Manages users and mandates.
|
||||
"""
|
||||
|
||||
def __init__(self, mandateId: int = None, userId: int = None):
|
||||
"""Initializes the Gateway Interface with optional mandate and user context."""
|
||||
# Context can be empty during initialization
|
||||
self.mandateId = mandateId
|
||||
self.userId = userId
|
||||
|
||||
# Import data model module
|
||||
try:
|
||||
self.modelModule = importlib.import_module("modules.gatewayModel")
|
||||
logger.info("gatewayModel successfully imported")
|
||||
except ImportError as e:
|
||||
logger.error(f"Error importing gatewayModel: {e}")
|
||||
raise
|
||||
|
||||
# Initialize database
|
||||
self._initializeDatabase()
|
||||
|
||||
# Load user information
|
||||
self.currentUser = self._getCurrentUserInfo()
|
||||
|
||||
# Initialize standard records if needed
|
||||
self._initRecords()
|
||||
|
||||
def _getCurrentUserInfo(self) -> Dict[str, Any]:
|
||||
"""Gets information about the current user including privileges."""
|
||||
# For initialization, set default values
|
||||
userInfo = {
|
||||
"id": self.userId,
|
||||
"mandateId": self.mandateId,
|
||||
"privilege": "user", # Default privilege level
|
||||
"language": "en"
|
||||
}
|
||||
|
||||
# Try to load actual user info if IDs are provided
|
||||
if self.userId:
|
||||
userRecords = self.db.getRecordset("users", recordFilter={"id": self.userId})
|
||||
if userRecords:
|
||||
user = userRecords[0]
|
||||
userInfo["privilege"] = user.get("privilege", "user")
|
||||
userInfo["language"] = user.get("language", "en")
|
||||
|
||||
return userInfo
|
||||
|
||||
def _initializeDatabase(self):
|
||||
"""Initializes the database connection."""
|
||||
self.db = DatabaseConnector(
|
||||
dbHost=APP_CONFIG.get("DB_SYSTEM_HOST"),
|
||||
dbDatabase=APP_CONFIG.get("DB_SYSTEM_DATABASE"),
|
||||
dbUser=APP_CONFIG.get("DB_SYSTEM_USER"),
|
||||
dbPassword=APP_CONFIG.get("DB_SYSTEM_PASSWORD_SECRET"),
|
||||
mandateId=self.mandateId if self.mandateId else 0,
|
||||
userId=self.userId if self.userId else 0
|
||||
)
|
||||
|
||||
def _initRecords(self):
|
||||
"""Initializes standard records in the database if they don't exist."""
|
||||
self._initRootMandate()
|
||||
self._initAdminUser()
|
||||
|
||||
def _initRootMandate(self):
|
||||
"""Creates the Root mandate if it doesn't exist."""
|
||||
existingMandateId = self.getInitialId("mandates")
|
||||
mandates = self.db.getRecordset("mandates")
|
||||
if existingMandateId is None or not mandates:
|
||||
logger.info("Creating Root mandate")
|
||||
rootMandate = {
|
||||
"name": "Root",
|
||||
"language": "de"
|
||||
}
|
||||
createdMandate = self.db.recordCreate("mandates", rootMandate)
|
||||
logger.info(f"Root mandate created with ID {createdMandate['id']}")
|
||||
|
||||
# Update mandate context
|
||||
self.mandateId = createdMandate['id']
|
||||
|
||||
def _initAdminUser(self):
|
||||
"""Creates the Admin user if it doesn't exist."""
|
||||
existingUserId = self.getInitialId("users")
|
||||
users = self.db.getRecordset("users")
|
||||
if existingUserId is None or not users:
|
||||
logger.info("Creating Admin user")
|
||||
adminUser = {
|
||||
"mandateId": self.mandateId,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"fullName": "Administrator",
|
||||
"disabled": False,
|
||||
"language": "de",
|
||||
"privilege": "sysadmin",
|
||||
"hashedPassword": self._getPasswordHash("The 1st Poweron Admin") # Use a secure password in production!
|
||||
}
|
||||
createdUser = self.db.recordCreate("users", adminUser)
|
||||
logger.info(f"Admin user created with ID {createdUser['id']}")
|
||||
|
||||
# Update user context
|
||||
self.userId = createdUser['id']
|
||||
|
||||
def _uam(self, table: str, recordset: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Unified user access management function that filters data based on user privileges.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordset: Recordset to filter based on access rules
|
||||
|
||||
Returns:
|
||||
Filtered recordset based on user privilege level
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
|
||||
# Apply filtering based on privilege
|
||||
if userPrivilege == "sysadmin":
|
||||
return recordset # System admins see all records
|
||||
elif userPrivilege == "admin":
|
||||
# Admins see records in their mandate
|
||||
return [r for r in recordset if r.get("mandateId") == self.mandateId]
|
||||
else: # Regular users
|
||||
# Users only see records they own within their mandate
|
||||
return [r for r in recordset
|
||||
if r.get("mandateId") == self.mandateId and r.get("userId") == self.userId]
|
||||
|
||||
def _canModify(self, table: str, recordId: Optional[int] = None) -> bool:
|
||||
"""
|
||||
Checks if the current user can modify (create/update/delete) records in a table.
|
||||
|
||||
Args:
|
||||
table: Name of the table
|
||||
recordId: Optional record ID for specific record check
|
||||
|
||||
Returns:
|
||||
Boolean indicating permission
|
||||
"""
|
||||
userPrivilege = self.currentUser.get("privilege", "user")
|
||||
|
||||
# System admins can modify anything
|
||||
if userPrivilege == "sysadmin":
|
||||
return True
|
||||
|
||||
# Check specific record permissions
|
||||
if recordId is not None:
|
||||
# Get the record to check ownership
|
||||
records = self.db.getRecordset(table, recordFilter={"id": recordId})
|
||||
if not records:
|
||||
return False
|
||||
|
||||
record = records[0]
|
||||
|
||||
# Admins can modify anything in their mandate
|
||||
if userPrivilege == "admin" and record.get("mandateId") == self.mandateId:
|
||||
# Exception: Can't modify Root mandate unless you are a sysadmin
|
||||
if table == "mandates" and recordId == 1 and userPrivilege != "sysadmin":
|
||||
return False
|
||||
return True
|
||||
|
||||
# Users can only modify their own records
|
||||
if (record.get("mandateId") == self.mandateId and
|
||||
record.get("userId") == self.userId):
|
||||
return True
|
||||
|
||||
return False
|
||||
else:
|
||||
# For general table modify permission (e.g., create)
|
||||
# Admins can create anything in their mandate
|
||||
if userPrivilege == "admin":
|
||||
return True
|
||||
|
||||
# Regular users can create most entities
|
||||
if table == "mandates":
|
||||
return False # Regular users can't create mandates
|
||||
return True
|
||||
|
||||
def getInitialId(self, table: str) -> Optional[int]:
|
||||
"""Returns the initial ID for a table."""
|
||||
return self.db.getInitialId(table)
|
||||
|
||||
def _getPasswordHash(self, password: str) -> str:
|
||||
"""Creates a hash for a password."""
|
||||
return pwdContext.hash(password)
|
||||
|
||||
def _verifyPassword(self, plainPassword: str, hashedPassword: str) -> bool:
|
||||
"""Checks if the password matches the hash."""
|
||||
return pwdContext.verify(plainPassword, hashedPassword)
|
||||
|
||||
def _getCurrentTimestamp(self) -> str:
|
||||
"""Returns the current timestamp in ISO format."""
|
||||
from datetime import datetime
|
||||
return datetime.now().isoformat()
|
||||
|
||||
# Mandate methods
|
||||
|
||||
def getAllMandates(self) -> List[Dict[str, Any]]:
|
||||
"""Returns mandates based on user access level."""
|
||||
allMandates = self.db.getRecordset("mandates")
|
||||
return self._uam("mandates", allMandates)
|
||||
|
||||
def getMandate(self, mandateId: int) -> Optional[Dict[str, Any]]:
|
||||
"""Returns a mandate by ID if user has access."""
|
||||
mandates = self.db.getRecordset("mandates", recordFilter={"id": mandateId})
|
||||
if not mandates:
|
||||
return None
|
||||
|
||||
filteredMandates = self._uam("mandates", mandates)
|
||||
return filteredMandates[0] if filteredMandates else None
|
||||
|
||||
def createMandate(self, name: str, language: str = "de") -> Dict[str, Any]:
|
||||
"""Creates a new mandate if user has permission."""
|
||||
if not self._canModify("mandates"):
|
||||
raise PermissionError("No permission to create mandates")
|
||||
|
||||
mandateData = {
|
||||
"name": name,
|
||||
"language": language
|
||||
}
|
||||
|
||||
return self.db.recordCreate("mandates", mandateData)
|
||||
|
||||
def updateMandate(self, mandateId: int, mandateData: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Updates a mandate if user has access."""
|
||||
# Check if the mandate exists and user has access
|
||||
mandate = self.getMandate(mandateId)
|
||||
if not mandate:
|
||||
raise ValueError(f"Mandate with ID {mandateId} not found")
|
||||
|
||||
if not self._canModify("mandates", mandateId):
|
||||
raise PermissionError(f"No permission to update mandate {mandateId}")
|
||||
|
||||
# Update the mandate
|
||||
return self.db.recordModify("mandates", mandateId, mandateData)
|
||||
|
||||
def deleteMandate(self, mandateId: int) -> bool:
|
||||
"""
|
||||
Deletes a mandate and all associated users and data if user has permission.
|
||||
"""
|
||||
# Check if the mandate exists and user has access
|
||||
mandate = self.getMandate(mandateId)
|
||||
if not mandate:
|
||||
return False
|
||||
|
||||
if not self._canModify("mandates", mandateId):
|
||||
raise PermissionError(f"No permission to delete mandate {mandateId}")
|
||||
|
||||
# Check if it's the initial mandate
|
||||
initialMandateId = self.getInitialId("mandates")
|
||||
if initialMandateId is not None and mandateId == initialMandateId:
|
||||
logger.warning(f"Attempt to delete the Root mandate was prevented")
|
||||
return False
|
||||
|
||||
# Find all users of the mandate
|
||||
users = self.getUsersByMandate(mandateId)
|
||||
|
||||
# Delete all users of the mandate and their associated data
|
||||
for user in users:
|
||||
self.deleteUser(user["id"])
|
||||
|
||||
# Delete the mandate
|
||||
success = self.db.recordDelete("mandates", mandateId)
|
||||
|
||||
if success:
|
||||
logger.info(f"Mandate with ID {mandateId} was successfully deleted")
|
||||
else:
|
||||
logger.error(f"Error deleting mandate with ID {mandateId}")
|
||||
|
||||
return success
|
||||
|
||||
# User methods
|
||||
|
||||
def getAllUsers(self) -> List[Dict[str, Any]]:
|
||||
"""Returns users based on user access level."""
|
||||
allUsers = self.db.getRecordset("users")
|
||||
filteredUsers = self._uam("users", allUsers)
|
||||
|
||||
# Remove password hashes
|
||||
for user in filteredUsers:
|
||||
if "hashedPassword" in user:
|
||||
del user["hashedPassword"]
|
||||
|
||||
return filteredUsers
|
||||
|
||||
def getUsersByMandate(self, mandateId: int) -> List[Dict[str, Any]]:
|
||||
"""Returns users for a specific mandate if user has access."""
|
||||
# First check if user has access to the mandate
|
||||
mandate = self.getMandate(mandateId)
|
||||
if not mandate:
|
||||
return []
|
||||
|
||||
# Get users for this mandate
|
||||
users = self.db.getRecordset("users", recordFilter={"mandateId": mandateId})
|
||||
filteredUsers = self._uam("users", users)
|
||||
|
||||
# Remove password hashes
|
||||
for user in filteredUsers:
|
||||
if "hashedPassword" in user:
|
||||
del user["hashedPassword"]
|
||||
|
||||
return filteredUsers
|
||||
|
||||
def getUserByUsername(self, username: str) -> Optional[Dict[str, Any]]:
|
||||
"""Returns a user by username."""
|
||||
users = self.db.getRecordset("users")
|
||||
for user in users:
|
||||
if user.get("username") == username:
|
||||
return user
|
||||
return None
|
||||
|
||||
def getUser(self, userId: int) -> Optional[Dict[str, Any]]:
|
||||
"""Returns a user by ID if user has access."""
|
||||
users = self.db.getRecordset("users", recordFilter={"id": userId})
|
||||
if not users:
|
||||
return None
|
||||
|
||||
filteredUsers = self._uam("users", users)
|
||||
if not filteredUsers:
|
||||
return None
|
||||
|
||||
user = filteredUsers[0]
|
||||
|
||||
# Remove password hash
|
||||
if "hashedPassword" in user:
|
||||
userCopy = user.copy()
|
||||
del userCopy["hashedPassword"]
|
||||
return userCopy
|
||||
|
||||
return user
|
||||
|
||||
def createUser(self, username: str, password: str, email: str = None,
|
||||
fullName: str = None, language: str = "de", mandateId: int = None,
|
||||
disabled: bool = False, privilege: str = "user") -> Dict[str, Any]:
|
||||
"""Creates a new user if current user has permission."""
|
||||
# Check if the username already exists
|
||||
existingUser = self.getUserByUsername(username)
|
||||
if existingUser:
|
||||
raise ValueError(f"User '{username}' already exists")
|
||||
|
||||
# Use the provided mandateId or the current context
|
||||
userMandateId = mandateId if mandateId is not None else self.mandateId
|
||||
|
||||
# Check if user has access to the mandate
|
||||
if userMandateId != self.mandateId and self.currentUser.get("privilege") != "sysadmin":
|
||||
raise PermissionError(f"No permission to create users in mandate {userMandateId}")
|
||||
|
||||
if not self._canModify("users"):
|
||||
raise PermissionError("No permission to create users")
|
||||
|
||||
# Check privilege escalation
|
||||
if (privilege == "sysadmin" or
|
||||
(privilege == "admin" and self.currentUser.get("privilege") == "user")):
|
||||
raise PermissionError(f"Cannot create user with higher privilege: {privilege}")
|
||||
|
||||
userData = {
|
||||
"mandateId": userMandateId,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"fullName": fullName,
|
||||
"disabled": disabled,
|
||||
"language": language,
|
||||
"privilege": privilege,
|
||||
"hashedPassword": self._getPasswordHash(password)
|
||||
}
|
||||
|
||||
createdUser = self.db.recordCreate("users", userData)
|
||||
|
||||
# Remove password hash from the response
|
||||
if "hashedPassword" in createdUser:
|
||||
del createdUser["hashedPassword"]
|
||||
|
||||
return createdUser
|
||||
|
||||
def authenticateUser(self, username: str, password: str) -> Optional[Dict[str, Any]]:
|
||||
"""Authenticates a user by username and password."""
|
||||
# Clear the users table from cache and reload it
|
||||
if "users" in self.db._tablesCache:
|
||||
del self.db._tablesCache["users"]
|
||||
|
||||
# Get fresh user data
|
||||
users = self.db.getRecordset("users")
|
||||
user = next((u for u in users if u.get("username") == username), None)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
if not self._verifyPassword(password, user.get("hashedPassword", "")):
|
||||
return None
|
||||
|
||||
# Check if the user is disabled
|
||||
if user.get("disabled", False):
|
||||
return None
|
||||
|
||||
# Create a copy without password hash
|
||||
authenticatedUser = {**user}
|
||||
if "hashedPassword" in authenticatedUser:
|
||||
del authenticatedUser["hashedPassword"]
|
||||
|
||||
return authenticatedUser
|
||||
|
||||
|
||||
def updateUser(self, userId: int, userData: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Updates a user if current user has permission."""
|
||||
# Check if the user exists and current user has access
|
||||
user = self.getUser(userId)
|
||||
if not user:
|
||||
# Try to get the raw user record for admin access check
|
||||
users = self.db.getRecordset("users", recordFilter={"id": userId})
|
||||
if not users:
|
||||
raise ValueError(f"User with ID {userId} not found")
|
||||
|
||||
# Check if current user is admin/sysadmin
|
||||
if not self._canModify("users", userId):
|
||||
raise PermissionError(f"No permission to update user {userId}")
|
||||
|
||||
user = users[0]
|
||||
|
||||
# Check privilege escalation
|
||||
if "privilege" in userData:
|
||||
currentPrivilege = self.currentUser.get("privilege")
|
||||
targetPrivilege = userData["privilege"]
|
||||
|
||||
if (targetPrivilege == "sysadmin" and currentPrivilege != "sysadmin") or (
|
||||
targetPrivilege == "admin" and currentPrivilege == "user"):
|
||||
raise PermissionError(f"Cannot escalate privilege to {targetPrivilege}")
|
||||
|
||||
# If the password is being changed, hash it
|
||||
if "password" in userData:
|
||||
userData["hashedPassword"] = self._getPasswordHash(userData["password"])
|
||||
del userData["password"]
|
||||
|
||||
# Update the user
|
||||
updatedUser = self.db.recordModify("users", userId, userData)
|
||||
|
||||
# Remove password hash from the response
|
||||
if "hashedPassword" in updatedUser:
|
||||
del updatedUser["hashedPassword"]
|
||||
|
||||
return updatedUser
|
||||
|
||||
def disableUser(self, userId: int) -> Dict[str, Any]:
|
||||
"""Disables a user if current user has permission."""
|
||||
return self.updateUser(userId, {"disabled": True})
|
||||
|
||||
def enableUser(self, userId: int) -> Dict[str, Any]:
|
||||
"""Enables a user if current user has permission."""
|
||||
return self.updateUser(userId, {"disabled": False})
|
||||
|
||||
def _deleteUserReferencedData(self, userId: int) -> None:
|
||||
"""Deletes all data associated with a user."""
|
||||
# Delete user attributes
|
||||
try:
|
||||
attributes = self.db.getRecordset("attributes", recordFilter={"userId": userId})
|
||||
for attribute in attributes:
|
||||
self.db.recordDelete("attributes", attribute["id"])
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting attributes for user {userId}: {e}")
|
||||
|
||||
logger.info(f"All referenced data for user {userId} has been deleted")
|
||||
|
||||
def deleteUser(self, userId: int) -> bool:
|
||||
"""Deletes a user and all associated data if current user has permission."""
|
||||
# Check if the user exists
|
||||
users = self.db.getRecordset("users", recordFilter={"id": userId})
|
||||
if not users:
|
||||
return False
|
||||
|
||||
# Check if current user has permission
|
||||
if not self._canModify("users", userId):
|
||||
raise PermissionError(f"No permission to delete user {userId}")
|
||||
|
||||
# Check if it's the initial user
|
||||
initialUserId = self.getInitialId("users")
|
||||
if initialUserId is not None and userId == initialUserId:
|
||||
logger.warning("Attempt to delete the Root Admin was prevented")
|
||||
return False
|
||||
|
||||
# Delete all data associated with the user
|
||||
self._deleteUserReferencedData(userId)
|
||||
|
||||
# Delete the user
|
||||
success = self.db.recordDelete("users", userId)
|
||||
|
||||
if success:
|
||||
logger.info(f"User with ID {userId} was successfully deleted")
|
||||
else:
|
||||
logger.error(f"Error deleting user with ID {userId}")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
# Singleton factory for GatewayInterface instances per context
|
||||
_gatewayInterfaces = {}
|
||||
|
||||
def getGatewayInterface(mandateId: int = None, userId: int = None) -> GatewayInterface:
|
||||
"""
|
||||
Returns a GatewayInterface instance for the specified context.
|
||||
Reuses existing instances.
|
||||
"""
|
||||
contextKey = f"{mandateId}_{userId}"
|
||||
if contextKey not in _gatewayInterfaces:
|
||||
_gatewayInterfaces[contextKey] = GatewayInterface(mandateId, userId)
|
||||
return _gatewayInterfaces[contextKey]
|
||||
|
||||
# Initialize an instance
|
||||
getGatewayInterface()
|
||||
123
static/43_defAttributes.py
Normal file
123
static/43_defAttributes.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
from pydantic import BaseModel, Field
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
# Define the model for attribute definitions
|
||||
class AttributeDefinition(BaseModel):
|
||||
name: str
|
||||
label: str
|
||||
type: str
|
||||
required: bool = False
|
||||
placeholder: Optional[str] = None
|
||||
defaultValue: Optional[Any] = None
|
||||
options: Optional[List[Dict[str, Any]]] = None
|
||||
editable: bool = True
|
||||
visible: bool = True
|
||||
order: int = 0
|
||||
validation: Optional[Dict[str, Any]] = None
|
||||
helpText: Optional[str] = None
|
||||
|
||||
# Helper classes for type mapping
|
||||
typeMappings = {
|
||||
"int": "number",
|
||||
"str": "string",
|
||||
"float": "number",
|
||||
"bool": "boolean",
|
||||
"List[int]": "array",
|
||||
"List[str]": "array",
|
||||
"Dict[str, Any]": "object",
|
||||
"Optional[str]": "string",
|
||||
"Optional[int]": "number",
|
||||
"Optional[Dict[str, Any]]": "object"
|
||||
}
|
||||
|
||||
# Special field types based on naming conventions
|
||||
specialFieldTypes = {
|
||||
"content": "textarea",
|
||||
"description": "textarea",
|
||||
"instructions": "textarea",
|
||||
"password": "password",
|
||||
"email": "email",
|
||||
"workspaceId": "select",
|
||||
"agentId": "select",
|
||||
"type": "select"
|
||||
}
|
||||
|
||||
# Function to convert a Pydantic model into attribute definitions
|
||||
def getModelAttributes(modelClass, userLanguage="de"):
|
||||
"""
|
||||
Converts a Pydantic model into a list of AttributeDefinition objects
|
||||
"""
|
||||
attributes = []
|
||||
|
||||
# Go through all fields in the model
|
||||
for i, (fieldName, field) in enumerate(modelClass.__fields__.items()):
|
||||
# Skip internal fields
|
||||
if fieldName.startswith('_') or fieldName in ["label", "fieldLabels"]:
|
||||
continue
|
||||
|
||||
# Determine the field type
|
||||
fieldType = typeMappings.get(str(field.type_), "string")
|
||||
|
||||
# Check for special field types
|
||||
if fieldName in specialFieldTypes:
|
||||
fieldType = specialFieldTypes[fieldName]
|
||||
|
||||
# Get the label (if available)
|
||||
fieldLabel = fieldName.replace('_', ' ').capitalize()
|
||||
if hasattr(modelClass, 'fieldLabels') and fieldName in modelClass.fieldLabels:
|
||||
labelObj = modelClass.fieldLabels[fieldName]
|
||||
fieldLabel = labelObj.getLabel(userLanguage)
|
||||
|
||||
# Determine default values and required status
|
||||
required = field.required
|
||||
defaultValue = field.default if not field.required else None
|
||||
|
||||
# Check for validation rules
|
||||
validation = None
|
||||
if field.validators:
|
||||
validation = {"hasValidators": True}
|
||||
|
||||
# Placeholder text
|
||||
placeholder = f"Please enter {fieldLabel}"
|
||||
|
||||
# Special options for Select fields
|
||||
options = None
|
||||
if fieldType == "select":
|
||||
if fieldName == "type" and modelClass.__name__ == "Agent":
|
||||
options = [
|
||||
{"value": "Analysis", "label": "Analysis"},
|
||||
{"value": "Transformation", "label": "Transformation"},
|
||||
{"value": "Generation", "label": "Generation"},
|
||||
{"value": "Classification", "label": "Classification"},
|
||||
{"value": "Custom", "label": "Custom"}
|
||||
]
|
||||
|
||||
# Extract description from Field object
|
||||
description = None
|
||||
# Try to get description from various possible sources
|
||||
if hasattr(field, 'field_info') and hasattr(field.field_info, 'description'):
|
||||
description = field.field_info.description
|
||||
elif hasattr(field, 'description'):
|
||||
description = field.description
|
||||
elif hasattr(field, 'schema') and hasattr(field.schema, 'description'):
|
||||
description = field.schema.description
|
||||
|
||||
# Create attribute definition
|
||||
attrDef = AttributeDefinition(
|
||||
name=fieldName,
|
||||
label=fieldLabel,
|
||||
type=fieldType,
|
||||
required=required,
|
||||
placeholder=placeholder,
|
||||
defaultValue=defaultValue,
|
||||
options=options,
|
||||
editable=fieldName not in ["id", "mandateId", "userId", "createdAt", "uploadDate"],
|
||||
visible=fieldName not in ["hashedPassword", "mandateId", "userId"],
|
||||
order=i,
|
||||
validation=validation,
|
||||
helpText=description or "" # Set empty string as default value if no description found
|
||||
)
|
||||
|
||||
attributes.append(attrDef)
|
||||
|
||||
return attributes
|
||||
42
static/44_email_preview.html
Normal file
42
static/44_email_preview.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Preview: Anfrage zur Terminverschiebung und Dokumentenanhang</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }
|
||||
.email-container { max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
|
||||
.email-header { background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }
|
||||
.email-content { padding: 20px; }
|
||||
.email-footer { background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
||||
.field { margin-bottom: 10px; }
|
||||
.field-label { font-weight: bold; color: #555; }
|
||||
.email-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<h2>Email Template Preview</h2>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
<div class="field">
|
||||
<div class="field-label">To:</div>
|
||||
<div>peter.muster@domain.com</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">Subject:</div>
|
||||
<div>Anfrage zur Terminverschiebung und Dokumentenanhang</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es möglich wäre, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.</p><p>Ich freue mich auf Ihre Rückmeldung.</p><p>Mit freundlichen Grüßen,<br>[Ihr Name]</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
static/45_email_template.json
Normal file
6
static/45_email_template.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"recipient": "peter.muster@domain.com",
|
||||
"subject": "Anfrage zur Terminverschiebung und Dokumentenanhang",
|
||||
"plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.\n\nIch freue mich auf Ihre R\u00fcckmeldung.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]",
|
||||
"htmlBody": "<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die Dokumente 'gatewayInterface.py' und 'defAttributes.py'.</p><p>Ich freue mich auf Ihre R\u00fcckmeldung.</p><p>Mit freundlichen Gr\u00fc\u00dfen,<br>[Ihr Name]</p>"
|
||||
}
|
||||
42
static/46_email_preview.html
Normal file
42
static/46_email_preview.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Preview: Wichtige Dokumente: gatewayInterface.py und defAttributes.py</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }
|
||||
.email-container { max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
|
||||
.email-header { background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }
|
||||
.email-content { padding: 20px; }
|
||||
.email-footer { background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
||||
.field { margin-bottom: 10px; }
|
||||
.field-label { font-weight: bold; color: #555; }
|
||||
.email-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<h2>Email Template Preview</h2>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
<div class="field">
|
||||
<div class="field-label">To:</div>
|
||||
<div>recipient@example.com</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">Subject:</div>
|
||||
<div>Wichtige Dokumente: gatewayInterface.py und defAttributes.py</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<html><body><p>Sehr geehrte Damen und Herren,</p><p>anbei finden Sie die angeforderten Dokumente <strong>'gatewayInterface.py'</strong> und <strong>'defAttributes.py'</strong>. Bitte überprüfen Sie die Anhänge und lassen Sie uns wissen, falls Sie weitere Informationen benötigen.</p><p>Mit freundlichen Grüßen,<br>Ihr Team</p></body></html>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
static/47_email_template.json
Normal file
6
static/47_email_template.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"recipient": "recipient@example.com",
|
||||
"subject": "Wichtige Dokumente: gatewayInterface.py und defAttributes.py",
|
||||
"plainBody": "Sehr geehrte Damen und Herren,\n\nanbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte \u00fcberpr\u00fcfen Sie die Anh\u00e4nge und lassen Sie uns wissen, falls Sie weitere Informationen ben\u00f6tigen.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Team",
|
||||
"htmlBody": "<html><body><p>Sehr geehrte Damen und Herren,</p><p>anbei finden Sie die angeforderten Dokumente <strong>'gatewayInterface.py'</strong> und <strong>'defAttributes.py'</strong>. Bitte \u00fcberpr\u00fcfen Sie die Anh\u00e4nge und lassen Sie uns wissen, falls Sie weitere Informationen ben\u00f6tigen.</p><p>Mit freundlichen Gr\u00fc\u00dfen,<br>Ihr Team</p></body></html>"
|
||||
}
|
||||
42
static/48_email_preview.html
Normal file
42
static/48_email_preview.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Preview: Resending Requested Documents</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }
|
||||
.email-container { max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
|
||||
.email-header { background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }
|
||||
.email-content { padding: 20px; }
|
||||
.email-footer { background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
||||
.field { margin-bottom: 10px; }
|
||||
.field-label { font-weight: bold; color: #555; }
|
||||
.email-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<h2>Email Template Preview</h2>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
<div class="field">
|
||||
<div class="field-label">To:</div>
|
||||
<div>recipient@example.com</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">Subject:</div>
|
||||
<div>Resending Requested Documents</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<html><body><p>Sehr geehrte/r Empfänger/in,</p><p>anbei finden Sie die angeforderten Dokumente <strong>'gatewayInterface.py'</strong> und <strong>'defAttributes.py'</strong>. Bitte lassen Sie mich wissen, falls Sie weitere Informationen benötigen.</p><p>Mit freundlichen Grüßen,<br>Ihr Team</p></body></html>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
static/49_email_template.json
Normal file
6
static/49_email_template.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"recipient": "recipient@example.com",
|
||||
"subject": "Resending Requested Documents",
|
||||
"plainBody": "Sehr geehrte/r Empf\u00e4nger/in,\n\nanbei finden Sie die angeforderten Dokumente 'gatewayInterface.py' und 'defAttributes.py'. Bitte lassen Sie mich wissen, falls Sie weitere Informationen ben\u00f6tigen.\n\nMit freundlichen Gr\u00fc\u00dfen,\nIhr Team",
|
||||
"htmlBody": "<html><body><p>Sehr geehrte/r Empf\u00e4nger/in,</p><p>anbei finden Sie die angeforderten Dokumente <strong>'gatewayInterface.py'</strong> und <strong>'defAttributes.py'</strong>. Bitte lassen Sie mich wissen, falls Sie weitere Informationen ben\u00f6tigen.</p><p>Mit freundlichen Gr\u00fc\u00dfen,<br>Ihr Team</p></body></html>"
|
||||
}
|
||||
BIN
static/50_LF-Nutshell.png
Normal file
BIN
static/50_LF-Nutshell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
BIN
static/51_PowerOn NDA 2025.pdf
Normal file
BIN
static/51_PowerOn NDA 2025.pdf
Normal file
Binary file not shown.
42
static/52_email_preview.html
Normal file
42
static/52_email_preview.html
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Preview: Anfrage zur Terminverschiebung und Dokumente im Anhang</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; }
|
||||
.email-container { max-width: 600px; margin: 20px auto; background-color: white; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
|
||||
.email-header { background-color: #f0f0f0; padding: 15px; border-bottom: 1px solid #ddd; }
|
||||
.email-content { padding: 20px; }
|
||||
.email-footer { background-color: #f0f0f0; padding: 15px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
||||
.field { margin-bottom: 10px; }
|
||||
.field-label { font-weight: bold; color: #555; }
|
||||
.email-body { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="email-container">
|
||||
<div class="email-header">
|
||||
<h2>Email Template Preview</h2>
|
||||
</div>
|
||||
<div class="email-content">
|
||||
<div class="field">
|
||||
<div class="field-label">To:</div>
|
||||
<div>peter.muster@domain.com</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="field-label">Subject:</div>
|
||||
<div>Anfrage zur Terminverschiebung und Dokumente im Anhang</div>
|
||||
</div>
|
||||
<div class="email-body">
|
||||
<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es möglich wäre, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die relevanten Dokumente.</p><p>Vielen Dank für Ihre Flexibilität und Unterstützung.</p><p>Mit freundlichen Grüßen,<br>[Ihr Name]</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-footer">
|
||||
<p>This is a preview of the email template. The actual email may appear differently in various email clients.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
6
static/53_email_template.json
Normal file
6
static/53_email_template.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"recipient": "peter.muster@domain.com",
|
||||
"subject": "Anfrage zur Terminverschiebung und Dokumente im Anhang",
|
||||
"plainBody": "Sehr geehrter Herr Muster,\n\nich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die relevanten Dokumente.\n\nVielen Dank f\u00fcr Ihre Flexibilit\u00e4t und Unterst\u00fctzung.\n\nMit freundlichen Gr\u00fc\u00dfen,\n\n[Ihr Name]",
|
||||
"htmlBody": "<p>Sehr geehrter Herr Muster,</p><p>ich hoffe, diese Nachricht trifft Sie wohl. Ich schreibe Ihnen, um zu fragen, ob es m\u00f6glich w\u00e4re, unseren Termin von 10 Uhr auf Freitag zu verschieben. Anbei finden Sie die relevanten Dokumente.</p><p>Vielen Dank f\u00fcr Ihre Flexibilit\u00e4t und Unterst\u00fctzung.</p><p>Mit freundlichen Gr\u00fc\u00dfen,<br>[Ihr Name]</p>"
|
||||
}
|
||||
Loading…
Reference in a new issue