src/westferry/backend/__init__.py \
src/westferry/backend/__version__.py \
src/westferry/backend/base.py \
+ src/westferry/backend/errors.py \
src/westferry/backend/graphs.py \
src/westferry/backend/system.py \
src/westferry/backend/users.py
westferry_handlers_PYTHON = \
src/westferry/handlers/__init__.py \
src/westferry/handlers/analytics.py \
+ src/westferry/handlers/auth.py \
src/westferry/handlers/base.py \
src/westferry/handlers/demo.py \
src/westferry/handlers/index.py \
src/templates/default.html \
src/templates/graphs.html
+templates_authdir = $(templatesdir)/auth
+
+dist_templates_auth_DATA = \
+ src/templates/auth/login.html
+
templates_demodir = $(templatesdir)/demo
dist_templates_demo_DATA = \
--- /dev/null
+{% extends "../base.html" %}
+
+{% block body %}
+ <div class="grid-container">
+ <div class="grid-x grid-padding-x align-center-middle">
+ <div class="cell medium-6 large-4">
+ <div class="{% if failed %}alert{% end %} callout">
+ <h5>{{ _("Login") }}</h5>
+
+ <form method="POST" action="">
+ {% raw xsrf_form_html() %}
+
+ {% if next %}
+ <input type="hidden" name="next" value="{{ next }}">
+ {% end %}
+
+ <input type="text" name="username" required
+ placeholder="{{ _("Username") }}">
+
+ <input type="password" name="password" required
+ placeholder="{{ _("Password") }}">
+
+ <button class="submit primary button expanded">
+ {{ _("Login") }}
+ </button>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+{% end block %}
# Use Cross-Site-Request-Forgery protection
"xsrf_cookies" : True,
+
+ # Send users to /login to authenticate
+ "login_url" : "/login",
}
settings.update(kwargs)
--- /dev/null
+#!/usr/bin/python3
+###############################################################################
+# #
+# Westferry - The IPFire web user interface #
+# Copyright (C) 2022 IPFire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+class UserNotFoundError(Exception):
+ """
+ Raised when a user object could not be found
+ """
+ pass
# #
###############################################################################
+import pam
import functools
import pydbus
+import logging
+log = logging.getLogger(__name__)
+
from . import base
+from .errors import UserNotFoundError
class UsersBackend(base.BaseBackend):
"""
"/org/freedesktop/sssd/infopipe/Users",
)
+ def authenticate(self, name, password):
+ """
+ This function will try to find the user and validate the password.
+
+ On success, a new session will be returned.
+ """
+ user = self.get_by_name(name)
+ if not user:
+ return
+
+ # Check password
+ if not user.authenticate(password):
+ return
+
+ # All okay. Create a new session
+ return True # XXX
+
def get_by_name(self, name):
try:
return User(self.backend, name)
- except NotFoundError:
+ except UserNotFoundError:
return
self.name = name
# Search for the user's path in dbus
- self.path = self.backend.users.sssd.FindByName(name)
- if not self.path:
- raise NotFoundError("Could not find user %s" % name)
+ try:
+ self.path = self.backend.users.sssd.FindByName(name)
+ # XXX this should be GDBus.Error:sbus.Error.NotFound
+ except Exception as e:
+ raise UserNotFoundError("Could not find user %s" % name) from e
def __repr__(self):
return "<%s uid=%s>" % (self.__class__.__name__, self.uid)
"""
return self.backend.system.bus.get("org.freedesktop.sssd.infopipe", self.path)
+ def authenticate(self, password):
+ log.debug("Trying to authenticate user %s" % self.name)
+
+ # Perform the authentication
+ if pam.authenticate(self.name, password):
+ log.debug("Successfully authenticated user %s" % self.name)
+ return True
+
+ log.error("Could not authenticate user %s" % self.name)
+ return False
+
@property
def gecos(self):
"""
from . import base
from . import analytics
+from . import auth
from . import demo
from . import index
from . import system
--- /dev/null
+#!/usr/bin/python3
+###############################################################################
+# #
+# Westferry - The IPFire web user interface #
+# Copyright (C) 2022 IPFire development team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+import tornado.web
+
+from . import base
+
+class LoginHandler(base.BaseHandler):
+ url = r"/login"
+
+ def get(self, next=None, failed=False):
+ # Tell the user there has been an error
+ if failed:
+ self.set_status(401)
+
+ self.render("auth/login.html", next=next, failed=failed)
+
+ def post(self):
+ username = self.get_argument("username")
+ password = self.get_argument("password")
+
+ # Where does the user want to be redirected to?
+ next = self.get_argument("next", None)
+
+ # Try authenticating the user
+ session = self.backend.users.authenticate(username, password)
+
+ # Credentials have no been valid, sending the user back
+ if not session:
+ # Render the login page again
+ return self.get(next=next, failed=True)
+
+ # Set the session cookie
+ # XXX
+
+ # Redirect the user back to where they want to go
+ self.redirect(next or "/")
+
+
+class LogoutHandler(base.BaseHandler):
+ url = r"/logout"
+
+ @tornado.web.authenticated
+ def get(self):
+ # Redirect back to the front page
+ self.redirect("/")
This function returns the currently logged-in user.
"""
# XXX This is just a demo
- return self.backend.users.get_by_name("ms")
+ #return self.backend.users.get_by_name("ms")
+ pass
@property
def topmenu(self):
# #
###############################################################################
+import tornado.web
+
from . import base
class IndexHandler(base.BaseHandler):
url = r"/"
+ @tornado.web.authenticated
def get(self):
self.render("base.html")