mirror of
https://framagit.org/framasoft/framaspace/argos.git
synced 2025-04-28 09:52:38 +02:00
✨🛂 — Allow partial or total anonymous access to web interface (fix #63)
This commit is contained in:
parent
841f8638de
commit
da221b856b
8 changed files with 103 additions and 7 deletions
|
@ -13,6 +13,7 @@
|
|||
BREAKING CHANGE: `mo` is no longer accepted for declaring a duration in month in the configuration
|
||||
You need to use `M`, `month` or `months`
|
||||
- ✨ - Allow to choose a frequency smaller than a minute
|
||||
- ✨🛂 — Allow partial or total anonymous access to web interface (#63)
|
||||
|
||||
## 0.5.0
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
general:
|
||||
# Except for frequency and recheck_delay settings, changes in general
|
||||
# section of the configuration will need a restart of argos server.
|
||||
db:
|
||||
# The database URL, as defined in SQLAlchemy docs :
|
||||
# https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
|
||||
|
@ -28,6 +30,12 @@ general:
|
|||
# If not present, the "Remember me" feature is not available
|
||||
# remember_me_duration: "1M"
|
||||
|
||||
# Unauthenticated access
|
||||
# If can grant an unauthenticated access to the dashboard or to all pages
|
||||
# To do so, choose either "dashboard", or "all"
|
||||
# If not present, all pages needs authentication
|
||||
# unauthenticated_access: "all"
|
||||
|
||||
# Default delay for checks.
|
||||
# Can be superseeded in domain configuration.
|
||||
# For ex., to run checks every 5 minutes:
|
||||
|
|
|
@ -27,6 +27,7 @@ from argos.schemas.utils import Method
|
|||
|
||||
Severity = Literal["warning", "error", "critical", "unknown"]
|
||||
Environment = Literal["dev", "test", "production"]
|
||||
Unauthenticated = Literal["dashboard", "all"]
|
||||
SQLiteDsn = Annotated[
|
||||
Url,
|
||||
UrlConstraints(
|
||||
|
@ -190,6 +191,7 @@ class General(BaseModel):
|
|||
cookie_secret: str
|
||||
session_duration: int = 10080 # 7 days
|
||||
remember_me_duration: int | None = None
|
||||
unauthenticated_access: Unauthenticated | None = None
|
||||
frequency: float
|
||||
recheck_delay: float | None = None
|
||||
root_path: str = ""
|
||||
|
|
|
@ -100,7 +100,7 @@ def setup_database(appli):
|
|||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
||||
def create_manager(cookie_secret):
|
||||
def create_manager(cookie_secret: str) -> LoginManager:
|
||||
if cookie_secret == "foo_bar_baz":
|
||||
logger.warning(
|
||||
"You should change the cookie_secret secret in your configuration file."
|
||||
|
|
|
@ -18,6 +18,9 @@ def get_config(request: Request):
|
|||
|
||||
|
||||
async def get_manager(request: Request) -> LoginManager:
|
||||
if request.app.state.config.general.unauthenticated_access is not None:
|
||||
return await request.app.state.manager.optional(request)
|
||||
|
||||
return await request.app.state.manager(request)
|
||||
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ from sqlalchemy.orm import Session
|
|||
from argos.checks.base import Status
|
||||
from argos.schemas import Config
|
||||
from argos.server import queries
|
||||
from argos.server.exceptions import NotAuthenticatedException
|
||||
from argos.server.models import Result, Task, User
|
||||
from argos.server.routes.dependencies import get_config, get_db, get_manager
|
||||
|
||||
|
@ -33,6 +34,12 @@ async def login_view(
|
|||
msg: str | None = None,
|
||||
config: Config = Depends(get_config),
|
||||
):
|
||||
if config.general.unauthenticated_access == "all":
|
||||
return RedirectResponse(
|
||||
request.url_for("get_severity_counts_view"),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
token = request.cookies.get("access-token")
|
||||
if token is not None and token != "":
|
||||
manager = request.app.state.manager
|
||||
|
@ -66,6 +73,12 @@ async def post_login(
|
|||
rememberme: Annotated[str | None, Form()] = None,
|
||||
config: Config = Depends(get_config),
|
||||
):
|
||||
if config.general.unauthenticated_access == "all":
|
||||
return RedirectResponse(
|
||||
request.url_for("get_severity_counts_view"),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
username = data.username
|
||||
user = await queries.get_user(db, username)
|
||||
invalid_credentials = templates.TemplateResponse(
|
||||
|
@ -103,7 +116,17 @@ async def post_login(
|
|||
|
||||
|
||||
@route.get("/logout")
|
||||
async def logout_view(request: Request, user: User | None = Depends(get_manager)):
|
||||
async def logout_view(
|
||||
request: Request,
|
||||
config: Config = Depends(get_config),
|
||||
user: User | None = Depends(get_manager),
|
||||
):
|
||||
if config.general.unauthenticated_access == "all":
|
||||
return RedirectResponse(
|
||||
request.url_for("get_severity_counts_view"),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
)
|
||||
|
||||
response = RedirectResponse(
|
||||
request.url_for("login_view").include_query_params(msg="logout"),
|
||||
status_code=status.HTTP_303_SEE_OTHER,
|
||||
|
@ -133,6 +156,7 @@ async def get_severity_counts_view(
|
|||
"agents": agents,
|
||||
"auto_refresh_enabled": auto_refresh_enabled,
|
||||
"auto_refresh_seconds": auto_refresh_seconds,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -141,9 +165,14 @@ async def get_severity_counts_view(
|
|||
async def get_domains_view(
|
||||
request: Request,
|
||||
user: User | None = Depends(get_manager),
|
||||
config: Config = Depends(get_config),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Show all tasks and their current state"""
|
||||
if config.general.unauthenticated_access == "dashboard":
|
||||
if user is None:
|
||||
raise NotAuthenticatedException
|
||||
|
||||
tasks = db.query(Task).all()
|
||||
|
||||
domains_severities = defaultdict(list)
|
||||
|
@ -184,6 +213,7 @@ async def get_domains_view(
|
|||
"last_checks": domains_last_checks,
|
||||
"total_task_count": len(tasks),
|
||||
"agents": agents,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -193,12 +223,23 @@ async def get_domain_tasks_view(
|
|||
request: Request,
|
||||
domain: str,
|
||||
user: User | None = Depends(get_manager),
|
||||
config: Config = Depends(get_config),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Show all tasks attached to a domain"""
|
||||
if config.general.unauthenticated_access == "dashboard":
|
||||
if user is None:
|
||||
raise NotAuthenticatedException
|
||||
|
||||
tasks = db.query(Task).filter(Task.domain.contains(f"//{domain}")).all()
|
||||
return templates.TemplateResponse(
|
||||
"domain.html", {"request": request, "domain": domain, "tasks": tasks}
|
||||
"domain.html",
|
||||
{
|
||||
"request": request,
|
||||
"domain": domain,
|
||||
"tasks": tasks,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -207,12 +248,23 @@ async def get_result_view(
|
|||
request: Request,
|
||||
result_id: int,
|
||||
user: User | None = Depends(get_manager),
|
||||
config: Config = Depends(get_config),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Show the details of a result"""
|
||||
if config.general.unauthenticated_access == "dashboard":
|
||||
if user is None:
|
||||
raise NotAuthenticatedException
|
||||
|
||||
result = db.query(Result).get(result_id)
|
||||
return templates.TemplateResponse(
|
||||
"result.html", {"request": request, "result": result, "error": Status.ERROR}
|
||||
"result.html",
|
||||
{
|
||||
"request": request,
|
||||
"result": result,
|
||||
"error": Status.ERROR,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
@ -225,6 +277,10 @@ async def get_task_results_view(
|
|||
config: Config = Depends(get_config),
|
||||
):
|
||||
"""Show history of a task’s results"""
|
||||
if config.general.unauthenticated_access == "dashboard":
|
||||
if user is None:
|
||||
raise NotAuthenticatedException
|
||||
|
||||
results = (
|
||||
db.query(Result)
|
||||
.filter(Result.task_id == task_id)
|
||||
|
@ -243,6 +299,7 @@ async def get_task_results_view(
|
|||
"task": task,
|
||||
"description": description,
|
||||
"error": Status.ERROR,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -251,9 +308,14 @@ async def get_task_results_view(
|
|||
async def get_agents_view(
|
||||
request: Request,
|
||||
user: User | None = Depends(get_manager),
|
||||
config: Config = Depends(get_config),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Show argos agents and the last time the server saw them"""
|
||||
if config.general.unauthenticated_access == "dashboard":
|
||||
if user is None:
|
||||
raise NotAuthenticatedException
|
||||
|
||||
last_seen = (
|
||||
db.query(Result.agent_id, func.max(Result.submitted_at).label("submitted_at"))
|
||||
.group_by(Result.agent_id)
|
||||
|
@ -261,7 +323,12 @@ async def get_agents_view(
|
|||
)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"agents.html", {"request": request, "last_seen": last_seen}
|
||||
"agents.html",
|
||||
{
|
||||
"request": request,
|
||||
"last_seen": last_seen,
|
||||
"user": user,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -63,6 +63,8 @@
|
|||
Agents
|
||||
</a>
|
||||
</li>
|
||||
{% set unauthenticated_access = request.app.state.config.general.unauthenticated_access %}
|
||||
{% if (user is defined and user is not none) or unauthenticated_access == "all" %}
|
||||
<li>
|
||||
<a href="#"
|
||||
id="reschedule-all"
|
||||
|
@ -72,13 +74,24 @@
|
|||
Reschedule non-ok checks
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user is defined and user is not none %}
|
||||
<li>
|
||||
<a href="{{ url_for('logout_view') }}"
|
||||
class="outline {{ 'contrast' if request.url == url_for('get_agents_view') }}"
|
||||
class="outline }}"
|
||||
role="button">
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
{% elif unauthenticated_access != "all" %}
|
||||
<li>
|
||||
<a href="{{ url_for('login_view') }}"
|
||||
class="outline }}"
|
||||
role="button">
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
|
|
|
@ -276,7 +276,9 @@ Options:
|
|||
|
||||
### Server user management
|
||||
|
||||
To access Argos’ web interface, you need to create at least one user.
|
||||
You can choose to protect Argos’ web interface with a user system, in which case you’ll need to create at least one user.
|
||||
|
||||
See [`unauthenticated_access` in the configuration file](configuration.md) to allow partial or total unauthenticated access to Argos.
|
||||
|
||||
You can manage users only through CLI.
|
||||
|
||||
|
|
Loading…
Reference in a new issue