Source code for isomer.ui.clientmanager.authentication

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

# Isomer - The distributed application framework
# ==============================================
# Copyright (C) 2011-2020 Heiko 'riot' Weinen <riot@c-base.org> and others.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""


Module clientmanager.authentication
===================================

Handles authentication and authorization related aspects


"""

import json

from circuits.net.events import write

from isomer.component import handler

from isomer.events.client import (
    clientdisconnect,
    send,
    authenticationrequest,
    userlogin,
)
from isomer.events.system import get_user_events, get_anonymous_events
from isomer.logger import debug, critical, verbose, error, warn, network, info
from isomer.misc import i18n as _

from isomer.ui.clientobjects import Client, User

from isomer.ui.clientmanager.basemanager import ClientBaseManager


[docs]class AuthenticationManager(ClientBaseManager): """Handles authentication and authorization related aspects"""
[docs] def __init__(self, *args, **kwargs): super(AuthenticationManager, self).__init__(*args, **kwargs) self.authorized_events = {} self.anonymous_events = {}
[docs] @handler("ready") def ready(self): """Compile events""" self.authorized_events = get_user_events() self.anonymous_events = get_anonymous_events()
[docs] @handler("authentication", channel="auth") def authentication(self, event): """Links the client to the granted account and profile, then notifies the client""" try: self.log( "Authorization has been granted by DB check:", event.username, lvl=debug ) account, profile, clientconfig = event.userdata useruuid = event.useruuid originatingclientuuid = event.clientuuid clientuuid = clientconfig.uuid if clientuuid != originatingclientuuid: self.log("Mutating client uuid to request id:", clientuuid, lvl=network) # Assign client to user if useruuid in self._users: signedinuser = self._users[useruuid] else: signedinuser = User(account, profile, useruuid) self._users[account.uuid] = signedinuser if clientuuid in signedinuser.clients: self.log("Client configuration already logged in.", lvl=critical) # TODO: What now?? # Probably senseful would be to add the socket to the # client's other socket # The clients would be identical then - that could cause # problems # which could be remedied by duplicating the configuration else: signedinuser.clients.append(clientuuid) self.log( "Active client (", clientuuid, ") registered to " "user", useruuid, lvl=debug, ) # Update socket.. socket = self._sockets[event.sock] socket.clientuuid = clientuuid self._sockets[event.sock] = socket # ..and client lists try: language = clientconfig.language except AttributeError: language = "en" # TODO: Rewrite and simplify this: newclient = Client( sock=event.sock, ip=socket.ip, clientuuid=clientuuid, useruuid=useruuid, name=clientconfig.name, config=clientconfig, language=language, ) del self._clients[originatingclientuuid] self._clients[clientuuid] = newclient authpacket = { "component": "auth", "action": "login", "data": account.serializablefields(), } self.log("Transmitting Authorization to client", authpacket, lvl=network) self.fireEvent(write(event.sock, json.dumps(authpacket)), "wsserver") profilepacket = { "component": "profile", "action": "get", "data": profile.serializablefields(), } self.log("Transmitting Profile to client", profilepacket, lvl=network) self.fireEvent(write(event.sock, json.dumps(profilepacket)), "wsserver") clientconfigpacket = { "component": "clientconfig", "action": "get", "data": clientconfig.serializablefields(), } self.log( "Transmitting client configuration to client", clientconfigpacket, lvl=network, ) self.fireEvent( write(event.sock, json.dumps(clientconfigpacket)), "wsserver" ) self.fireEvent(userlogin(clientuuid, useruuid, clientconfig, signedinuser)) self.log( "User configured: Name", signedinuser.account.name, "Profile", signedinuser.profile.uuid, "Clients", signedinuser.clients, lvl=debug, ) except Exception as e: self.log( "Error (%s, %s) during auth grant: %s" % (type(e), e, event), lvl=error )
def _handle_authentication_events(self, data, action, clientuuid, sock): """Handler for authentication events""" # TODO: Move this stuff over to ./auth.py if action in ("login", "autologin"): try: self.log("Login request", lvl=verbose) if action == "autologin": username = password = None requested_clientuuid = data auto = True self.log("Autologin for", requested_clientuuid, lvl=debug) else: username = data["username"] password = data["password"] if "clientuuid" in data: requested_clientuuid = data["clientuuid"] else: requested_clientuuid = None auto = False self.log("Auth request by", username, lvl=verbose) self.fireEvent( authenticationrequest( username, password, clientuuid, requested_clientuuid, sock, auto ), "auth", ) return except Exception as e: self.log("Login failed: ", e, type(e), lvl=warn, exc=True) elif action == "logout": self.log("User logged out, refreshing client.", lvl=network) try: if clientuuid in self._clients: client = self._clients[clientuuid] user_id = client.useruuid if client.useruuid: self.log("Logout client uuid: ", clientuuid) self._logout_client(client.useruuid, clientuuid) self.fireEvent(clientdisconnect(clientuuid)) else: self.log("Client is not connected!", lvl=warn) except Exception as e: self.log( "Error during client logout: ", e, type(e), lvl=error, exc=True ) else: self.log("Unsupported auth action requested:", action, lvl=warn) def _handle_authorized_events(self, component, action, data, user, client): """Isolated communication link for authorized events.""" try: if component == "debugger": self.log(component, action, data, user, client, lvl=info) if not user and component in self.authorized_events.keys(): self.log( "Unknown client tried to do an authenticated " "operation: %s", component, action, data, user, ) return event = self.authorized_events[component][action]["event"]( user, action, data, client ) self.log("Authorized event roles:", event.roles, lvl=verbose) if not self._check_permissions(user, event): result = { "component": "isomer.ui.clientmanager", "action": "Permission", "data": _("You have no role that allows this action.", lang="de"), } self.fireEvent(send(event.client.uuid, result)) return self.log("Firing authorized event: ", event, lvl=debug) # self.log("", (user, action, data, client), lvl=critical) self.fireEvent(event) except Exception as e: self.log( "Critical error during authorized event handling:", component, action, e, type(e), lvl=critical, exc=True, ) def _handle_anonymous_events(self, component, action, data, client): """Handler for anonymous (public) events""" try: event = self.anonymous_events[component][action]["event"] self.log( "Firing anonymous event: ", component, action, str(data)[:20], lvl=network, ) # self.log("", (user, action, data, client), lvl=critical) self.fireEvent(event(action, data, client)) except Exception as e: self.log( "Critical error during anonymous event handling:", component, action, e, type(e), lvl=critical, exc=True, ) def _check_permissions(self, user, event): """Checks if the user has in any role that allows to fire the event.""" for role in user.account.roles: if role in event.roles: self.log("Access granted", lvl=verbose) return True self.log("Access denied", lvl=verbose) return False