5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
22 from peewee import Model
23 from peewee import MySQLDatabase
24 from peewee import CharField, BooleanField, ForeignKeyField
25 from peewee import DoesNotExist
27 AM_DB = MySQLDatabase(None)
30 class BaseAMModel(Model):
32 Base model for AM database
39 class AMdbUser(BaseAMModel):
40 user_uuid = CharField(null=False, unique=True)
41 name = CharField(null=False, unique=True)
42 is_service = BooleanField(default=False)
43 email = CharField(default='')
49 class AMdbResource(BaseAMModel):
50 path = CharField(null=False)
51 op = CharField(null=False)
52 desc = CharField(default='')
58 class AMdbRole(BaseAMModel):
59 name = CharField(null=False, unique=True)
60 is_service = BooleanField(default=False)
61 is_chroot = BooleanField(default=False)
62 desc = CharField(default='')
68 class AMdbRoleResource(BaseAMModel):
69 role_id = ForeignKeyField(AMdbRole, to_field='id', db_column='role_id')
70 res_id = ForeignKeyField(AMdbResource, to_field='id', db_column='res_id')
73 db_table = 'role_resource'
76 class AMdbUserRole(BaseAMModel):
77 user_id = ForeignKeyField(AMdbUser, to_field='id', db_column='user_id')
78 role_id = ForeignKeyField(AMdbRole, to_field='id', db_column='role_id')
81 db_table = 'user_role'
84 class NotExist(Exception):
85 def __init__(self, value):
89 return repr(self.value)
92 class AlreadyExist(Exception):
93 def __init__(self, value):
97 return repr(self.value)
100 class NotAllowedOperation(Exception):
101 def __init__(self, value):
105 return repr(self.value)
108 class AMDatabase(object):
109 """ AM Database handler class """
111 def __init__(self, db_name, db_user, db_pwd, db_addr, db_port, logger, management_mode=False):
113 Creates an instance of AM database
115 :param db_name: Name of AM's MySQL database
116 :param db_user: Username of the MySQL user
117 :param db_pwd: Password of the MySQL user
118 :param db_addr: Address of the MySQL server
119 :param db_port: Port of the MySQL server (type int)
120 :param logger: Logger instance to be used
123 db = AMDatabase(db_name='am_database',db_addr='127.0.0.1',
124 db_port=3306, db_user='db_user',db_pwd='db_pwd',
125 logger=logging.getLogger("Example"))
130 self.db_name = db_name
131 self.db_user = db_user
133 self.db_host = db_addr
134 self.db_port = db_port
136 self.management_mode = management_mode
139 raise Exception("You did not give me a logger to use. That's a no-no!")
145 Connects to the database
146 :raise Exception on failure
150 self.am_db.init(self.db_name,
151 host=self.db_host, port=self.db_port,
152 user=self.db_user, password=self.db_pwd)
154 self.logger.debug('Connected to database')
155 except Exception as ex:
156 self.logger.error('Error occured while connecting to database')
157 raise Exception('Error occured while connecting to database')
161 Closes the database connection
162 :raise Exception on failure
167 self.logger.debug('Database closed')
168 except Exception as ex:
169 self.logger.error('Error closing connection to database')
170 raise Exception('Error closing connection to database')
172 def create_tables(self):
173 self.am_db.create_tables([AMdbUser, AMdbRole, AMdbResource, AMdbUserRole, AMdbRoleResource], safe=True)
174 # AMdbUser.create_table(safe=True)
175 # AMdbRole.create_table(safe=True)
176 # AMdbResource.create_table(safe=True)
177 # AMdbUserRole.create_table(safe=True)
178 # AMdbRoleResource.create_table(safe=True)
180 def create_user(self, uuid, name, em='', service=False):
182 Creates a user with a UUID and optional email parameter
184 :param uuid: User identifier
185 :param name: User name
187 :param service: is_service parameter value
188 :return: returns AMdbUser instance
190 :raise AlreadyExist on failure
193 self.logger.debug('Called DB function: create_user')
194 query = (AMdbUser.select().where((AMdbUser.user_uuid == uuid) | (AMdbUser.name == name)))
196 if query.namedtuples():
198 'User already exists in table: {0}'.format(uuid))
200 user = AMdbUser.create(user_uuid=uuid, name=name, is_service=self.management_mode, email=em)
202 user = AMdbUser.create(user_uuid=uuid, name=name, is_service=False, email=em)
205 def get_user(self, uuid):
207 Returns the user record based on user UUID
209 :return: returns AMdbUser instance created
210 :raise NotExist on failure
212 self.logger.debug('Called DB function: get_user')
214 return AMdbUser.get(AMdbUser.user_uuid == uuid)
216 raise NotExist('User does not exist: {0}'.format(uuid))
218 def delete_user(self, uuid):
220 Deletes user; also removes reference from other tables
223 :raise NotAllowedOperation if user is service user
225 self.logger.debug('Called DB function: delete_user')
227 user = self.get_user(uuid)
228 except NotExist as ex:
231 if user.is_service and not self.management_mode:
232 raise NotAllowedOperation(
233 'Deleting service user is not allowed: {0}'.format(uuid))
234 query = (AMdbUserRole
235 .delete().where(AMdbUserRole.user_id == user.id))
237 query = (AMdbUser.delete().where(AMdbUser.user_uuid == uuid))
240 def set_user_param(self, uuid, email=''):
242 Sets extra parameters for users
243 :param uuid: user identifier
244 :param email: email address of the user
245 :raise NotAllowedOperation if the user is a service user
247 self.logger.debug('Called DB function: set_user_param')
249 user = self.get_user(uuid)
250 except NotExist as ex:
253 if user.is_service and not self.management_mode:
254 raise NotAllowedOperation(
255 'Modifying service user is not allowed: {0}'.format(uuid))
257 .update({AMdbUser.email: email})
258 .where(AMdbUser.user_uuid == uuid))
261 def get_all_users(self):
263 Returns all of the users
265 :return: Values are in a dict
268 self.logger.debug('Called DB function: get_all_users')
269 query = (AMdbUser.select(AMdbUser.user_uuid,
274 for user in query.namedtuples():
275 ret[user.user_uuid] = {'user_uuid': user.user_uuid,
276 'is_service': user.is_service,
280 def create_role(self, role_name, role_desc='', is_chroot=False):
284 :param role_name: role name
285 :param role_desc: description of the role
286 :param is_chroot: is_chroot value
287 :return: returns AMdbRole instance created
289 :raise AlreadyExist if the role is already present
291 self.logger.debug('Called DB function: create_role')
292 query = (AMdbRole.select().where(AMdbRole.name == role_name))
294 if query.namedtuples():
296 'Role already exists in table: {0}'.format(role_name))
297 role = AMdbRole.create(name=role_name, desc=role_desc, is_chroot=is_chroot, is_service=self.management_mode)
300 def get_role(self, role_name):
302 Gets role by role name
304 :param role_name: role name
305 :return: AMdbRole instance
307 :raise NotExist if role not exist
309 self.logger.debug('Called DB function: get_role')
311 return AMdbRole.get(AMdbRole.name == role_name)
313 raise NotExist('Role does not exsist: {}'.format(role_name))
315 def delete_role(self, role_name):
317 Deletes role by role name;
318 also removes from role_resource and user_role table
320 :param role_name: role name
321 :raise NotAllowedOperation if role is service role
323 self.logger.debug('Called DB function: delete_role')
324 role = self.get_role(role_name)
325 if role.is_service and not self.management_mode:
326 raise NotAllowedOperation(
327 'Deleting service role is not allowed: {0}'.format(role_name))
328 query = (AMdbUserRole
329 .delete().where(AMdbUserRole.role_id == role.id))
331 query = (AMdbRoleResource
332 .delete().where(AMdbRoleResource.role_id == role.id))
334 query = (AMdbRole.delete().where(AMdbRole.id == role.id))
337 def set_role_param(self, role_name, desc=None, is_chroot=False):
339 Sets role optional parameters
341 :param role_name: role name
342 :param desc: role description
343 :param is_chroot: is_chroot value
344 :raise NotAllowedOperation if role is service role
346 self.logger.debug('Called DB function: set_role_param')
347 role = self.get_role(role_name)
348 if role.is_service and not self.management_mode:
349 raise NotAllowedOperation(
350 'Modifying service role is not allowed: {0}'.format(role_name))
352 .update({AMdbRole.desc: desc, AMdbRole.is_chroot: is_chroot, AMdbRole.is_service: self.management_mode})
353 .where(AMdbRole.name == role_name))
356 def get_all_roles(self):
358 Returns all roles in a dict
360 :return: Values are in a dict
363 self.logger.debug('Called DB function: get_all_roles')
364 roles = AMdbRole.select()
367 res[role.name] = {'role_name': role.name,
368 'is_service': role.is_service,
370 'is_chroot': role.is_chroot}
373 def is_chroot_role(self, role_name):
375 Checks if the role is a chroot role or not
377 :param role_name: role name
378 :return: bool; true if role is chroot role
380 self.logger.debug('Called DB function: is_chroot_role')
381 role = self.get_role(role_name)
382 return role.is_chroot
384 def get_resource_with_operations(self, res_path):
386 Gets resource by resource name
387 returns a dict: key is the resource path,
388 values are allowed operations in a list
389 In other words: returns the allowed operations on a resource
391 :param res_path: resource path
392 :raise NotExist if the resource is not present
393 :returns dict where resource is the key, values are the operations
395 self.logger.debug('Called DB function: get_resource_with_operations')
396 query = (AMdbResource.select().where(AMdbResource.path == res_path))
399 if not query.namedtuples():
400 raise NotExist('Resource does not exist: {0}'.format(res_path))
401 for row in query.namedtuples():
402 if row[1] not in res.keys():
403 res[row[1]] = [row[2]]
405 res[row[1]].append(row[2])
408 def get_resource(self, res_path, res_op):
410 Gets resource by resource name and path
412 :param res_path: resource path
413 :param res_op: operation
414 :returns: AMdbResource instance
416 :raise NotExist if resource is not present
418 self.logger.debug('Called DB function: get_resource')
420 return AMdbResource.get(AMdbResource.path == res_path,
421 AMdbResource.op == res_op)
423 raise NotExist('Resource {0} with op {1} does not exsist'
424 .format(res_path, res_op))
426 def get_resources(self):
428 Gets all resources with operations
430 :returns: dict[str:list[str]] values
433 self.logger.debug('Called DB function: get_resources')
434 resources = AMdbResource.select()
436 for res in resources:
437 if res.path not in ret.keys():
438 ret[res.path] = [res.op]
440 ret[res.path].append(res.op)
443 def add_user_role(self, uuid, role_name):
445 Adds a role to a user
447 :param uuid: user identifier
448 :param role_name: name of the role
449 :raise AlreadyExist if the user-role is already present,
450 NotAllowedOperation if the user is a service user
453 self.logger.debug('Called DB function: add_user_role')
455 user = self.get_user(uuid)
456 except NotExist as ex:
459 if user.is_service and not self.management_mode:
460 raise NotAllowedOperation(
461 'Service user roles cannot be modified: {0}'.format(uuid))
462 role = self.get_role(role_name)
463 query = (AMdbUserRole.select()
464 .where(AMdbUserRole.user_id == user.id,
465 AMdbUserRole.role_id == role.id))
467 if query.namedtuples():
468 raise AlreadyExist('Role for user already exists in table: {0}:{1}'
469 .format(uuid, role_name))
471 query = (AMdbUserRole
472 .insert({AMdbUserRole.user_id: user.id,
473 AMdbUserRole.role_id: role.id}))
476 def delete_user_role(self, uuid, role_name):
478 Deletes role for a given user (removes permission).
479 Does not delete the role itself.
481 :param uuid: user identifier
482 :param role_name: name of the role
483 :raise NotExist if the user has no such role,
484 NotAllowedOperation if the user is a service user
486 self.logger.debug('Called DB function: delete_user_role')
488 user = self.get_user(uuid)
489 except NotExist as ex:
492 if user.is_service and not self.management_mode:
493 raise NotAllowedOperation(
494 'Service user roles cannot be modified: {0}'.format(uuid))
495 role = self.get_role(role_name)
496 query = (AMdbUserRole
497 .delete().where(AMdbUserRole.user_id == user.id,
498 AMdbUserRole.role_id == role.id))
499 ret = query.execute()
501 raise NotExist('User {0} has no role {1}.'
502 .format(user.user_uuid, role_name))
504 def get_user_roles(self, uuid):
506 Gets role belonging to a user
508 :param uuid: user identifier
509 :returns: list of roles for the user
512 self.logger.debug('Called DB function: get_user_roles')
514 user = self.get_user(uuid)
515 except NotExist as ex:
518 query = (AMdbUserRole.select()
519 .where(AMdbUserRole.user_id == user.id)
520 ).join(AMdbRole).where(AMdbRole.id == AMdbUserRole.role_id
521 ).select(AMdbRole.name)
523 for row in query.namedtuples():
527 def get_user_resources(self, uuid):
529 Gets resources belonging to a user, returns a list of resources
530 returns a dict whith resource path as keys,
531 allowed operations as values in a list
533 :param uuid: user identifier
534 :returns: a dict where the keys are resource names,
535 values are the operations in a list
536 :rtype: dict[str:list[str]]
538 self.logger.debug('Called DB function: get_user_resources')
540 user = self.get_user(uuid)
541 except NotExist as ex:
544 roles = AMdbUserRole.select().where(
545 AMdbUserRole.user_id == user.id)
547 for role_row in roles:
548 role_res = AMdbRoleResource.select().where(
549 AMdbRoleResource.role_id == role_row.role_id)
550 for r_res_row in role_res:
551 if r_res_row.res_id.path not in res.keys():
552 res[r_res_row.res_id.path] = [r_res_row.res_id.op]
554 res[r_res_row.res_id.path].append(r_res_row.res_id.op)
557 def add_resource_to_role(self, role_name, res_path, res_op):
559 Assings a resource+operation to a role
561 :param role_name: role name
562 :param res_path: resource path
563 :param res_op: resource operation
564 :raise AlreadyExist if there's such a pairing,
565 NotAllowedOperation if the role is a service role
567 self.logger.debug('Called DB function: add_resource_to_role')
568 role = self.get_role(role_name)
569 if role.is_service and not self.management_mode:
570 raise NotAllowedOperation('Service role cannot be modified: {0}'
572 res = self.get_resource(res_path, res_op)
573 query = (AMdbRoleResource.select().where(
574 AMdbRoleResource.role_id == role.id,
575 AMdbRoleResource.res_id == res.id))
577 if query.namedtuples():
579 'Role-resource already exists in table: {0}:{1}, {2}'
580 .format(role_name, res_path, res_op))
582 query = (AMdbRoleResource
583 .insert({AMdbRoleResource.role_id: role.id,
584 AMdbRoleResource.res_id: res.id}))
587 def get_role_resources(self, role_name):
589 Gets resources belonging to a role (like giving permission)
591 :param role_name: role name
592 :returns: dictionary, where keys are resource paths and values are
594 :rtype: dict[str:list[str]]
596 self.logger.debug('Called DB function: get_role_resources')
597 role = self.get_role(role_name)
598 query = (AMdbRoleResource.select()
599 .where(AMdbRoleResource.role_id == role.id)
601 .where(AMdbResource.id == AMdbRoleResource.res_id)
602 ).select(AMdbResource.path, AMdbResource.op)
605 for row in query.namedtuples():
606 if row[0] not in res.keys():
607 res[row[0]] = [row[1]]
609 res[row[0]].append(row[1])
612 def delete_role_resource(self, role_name, res_path, res_op):
614 Deletes a resource from a role (like removing permission)
615 :param role_name: role name
616 :param res_path: resource path
617 :param res_op: resource operation
619 self.logger.debug('Called DB function: delete_role_resource')
620 role = self.get_role(role_name)
621 if role.is_service and not self.management_mode:
622 raise NotAllowedOperation('Service role cannot be modified: {0}'
624 res = self.get_resource(res_path, res_op)
625 query = (AMdbRoleResource.delete()
626 .where(AMdbRoleResource.role_id == role.id,
627 AMdbRoleResource.res_id == res.id))
628 ret = query.execute()
630 raise NotExist('Role {0} has no such resource:operation : {1}:{2}'
631 .format(role_name, res_path, res_op))
633 def create_resource(self, res_path, res_op, res_desc=''):
634 """ Creates resource """
635 self.logger.debug('Called DB function: create_resource')
636 if self.management_mode:
637 q = (AMdbResource.select().where(AMdbResource.path == res_path, AMdbResource.op == res_op))
639 if len(q.namedtuples()) > 0:
640 print 'Resource and operation already exists in table'
641 raise Exception(res_path+':'+res_op)
642 resource = AMdbResource.create(path=res_path, op=res_op, desc=res_desc)
645 def update_resource(self, res_path, res_op, res_desc):
646 """ updates resource """
647 self.logger.debug('Called DB function: update_resource')
648 if self.management_mode:
649 q = (AMdbResource.select().where(AMdbResource.path == res_path, AMdbResource.op == res_op))
651 if len(q.namedtuples()) == 0:
652 print 'Resource does not exist'
653 raise Exception(res_path+":"+res_op)
654 q = (AMdbResource.update({AMdbResource.desc: res_desc})
655 .where(AMdbResource.path == res_path, AMdbResource.op == res_op))
658 def get_user_uuid(self, name):
660 Returns the user UUID based on user name
662 :return: returns UUID
663 :raise NotExist on failure
665 self.logger.debug('Called DB function: get_user_uuid')
667 return AMdbUser.get(AMdbUser.name == name).user_uuid
669 raise NotExist('User does not exist: {0}'.format(name))
671 def get_user_name(self, uuid):
673 Returns the user name based on user UUID
675 :return: returns username
676 :raise NotExist on failure
678 self.logger.debug('Called DB function: get_user_name')
680 return AMdbUser.get(AMdbUser.user_uuid == uuid).name
682 raise NotExist('User does not exist: {0}'.format(uuid))
684 def get_role_users(self, role_name):
686 Gets users associated to a given role
687 :param role_name: role name
688 :returns list containing uuids; if there are no users
689 associated to the role, an empty list
691 :raise AlreadyExist if there's no such role
693 self.logger.debug('Called DB function: get_role_users')
694 role = self.get_role(role_name)
695 query = (AMdbUserRole.select(AMdbUserRole.user_id)
696 .where(AMdbUserRole.role_id == role.id)
697 .join(AMdbUser).where(AMdbUser.id == AMdbUserRole.user_id))
698 res = query.execute()
699 return [row.user_id.user_uuid for row in res]
701 def get_user_table(self):
704 :returns list of each row in dict format
708 query = AMdbUser.select().dicts()
713 def get_role_table(self):
716 :returns list of each row in dict format
720 query = AMdbRole.select().dicts()
725 def get_resource_table(self):
728 :returns list of each row in dict format
732 query = AMdbResource.select().dicts()
737 def get_user_role_table(self):
740 :returns list of each row in dict where ids are replaced:
746 query = AMdbUserRole.select(AMdbUser.name.alias('user_name'), AMdbRole.name.alias('role_name'))\
747 .join(AMdbUser).switch(AMdbUserRole).join(AMdbRole).dicts()
748 result = [row for row in query]
751 def get_role_resource_table(self):
753 Gets role_resource table
754 :returns list of each row in dict where ids are replaced:
756 res_id -> resource.path + resource.op
760 query = AMdbRoleResource.select(AMdbRole.name, AMdbResource.path, AMdbResource.op)\
761 .join(AMdbRole).switch(AMdbRoleResource).join(AMdbResource).dicts()
762 result = [row for row in query]
765 def get_roles_for_permission(self, perm_name, op):
767 Gets all roles where the permission is included
768 :returns list of role
769 perm_name -> resource.name
773 self.logger.debug('Called DB function: get_roles_for_permission')
775 res_id = self.get_resource(perm_name, op).id
776 query = AMdbRoleResource.select(AMdbRole.name).join(AMdbRole).where(AMdbRoleResource.res_id == res_id).dicts()
778 result.append(row["name"])
781 def get_all_role_perms(self):
783 Gets all roles/permissions where the permission is included
784 :returns hashmap of permission, operation+roles
787 self.logger.debug('Called DB function: get_all_role_perms')
789 res_lst = self.get_resources()
790 query = AMdbResource.select(AMdbResource.path, AMdbResource.op).dicts()
792 result[row["path"]+":"+row["op"]] = self.get_roles_for_permission(row["path"], row["op"])