"""Provide factory class to create/obtain users."""
# Copyright (c) 2016 - I.T. Dev Ltd
#
# This file is part of MCVirt.
#
# MCVirt 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 2 of the License, or
# (at your option) any later version.
#
# MCVirt 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 MCVirt. If not, see <http://www.gnu.org/licenses/>
import Pyro4
from mcvirt.mcvirt_config import MCVirtConfig
from mcvirt.exceptions import (IncorrectCredentials, InvalidUsernameException,
UserDoesNotExistException, InvalidUserTypeException,
UserAlreadyExistsException, BlankPasswordException)
from mcvirt.rpc.pyro_object import PyroObject
from mcvirt.auth.user_base import UserBase
from mcvirt.auth.user import User
from mcvirt.auth.connection_user import ConnectionUser
from mcvirt.auth.cluster_user import ClusterUser
from mcvirt.auth.permissions import PERMISSIONS
[docs]class Factory(PyroObject):
"""Class for obtaining user objects"""
USER_CLASS = UserBase
[docs] def get_user_types(self):
"""Return the available user classes."""
return [User, ConnectionUser, ClusterUser]
[docs] def ensure_valid_user_type(self, user_type):
"""Ensure that a given user_type is valid."""
if user_type not in self.get_user_types():
raise InvalidUserTypeException('An invalid user type has been passed')
@Pyro4.expose()
[docs] def create(self, username, password, user_type=User):
"""Create a user."""
self._get_registered_object('auth').assert_permission(
PERMISSIONS.MANAGE_USERS
)
if password == '':
raise BlankPasswordException('Password cannot be blank')
# Ensure that username is not part of a reserved namespace
for user_class in self.get_user_types():
if (user_class is not user_type and
user_class.USER_PREFIX is not None and
username.startswith(user_class.USER_PREFIX)):
raise InvalidUsernameException(
'Username is within a reserved namespace'
)
# Ensure that there is not a duplicate user
if UserBase._check_exists(username):
raise UserAlreadyExistsException('There is a user with the same username \'%s\'' %
username)
# Ensure valid user type
self.ensure_valid_user_type(user_type)
# Generate password salt for user and hash password
salt = UserBase._generate_salt()
hashed_password = UserBase._hash_string(password, salt)
# Create config for user and update MCVirt config
user_config = user_type.get_default_config()
user_config['password'] = hashed_password
user_config['salt'] = salt
user_config['user_type'] = user_type.__name__
def update_config(config):
config['users'][username] = user_config
MCVirtConfig().update_config(update_config, 'Create user \'%s\'' % username)
if user_type.DISTRIBUTED and self._is_cluster_master:
# Create the user on the other nodes in the cluster
def remote_command(node_connection):
remote_user_factory = node_connection.get_connection('user_factory')
remote_user_factory.create(username, password)
cluster = self._get_registered_object('cluster')
cluster.run_remote_command(remote_command)
@Pyro4.expose()
[docs] def add_config(self, username, user_config):
"""Add a user config to the local node."""
# Ensure this is being run as a Cluster User
self._get_registered_object('auth').check_user_type('ClusterUser')
def update_config(config):
config['users'][username] = user_config
MCVirtConfig().update_config(update_config, 'Adding user %s' % username)
[docs] def authenticate(self, username, password):
"""Attempt to authenticate a user, using username/password."""
try:
user_object = self.get_user_by_username(username)
if user_object._check_password(password):
return user_object
except UserDoesNotExistException:
pass
raise IncorrectCredentials('Incorrect username/password')
@Pyro4.expose()
[docs] def get_user_by_username(self, username):
"""Obtain a user object for the given username."""
generic_object = UserBase(username=username)
for user_class in UserBase.__subclasses__():
if str(user_class.__name__) == str(generic_object.get_user_type()):
user_object = user_class(username=username)
self._register_object(user_object)
return user_object
raise InvalidUserTypeException('Failed to determine user type for %s' %
generic_object.get_username())
@Pyro4.expose()
[docs] def get_all_users(self):
"""Return all the users, excluding built-in users."""
return self.get_all_user_objects(user_class=User)
@Pyro4.expose()
[docs] def get_all_user_objects(self, user_class=None):
"""Return the user objects for all users, optionally filtered by user type."""
if user_class is not None:
# Ensure valid user type
self.ensure_valid_user_type(user_class)
# Obtain all usernames
all_usernames = MCVirtConfig().get_config()['users'].keys()
user_objects = []
for username in all_usernames:
user_object = self.get_user_by_username(username)
# Is the user object is the same type as specified, or the user type
# has not been specified, add to user objects list
if user_class is None or user_object.get_user_type() == user_class.__name__:
user_objects.append(user_object)
# Return found user objects
return user_objects
[docs] def generate_user(self, user_type):
"""Remove any existing connection user and generates credentials for a new
connection user.
"""
# Ensure valid user type
self.ensure_valid_user_type(user_type)
# Ensure that users can be generated
if not user_type.CAN_GENERATE:
raise InvalidUserTypeException('Users of type \'%s\' cannot be generated' %
user_type.__name__)
# Delete any old connection users
for old_user_object in self.get_all_user_objects(user_class=user_type):
old_user_object.delete()
username = user_type.USER_PREFIX + user_type.generate_password(32, numeric_only=True)
password = user_type.generate_password(32)
self.create(username=username, password=password, user_type=user_type)
return username, password
@Pyro4.expose()
[docs] def get_cluster_user_by_node(self, node):
"""Obtain a cluster user for a given node"""
for user in self.get_all_user_objects(user_class=ClusterUser):
if user.node == node:
return user
raise UserDoesNotExistException('No user found for node %s' % node)