mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-01-28 08:03:36 +00:00
This change prevents the plinth user to become a superuser without
knowing an admin password.
Users module and action script:
- User credentials are now required for the subcommands: create-user,
set-user-password, add-user-to-group (if the group is admin),
remove-user-from-group (if the group is admin), set-user-status,
remove-user (if the removed user is the last admin user.
Note: the web UI doesn't allow to delete last admin user).
- subcommand remove-users requires authentication if the user is last
admin user. Password must be provided through standard input.
- subcommand remove-group: do not allow to remove group 'admin'
- User credentials must be provided using the argument
--auth-user and a passsword must be provided through standard input.
- If there are no users in the admin group, no admin password is
required and if the --auth-user argument is required, it can be an
empty string.
Users web UI:
- An admin needs to enter current password to create and edit a user
and to change user's password.
- Show more detailed error text on exceptions when submitting forms.
- Show page title on the edit and create user pages.
Users unit and functional tests:
- Added a configuration parameters to the pytest configuration file
to set current admin user/password.
- Added a configuration parameter 'ssh_port' to the functional tests.
You can overwrite this with the FREEDOMBOX_SSH_PORT environment
variable. Modified HACKING.md accordingly.
- Added an unit test:
- test changing the password as a non-admin user.
- test invalid admin password input.
- test that removing the admin group fails.
- Capture stdout and stderr in the unit tests when calling an action
script to be able to see more info on exceptions.
- Added functional tests for setting ssh keys and changing passwords
for admin and non-admin users.
- Added a functional test for setting a user as active/inactive.
Changes during review [sunil]:
- Move uncommon functional step definitions to users module from global. This is
keep the common functional step definitions to minimal level and promote when
needed.
- Minor styling changes, flake8 fixes.
- Don't require pampy module when running non-admin tests. This allows tests to
be run from outside the container on the host machine without python3-pam
installed.
- Call the confirm password field 'Authorization Password'. This avoid confusion
with a very common field 'Confirm Password' which essentially means retype
your password to ensure you didn't get it wrong. Add label explaining why the
field exists.
- Don't hard-code /tmp path in test_actions.py. Use tmp_path_factory fixture
provided by pytest.
- Remove unused _get_password_hash() from actions/users.
- Undo splitting ldapgid output before parsing. It does not seem correct and
could introduce problems when field values contain spaces.
Tests performed:
- No failed unit tests (run with and without sudo).
- All 'users' functional tests pass.
- Creating an admin user during the first boot wizard succeeds.
- Creating a user using the web UI with an empty or wrong admin
password fails and with the correct admin password succeeds.
- Editing a user using the web UI with an empty or wrong admin
password fails and with the correct admin password succeeds.
- Changing user's password using the web UI with an empty or wrong
admin password fails and with the correct admin password succeeds.
- Above mentioned user action script commands can't be run without
correct credentials.
- Adding the daemon user to the freedombox-share group succeeds when
installing certain apps (deluge, mldonkey, syncthing, transmission).
Signed-off-by: Veiko Aasa <veiko17@disroot.org>
[sunil: Move uncommon functional step definitions to users module from global]
[sunil: Minor styling changes, flake8 fixes]
[sunil: Don't require pampy module when running non-admin tests]
[sunil: Call the confirm password field 'Authorization Password']
[sunil: Don't hard-code /tmp path in test_actions.py]
[sunil: Remove unused _get_password_hash() from actions/users]
[sunil: Undo splitting ldapgid output before parsing]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
256 lines
11 KiB
HTML
256 lines
11 KiB
HTML
{% load i18n %}
|
|
{% load static %}
|
|
{% load plinth_extras %}
|
|
|
|
{% comment %}
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
{% endcomment %}
|
|
<!doctype html>
|
|
<!--[if lt IE 7 ]> <html class="ie ie6 no-js" lang="en"> <![endif]-->
|
|
<!--[if IE 7 ]> <html class="ie ie7 no-js" lang="en"> <![endif]-->
|
|
<!--[if IE 8 ]> <html class="ie ie8 no-js" lang="en"> <![endif]-->
|
|
<!--[if IE 9 ]> <html class="ie ie9 no-js" lang="en"> <![endif]-->
|
|
<!--[if gt IE 9]><!--> <html class="no-js" lang="en"> <!--<![endif]-->
|
|
<!-- the "no-js" class is for Modernizr -->
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
|
|
<!-- Always force latest IE rendering engine and Chrome Frame -->
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
|
|
|
<!-- Mobile Viewport Fix http://j.mp/mobileviewport & http://davidbcalhoun.com/2010/viewport-metatag
|
|
device-width : Occupy full width of the screen in its current orientation
|
|
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
|
|
maximum-scale = 1.0 retains dimensions instead of zooming in if page width < device width
|
|
-->
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
<!-- R2D2, you know better than to trust a strange computer! -->
|
|
<meta name="robots" content="noindex, nofollow, noarchive" />
|
|
<meta name="googlebot" content="noindex, nofollow, noarchive, nosnippet, noodp, noimageindex, notranslate" />
|
|
<meta name="msnbot" content="noindex, nofollow, noarchive, noodp" />
|
|
<meta name="slurp" content="noindex, nofollow, noarchive, noodp, noydir" />
|
|
<meta name="description"
|
|
content="{% blocktrans trimmed %}
|
|
Core functionality and web interface for {{ box_name }}
|
|
{% endblocktrans %}" />
|
|
{% block title %}
|
|
<title>
|
|
{% if title %} {{ title }} {% else %} {{ box_name }} {% endif %}
|
|
</title>
|
|
{% endblock %}
|
|
|
|
<!-- Favicon. Works with IE >= 11 and other GUI browsers -->
|
|
<link rel="icon" type="image/png"
|
|
href="{% static 'theme/img/freedombox-logo-32px.png' %}"/>
|
|
|
|
<!-- The is the icon for iOS's Web Clip. Size: 57x57 for older iPhones, 72x72 for iPads, 114x114 for iPhone4
|
|
- To prevent iOS from applying its styles to the icon name it thusly: apple-touch-icon-precomposed.png
|
|
- Transparency is not recommended (iOS will put a black BG behind the icon) -->
|
|
<link rel="apple-touch-icon" sizes="57x57" href="{% static 'theme/img/apple-touch-icon-57px-precomposed.png' %}"/>
|
|
<link rel="apple-touch-icon" sizes="72x72" href="{% static 'theme/img/apple-touch-icon-72px-precomposed.png' %}"/>
|
|
<link rel="apple-touch-icon" sizes="114x114" href="{% static 'theme/img/apple-touch-icon-114px-precomposed.png' %}"/>
|
|
|
|
<!-- Bootstrap base CSS -->
|
|
<link rel="stylesheet" href="{% static '/javascript/bootstrap/css/bootstrap.min.css' %}"/>
|
|
<link rel="stylesheet" href="{% static '/javascript/fork-awesome/css/fork-awesome.css' %}"/>
|
|
<link rel="stylesheet" href="{% static 'theme/css/main.css' %}"/>
|
|
<!-- Local link to system Modernizr (includes HTML5 Shiv) -->
|
|
<script type="text/javascript" src="{% static '/javascript/modernizr/modernizr.min.js' %}" defer></script>
|
|
<!-- Local link to system jQuery -->
|
|
<!-- TODO Deferring jQuery is causing scripts to be loaded before jQuery is available -->
|
|
<script type="text/javascript" src="{% static '/javascript/jquery/jquery.min.js' %}"></script>
|
|
<!-- Local link to system Bootstrap JS -->
|
|
<script type="text/javascript" src="{% static '/javascript/bootstrap/js/bootstrap.min.js' %}" defer></script>
|
|
|
|
<script type="text/javascript" src="{% static 'theme/js/main.js' %}" defer></script>
|
|
|
|
{% if refresh_page_sec %}
|
|
<noscript>
|
|
<meta http-equiv="refresh" content="{{ refresh_page_sec }}" />
|
|
</noscript>
|
|
{% endif %}
|
|
|
|
{% block app_head %}<!-- placeholder for app/module-specific head files -->{% endblock %}
|
|
{% block page_head %}<!-- placeholder for page-specific head files -->{% endblock %}
|
|
</head>
|
|
|
|
<body class="{%block body_class %}{%endblock%}"
|
|
{% if refresh_page_sec %}
|
|
data-refresh-page-sec="{{ refresh_page_sec }}"
|
|
{% endif %}>
|
|
<div id="wrapper">
|
|
<!--[if lt IE 7]><p class=chromeframe>Your browser is <em>ancient!</em> <a href="http://mozilla.org/firefox">Upgrade to a modern version of Firefox</a> to experience this site.</p><![endif]-->
|
|
<div class="navbar navbar-fixed-top navbar-default main-header" role="navigation">
|
|
<div class="container">
|
|
<div class="navbar-header">
|
|
<button type="button" class="navbar-toggle" data-toggle="collapse"
|
|
data-target=".navbar-collapse">
|
|
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
<span class="icon-bar"></span>
|
|
</button>
|
|
|
|
<ul class="nav navbar-nav">
|
|
{% include "notifications-dropdown.html" %}
|
|
</ul>
|
|
|
|
{% block mainmenu_left %}
|
|
<a href="{% url 'index' %}" class="navbar-brand
|
|
{% if not submenu.url %} menu_link_active {% else %}
|
|
menu_link {% endif %}" title="{{ box_name }}">
|
|
<i class="fa fa-freedombox fa-2x fa-inverse" aria-hidden="true"></i>
|
|
</a>
|
|
{% endblock %}
|
|
</div>
|
|
<div class="collapse navbar-collapse">
|
|
<ul class="nav navbar-nav">
|
|
{% block mainmenu_left_collapse %}
|
|
{% if user.is_authenticated and user_is_admin %}
|
|
<li>
|
|
{% url 'index' as index_url %}
|
|
<a href="{{ index_url }}" title='{% trans "Home" %}'
|
|
class="{% if not submenu.url %}
|
|
menu_link_active {% else %} menu_link {% endif %}">
|
|
{% trans "Home" %}
|
|
</a>
|
|
</li>
|
|
<li>
|
|
{% url 'apps' as apps_url %}
|
|
<a href="{{ apps_url }}" title='{% trans "Apps" %}'
|
|
class="{% if apps_url == submenu.url %}
|
|
menu_link_active {% else %} menu_link {% endif %}">
|
|
<span class="fa fa-th"></span>
|
|
{% trans "Apps" %}
|
|
</a>
|
|
</li>
|
|
<li>
|
|
{% url 'system' as system_url %}
|
|
<a href="{{ system_url }}" title='{% trans "System" %}'
|
|
class="{% if system_url == submenu.url %}
|
|
menu_link_active {% else %} menu_link {% endif %}">
|
|
<span class="fa fa-cog nav-icon"></span>
|
|
{% trans "System" %}
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endblock %}
|
|
</ul>
|
|
<ul class="nav navbar-nav navbar-right">
|
|
{% block mainmenu_right %}
|
|
|
|
{% if user.is_authenticated %}
|
|
|
|
{% include "notifications-dropdown.html" %}
|
|
|
|
{% include "help-menu.html" %}
|
|
|
|
<li id="id_user_menu" class="dropdown">
|
|
<a href="{% url 'users:edit' request.user.username %}"
|
|
class="dropdown-toggle" data-toggle="dropdown"
|
|
role="button" aria-expanded="false">
|
|
<i class="fa fa-user nav-icon"></i>
|
|
{{ request.user.username }}
|
|
<span class="caret"></span>
|
|
</a>
|
|
<ul class="dropdown-menu" role="menu">
|
|
<li>
|
|
<a id="id_user_edit_menu"
|
|
href="{% url 'users:edit' request.user.username %}"
|
|
title="{% trans "Edit"%}">
|
|
{% trans "Edit" %}
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a id='id_change_password_menu'
|
|
href="{% url 'users:change_password' request.user.username %}"
|
|
title="{% trans "Change password" %}">
|
|
{% trans "Change password" %}
|
|
</a>
|
|
</li>
|
|
<li class="divider hidden-xs"></li>
|
|
{% if user_is_admin %}
|
|
<li class="hidden-xs">
|
|
<a href="{% url 'power:restart' %}"
|
|
title="{% trans "Restart"%}">
|
|
{% trans "Restart" %}
|
|
</a>
|
|
</li>
|
|
<li class="hidden-xs">
|
|
<a href="{% url 'power:shutdown' %}"
|
|
title="{% trans "Shut down" %}">
|
|
{% trans "Shut down" %}
|
|
</a>
|
|
</li>
|
|
<li class="divider hidden-xs"></li>
|
|
{% endif %}
|
|
<li>
|
|
<a href="{% url 'users:logout' %}"
|
|
title="{% trans "Log out" %}">
|
|
{% trans "Log out" %}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
{% else %}
|
|
<li>
|
|
<a href="{% url 'language-selection' %}?next={{ request.path|iriencode }}"
|
|
title="{% trans "Select language" %}">
|
|
<span class="fa fa-globe-w nav-icon"></span>
|
|
<span class="nav-text hidden-sm hidden-md hidden-lg">
|
|
{% trans "Select language" %}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'users:login' %}" title="{% trans "Log in" %}">
|
|
<i class="fa fa-user nav-icon"></i>
|
|
{% trans "Log in" %}</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% if user.is_authenticated %}
|
|
<li id="logout-nojs">
|
|
<a href="{% url 'users:logout' %}" title="{% trans "Log out" %}">
|
|
<i class="fa fa-times-circle nav-icon"></i>
|
|
{% trans "Log out" %}</a>
|
|
</li>
|
|
{% endif %}
|
|
{% endblock %}
|
|
</ul>
|
|
</div>
|
|
|
|
{% include "notifications.html" %}
|
|
</div>
|
|
</div>
|
|
|
|
{% block container %}
|
|
<div class="container content-container">
|
|
{% block content_row %}
|
|
{% include 'messages.html' %}
|
|
|
|
{% block content %}
|
|
{# main content goes here #}
|
|
{% endblock %}
|
|
{% endblock %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
<footer>
|
|
<div class="container">
|
|
{% block footer_block %}
|
|
{% endblock %}
|
|
</div>
|
|
|
|
<div class="hidden">
|
|
<a href="{% static 'jslicense.html' %}" data-jslicense="1">
|
|
{% trans "JavaScript license information" %}</a>
|
|
</div>
|
|
</footer><!--/.footer-->
|
|
</div><!--/#container-->
|
|
{% block app_js %}<!-- placeholder for app-specific js files -->{% endblock %}
|
|
{% block page_js %}<!-- placeholder for page-specific js files -->{% endblock %}
|
|
</body>
|
|
</html>
|