]> git.ipfire.org Git - people/ms/westferry.git/commitdiff
Implement some authentication
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 9 May 2025 13:10:51 +0000 (13:10 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 9 May 2025 13:11:15 +0000 (13:11 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/templates/auth/login.html [new file with mode: 0644]
src/westferry/application.py
src/westferry/backend/errors.py [new file with mode: 0644]
src/westferry/backend/users.py
src/westferry/handlers/__init__.py
src/westferry/handlers/auth.py [new file with mode: 0644]
src/westferry/handlers/base.py
src/westferry/handlers/index.py

index 70b589fa75f966af6980296c3c8ef2f6d802c0a1..a41768ce684a80d6d043e881f0b3220adf98aaf1 100644 (file)
@@ -102,6 +102,7 @@ westferry_backend_PYTHON = \
        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
@@ -111,6 +112,7 @@ westferry_backenddir = $(pythondir)/westferry/backend
 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 \
@@ -139,6 +141,11 @@ dist_templates_DATA = \
        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 = \
diff --git a/src/templates/auth/login.html b/src/templates/auth/login.html
new file mode 100644 (file)
index 0000000..a219174
--- /dev/null
@@ -0,0 +1,31 @@
+{% 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 %}
index a402848f8f981e204dcbe8e2fa4606672155fd60..0898940039b7c7db60cb5e5b1a56ec2dd19a56f9 100644 (file)
@@ -41,6 +41,9 @@ class WebApplication(tornado.web.Application):
 
                        # Use Cross-Site-Request-Forgery protection
                        "xsrf_cookies" : True,
+
+                       # Send users to /login to authenticate
+                       "login_url" : "/login",
                }
                settings.update(kwargs)
 
diff --git a/src/westferry/backend/errors.py b/src/westferry/backend/errors.py
new file mode 100644 (file)
index 0000000..0b68729
--- /dev/null
@@ -0,0 +1,26 @@
+#!/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
index 81ba5cfe18492c57f697a8db4fb2a33361522f62..4f2c4d5dab57757ba60a33db9207974d0b8e9b1b 100644 (file)
 #                                                                             #
 ###############################################################################
 
+import pam
 import functools
 import pydbus
 
+import logging
+log = logging.getLogger(__name__)
+
 from . import base
+from .errors import UserNotFoundError
 
 class UsersBackend(base.BaseBackend):
        """
@@ -38,10 +43,27 @@ 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
 
 
@@ -51,9 +73,11 @@ class User(base.BaseBackend):
                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)
@@ -68,6 +92,17 @@ class User(base.BaseBackend):
                """
                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):
                """
index 5a84838513603531a75a3397f01f5b94ad5aa6ef..81706ba47359da59c1361fc867bad2f5da791e96 100644 (file)
@@ -21,6 +21,7 @@
 
 from . import base
 from . import analytics
+from . import auth
 from . import demo
 from . import index
 from . import system
diff --git a/src/westferry/handlers/auth.py b/src/westferry/handlers/auth.py
new file mode 100644 (file)
index 0000000..bbeee86
--- /dev/null
@@ -0,0 +1,64 @@
+#!/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("/")
index abbe1a70333ddbb7ad894f6ae38e992c6ab4a3ba..59c0e58b5bcb20fd1a06cb9d2c0936027551d31f 100644 (file)
@@ -66,7 +66,8 @@ class BaseHandler(tornado.web.RequestHandler, metaclass=HandlerRegistration):
                        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):
index 6198568b189a100bec1820176d54848636f7a110..76f1c0b4c03f50650a2d095745ce043788073572 100644 (file)
 #                                                                             #
 ###############################################################################
 
+import tornado.web
+
 from . import base
 
 class IndexHandler(base.BaseHandler):
        url = r"/"
 
+       @tornado.web.authenticated
        def get(self):
                self.render("base.html")