- Consent giving UI introduced
- Possibility to view the document version the consent is given to - Blocking functionality is no valid consent
This commit is contained in:
@@ -135,14 +135,14 @@ def view_content(content_type):
|
||||
titles = {
|
||||
'changelog': 'Release Notes',
|
||||
'terms': 'Terms & Conditions',
|
||||
'dpadpa': 'Data Privacy Agreement',
|
||||
'dpa': 'Data Privacy Agreement',
|
||||
# Voeg andere types toe indien nodig
|
||||
}
|
||||
|
||||
descriptions = {
|
||||
'changelog': 'EveAI Release Notes',
|
||||
'terms': "Terms & Conditions for using AskEveAI's Evie",
|
||||
'dpadpa': "Data Privacy Agreement for AskEveAI's Evie",
|
||||
'dpa': "Data Privacy Agreement for AskEveAI's Evie",
|
||||
# Voeg andere types toe indien nodig
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,3 @@ class EditConsentVersionForm(FlaskForm):
|
||||
consent_valid_from = DateField('Consent Valid From', id='form-control datepicker', validators=[DataRequired()])
|
||||
consent_valid_to = DateField('Consent Valid To', id='form-control datepicker', validators=[Optional()])
|
||||
|
||||
|
||||
class TenantConsentForm(FlaskForm):
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from wtforms import BooleanField
|
||||
|
||||
from common.models.user import User, Tenant, Role, TenantDomain, TenantProject, PartnerTenant, TenantMake, \
|
||||
ConsentVersion
|
||||
from common.extensions import db, security, minio_client, simple_encryption, cache_manager
|
||||
from common.extensions import db, security, minio_client, simple_encryption, cache_manager, content_manager
|
||||
from common.utils.dynamic_field_utils import create_default_config_from_type_config
|
||||
from common.utils.security_utils import send_confirmation_email, send_reset_email
|
||||
from config.type_defs.service_types import SERVICE_TYPES
|
||||
@@ -31,36 +31,12 @@ from eveai_app.views.list_views.user_list_views import get_tenants_list_view, ge
|
||||
get_tenant_domains_list_view, get_tenant_projects_list_view, get_tenant_makes_list_view, \
|
||||
get_tenant_partner_services_list_view, get_consent_versions_list_view
|
||||
from eveai_app.views.list_views.list_view_utils import render_list_view
|
||||
from common.services.user.consent_service import ConsentService
|
||||
from common.models.user import ConsentStatus
|
||||
|
||||
user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
|
||||
|
||||
|
||||
# --- Consent flow placeholder views ---
|
||||
@user_bp.route('/consent/tenant', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def tenant_consent():
|
||||
# Placeholder view; UI can be implemented in templates
|
||||
return render_template('user/tenant_consent.html') if current_app.jinja_env.loader else "Tenant Consent"
|
||||
|
||||
|
||||
@user_bp.route('/consent/no_access', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def no_consent():
|
||||
return render_template('user/no_consent.html') if current_app.jinja_env.loader else "Consent required - contact your admin"
|
||||
|
||||
|
||||
@user_bp.route('/consent/tenant_renewal', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def tenant_consent_renewal():
|
||||
return render_template('user/tenant_consent_renewal.html') if current_app.jinja_env.loader else "Tenant Consent Renewal"
|
||||
|
||||
|
||||
@user_bp.route('/consent/renewal', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def consent_renewal():
|
||||
return render_template('user/consent_renewal.html') if current_app.jinja_env.loader else "Consent renewal in progress"
|
||||
|
||||
|
||||
@user_bp.before_request
|
||||
def log_before_request():
|
||||
current_app.logger.debug(f'Before request: {request.path} =====================================')
|
||||
@@ -723,6 +699,7 @@ def tenant_partner_services():
|
||||
return render_list_view('list_view.html', **config)
|
||||
|
||||
|
||||
# Consent Version Management ----------------------------------------------------------------------
|
||||
@user_bp.route('/consent_versions', methods=['GET', 'POST'])
|
||||
@roles_accepted('Super User')
|
||||
def consent_versions():
|
||||
@@ -805,13 +782,138 @@ def edit_consent_version(consent_version_id):
|
||||
return render_template('user/edit_consent_version.html', form=form, consent_version_id=consent_version_id)
|
||||
|
||||
|
||||
@user_bp.route('/tenant_consent', methods=['GET', 'POST'])
|
||||
# Tenant Consent Management -----------------------------------------------------------------------
|
||||
@user_bp.route('/consent/tenant', methods=['GET'])
|
||||
@roles_accepted('Tenant Admin')
|
||||
def tenant_consent():
|
||||
dpa_consent = BooleanField("DPA Consent", default=False)
|
||||
t_c_consent = BooleanField("T&C Consent", default=False)
|
||||
# Overview for current session tenant
|
||||
tenant_id = session.get('tenant', {}).get('id') or getattr(current_user, 'tenant_id', None)
|
||||
if not tenant_id:
|
||||
flash('No tenant context.', 'danger')
|
||||
return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True))
|
||||
types = ConsentService.get_required_consent_types()
|
||||
statuses = [ConsentService.evaluate_type_status(tenant_id, t) for t in types]
|
||||
if current_app.jinja_env.loader:
|
||||
return render_template('user/tenant_consent.html', statuses=statuses, tenant_id=tenant_id)
|
||||
# Fallback text if no templates
|
||||
lines = [f"{s.consent_type}: {s.status} (active={s.active_version}, last={s.last_version})" for s in statuses]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@user_bp.route('/consent/no_access', methods=['GET'])
|
||||
def no_consent():
|
||||
return render_template('user/no_consent.html') if current_app.jinja_env.loader else "Consent required - contact your admin"
|
||||
|
||||
|
||||
@user_bp.route('/consent/tenant_renewal', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def tenant_consent_renewal():
|
||||
# Show renewal statuses only
|
||||
tenant_id = session.get('tenant', {}).get('id') or getattr(current_user, 'tenant_id', None)
|
||||
types = ConsentService.get_required_consent_types()
|
||||
statuses = [s for s in [ConsentService.evaluate_type_status(tenant_id, t) for t in types] if s.status != ConsentStatus.CONSENTED]
|
||||
if current_app.jinja_env.loader:
|
||||
return render_template('user/tenant_consent_renewal.html', statuses=statuses, tenant_id=tenant_id)
|
||||
return "\n".join([f"{s.consent_type}: {s.status}" for s in statuses])
|
||||
|
||||
|
||||
@user_bp.route('/consent/renewal', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def consent_renewal():
|
||||
return render_template('user/consent_renewal.html') if current_app.jinja_env.loader else "Consent renewal in progress"
|
||||
|
||||
|
||||
@user_bp.route('/tenants/<int:tenant_id>/consents', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin')
|
||||
def view_tenant_consents(tenant_id: int):
|
||||
# Authorization: Tenant Admin for own tenant or Management Partner
|
||||
allowed, mode, _, _ = ConsentService.can_consent_on_behalf(tenant_id)
|
||||
if not (allowed or current_user.has_roles('Super User')):
|
||||
flash('Not authorized to view consents for this tenant', 'danger')
|
||||
return redirect(prefixed_url_for('user_bp.tenants', for_redirect=True))
|
||||
types = ConsentService.get_required_consent_types()
|
||||
statuses = [ConsentService.evaluate_type_status(tenant_id, t) for t in types]
|
||||
if current_app.jinja_env.loader:
|
||||
return render_template('user/tenant_consents_overview.html', statuses=statuses, tenant_id=tenant_id)
|
||||
return "\n".join([f"{s.consent_type}: {s.status}" for s in statuses])
|
||||
|
||||
|
||||
@user_bp.route('/tenants/<int:tenant_id>/consents/<string:consent_type>/accept', methods=['POST'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def accept_tenant_consent(tenant_id: int, consent_type: str):
|
||||
try:
|
||||
tc = ConsentService.record_consent(tenant_id, consent_type)
|
||||
flash(f"Consent for {consent_type} recorded (version {tc.consent_version})", 'success')
|
||||
except PermissionError:
|
||||
flash('Not authorized to accept this consent for the tenant', 'danger')
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Failed to record consent: {e}")
|
||||
flash('Failed to record consent', 'danger')
|
||||
if current_user.has_roles('Tenant Admin'):
|
||||
return redirect(prefixed_url_for('user_bp.tenant_consent', for_redirect=True))
|
||||
else:
|
||||
return redirect(prefixed_url_for('user_bp.view_tenant_consents', tenant_id=tenant_id, for_redirect=True))
|
||||
|
||||
|
||||
@user_bp.route('/consents/<path:consent_type>/<string:version>/view', methods=['GET'])
|
||||
@roles_accepted('Super User', 'Partner Admin', 'Tenant Admin')
|
||||
def view_consent_markdown(consent_type: str, version: str):
|
||||
"""Render the consent markdown for a given type and version as an HTML fragment, using content_manager."""
|
||||
try:
|
||||
current_app.logger.debug(f"Rendering markdown for {consent_type} version {version}")
|
||||
# Validate type against config
|
||||
valid_types = set(ConsentService.get_required_consent_types())
|
||||
if consent_type not in valid_types:
|
||||
for t in valid_types:
|
||||
if t.lower() == consent_type.lower():
|
||||
consent_type = t
|
||||
break
|
||||
if consent_type not in valid_types:
|
||||
current_app.logger.warning(f"Unknown consent type requested for view: {consent_type}")
|
||||
return (render_template('user/partials/consent_markdown_fragment.html', markdown_content=f"Unknown consent type: {consent_type}"), 404)
|
||||
|
||||
# Version must exist in ConsentVersion for the type
|
||||
cv = ConsentVersion.query.filter_by(consent_type=consent_type, consent_version=version).first()
|
||||
if not cv:
|
||||
current_app.logger.warning(f"Unknown consent version requested: type={consent_type}, version={version}")
|
||||
return (render_template('user/partials/consent_markdown_fragment.html', markdown_content=f"Document not found for version {version}"), 404)
|
||||
|
||||
# Map consent type to content_manager content_type
|
||||
type_map = {
|
||||
'Data Privacy Agreement': 'dpa',
|
||||
'Terms & Conditions': 'terms',
|
||||
}
|
||||
content_type = type_map.get(consent_type)
|
||||
if not content_type:
|
||||
current_app.logger.warning(f"No content_type mapping for consent type {consent_type}")
|
||||
return (render_template('user/partials/consent_markdown_fragment.html', markdown_content=f"Unknown content mapping for {consent_type}"), 404)
|
||||
|
||||
# Parse major.minor and patch from version (e.g., 1.2.3 -> 1.2 and 1.2.3)
|
||||
parts = version.split('.')
|
||||
current_app.logger.debug(f"Parts of version {version}: {parts}")
|
||||
if len(parts) < 3:
|
||||
current_app.logger.warning(f"Version does not follow a.b.c pattern: {version}")
|
||||
major_minor = '.'.join(parts[:2]) if len(parts) >= 2 else version
|
||||
patch = ''
|
||||
else:
|
||||
major_minor = '.'.join(parts[:2])
|
||||
patch = parts[2]
|
||||
|
||||
current_app.logger.debug(f"Retrieving markdown for {consent_type}/{content_type} for major.minor={major_minor} and patch={patch} ")
|
||||
|
||||
# Use content_manager to read content
|
||||
content_data = content_manager.read_content(content_type, major_minor, patch)
|
||||
if not content_data or not content_data.get('content'):
|
||||
markdown_content = f"# Document not found\nThe consent document for {consent_type} version {version} could not be located."
|
||||
status = 404
|
||||
else:
|
||||
markdown_content = content_data['content']
|
||||
status = 200
|
||||
|
||||
return render_template('user/partials/consent_markdown_fragment.html', markdown_content=markdown_content), status
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error in view_consent_markdown: {e}")
|
||||
return (render_template('user/partials/consent_markdown_fragment.html', markdown_content="Unexpected error rendering document."), 500)
|
||||
|
||||
|
||||
def reset_uniquifier(user):
|
||||
|
||||
Reference in New Issue
Block a user