Skip to content

Feature/admin configurable access denied message#772

Open
vivche wants to merge 3 commits intomicrosoft:Developmentfrom
vivche:feature/admin-configurable-access-denied-message
Open

Feature/admin configurable access denied message#772
vivche wants to merge 3 commits intomicrosoft:Developmentfrom
vivche:feature/admin-configurable-access-denied-message

Conversation

@vivche
Copy link
Contributor

@vivche vivche commented Mar 5, 2026

Overview

Adds a user-facing message shown on the home page when a signed-in user lacks the required roles. Admins can set and update this message in Admin Settings; the value is persisted in Azure Cosmos DB. A stable, hard-coded default remains in place when no admin override is set.

Version Implemented: 0.239.003

Purpose

  • Allow tenant admins to clarify the next steps for users who land in the app but don't have access.
  • Keep default behaviour predictable across environments (no dependency on App Service environment variables).

Technical Specification

  • Default value is defined in application/single_app/functions_settings.py under default_settings['access_denied_message'].
  • A custom Jinja nl2br filter is registered in application/single_app/app.py; it HTML-escapes the value then converts \n characters to <br> tags.
  • The Admin Settings form posts access_denied_message and persists it via update_settings() to Cosmos DB.
  • The home page template reads sanitized app_settings and renders the message through the nl2br filter.

Files Changed

File Change
application/single_app/functions_settings.py Added access_denied_message default value to default_settings.
application/single_app/app.py Registered the nl2br Jinja filter (HTML-escape + newline-to-<br> conversion).
application/single_app/route_frontend_admin_settings.py Added access_denied_message read from POST form data and included in the settings update payload.
application/single_app/templates/admin_settings.html Added the "Access Denied Message" textarea field to the Admin Settings UI.
application/single_app/templates/index.html Replaced hard-coded text with dynamic app_settings.access_denied_message rendered via the nl2br filter.
application/single_app/config.py Version bumped to 0.239.003.

Usage

  1. Navigate to Admin Settings.
  2. Find "Access Denied Message" and enter your text.
  3. Press Enter to create line breaks; they render as visible line breaks in the UI.
  4. Save. The updated message is persisted to Cosmos DB and shown immediately to users without the required roles.

Limitations

  • Typed literal HTML such as <br> entered in the textarea will display as plain text, not a line break, because the filter HTML-escapes the value before processing newlines.
  • Recommended: Use the Enter key for line breaks rather than typing HTML tags.

Testing & Validation

  • Hard-coded default appears when no Cosmos DB override exists.
  • Admin override persists to Cosmos and is used at runtime.
  • Newlines (Enter key) render as <br> line breaks in the UI.
  • Known issue: literal <br> in the stored message displays as text in some input paths.

- Add access_denied_message to default_settings in functions_settings.py
- Persist access_denied_message from Admin Settings form in route_frontend_admin_settings.py
- Add Access Denied Message textarea to Admin Settings UI
- Render dynamic message on index.html for signed-in users lacking required roles

Fix Copilot PR microsoft#557 findings:
- Finding 1: default message in functions_settings.py now matches index.html fallback
  (both use 'Please contact an administrator for access.')
- Finding 2: replaced '| e | replace(\\n, <br>) | safe' filter chain with
  a proper nl2br Jinja filter registered in app.py, avoiding potential
  filter-order confusion flagged by Copilot review
@paullizer paullizer changed the base branch from main to Development March 5, 2026 16:02
paullizer
paullizer previously approved these changes Mar 5, 2026
Copilot AI review requested due to automatic review settings March 5, 2026 16:03
paullizer
paullizer previously approved these changes Mar 5, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an admin-configurable “access denied” message for signed-in users who lack required roles, persisted in Cosmos settings and rendered on the home page with newline support.

Changes:

  • Adds access_denied_message to default application settings (Cosmos-backed).
  • Extends Admin Settings to edit/persist access_denied_message.
  • Adds a Jinja nl2br filter and updates the home page to render the configured message with line breaks.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
application/single_app/functions_settings.py Introduces access_denied_message in default_settings (Cosmos merge default).
application/single_app/route_frontend_admin_settings.py Persists access_denied_message from Admin Settings form submissions.
application/single_app/templates/admin_settings.html Adds the “Access Denied Message” textarea to the Admin Settings UI.
application/single_app/app.py Registers nl2br Jinja filter (escape + newline-to-<br>).
application/single_app/templates/index.html Renders the (sanitized) configured access denied message via nl2br.

…ture

Finding 1 (index.html redundant fallback):
- Removed hardcoded 'or ...' fallback from access_denied_message rendering in
  templates/index.html; default lives exclusively in functions_settings.py and
  is guaranteed present after get_settings() deep-merge

Finding 2 (route silent data loss):
- Changed access_denied_message in route_frontend_admin_settings.py to fall back
  to settings.get('access_denied_message', '') instead of '' so an older/cached
  form submission that omits the field does not wipe the existing stored value

Finding 3 (missing functional regression test):
- Added functional_tests/test_access_denied_message_feature.py (4/4 passing):
  (1) admin_settings.html exposes textarea name=access_denied_message with label
  (2) route uses safe settings.get() fallback, not bare empty string
  (3) index.html renders via nl2br with no inline hardcoded fallback
  (4) functions_settings.py defines a non-empty default value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants